From d98c3c00db4803cb3e54b350012aaa2e97699471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 9 Apr 2025 11:53:11 -0400 Subject: [PATCH 1/6] WIP: express 5 fixes --- install/package.json | 2 +- src/controllers/home.js | 5 +++-- src/middleware/index.js | 3 ++- src/posts/recent.js | 2 +- src/routes/activitypub.js | 4 ++-- src/routes/admin.js | 2 +- src/routes/api.js | 2 +- src/routes/index.js | 24 ++++++++++++++---------- src/routes/user.js | 8 ++++---- src/routes/well-known.js | 2 +- src/routes/write/posts.js | 3 ++- src/routes/write/users.js | 3 ++- 12 files changed, 34 insertions(+), 26 deletions(-) diff --git a/install/package.json b/install/package.json index 5fc4b3418e..6169ec2b4b 100644 --- a/install/package.json +++ b/install/package.json @@ -68,7 +68,7 @@ "daemon": "1.1.0", "diff": "7.0.0", "esbuild": "0.25.2", - "express": "4.21.2", + "express": "5.1.0", "express-session": "1.18.1", "express-useragent": "1.0.15", "fetch-cookie": "3.1.0", diff --git a/src/controllers/home.js b/src/controllers/home.js index ea596f972f..6629df57dd 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -1,6 +1,7 @@ 'use strict'; const url = require('url'); +const querystring = require('querystring'); const plugins = require('../plugins'); const meta = require('../meta'); @@ -40,11 +41,11 @@ async function rewrite(req, res, next) { const { pathname } = parsedUrl; const hook = `action:homepage.get:${pathname}`; if (!plugins.hooks.hasListeners(hook)) { - req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + pathname; + const queryString = querystring.stringify(req.query); + req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + pathname + (queryString ? `?${queryString}` : ''); } else { res.locals.homePageRoute = pathname; } - req.query = Object.assign(parsedUrl.query, req.query); next(); } diff --git a/src/middleware/index.js b/src/middleware/index.js index fd0597fb6e..a23bb6c117 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -170,7 +170,8 @@ middleware.exposeUid = helpers.try(async (req, res, next) => { }); async function expose(exposedField, method, field, req, res, next) { - if (!req.params.hasOwnProperty(field)) { + const _params = { ...req.params }; + if (!_params.hasOwnProperty(field)) { return next(); } const param = String(req.params[field]).toLowerCase(); diff --git a/src/posts/recent.js b/src/posts/recent.js index 2ad84b0c7c..fc114cd9b2 100644 --- a/src/posts/recent.js +++ b/src/posts/recent.js @@ -15,7 +15,7 @@ module.exports = function (Posts) { Posts.getRecentPosts = async function (uid, start, stop, term) { let min = 0; - if (terms[term]) { + if (terms.hasOwnProperty(term) && term[term]) { min = Date.now() - terms[term]; } diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js index b6a516b481..d179c742be 100644 --- a/src/routes/activitypub.js +++ b/src/routes/activitypub.js @@ -40,13 +40,13 @@ module.exports = function (app, middleware, controllers) { app.get('/post/:pid', [...middlewares, middleware.assert.post], controllers.activitypub.actors.note); app.get('/post/:pid/replies', [...middlewares, middleware.assert.post], controllers.activitypub.actors.replies); - app.get('/topic/:tid/:slug?', [...middlewares, middleware.assert.topic], controllers.activitypub.actors.topic); + app.get('/topic/:tid{/:slug}', [...middlewares, middleware.assert.topic], controllers.activitypub.actors.topic); app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], controllers.activitypub.getInbox); app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], controllers.activitypub.postInbox); app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.getCategoryOutbox); app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox); - app.get('/category/:cid/:slug?', [...middlewares, middleware.assert.category], controllers.activitypub.actors.category); + app.get('/category/:cid{/:slug}', [...middlewares, middleware.assert.category], controllers.activitypub.actors.category); app.get('/message/:mid', [...middlewares, middleware.assert.message], controllers.activitypub.actors.message); }; diff --git a/src/routes/admin.js b/src/routes/admin.js index 89a3050ee6..fc89bf8413 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -18,7 +18,7 @@ module.exports = function (app, name, middleware, controllers) { helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id/analytics`, middlewares, controllers.admin.categories.getAnalytics); helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id/federation`, middlewares, controllers.admin.categories.getFederation); - helpers.setupAdminPageRoute(app, `/${name}/manage/privileges/:cid?`, middlewares, controllers.admin.privileges.get); + helpers.setupAdminPageRoute(app, `/${name}/manage/privileges{/:cid}`, middlewares, controllers.admin.privileges.get); helpers.setupAdminPageRoute(app, `/${name}/manage/tags`, middlewares, controllers.admin.tags.get); helpers.setupAdminPageRoute(app, `/${name}/manage/users`, middlewares, controllers.admin.users.index); diff --git a/src/routes/api.js b/src/routes/api.js index 0fe575a326..8b074d99bf 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -18,7 +18,7 @@ module.exports = function (app, middleware, controllers) { router.get('/user/email/:email', [...middlewares, middleware.canViewUsers], helpers.tryRoute(controllers.user.getUserByEmail)); router.get('/categories/:cid/moderators', [...middlewares], helpers.tryRoute(controllers.api.getModerators)); - router.get('/recent/posts/:term?', [...middlewares], helpers.tryRoute(controllers.posts.getRecentPosts)); + router.get('/recent/posts{/:term}', [...middlewares], helpers.tryRoute(controllers.posts.getRecentPosts)); router.get('/unread/total', [...middlewares, middleware.ensureLoggedIn], helpers.tryRoute(controllers.unread.unreadTotal)); router.get('/topic/teaser/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.teaser)); router.get('/topic/pagination/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.pagination)); diff --git a/src/routes/index.js b/src/routes/index.js index 71f722f397..9c551aae51 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -36,7 +36,7 @@ _mounts.main = (app, middleware, controllers) => { setupPageRoute(app, '/confirm/:code', [], controllers.confirmEmail); setupPageRoute(app, '/outgoing', [], controllers.outgoing); setupPageRoute(app, '/search', [], controllers.search.search); - setupPageRoute(app, '/reset/:code?', [middleware.delayLoading], controllers.reset); + setupPageRoute(app, '/reset{/:code}', [middleware.delayLoading], controllers.reset); setupPageRoute(app, '/tos', [], controllers.termsOfUse); setupPageRoute(app, '/email/unsubscribe/:token', [], controllers.accounts.settings.unsubscribe); @@ -48,7 +48,7 @@ _mounts.main = (app, middleware, controllers) => { _mounts.mod = (app, middleware, controllers) => { setupPageRoute(app, '/flags', [], controllers.mods.flags.list); setupPageRoute(app, '/flags/:flagId', [], controllers.mods.flags.detail); - setupPageRoute(app, '/post-queue/:id?', [], controllers.mods.postQueue); + setupPageRoute(app, '/post-queue{/:id}', [], controllers.mods.postQueue); }; _mounts.globalMod = (app, middleware, controllers) => { @@ -57,8 +57,8 @@ _mounts.globalMod = (app, middleware, controllers) => { }; _mounts.topic = (app, name, middleware, controllers) => { - setupPageRoute(app, `/${name}/:topic_id/:slug/:post_index?`, [], controllers.topics.get); - setupPageRoute(app, `/${name}/:topic_id/:slug?`, [], controllers.topics.get); + setupPageRoute(app, `/${name}/:topic_id/:slug{/:post_index}`, [], controllers.topics.get); + setupPageRoute(app, `/${name}/:topic_id{/:slug}`, [], controllers.topics.get); }; _mounts.post = (app, name, middleware, controllers) => { @@ -86,7 +86,7 @@ _mounts.categories = (app, name, middleware, controllers) => { _mounts.category = (app, name, middleware, controllers) => { setupPageRoute(app, `/${name}/:category_id/:slug/:topic_index`, [], controllers.category.get); - setupPageRoute(app, `/${name}/:category_id/:slug?`, [], controllers.category.get); + setupPageRoute(app, `/${name}/:category_id{/:slug}`, [], controllers.category.get); }; _mounts.users = (app, name, middleware, controllers) => { @@ -131,9 +131,13 @@ module.exports = async function (app, middleware) { } }); - router.all('(/+api|/+api/*?)', middleware.prepareAPI); - router.all(`(/+api/admin|/+api/admin/*?${mounts.admin !== 'admin' ? `|/+api/${mounts.admin}|/+api/${mounts.admin}/*?` : ''})`, middleware.authenticateRequest, middleware.ensureLoggedIn, middleware.admin.checkPrivileges); - router.all(`(/+admin|/+admin/*?${mounts.admin !== 'admin' ? `|/+${mounts.admin}|/+${mounts.admin}/*?` : ''})`, middleware.ensureLoggedIn, middleware.applyCSRF, middleware.admin.checkPrivileges); + // TODO: upgrade these routes to express 5.x + // router.all('(/+api|/+api/*?)', middleware.prepareAPI); + router.all(['/api/*splat', '/api'], middleware.prepareAPI); + + // router.all(`(/+api/admin|/+api/admin/*?${mounts.admin !== 'admin' ? `|/+api/${mounts.admin}|/+api/${mounts.admin}/*?` : ''})`, middleware.authenticateRequest, middleware.ensureLoggedIn, middleware.admin.checkPrivileges); + + // router.all(`(/+admin|/+admin/*?${mounts.admin !== 'admin' ? `|/+${mounts.admin}|/+${mounts.admin}/*?` : ''})`, middleware.ensureLoggedIn, middleware.applyCSRF, middleware.admin.checkPrivileges); app.use(middleware.stripLeadingSlashes); @@ -195,8 +199,8 @@ function addCoreRoutes(app, router, middleware, mounts) { res.redirect(`${relativePath}/assets/plugins${req.path}${req._parsedUrl.search || ''}`); }); - app.use(`${relativePath}/assets/client-*.css`, middleware.buildSkinAsset); - app.use(`${relativePath}/assets/client-*-rtl.css`, middleware.buildSkinAsset); + app.use(`${relativePath}/assets/client-*splat.css`, middleware.buildSkinAsset); + app.use(`${relativePath}/assets/client-*splat-rtl.css`, middleware.buildSkinAsset); app.use(controllers['404'].handle404); app.use(controllers.errors.handleURIErrors); diff --git a/src/routes/user.js b/src/routes/user.js index c2d1cc6f32..c933767d37 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -17,8 +17,8 @@ module.exports = function (app, name, middleware, controllers) { ]; setupPageRoute(app, '/me', [], middleware.redirectMeToUserslug); - setupPageRoute(app, '/me/*', [], middleware.redirectMeToUserslug); - setupPageRoute(app, '/uid/:uid*', [], middleware.redirectUidToUserslug); + setupPageRoute(app, '/me/*splat', [], middleware.redirectMeToUserslug); + setupPageRoute(app, '/uid{/:uid}', [], middleware.redirectUidToUserslug); setupPageRoute(app, `/${name}/:userslug`, middlewares, controllers.accounts.profile.get); setupPageRoute(app, `/${name}/:userslug/following`, middlewares, controllers.accounts.follow.getFollowing); @@ -51,8 +51,8 @@ module.exports = function (app, name, middleware, controllers) { setupPageRoute(app, `/${name}/:userslug/sessions`, accountMiddlewares, controllers.accounts.sessions.get); setupPageRoute(app, '/notifications', [middleware.ensureLoggedIn], controllers.accounts.notifications.get); - setupPageRoute(app, `/${name}/:userslug/chats/:roomid?/:index?`, [middleware.exposeUid, middleware.canViewUsers], controllers.accounts.chats.get); - setupPageRoute(app, '/chats/:roomid?/:index?', [middleware.ensureLoggedIn], controllers.accounts.chats.redirectToChat); + setupPageRoute(app, `/${name}/:userslug/chats{/:roomid}{/:index}`, [middleware.exposeUid, middleware.canViewUsers], controllers.accounts.chats.get); + setupPageRoute(app, '/chats{/:roomid}{/:index}', [middleware.ensureLoggedIn], controllers.accounts.chats.redirectToChat); setupPageRoute(app, `/message/:mid`, [middleware.ensureLoggedIn], controllers.accounts.chats.redirectToMessage); }; diff --git a/src/routes/well-known.js b/src/routes/well-known.js index 5ea46c41f6..83f8cb0895 100644 --- a/src/routes/well-known.js +++ b/src/routes/well-known.js @@ -26,7 +26,7 @@ module.exports = function (app, middleware, controllers) { }); }); - app.get('/nodeinfo/2.0(.json)?', helpers.tryRoute(async (req, res) => { + app.get('/nodeinfo/2.0{.json}', helpers.tryRoute(async (req, res) => { const getDaysInMonth = (year, month) => new Date(year, month, 0).getDate(); function addMonths(input, months) { diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index 829dd56df9..b1525d33f8 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -42,7 +42,8 @@ module.exports = function () { setupApiRoute(router, 'get', '/:pid/replies', [middleware.assert.post], controllers.write.posts.getReplies); // Shorthand route to access post routes by topic index - router.all('/+byIndex/:index*?', [middleware.checkRequired.bind(null, ['tid'])], controllers.write.posts.redirectByIndex); + // TODO: upgrade to express 5 + // router.all('/+byIndex/:index*?', [middleware.checkRequired.bind(null, ['tid'])], controllers.write.posts.redirectByIndex); return router; }; diff --git a/src/routes/write/users.js b/src/routes/write/users.js index 139ed483c3..d52933ec7f 100644 --- a/src/routes/write/users.js +++ b/src/routes/write/users.js @@ -62,7 +62,8 @@ function authenticatedRoutes() { setupApiRoute(router, 'post', '/:uid/exports/:type', [...middlewares, middleware.assert.user, middleware.checkAccountPermissions], controllers.write.users.generateExportsByType); // Shorthand route to access user routes by userslug - router.all('/+bySlug/:userslug*?', [], controllers.write.users.redirectBySlug); + // TODO: upgrade to express 5 + // router.all('/+bySlug/:userslug*?', [], controllers.write.users.redirectBySlug); } module.exports = function () { From 880768b944736979f690c8ec5eb75acd66630bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 9 Apr 2025 12:53:51 -0400 Subject: [PATCH 2/6] some req.query fixes --- src/controllers/search.js | 21 +++++++++++---------- src/controllers/unread.js | 4 ++-- src/middleware/headers.js | 2 ++ src/routes/write/posts.js | 5 ++++- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/controllers/search.js b/src/controllers/search.js index 8b21189e7d..081118dcc5 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -32,10 +32,11 @@ searchController.search = async function (req, res, next) { 'search:content': privileges.global.can('search:content', req.uid), 'search:tags': privileges.global.can('search:tags', req.uid), }); - req.query.in = req.query.in || meta.config.searchDefaultIn || 'titlesposts'; - let allowed = (req.query.in === 'users' && userPrivileges['search:users']) || - (req.query.in === 'tags' && userPrivileges['search:tags']) || - (req.query.in === 'categories') || + const query = { ...req.query }; + query.in = req.query.in || meta.config.searchDefaultIn || 'titlesposts'; + let allowed = (query.in === 'users' && userPrivileges['search:users']) || + (query.in === 'tags' && userPrivileges['search:tags']) || + (query.in === 'categories') || (['titles', 'titlesposts', 'posts', 'bookmarks'].includes(req.query.in) && userPrivileges['search:content']); ({ allowed } = await plugins.hooks.fire('filter:search.isAllowed', { uid: req.uid, @@ -47,20 +48,20 @@ searchController.search = async function (req, res, next) { } if (req.query.categories && !Array.isArray(req.query.categories)) { - req.query.categories = [req.query.categories]; + query.categories = [req.query.categories]; } if (req.query.hasTags && !Array.isArray(req.query.hasTags)) { - req.query.hasTags = [req.query.hasTags]; + query.hasTags = [req.query.hasTags]; } const data = { query: req.query.term, - searchIn: req.query.in, + searchIn: query.in, matchWords: req.query.matchWords || 'all', postedBy: req.query.by, - categories: req.query.categories, + categories: query.categories, searchChildren: req.query.searchChildren, - hasTags: req.query.hasTags, + hasTags: query.hasTags, replies: validator.escape(String(req.query.replies || '')), repliesFilter: validator.escape(String(req.query.repliesFilter || '')), timeRange: validator.escape(String(req.query.timeRange || '')), @@ -70,7 +71,7 @@ searchController.search = async function (req, res, next) { page: page, itemsPerPage: req.query.itemsPerPage, uid: req.uid, - qs: req.query, + qs: query, }; const [searchData] = await Promise.all([ diff --git a/src/controllers/unread.js b/src/controllers/unread.js index 9ff73da6ff..64e5257d38 100644 --- a/src/controllers/unread.js +++ b/src/controllers/unread.js @@ -59,8 +59,8 @@ unreadController.get = async function (req, res) { }); if (userSettings.usePagination && (page < 1 || page > data.pageCount)) { - req.query.page = Math.max(1, Math.min(data.pageCount, page)); - return helpers.redirect(res, `/unread?${querystring.stringify(req.query)}`); + const query = { ...req.query, page: Math.max(1, Math.min(data.pageCount, page))}; + return helpers.redirect(res, `/unread?${querystring.stringify(query)}`); } data.canPost = canPost; data.showSelect = true; diff --git a/src/middleware/headers.js b/src/middleware/headers.js index e424f8af95..5a6f3e2681 100644 --- a/src/middleware/headers.js +++ b/src/middleware/headers.js @@ -86,6 +86,7 @@ module.exports = function (middleware) { if (req.query.lang) { const langs = await listCodes(); if (!langs.includes(req.query.lang)) { + // TODO: cant set req.query.lang req.query.lang = meta.config.defaultLang; } return next(); @@ -97,6 +98,7 @@ module.exports = function (middleware) { if (!lang) { return next(); } + // TODO: cant set req.query.lang req.query.lang = lang; } diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index b1525d33f8..efbb52fc2e 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -43,7 +43,10 @@ module.exports = function () { // Shorthand route to access post routes by topic index // TODO: upgrade to express 5 - // router.all('/+byIndex/:index*?', [middleware.checkRequired.bind(null, ['tid'])], controllers.write.posts.redirectByIndex); + + // router.all('/+byIndex/:index*?', [ + // middleware.checkRequired.bind(null, ['tid']), + // ], controllers.write.posts.redirectByIndex); return router; }; From 7ce9fe6c9496e81889a14dda893b87f6620b6311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 9 Apr 2025 12:56:53 -0400 Subject: [PATCH 3/6] lint --- src/routes/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/index.js b/src/routes/index.js index 9c551aae51..eb38a62dcd 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -135,8 +135,10 @@ module.exports = async function (app, middleware) { // router.all('(/+api|/+api/*?)', middleware.prepareAPI); router.all(['/api/*splat', '/api'], middleware.prepareAPI); + // eslint-disable-next-line max-len // router.all(`(/+api/admin|/+api/admin/*?${mounts.admin !== 'admin' ? `|/+api/${mounts.admin}|/+api/${mounts.admin}/*?` : ''})`, middleware.authenticateRequest, middleware.ensureLoggedIn, middleware.admin.checkPrivileges); + // eslint-disable-next-line max-len // router.all(`(/+admin|/+admin/*?${mounts.admin !== 'admin' ? `|/+${mounts.admin}|/+${mounts.admin}/*?` : ''})`, middleware.ensureLoggedIn, middleware.applyCSRF, middleware.admin.checkPrivileges); app.use(middleware.stripLeadingSlashes); From 8297f044690aa7496fbe92acf3806b48a348d2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 9 Apr 2025 13:05:54 -0400 Subject: [PATCH 4/6] test: api tests no longer work --- test/api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/api.js b/test/api.js index d10433a88b..f1cd540290 100644 --- a/test/api.js +++ b/test/api.js @@ -362,6 +362,8 @@ describe('API', async () => { path: (prefix || '') + dispatch.route.path, }; } else if (dispatch.name === 'router') { + console.log(dispatch); + // TODO: dispatch.regexp no longer exists const prefix = dispatch.regexp.toString().replace('/^', '').replace('\\/?(?=\\/|$)/i', '').replace(/\\\//g, '/'); return buildPaths(dispatch.handle.stack, prefix); } @@ -373,7 +375,7 @@ describe('API', async () => { return _.flatten(paths); }; - let paths = buildPaths(webserver.app._router.stack).filter(Boolean).map((pathObj) => { + let paths = buildPaths(webserver.app.router.stack).filter(Boolean).map((pathObj) => { pathObj.path = pathObj.path.replace(/\/:([^\\/]+)/g, '/{$1}'); return pathObj; }); From c51022993b032c7b4c3618dde7ca10435aa884f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 13 Feb 2026 21:41:49 -0500 Subject: [PATCH 5/6] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 7005852a65ed90966945ff61d4e9ecc49e173b0a Merge: efd322737d 71d4a6fc4e Author: Barış Soner Uşaklı Date: Fri Feb 13 20:14:04 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit efd322737d2b683441568b7c588d8ef8c2d2fadf Author: Barış Soner Uşaklı Date: Fri Feb 13 20:13:59 2026 -0500 moved to harmony commit 71d4a6fc4e98117d203f93b083a4804bdecaf718 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Feb 13 12:29:23 2026 -0500 fix(deps): update dependency sortablejs to v1.15.7 (#13985) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ff292f7dee5890b1711b3347b2baad94d77e045e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Feb 13 12:29:07 2026 -0500 fix(deps): update dependency nodebb-plugin-composer-default to v10.3.16 (#13991) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 25f866cac13a8abb8d49aa8ab01ae4f7571c26a4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Feb 13 12:27:29 2026 -0500 chore(deps): update postgres docker tag to v18.2 (#13987) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1ca9841ce5c1283b68b4f3a5acb93efa33bdc657 Author: Barış Soner Uşaklı Date: Fri Feb 13 12:26:54 2026 -0500 fix: dont call getInbox for /recent make sure there are no dupes if called commit 3756a8fe6cc7038b3536eb63a0bfd2ddfd4dc2a4 Author: Barış Soner Uşaklı Date: Fri Feb 13 11:29:16 2026 -0500 refactor: updateTags to modern js commit a8c68ddc65c446fcda641eef174c835e2a6c8609 Author: Barış Soner Uşaklı Date: Fri Feb 13 10:39:54 2026 -0500 test: fix redis, from was string in map, but int in notif object commit 8c8782fd242e9192ea51608dab6da706353f9aef Author: Barış Soner Uşaklı Date: Fri Feb 13 10:17:34 2026 -0500 fix: #13990, don't blindly set `user` field on notification objects that don't have a "from" property commit a84464cffbcf8310ed3176e8a50ee977e5a96104 Author: Barış Soner Uşaklı Date: Fri Feb 13 09:45:56 2026 -0500 chore: up themes commit 0e2a42d547ab1736fb4f92c727500051a4f6d49b Author: Barış Soner Uşaklı Date: Thu Feb 12 23:22:10 2026 -0500 test: fix spec commit 0b7df274c3ff973fc761c9b454f48a26f492f29b Author: Barış Soner Uşaklı Date: Thu Feb 12 22:49:55 2026 -0500 fix: unbans not triggering if user data is loaded wit 'banned' property only this was happening because of `fieldsToRemove` running before unban logic and clearing out 'banned:expire' field to undefined commit b0f2feadf48964e438a2ae9d663c56821248e62d Author: Barış Soner Uşaklı Date: Thu Feb 12 21:26:40 2026 -0500 refactor: shorter check commit 1020092b978e8e6a5b8ffcefb4187ea3da9e3949 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Feb 12 21:02:52 2026 -0500 fix(deps): update dependency webpack to v5.105.2 (#13986) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6e4e02a68bb56eed1bc0c6bd01adc61a4eb8646b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Feb 12 21:02:40 2026 -0500 fix(deps): update dependency qs to v6.14.2 (#13978) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e61989a3207b984ad770c8a81031c3e4cf2824df Author: Barış Soner Uşaklı Date: Thu Feb 12 18:23:36 2026 -0500 add data-sort-value, post size has an input in column commit 4b9b3648c107a1af33f4a4952b532c28d107c66a Author: Barış Soner Uşaklı Date: Thu Feb 12 18:10:19 2026 -0500 fix: #13894, buildCategoryLabel helper checks fa-nbb-none commit e16c56777d916b8200d884619aedcc89f6de5907 Author: Barış Soner Uşaklı Date: Thu Feb 12 18:04:48 2026 -0500 fix: missing gap commit 5c73d33812daabcdd275011cc78fb2a7e6e5e6a3 Author: Barış Soner Uşaklı Date: Thu Feb 12 17:14:45 2026 -0500 test: fix missing priv in tests commit 0708cf18e9c16128a4e0ee843a28adb2533915ac Merge: 5b5960244e 7f6fcd05ff Author: Barış Soner Uşaklı Date: Thu Feb 12 15:50:26 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 5b5960244e7809ea87608791904a515306c50f26 Author: Barış Soner Uşaklı Date: Thu Feb 12 15:50:21 2026 -0500 brite btn fix commit 7f6fcd05ff0ef6e736a44a50df4bd69d5a22cb9b Author: Misty Release Bot Date: Thu Feb 12 20:14:26 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-privileges commit fe8fd9d6e594560f90e5a6efeef223fbc78b01a6 Author: Julian Lam Date: Thu Feb 12 15:13:55 2026 -0500 fix: gate crossposting behind new topics:crosspost privilege commit 5c35dc866c7802716ce2f9da61e5fa7235392821 Author: Julian Lam Date: Thu Feb 12 14:18:19 2026 -0500 feat: introduce new topics:crosspost privilege commit 803473cace2c82222c53d0b46e305821df07ae03 Author: Barış Soner Uşaklı Date: Thu Feb 12 14:41:36 2026 -0500 closes: #13982, dont use btn-group on dropdowns commit 26af029af0d05925e54632786afcaecc1d57f8f1 Author: Barış Soner Uşaklı Date: Thu Feb 12 14:22:51 2026 -0500 https://github.com/NodeBB/NodeBB/issues/13982 commit c4411423b6481ac6a8e7899e2817428284412af9 Author: Julian Lam Date: Thu Feb 12 14:02:49 2026 -0500 fix: #13983, show only local categories in ACP privilege selector commit 292e70f70a9afa8060f51d5c678eeed65f533c79 Author: Julian Lam Date: Thu Feb 12 13:43:35 2026 -0500 fix: add example value for failing schema test commit bafd5db07ced020661118242407de1c75fbbe3a2 Author: Barış Soner Uşaklı Date: Thu Feb 12 12:49:41 2026 -0500 chore: up themes commit 1598004eaa4ef34f3f4371e62bf4505dccf2ef95 Author: Julian Lam Date: Thu Feb 12 12:30:19 2026 -0500 fix: lint commit 7eb491367101a853b8dc5d4602426f000dc45628 Author: Julian Lam Date: Thu Feb 12 12:01:54 2026 -0500 fix: bad relative path commit 781a900c0fccb79d51f6f83fbfafd9d47aa0c180 Author: Misty Release Bot Date: Thu Feb 12 16:52:27 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.topic commit c528d61f166a2baf909695e6b99bbb9e44920e4e Merge: 9da67474c2 072dd1aeb3 Author: Barış Soner Uşaklı Date: Thu Feb 12 11:51:20 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 9da67474c221fae02aab3b86d71674a625e78556 Author: Barış Soner Uşaklı Date: Thu Feb 12 11:51:14 2026 -0500 feat: add guest-cta.tpl and lang strings commit 072dd1aeb3b3246d9cdd17fac1dc6ab675283b1a Author: Julian Lam Date: Thu Feb 12 11:47:17 2026 -0500 docs: OpenAPI schema for rules re-ordering route commit 1dcbcd7ca64bb8dc4454af67e07fd2b562d40d01 Merge: 1204770ae3 64dad9db8d Author: Barış Soner Uşaklı Date: Thu Feb 12 10:38:42 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 1204770ae3aa096f93a089f76349298822be1f94 Author: Barış Soner Uşaklı Date: Thu Feb 12 10:38:37 2026 -0500 fix key commit 64dad9db8dbdac082edc89a7b40b05f91fdb158e Author: Misty Release Bot Date: Thu Feb 12 15:31:47 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-advanced-cache commit 9ac507e5b4d1f73ab0b33e7d39c24ae3133c90c1 Author: Barış Soner Uşaklı Date: Thu Feb 12 10:31:18 2026 -0500 feat: track all caches created in acp closes #13979 commit 0c2ab2326899680694e15cf83d9121892c6a08b3 Author: Barış Soner Uşaklı Date: Wed Feb 11 21:19:43 2026 -0500 test: add test to check picture!=uploadedpicture commit b95cd882149b7d4fb6669b0026874c6d9a88a047 Author: Barış Soner Uşaklı Date: Wed Feb 11 21:14:50 2026 -0500 fix: regression from refactor of uploadedpicture refactor commit f0fb661c29223fdcf7764eb242667052288a7b35 Author: Misty Release Bot Date: Thu Feb 12 01:17:30 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-advanced-cache commit 7336c58cdfe94b0e59feba1de407df789254c6b0 Author: Barış Soner Uşaklı Date: Wed Feb 11 20:17:06 2026 -0500 refactor: cache page to table display notif cache too commit 756e2434ad2416ed26403b781f46022f5e7db577 Author: Misty Release Bot Date: Thu Feb 12 00:28:27 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-chat, nodebb.admin-settings-notifications commit a55651d12f5b659275bf81ceca3408ad1512c7dc Author: Barış Soner Uşaklı Date: Wed Feb 11 19:27:47 2026 -0500 feat: closes #5867, dont email if user already read notification instead of immediately sending emails, put them in ttl cache, once cache entry expires check if the user already read the notification, if its read dont send the email commit fd43368a92abde33ce2380cfaf493a02b5b5e7c3 Author: Julian Lam Date: Wed Feb 11 12:53:59 2026 -0500 feat: allow re-ordering of auto-categorization rules commit 78d7130c7a18e0e8f4ee98a923444329ba8b68ce Author: Julian Lam Date: Wed Feb 11 12:35:14 2026 -0500 fix: organize rules and relays logic to separate methods commit 1747cf81864de2dc9be0c347940b1ca12ff74166 Author: Barış Soner Uşaklı Date: Wed Feb 11 12:37:19 2026 -0500 lint: remove unused regex commit 2cddaf861a00b590e815fc552be2aa9fe215d188 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Feb 11 12:00:26 2026 -0500 fix(deps): update dependency lru-cache to v11.2.6 (#13970) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c4420da354055c2f51f528c4c4bef3104dacddb3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Feb 11 11:55:24 2026 -0500 fix(deps): update dependency satori to v0.19.2 (#13974) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 48929aaedf6cb05ad7bdbddaa0d8a08c5c932f60 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Feb 11 11:50:53 2026 -0500 fix(deps): update dependency webpack to v5.105.1 (#13975) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9608cce693d43579b54ef2500133f0630f7a96c3 Author: Julian Lam Date: Wed Feb 11 11:50:06 2026 -0500 refactor: emoji replacement code into helper function, remove use of regex on untrusted user input commit bb5e711802746ae130281d02a838f2a9102ea27e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Feb 11 11:50:26 2026 -0500 chore(deps): update redis docker tag to v8.6.0 (#13976) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c65af1998565380bd04deee915a152e04547c875 Author: Barış Uşaklı Date: Wed Feb 11 11:38:03 2026 -0500 refactor: add createFieldChecker (#13973) * refactor: add createFieldChecker * refactor: use hasField in topic/data.js * refactor: use hasField in categories/data.js * test: fix category nickname logic * test: fix spec commit 52a807e7956fa90c7b156a7275d1f84b7a576b0b Merge: bc1fd892df d133f9108b Author: Barış Soner Uşaklı Date: Tue Feb 10 22:50:10 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit bc1fd892dfcff3c76bc3b6accdccfe23bc777ae1 Author: Barış Soner Uşaklı Date: Tue Feb 10 22:50:06 2026 -0500 chore: up mentions commit d133f9108b1e5dc12e5d7924e0e9a695ba6288a7 Author: Misty Release Bot Date: Wed Feb 11 03:48:10 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.groups commit 0fd8200a0480be7c6f03bda5c0b84bbf94b51564 Author: Barış Soner Uşaklı Date: Tue Feb 10 22:47:45 2026 -0500 chore: up mentions commit 5976ef42a6a2d5f51aa7044bebd98014e506fe97 Author: Barış Soner Uşaklı Date: Tue Feb 10 22:46:56 2026 -0500 add x-members commit c9f31e6507d292cd73555fe344340464e7e53764 Merge: d2b8d4c136 4bccc311db Author: Barış Soner Uşaklı Date: Tue Feb 10 22:12:02 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit d2b8d4c136c28c95eabec2eb9a17213cc87551fc Author: Barış Soner Uşaklı Date: Tue Feb 10 22:11:58 2026 -0500 quick reply autocomplete style change up mentions commit 4bccc311db03103b704e9bc5bba35b70c79c5210 Author: Misty Release Bot Date: Wed Feb 11 03:03:39 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.groups commit 45ff9f0d45bb9676f49b95192f204823b44f4d34 Author: Barış Soner Uşaklı Date: Tue Feb 10 22:03:15 2026 -0500 feat: add group tx key commit d52b135954bb4c70f80053f5105150f1fe17a5d6 Author: Barış Soner Uşaklı Date: Tue Feb 10 21:43:44 2026 -0500 test: fix hasOwn commit ffc4c0dd99567f204f695480684c748f23ad7c12 Author: Barış Soner Uşaklı Date: Tue Feb 10 21:39:57 2026 -0500 test: group members test commit 87fdca2a8a4fdd73ad4ce59ff8799efd318c9339 Author: Barış Soner Uşaklı Date: Tue Feb 10 21:31:47 2026 -0500 test: add more info to failiing response commit 9a198c382e1ef6faff774efab5ea0cd62a6d64f2 Author: Barış Soner Uşaklı Date: Tue Feb 10 21:12:41 2026 -0500 refactor: run searches in parallel commit 9cd87fca52612f03a8534bbe89aa6a5168c87823 Author: Barış Soner Uşaklı Date: Tue Feb 10 20:26:34 2026 -0500 chore: up mentions commit 06f4f70078426e6b98be5918cdc2af7bd92f8972 Author: Barış Soner Uşaklı Date: Tue Feb 10 20:20:07 2026 -0500 chore: up mentions/composer commit dcbbc187ab656f46af30508d25503d13181bf5cc Author: Barış Soner Uşaklı Date: Tue Feb 10 17:58:04 2026 -0500 fx: filter at the end of user.search remove commented out code commit 4a38d67c553dbc722f4d0e4bd7733ed0dd593357 Author: Julian Lam Date: Tue Feb 10 14:13:11 2026 -0500 fix: #13969, bump mentions commit 62d88555aee73251283419dd49f35e73b10c5729 Author: Barış Uşaklı Date: Tue Feb 10 12:39:11 2026 -0500 feat: eslint10 (#13967) * feat: eslint10 * lint: no-useless-assignment commit e4455b1cb3e0facc9fc9f309b2097bb3b8b7de63 Author: Misty Release Bot Date: Tue Feb 10 15:38:02 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-web-crawler commit fe35ad4f47fa752b68b8a938e0c18a5ab6ad6102 Author: Barış Soner Uşaklı Date: Tue Feb 10 10:37:02 2026 -0500 feat: closes #13968, add sitemap cache duration commit 4aac65624811864a198949da6a2a3a1cbdfff727 Author: Barış Soner Uşaklı Date: Tue Feb 10 10:21:56 2026 -0500 chore: up link-preview commit abcfc1a5ae533c5a41542e1d97c2798a622db034 Author: Barış Soner Uşaklı Date: Tue Feb 10 09:46:23 2026 -0500 feat: add data-field values from tpl into search dict closes #9709 remove quotes from strings so show help actually finds show "help" tab commit 05dd46c31fd09dad94a9a8e1adb513cc492fe7dd Author: Barış Soner Uşaklı Date: Mon Feb 9 18:23:29 2026 -0500 test: fix spec, remove log commit 4edec6aa19b6fc7c396ea68108af90aa12e152c1 Merge: 52f1c7372c 9ebd8f4f6a Author: Barış Soner Uşaklı Date: Mon Feb 9 17:03:50 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 52f1c7372c468c758e07d6b03f2e6d514d15bbaa Author: Barış Soner Uşaklı Date: Mon Feb 9 14:01:59 2026 -0500 remove generatedTitle from hash, its added later commit 9ebd8f4f6ad402940753975646ac021eae72b9e6 Author: Julian Lam Date: Mon Feb 9 13:56:26 2026 -0500 fix: lint commit 0fe75acf47c49f8cb3e3e6b09d16afd54b48d6a0 Merge: ca237e6766 9f1369a272 Author: Barış Soner Uşaklı Date: Mon Feb 9 13:53:01 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 9f1369a272f250e2e61e76b197ca39e224289c93 Author: Julian Lam Date: Mon Feb 9 13:45:31 2026 -0500 fix: #13962, infinite scroll and pagination not working on world commit ca237e676610c9548c09f585fae8dda408865504 Author: Barış Soner Uşaklı Date: Mon Feb 9 12:53:02 2026 -0500 test: favicon test commit c61326dfa81cbc8b27f18579564138f1b0d6b556 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Feb 9 12:42:40 2026 -0500 chore(deps): update dependency @stylistic/eslint-plugin to v5.8.0 (#13965) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3f67a0002a0f0953f71aa33e01ad158136b2e4b6 Author: Barış Soner Uşaklı Date: Mon Feb 9 12:41:45 2026 -0500 refactor: shorter commit 040567ad45bd92f6f4baeb941b6e8976cf238d7f Merge: 007efc21d4 b527cb5a37 Author: Barış Soner Uşaklı Date: Mon Feb 9 12:41:27 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit b527cb5a3756ad0d3a9c3bb470dbe914ca261ff9 Author: Misty Release Bot Date: Mon Feb 9 17:19:45 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.world commit 76fe4bdd983a5944f7b2d99855ce57260ff9d912 Author: Julian Lam Date: Mon Feb 9 12:19:06 2026 -0500 fix: bump themes, l10n world sort label commit 007efc21d42d2de9f7baf7e1b7f6cc854d428a4d Author: Barış Soner Uşaklı Date: Mon Feb 9 11:47:28 2026 -0500 feat: closes https://github.com/NodeBB/NodeBB/pull/11970 allow favicons via upload plugins dont hardcoded favicon url to /assets/uploads and use whats saved in acp field commit c2695d89ee909a793df93b0ff57c695c34298833 Author: Julian Lam Date: Mon Feb 9 10:24:32 2026 -0500 chore: forcibly resetting all translations for custom-reason.json commit 433d318f2760bb0c643e9054cb1874e2b172c3a8 Author: Julian Lam Date: Mon Feb 9 10:07:43 2026 -0500 fix: rename translations as well commit dda0480abfffb13d48682fe7b236c0d6bb2896ae Author: Barış Soner Uşaklı Date: Sun Feb 8 23:41:21 2026 -0500 simplify returnPath logic via composer default commit e4d852b4ca69a555f67006dfa53e177c9aa60c05 Author: Barış Soner Uşaklı Date: Sun Feb 8 20:46:16 2026 -0500 test: dont return cover:url if its not requested commit db07456bc01fdf4bd6db2321d60c9ccabbae3a6b Author: Barış Soner Uşaklı Date: Sun Feb 8 20:30:01 2026 -0500 test: fix username test move cover:url code to user/data.js like uploadedpicture commit 694b545c1ff1ce529b4d391213becd86b2700bc4 Author: Barış Soner Uşaklı Date: Sun Feb 8 19:33:41 2026 -0500 test: fix test that explodes on new URL commit 94873c33e1fc96e7ae11f5c9ad68ea600b33fec3 Author: Barış Soner Uşaklı Date: Sun Feb 8 19:21:35 2026 -0500 test: another test fix commit 6cb6cf7d4d7fd37c639107890ee9669d7157cc4b Author: Barış Soner Uşaklı Date: Sun Feb 8 19:15:08 2026 -0500 fix: cant store URL in nconf commit 7f4d537d4f4a100a86147b87e83b35ec731abe77 Author: Barış Soner Uşaklı Date: Sun Feb 8 18:55:11 2026 -0500 refactor: remove unused url commit 157959df6086fc37a57e6e32e9ce0dd25acb83ef Author: Barış Soner Uşaklı Date: Sun Feb 8 18:52:55 2026 -0500 refactor: get rid of url.parse in core commit fb460725ad679554034147600904207a66821c79 Author: Barış Soner Uşaklı Date: Sun Feb 8 13:14:33 2026 -0500 error:invalid-username doesnt have params commit 7703140b7c309480d2d16f1016f045887aec6f89 Author: Barış Uşaklı Date: Sun Feb 8 13:11:40 2026 -0500 Xregexp remove, dont allow invalid slugs (#13963) * feat: remove xregexp add slugify tests, dont accept invalid slugs like `.`, `..` add isSlugValid function * test: add more tests, check slug on rename as well commit d6b7f27c65b610fdc3668c86f1190f0ac0d02c88 Author: Barış Soner Uşaklı Date: Sun Feb 8 10:01:18 2026 -0500 chore: up harmony commit c3c35b798b357fe6a5c4f35c07f82e0a59e2ebb8 Author: Barış Soner Uşaklı Date: Sun Feb 8 09:57:39 2026 -0500 use align-items-baseline commit 05d4d8576630399b28cda00977daaae920a7f8d2 Author: Barış Soner Uşaklı Date: Sat Feb 7 21:47:17 2026 -0500 test: fix file name commit 91dc3873be9edc8f895263a3f4fa266a57e2fcff Merge: d601847a76 b04d2dbc6f Author: Barış Soner Uşaklı Date: Sat Feb 7 21:46:45 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit d601847a768d81fda0aa9ed44b7f3a5eaacc7ffa Author: Barış Soner Uşaklı Date: Sat Feb 7 21:46:40 2026 -0500 test: fix spec commit b04d2dbc6f623a81f4132f78634659d4f74bc946 Author: Misty Release Bot Date: Sun Feb 8 02:42:24 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-users, nodebb.notifications commit 0eaf2beeb24ad26cb8132c2110a1e49e644d6e4f Author: Barış Soner Uşaklı Date: Sat Feb 7 21:41:11 2026 -0500 feat: closes #13961, rename ban-reasons to custom reasons use them for ban, mute and post queue depending on the type selected if type is set to all, the reason is displayed in ban/mute and post queue move reason label + dropdown + textarea to a partial commit 1d17352f67067efb670f7a6de1dfa61b42e1d90d Author: Julian Lam Date: Sat Feb 7 14:56:08 2026 -0500 chore: new fallbacks commit 1e109c2ecd4497d8b424fdae83d278c0986ee8c6 Author: Barış Soner Uşaklı Date: Sat Feb 7 14:44:30 2026 -0500 fix: update tx config commit 2c0a60c49bed25a7a26946c05b9ef9c87bd466e9 Author: Misty Release Bot Date: Sat Feb 7 19:32:30 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-users commit d086ed2c27c779dacd7581a0c14fc53a23496459 Author: Barış Uşaklı Date: Sat Feb 7 14:32:05 2026 -0500 feat: ban/mute reasons (#13960) add acp page to create reasons add dropdown to insert them into reason change reason field into textarea translate and parse reason before sending ban email commit 15ba76e330b5d86ffe8c8df0a914bf264e14f244 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Feb 7 11:38:42 2026 -0500 fix(deps): update dependency esbuild to v0.27.3 (#13957) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit fe66c812bc2ec959b2d354dcb712ace849196d47 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Feb 7 11:38:15 2026 -0500 fix(deps): update dependency semver to v7.7.4 (#13958) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ab60c39cbfab4975e064af37979253710332ffbc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Feb 7 08:47:35 2026 -0500 fix(deps): update dependency nodemailer to v8 (#13951) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1da745cd8e109229b7b298561d70158f10555f3c Author: Barış Soner Uşaklı Date: Fri Feb 6 23:09:51 2026 -0500 fix menu commit aef0bd97cd9e1185994f7eba1b4a77fbdd6de18a Author: Barış Soner Uşaklı Date: Fri Feb 6 16:54:48 2026 -0500 fix: derpy api page commit 13422bc8220fb1951d9bc16018a21391f5d3d3c4 Author: Julian Lam Date: Fri Feb 6 14:45:23 2026 -0500 fix: guard against incomplete objects when building context/chain commit 725107347b4c2dbd4bd6e77412291e39a58ee306 Author: Barış Soner Uşaklı Date: Fri Feb 6 14:29:59 2026 -0500 chore: up harmony commit 2a5ab6dd5f8c76002d6e1c2c480a101447b4d56b Author: Barış Soner Uşaklı Date: Fri Feb 6 14:20:49 2026 -0500 chore: up harmony commit 7d4a440a996db62c8fc869ef34bfcdae8a7c39f9 Author: Barış Soner Uşaklı Date: Fri Feb 6 14:12:47 2026 -0500 chore: up harmony commit b62337d014ed21bdf8bb6dc771a0b7ef9a41609c Merge: d9fa746483 5bf6b33531 Author: Barış Soner Uşaklı Date: Fri Feb 6 14:12:02 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit d9fa746483814c73b698f69eb081b8e844a7e1c2 Author: Barış Soner Uşaklı Date: Fri Feb 6 14:10:30 2026 -0500 my flags commit 5bf6b335318e64397894ebcd5300541c962ebeb6 Author: Misty Release Bot Date: Fri Feb 6 19:08:06 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.category, nodebb.world commit 0fbaa937e44eead86430e4a7d620305a0719ed20 Author: Julian Lam Date: Fri Feb 6 14:07:25 2026 -0500 Refactor /world page, closes #13927 (#13954) * refactor: wholesale UI/data refactor of world to display in feed-like format * fix: openapi schema * fix: remove console log * fix: restrict 'generatedTitle' from being passed-in via topics API * fix(deps): bumping themes for world refactor support * fix: /world title and description update * fix: missing handleIgnoreWatch in world client side js commit 86c6270890702d08da06778516894d42158dd194 Author: Barış Soner Uşaklı Date: Fri Feb 6 13:53:07 2026 -0500 chore: up markdown commit 3de603f6db1553987c3531b7c5d6250efadae771 Author: Barış Soner Uşaklı Date: Fri Feb 6 13:35:55 2026 -0500 chore: up composer commit b380765680b7fa2ae5dc13e5bc084a122aff9641 Author: Barış Soner Uşaklı Date: Fri Feb 6 11:38:51 2026 -0500 chore: up deps commit fd50b266c68085ca7529b7be11993b6300453039 Author: Barış Soner Uşaklı Date: Fri Feb 6 10:10:43 2026 -0500 Add utilities to hide first/last child elements commit a241c624165c70e6b92431dfd806d17b6ef118be Author: Barış Soner Uşaklı Date: Fri Feb 6 09:22:48 2026 -0500 chore: up deps commit a8a1089eda4d723e9878f64b506dcc72aee4e6e7 Author: Barış Soner Uşaklı Date: Fri Feb 6 09:03:59 2026 -0500 fix: closes #13953, show uid pic in post queue notification commit 0b45e73fd5a25d5a1548d2f9948e521db663c028 Merge: f06557b7a4 2a8b6d4462 Author: Barış Soner Uşaklı Date: Thu Feb 5 22:07:35 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit f06557b7a4725aeac2cc4acac3054ad146831123 Author: Barış Soner Uşaklı Date: Thu Feb 5 22:07:25 2026 -0500 chore: up composer commit 2a8b6d44628cd5daf52e6d6cd4598bd8d404ce0f Author: Misty Release Bot Date: Fri Feb 6 02:58:06 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.notifications commit 3bd6ce3fe85b4fcf68d257158d1a8ed458fc18f2 Author: Barış Soner Uşaklı Date: Thu Feb 5 21:57:41 2026 -0500 fix category notif commit 30b9e88f94f0015fa86d5543465c68f8a9dd0f76 Merge: 0d19294a17 304a2ab1d3 Author: Barış Soner Uşaklı Date: Thu Feb 5 21:53:44 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 0d19294a1704cc97870c84c686de22cf4c20c436 Author: Barış Soner Uşaklı Date: Thu Feb 5 21:53:39 2026 -0500 test: fix tests commit 304a2ab1d3f012d67cf8083f49e59c5cd48fa910 Author: Misty Release Bot Date: Fri Feb 6 02:28:42 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.notifications commit 89f8ce68c30e36c84f02f494a602bf236a0ca8fb Author: Barış Soner Uşaklı Date: Thu Feb 5 21:28:12 2026 -0500 remove brs commit 4d3c89c14a7325e8d1dc748dc09c0516aa78b294 Author: Barış Soner Uşaklı Date: Thu Feb 5 21:16:09 2026 -0500 update text contain commit 118ceb72bf6a6c145a0b52f20b62fe9f4f5e3d4a Author: Barış Soner Uşaklı Date: Thu Feb 5 20:57:08 2026 -0500 chore: harmony commit 43c84f4b50fb8070f6f0ba817dd4d5a6091cb4cb Author: Barış Soner Uşaklı Date: Thu Feb 5 20:56:24 2026 -0500 add text-contain commit 3c6804d43c034ceaf5e0a8a14bf79964f9c5856d Author: Misty Release Bot Date: Fri Feb 6 01:19:50 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.notifications commit d6c694652cdffaeb08e63d68ee1b3a92338d0975 Author: Barış Soner Uşaklı Date: Thu Feb 5 20:19:16 2026 -0500 test: fix tests, update mentions composer commit 18c04d34a659ce2fa658e1180607eaa538cb57ae Author: Barış Soner Uşaklı Date: Thu Feb 5 19:44:30 2026 -0500 fix: remote post notifs missing bodyLong commit 11d4dbcc8dbcf8e2553dd611d5670a2b5bfdb0b9 Author: Barış Soner Uşaklı Date: Thu Feb 5 19:35:13 2026 -0500 closes #12545 commit a2f4c185e55a5c058f68e4d463fef99223424b49 Author: Barış Soner Uşaklı Date: Thu Feb 5 19:34:24 2026 -0500 refactor: use translator.compile which escapes % and , commit 381334f42461a87e00ecc4100ff7e556818444d2 Author: Julian Lam Date: Thu Feb 5 14:49:51 2026 -0500 fix: double-ajaxify on socket connect commit f279575324705637caa6330113ece6c6f0ec7eb9 Author: Barış Soner Uşaklı Date: Thu Feb 5 14:32:26 2026 -0500 chore: up harmony commit e0bbee48b26d49dfac9b06d9a231f4888ebc20b2 Author: Barış Soner Uşaklı Date: Thu Feb 5 14:29:46 2026 -0500 hide pre commit 6505068185b80fb127093b2742f92157798fd7bd Merge: 5c3f26516d 317bcd893c Author: Barış Soner Uşaklı Date: Thu Feb 5 14:11:41 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 5c3f26516d01a5289834218625e4d066df04444d Author: Barış Soner Uşaklı Date: Thu Feb 5 14:11:37 2026 -0500 chore: up harmony commit 317bcd893c7f4458256ce7c60b5f8c2faa3101e5 Author: Misty Release Bot Date: Thu Feb 5 19:09:16 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.modules, nodebb.notifications commit 30541a9693f0c206c2378cd8928a139185efcfb5 Author: Barış Soner Uşaklı Date: Thu Feb 5 14:08:31 2026 -0500 feat: show bodyLong in notifications, closes #4767 dont show blockquotes show post content in flag notification commit 149d649a6cede1936a205bd16004eca1593b3c5f Author: Barış Soner Uşaklı Date: Thu Feb 5 14:01:33 2026 -0500 fix: dont update teaser for public chats commit 17bfd73edf597c14e4e562f3686a1297e2d2c1d7 Author: Barış Soner Uşaklı Date: Wed Feb 4 21:19:50 2026 -0500 fix: acp graph labels, dont use indices commit 43be594a0d587e56d1478a62c85f2df75cfd1ef7 Author: Barış Soner Uşaklı Date: Wed Feb 4 20:58:23 2026 -0500 test: fix typo in spec commit 2eae987ab3febc0fe6d48201724236f2445011a9 Merge: d1a39554e5 85e99d6b09 Author: Barış Soner Uşaklı Date: Wed Feb 4 20:49:52 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit d1a39554e5e51d87d21d16317b125f362a6b0fd0 Author: Barış Soner Uşaklı Date: Wed Feb 4 20:49:50 2026 -0500 test: fix spec commit 85e99d6b09338a90da483aed1044ba85cc8b34e4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Feb 4 20:40:18 2026 -0500 fix(deps): update dependency mongodb to v7.1.0 (#13950) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b32e2ab12ecb250b2ee5ec7ddf67db51920b3506 Merge: 9e8db11020 54743724f3 Author: Barış Soner Uşaklı Date: Wed Feb 4 19:55:11 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 9e8db1102087af3be5acfe8a87f876855db20ea6 Author: Barış Soner Uşaklı Date: Wed Feb 4 19:55:05 2026 -0500 remove whitespace commit 54743724f32a0323d76c55988bbbc0a0baebebcc Author: Misty Release Bot Date: Thu Feb 5 00:51:59 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-development-info commit 13e22e41bbb378fbddffe7b968ce7f7e0e456333 Author: Barış Soner Uşaklı Date: Wed Feb 4 19:51:29 2026 -0500 show rss/heap in info table commit 43203d877fc4f87b3d3846dc79f43bfefb7e7b62 Merge: 30014f4139 f8d6c4e88a Author: Barış Soner Uşaklı Date: Wed Feb 4 13:00:00 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 30014f4139ace0fe006680b82a7d9d0d865ac62a Author: Barış Soner Uşaklı Date: Wed Feb 4 12:59:55 2026 -0500 test: add missing spec for admin page commit f8d6c4e88a8a0c81d475b0c5962911bccc24b142 Author: Misty Release Bot Date: Wed Feb 4 17:55:24 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-uploads commit 472a8fc13c58e1718ac359dc30ef0a504c6427ba Author: Barış Soner Uşaklı Date: Wed Feb 4 12:54:58 2026 -0500 feat: allow converting pasted images, closes #10352 commit b3dc7f4303cb55badb3bb764cdf347b10783a3fe Author: Barış Soner Uşaklı Date: Wed Feb 4 10:01:41 2026 -0500 refactor: shorter commit ff1376b37ee30b32c8aee4f65f714af5b7e10070 Author: Barış Soner Uşaklı Date: Wed Feb 4 09:47:14 2026 -0500 refactor: remove log commit 94885109fa6e0e44972baed64fb3a16e90ee763a Author: Barış Soner Uşaklı Date: Tue Feb 3 21:41:19 2026 -0500 fix: closes #8642, stricter username check don't allow invisible unicode characters commit 065abbf249e05745ac7db05742314224045e9803 Author: Barış Soner Uşaklı Date: Tue Feb 3 20:20:30 2026 -0500 refactor: get rid of cache for tid posters, was never cleared commit 6f032fd301f884c20a2476912025f0621ecb8e6f Merge: 931ae67dfc 77eef491da Author: Barış Soner Uşaklı Date: Tue Feb 3 19:08:49 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 931ae67dfca2c7f9cc35418bdcf303dcb63872d6 Author: Barış Soner Uşaklı Date: Tue Feb 3 19:08:45 2026 -0500 chore: up composer & harmony commit 77eef491da3ce9e99f5b1c9d093aa8eb3bbbfd3f Author: Misty Release Bot Date: Tue Feb 3 23:59:02 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.topic commit 0125ab558e87a93e90120e6d67825a0e9091cab8 Author: Barış Soner Uşaklı Date: Tue Feb 3 18:58:37 2026 -0500 feat: add language key for untitled topic draft https://github.com/NodeBB/NodeBB/issues/12245 commit a4e3fe105ff45e37ed88263407f2fa99e54fba17 Author: Barış Soner Uşaklı Date: Tue Feb 3 18:51:41 2026 -0500 refactor: dont use module, explodes on latest webpack commit f249699d0b8b9c45987c78381aee7e6eef5cabab Author: Barış Soner Uşaklı Date: Tue Feb 3 14:14:08 2026 -0500 fix: if there is no hr create one happens if its the only chat or if its the last one in the list commit e7101330d0cf5649ef341e236b8f6d10679dcbbe Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Feb 3 13:57:03 2026 -0500 fix(deps): update dependency webpack to v5.105.0 (#13949) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c8cd34bfd52eff9deb95a9db86e510307370154f Author: Barış Soner Uşaklı Date: Tue Feb 3 13:00:05 2026 -0500 refactor: use lru directly commit 0a9c5d30c60684b740c9360a7c10ec8e79bbda95 Author: Barış Soner Uşaklı Date: Tue Feb 3 10:57:42 2026 -0500 fix: closes #13240, move the updatedTeaser to the top of the recent chat list commit cc2772ba5518c80efa7fa3dd80374db0d6c910c0 Author: Misty Release Bot Date: Tue Feb 3 01:18:01 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.themes-harmony commit 870c6310df07c0b5c95ee98c27616740391cfa23 Author: Barış Soner Uşaklı Date: Mon Feb 2 20:17:34 2026 -0500 feat: add missing lang key for light/dark commit 018e1c5f094b3446675651d3df8af63005d3b7a4 Author: Barış Soner Uşaklı Date: Mon Feb 2 13:55:06 2026 -0500 test: remove unused commit 37f2c8ef713f412f604a90baa6f1634b059ecd10 Merge: bc1593b208 c26698d68f Author: Barış Soner Uşaklı Date: Mon Feb 2 13:43:17 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit bc1593b208840b599a1540b35b1e0a007e48012d Author: Barış Soner Uşaklı Date: Mon Feb 2 13:43:12 2026 -0500 test: fix spec commit c26698d68fc5be87c2f9066975123de1e63dab5e Author: Misty Release Bot Date: Mon Feb 2 18:37:04 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-email commit c848801268ef22bb438faff2aab07c0820f71070 Author: Barış Soner Uşaklı Date: Mon Feb 2 13:36:38 2026 -0500 feat: closes #13009, add dedicated test smtp button which uses the dirty settings on the page add clarification under send test email button add missing lang keys commit b61fa42625be0656f6f337aa181d586359ed71ff Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Feb 2 12:04:23 2026 -0500 chore(deps): update dependency jsdom to v28 (#13947) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 765c1291c906036285350feae2a8936a5fdba9f8 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Feb 2 11:47:15 2026 -0500 fix(deps): update dependency commander to v14.0.3 (#13946) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a9042602f40636a65cc8c9bb71dcde1dcc2c9fed Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Feb 2 09:54:05 2026 -0500 chore(deps): update commitlint monorepo to v20.4.1 (#13945) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 61662f19ee5d5be55fa868bceeacf9c8c53224a0 Author: Barış Soner Uşaklı Date: Mon Feb 2 09:49:52 2026 -0500 feat: closes #13203, make users room owners on private chats commit 1f28529307089a5e6fc79bf447b5eb689513119b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Feb 2 09:10:10 2026 -0500 fix(deps): update dependency pg-cursor to v2.17.0 (#13942) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 070d77fd63766a8c92e97f185259786cc4467216 Author: Barış Soner Uşaklı Date: Sat Jan 31 12:38:00 2026 -0500 refactor: format commit e2fc349d43100a66daeaa84ec6b9d611de7b85ca Author: Barış Soner Uşaklı Date: Sat Jan 31 12:07:29 2026 -0500 refactor: shorter tpl commit 4eb8854c9c498e40e8fd44578f4112f91a91a522 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 30 16:14:16 2026 -0500 fix(deps): update dependency pg to v8.18.0 (#13941) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b837c2530111308a5abce8815def1518736c67bd Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 30 16:04:10 2026 -0500 fix(deps): update dependency autoprefixer to v10.4.24 (#13940) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5e324108279239ac3d771676c903375382c650ab Merge: 3fed066594 b7ea2767a4 Author: Barış Soner Uşaklı Date: Fri Jan 30 15:58:51 2026 -0500 Merge branch 'master' into develop commit b7ea2767a457ada60992031abdfad1c864c513b2 Author: Barış Soner Uşaklı Date: Fri Jan 30 15:58:42 2026 -0500 fix: regression with updateHistory going from /recent to index was not updating the url if empty string is passed to replaceState commit 3fed06659423d794cfa30b6cc4079fef4a20bad2 Merge: 531b837482 e673794144 Author: Barış Soner Uşaklı Date: Fri Jan 30 12:14:50 2026 -0500 Merge branch 'master' into develop commit e67379414459fe98ee43be41476f7fcdf8f96a14 Author: Barış Soner Uşaklı Date: Fri Jan 30 12:14:44 2026 -0500 chore: up harmony commit 531b83748258246f20e1b051c80750aab8a252f1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 30 11:16:02 2026 -0500 chore(deps): update commitlint monorepo to v20.4.0 (#13938) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 22d55b307c498d90902bc65fcfd780eb20ea2901 Merge: d8595d6908 2dc49c8228 Author: Barış Soner Uşaklı Date: Fri Jan 30 10:46:22 2026 -0500 Merge branch 'master' into develop commit 2dc49c82289401e2bc98a795ee2b1f88af56b19b Author: Barış Soner Uşaklı Date: Fri Jan 30 10:46:13 2026 -0500 fix: #13939, dont append / if url is empty dont call updateHistory twice on page load commit d8595d6908213c73d0019103abfb0f769c2fd767 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jan 28 18:01:41 2026 -0500 chore(deps): update dependency smtp-server to v3.18.1 (#13936) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 01f56e6cdf24155d47902a5d9c86ebb911d02ae8 Merge: 9ec96aecc9 8d6b6f6a59 Author: Barış Soner Uşaklı Date: Wed Jan 28 12:42:33 2026 -0500 Merge branch 'master' into develop commit 9ec96aecc95eda60d2caba1ae5be390925a65a46 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jan 28 11:35:38 2026 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.37 (#13935) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8d6b6f6a59f6c1864309fc645c73457495e62461 Author: Barış Soner Uşaklı Date: Wed Jan 28 11:33:15 2026 -0500 chore: up harmony commit ef75f1bac9bded9cc0786f96698f4f1ea8c05d88 Author: Barış Soner Uşaklı Date: Wed Jan 28 11:23:34 2026 -0500 fix: acp category selector when category has image commit eab4025bddc898b0e01b5864a7f8e7e5fa3291c6 Author: Barış Soner Uşaklı Date: Wed Jan 28 10:48:09 2026 -0500 fix: pagination always getting set to default dont allow 0 or negative commit f395ba3b754fa39aabbfda93d07ddb2dfc216827 Merge: 290198b188 64d57129b7 Author: Barış Soner Uşaklı Date: Wed Jan 28 09:24:35 2026 -0500 Merge branch 'master' into develop commit 64d57129b7f94fbff65ae671c6f4d0c9299438ab Merge: f05f8b63bb f53aab43ac Author: Barış Soner Uşaklı Date: Wed Jan 28 09:24:25 2026 -0500 Merge branch 'master' of https://github.com/NodeBB/NodeBB commit f05f8b63bb7e5d7674069880373936224da0e9b4 Author: Barış Soner Uşaklı Date: Wed Jan 28 09:24:21 2026 -0500 fix: folder name commit f53aab43ac847fb9063b593511ab6b8173f9f866 Author: Misty Release Bot Date: Wed Jan 28 14:19:09 2026 +0000 chore: update changelog for v4.8.1 commit 1cf0ea60f964005c47f4dada8c4cc5c967dc6035 Author: Misty Release Bot Date: Wed Jan 28 14:19:08 2026 +0000 chore: incrementing version number - v4.8.1 commit 290198b1886adfc41aaf6556461ce6e9b32512d1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jan 27 10:20:44 2026 -0500 fix(deps): update dependency nodemailer to v7.0.13 (#13934) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 82d6f35b14262a2d6937e609a8f5c677588c0fb0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jan 26 10:47:24 2026 -0500 fix(deps): update dependency express-useragent to v2.1.0 (#13929) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b4c5657aa4627341f5b102415625c2d5fcfc1c9d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jan 26 10:46:55 2026 -0500 fix(deps): update dependency lru-cache to v11.2.5 (#13932) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 717f3e321332cc55e57bcbdcfaa6da0ac29e179b Merge: 561e0284df 03b7374c69 Author: Barış Soner Uşaklı Date: Sun Jan 25 20:03:33 2026 -0500 Merge branch 'master' into develop commit 03b7374c69b77dd9c20814c32485402be0cf3a54 Author: Barış Soner Uşaklı Date: Sun Jan 25 20:03:27 2026 -0500 fix: upgrade script to handle topics that were already pruned get the tid from the zsets intead of topic hash since its gone already commit 561e0284df689f23fac2338189cd55e2840a7885 Merge: 5c15a0db7d a82f18ccfb Author: Barış Soner Uşaklı Date: Sun Jan 25 19:56:17 2026 -0500 Merge branch 'master' into develop commit a82f18ccfb07f829f152182dffeab717a217e29d Author: Barış Soner Uşaklı Date: Sun Jan 25 19:56:11 2026 -0500 chore: fix progress commit 619819dedcd80a85b2d9ad0733c03c683ac37a1f Author: Barış Soner Uşaklı Date: Sun Jan 25 19:48:55 2026 -0500 chore: fix typo in upgrade script name commit 5c15a0db7dcee6564f57b9b9229aa247bea39711 Merge: b2b1f3b922 f98de3e985 Author: Barış Soner Uşaklı Date: Sun Jan 25 19:45:31 2026 -0500 Merge branch 'master' into develop commit f98de3e98591b50fb1a649e5a3a0bf0bc6cd1bb0 Author: Barış Soner Uşaklı Date: Sun Jan 25 19:45:23 2026 -0500 fix: closes #13899 commit 871089da7da4606f99895d076ab12d2731533dbd Author: Barış Soner Uşaklı Date: Sun Jan 25 10:16:32 2026 -0500 chore: up composer commit a061672dcfaab3527433ed3b121615af077218ab Author: Barış Soner Uşaklı Date: Sun Jan 25 10:13:02 2026 -0500 chore: up composer commit b2b1f3b922acd8af60945e6470c35ce7218d7d95 Merge: cd2eaafc2d 385a4d034f Author: Barış Soner Uşaklı Date: Fri Jan 23 19:56:07 2026 -0500 Merge branch 'master' into develop commit 385a4d034f05ad828d9ef398c479ba196c6f8cb5 Author: Barış Soner Uşaklı Date: Fri Jan 23 19:55:56 2026 -0500 fix: #10682, fix all the other rss routes as well commit cd2eaafc2df30ee520c5cd04503fd3392c5eb896 Merge: 3e0f3a6846 da5605e0b7 Author: Barış Soner Uşaklı Date: Fri Jan 23 17:34:18 2026 -0500 Merge branch 'master' into develop commit da5605e0b71f4ab80cb05aac6c30899de54db831 Author: Barış Soner Uşaklı Date: Fri Jan 23 17:34:11 2026 -0500 fix: protocol commit 3e0f3a6846ac3b8022c44651310339ce1729cf72 Merge: d911a736b0 310e90c782 Author: Barış Soner Uşaklı Date: Fri Jan 23 17:13:38 2026 -0500 Merge branch 'master' into develop commit 310e90c782100dc5d6f7710debdb275257ee2852 Author: Barış Soner Uşaklı Date: Fri Jan 23 17:13:27 2026 -0500 fix: closes #12986 fix paths in topic thumbs commit d911a736b043dd99e6cde6fb5420d7794653b2b7 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 13:44:58 2026 -0500 fix(deps): update dependency ace-builds to v1.43.6 (#13922) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b036034a0c926315b3642fb2c03326b3f8cd93aa Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 13:35:41 2026 -0500 chore(deps): update dependency @stylistic/eslint-plugin to v5.7.1 (#13920) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 87c4d4161b1fff538cf83b8d4dc8258e612196c1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 13:20:04 2026 -0500 fix(deps): update dependency sass to v1.97.3 (#13925) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e0e7c5ea158594b09ebdec1a6e60079f8b9f54f8 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 13:19:54 2026 -0500 fix(deps): update dependency express-session to v1.19.0 (#13926) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 75a04aedf1eedb8ad0698507f7c26a092d793d22 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 12:36:23 2026 -0500 chore(deps): update dependency sass-embedded to v1.97.3 (#13921) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit fe081ebd7b7ef904c0b96c05c2d27c21fad71433 Merge: abfb10e34d b2c6fbeddb Author: Barış Soner Uşaklı Date: Fri Jan 23 12:35:35 2026 -0500 Merge branch 'master' into develop commit b2c6fbeddb31a499046fc039a44b367a0dd5efc5 Author: Barış Soner Uşaklı Date: Fri Jan 23 12:34:59 2026 -0500 fix: #13919 commit 090b9f55bce880b437cc43ae1788c3675f33bcd3 Author: Barış Soner Uşaklı Date: Fri Jan 23 12:31:46 2026 -0500 fix: use min commit abfb10e34dc32dc0182fed86ce20f3f5976a09ed Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 12:31:22 2026 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.36 (#13923) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d25e7726a7d52f86acc0e12bb67a794f3b87ceab Author: Barış Soner Uşaklı Date: Fri Jan 23 12:31:09 2026 -0500 fix: #13918, make arrayLimit configurable increase default to 50 cap at 100 commit 14e20a320286b45a51ec711ace9b5f53662f5001 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 12:06:54 2026 -0500 fix(deps): update dependency pg-cursor to v2.16.2 (#13915) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0b822c96a7777bc7ef5011b43fdf73c7b55f484c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 23 12:06:45 2026 -0500 fix(deps): update dependency lodash to v4.17.23 (#13916) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 69c5f94193fae0a127356b9992c526b0362b0116 Author: Julian Lam Date: Fri Jan 23 11:35:32 2026 -0500 fix: proper attachment generation on replies, fixed replies getting thumb attachment when it wasn't part of it commit d2e1629f58e05655915e4b428a4793205458bc1b Author: Barış Soner Uşaklı Date: Thu Jan 22 17:07:09 2026 -0500 chore: up themes commit e231c010f6b9612e63fe039bbbfd2bdb0ce6e692 Author: Barış Soner Uşaklı Date: Thu Jan 22 11:49:16 2026 -0500 refactor: get rid of map, move parallel calls into promise.all commit f29c9f064b93ca7f7607419abcc2c82f2728d834 Author: Barış Soner Uşaklı Date: Thu Jan 22 11:31:59 2026 -0500 fix: closes #13258, dont mark digest as delivered if it fails show fail count and sent count separately commit 252d1d09a0183d1c852e6b44a4a267d9400a970f Author: Barış Soner Uşaklı Date: Wed Jan 21 20:31:16 2026 -0500 fix: closes #13734, set process.env.NODE_ENV early using argv if commander or one of the core deps isn't found then packageInstall.installAll is called and uses process.env.NODE_ENV, which was always undefined. commit 07d1f22401cffad5c5125bf23143400b70ce9ea2 Author: Barış Soner Uşaklı Date: Wed Jan 21 20:14:15 2026 -0500 refactor: get rid of global.env, use process.env.NODE_ENV commit 3272ea576f717ae166e9c9cca08458266319029c Author: Barış Soner Uşaklı Date: Wed Jan 21 18:42:31 2026 -0500 fix missing await on appendFile commit 2ded68139691fae56f961ff4b3a183564b745ade Author: Barış Soner Uşaklı Date: Wed Jan 21 18:41:15 2026 -0500 refactor: make custom user field icons fixed width in the acp commit 7ac5446a13fe96beea9092ee77e116b97c506636 Author: Barış Soner Uşaklı Date: Wed Jan 21 18:39:45 2026 -0500 refactor: use local cache for plugin isActive check commit 1b08aef2d0fc5ea1aefa81b7a62c38f8fdb8786a Author: Barış Soner Uşaklı Date: Wed Jan 21 18:31:58 2026 -0500 fix: wrap fields in quotes in user csv export commit 3f50d52a4e09cd81e129fcab486005ed38c4fd72 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jan 21 18:20:36 2026 -0500 fix(deps): update dependency pg to v8.17.2 (#13914) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ad27347fa20815988a30c03b3a1784d3b6890918 Author: Julian Lam Date: Wed Jan 21 14:43:31 2026 -0500 fix: update buildRecipents to add option to skip target creation step, update ap actors for note to not bother building targets commit fac3185974597ae548c0c0a83cc0d7df1ed63283 Author: Julian Lam Date: Wed Jan 21 14:42:16 2026 -0500 refactor: Actors.getLocalFollowers to Actors.getFollowers, can pass in both local and remote ids commit eb27b96430d9d941d2b5d8e949736672fac609db Author: Julian Lam Date: Wed Jan 21 12:53:41 2026 -0500 fix: notes announce cache, use cache when retrieving tid posters commit e697d600d1c1a36e0faf3b4af87653f2e79b4e71 Author: Julian Lam Date: Wed Jan 21 12:04:53 2026 -0500 fix: optimizations - plugins.isActive response now cached in nconf - public addresses filtered out of actor assertion logic during qualification stage - bump mentions to fix db call with empty values - update buildRecipients to exclude public addresses and local URIs when building targeting array commit f9affbad58551e2d7f05dd7a283c9427eb405237 Author: Julian Lam Date: Wed Jan 21 10:51:15 2026 -0500 fix: cache detection logic in context parseItem executing earlier than needed causing false positives commit ec4e7ef1b7b02e533edbf3b15bea1afc19a92fe2 Author: Barış Soner Uşaklı Date: Tue Jan 20 22:19:22 2026 -0500 fix: closes #13199 normalize accept header @julianlam tried accepts module ran into issues when the route was requested via browser or via $.ajax with the default headers, for example accepts(req).type(activitypub._constants.acceptableTypes) still returns true when /post/123 is loaded via browser or via $.ajax commit 7bc9fe3b754ca2e81f6fe1944b058d4bff9db36f Author: Barış Soner Uşaklı Date: Tue Jan 20 21:08:38 2026 -0500 refactor: dont include scheduled topics in unread commit d867d8adbb373f61c4ef502e85a7934674bb26c0 Author: Barış Soner Uşaklı Date: Tue Jan 20 20:50:04 2026 -0500 fix: closes #10682, strip unicode control chars that explode rss module commit d3f653e6489ccc3c3bd4e2761a9aac084dae67e6 Author: Barış Soner Uşaklı Date: Tue Jan 20 20:15:54 2026 -0500 fix: require commit beb3f8ff941f47a52e7af50e26cbf475f2a735b7 Merge: 13bf64c956 fffe039f46 Author: Barış Soner Uşaklı Date: Tue Jan 20 20:15:07 2026 -0500 Merge branch 'master' into develop commit 13bf64c95675d770c9b9ae853591bf7e10f431a2 Author: Barış Soner Uşaklı Date: Tue Jan 20 20:14:40 2026 -0500 fix: closes #12458, on socket.io reconnect load messages after last data-index commit fffe039f465c7133b87fe16f671b47c02afd4a7f Author: Barış Soner Uşaklı Date: Tue Jan 20 19:09:32 2026 -0500 refactor: remove chats.initialized, all events handlers are removed before being added commit ab39e7f8aeac7125fb6adee7aa4103445db79b78 Author: Barış Soner Uşaklı Date: Tue Jan 20 19:02:43 2026 -0500 refactor: move chat page events to a new file commit 07d2c9463ec27762cefde214baf1232d1fdfd9cd Author: Barış Soner Uşaklı Date: Tue Jan 20 18:42:31 2026 -0500 fix: remove bidi chars from displayname fixes chat teasers and probably every other place where display name is shown commit 50c26dd5838401ae26b953ad596d5e4a1e63ba05 Author: Barış Soner Uşaklı Date: Tue Jan 20 17:57:55 2026 -0500 fix: closes #11499 commit 6b3ec63621d28de43b2797f2142748107dbb443b Author: Barış Soner Uşaklı Date: Tue Jan 20 12:05:52 2026 -0500 refactor: add guards against bad data & infi loops commit 2ba8907ac873c6cdd1b5bbac9272cd1fbea0c7da Author: Barış Soner Uşaklı Date: Tue Jan 20 11:57:22 2026 -0500 refactor: tags were moved into topic hash a while ago commit 512b1e7296b8f80853eb4c0a2feeceb2b1f8021e Author: Barış Soner Uşaklı Date: Tue Jan 20 11:17:30 2026 -0500 fix: remove lowercase bidi controls as well commit 37675689f8be605f5a50b2eaef82c7f2ccc7e4b7 Author: Barış Soner Uşaklı Date: Mon Jan 19 22:43:39 2026 -0500 refactor: move username check to createOrQueue commit bb4fd319391de6193ac9621c936ea96f205ef770 Merge: 7e27da61ab aaa9570e7b Author: Barış Soner Uşaklı Date: Mon Jan 19 22:26:13 2026 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 7e27da61ab2c4529d182e0d2a679924febbbfb62 Author: Barış Soner Uşaklı Date: Mon Jan 19 22:26:09 2026 -0500 refactor: checkUsername function https://github.com/NodeBB/NodeBB/issues/10864 commit aaa9570e7bff83fed70ada3dba71a9c9b6c004f7 Author: Misty Release Bot Date: Tue Jan 20 03:18:46 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.error commit 84bd409a7cc46942c054c900f2e847c56b762621 Merge: 0262bb83f3 635715ef51 Author: Barış Soner Uşaklı Date: Mon Jan 19 22:18:20 2026 -0500 Merge branch 'master' into develop commit 635715ef51f2b750dd2304d4b2561d5e3c3b00a8 Author: Barış Soner Uşaklı Date: Mon Jan 19 22:16:37 2026 -0500 refactor: already checked inside user.isPasswordValid commit 0262bb83f33cd8e10c5dcb50a07f766d81444d6c Author: Julian Lam Date: Mon Jan 19 21:44:14 2026 -0500 fix: restrict topic backfill to logged-in users when browsing to a category commit 428b6e730a724c6d7822e21e930ba6bba3d77676 Author: Julian Lam Date: Mon Jan 19 21:37:23 2026 -0500 fix: replace attachment generation logic in notes.public Previously, the logic retrieved the list of uploads, checked if they were thumbs, and set attachment (and noteAttachment) depending on object type. It was complicated and didn't really work so well, so I simplified it. Now thumbs.get is called, and attachment is appended with all thumbs and uploads. Sizing is not provided. Maybe later. Image is also now set, which is the first image in attachment. commit f90c86492a5ce684e8445a585fd4655c977ae2bb Author: Barış Soner Uşaklı Date: Mon Jan 19 21:36:26 2026 -0500 chore: up link-preview commit 39af83837692031baef2d0b7ed4a057b77247ef7 Author: Barış Soner Uşaklı Date: Mon Jan 19 20:59:55 2026 -0500 fix: #13909, show 413 error properly add tx string commit dce82aaecaf19d5da73d80a97bb9ab8a87718fb5 Author: Misty Release Bot Date: Tue Jan 20 00:07:11 2026 +0000 chore(i18n): fallback strings for new resources: nodebb.topic commit 0c79eaa529e7f0265f0cd5339a305593f1292f61 Author: Julian Lam Date: Mon Jan 19 19:06:23 2026 -0500 feat: topic crossposts generate topic events, #13908 commit e2e1744824303248aa5d45e86c8eabf8ba0baabb Author: Barış Uşaklı Date: Mon Jan 19 18:40:48 2026 -0500 User create / registeration queue refactor (#13905) * feat: add options parameter to User.create add emailVerification: ('send'|'verify'|'skip') param to User.create to control email verification add a new method User.createOrQueue(). store options that will be passed to User.create() when registration is accepted in _opts If there is no password passed to registration queue(SSO register) don't store hashedPassword removed the isFirstUser hack in user.create, when creating the admin user in install.js passing `emailVerification: 'verify'` to immediately verify the email, same with all the hacks in tests auth: if an SSO plugin sends back an info object, redirect to root and display the message * refactor: make function private * refactor: destruct return * test: fix flag test * test: group tests * feat: show ssoIcon if available in register queue * add icon/title commit 6383bb58e9d9806b6361529cc1523a6a6f80ce84 Author: Julian Lam Date: Mon Jan 19 14:50:57 2026 -0500 fix: #13900, assertion re-index commit 39582cbd02bce2f38c844e2c45a50843cc0dc2ce Author: Julian Lam Date: Mon Jan 19 14:44:22 2026 -0500 fix: simplicity tweak commit 560ad81f323664712d28078fe7bebb714819c500 Author: Julian Lam Date: Mon Jan 19 14:41:53 2026 -0500 feat: remote account banning, #13904 commit 1cbc128a75ea2b02f836f017838c9d506fda9fce Author: Barış Soner Uşaklı Date: Mon Jan 19 14:13:29 2026 -0500 refactor: get rid of intersect and use a faster method to load recently created tids commit 271239d41f84f1b5d866319d0986d3ef0cfa9bf9 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun Jan 18 13:21:26 2026 -0500 fix(deps): update dependency pg to v8.17.1 (#13901) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 442f9f1d5bc6d31f615956a20d420ba4a5f07cbc Author: Barış Soner Uşaklı Date: Sat Jan 17 14:25:32 2026 -0500 fix: closes #11606, detect musl and use sass instead commit bb6ed76e130be3304190441247be289af0cca0cf Author: Barış Soner Uşaklı Date: Sat Jan 17 12:16:06 2026 -0500 refactor: shorter params commit 05e76eddeeaa54bfc56d2ce9e6b5627cce649413 Author: Barış Soner Uşaklı Date: Sat Jan 17 12:16:06 2026 -0500 refactor: shorter params commit 61d7101ad1a4799091de2832c8c2dba8fe0b6272 Author: Barış Soner Uşaklı Date: Fri Jan 16 18:47:21 2026 -0500 Revert "fix(deps): update dependency pg to v8.17.1 (#13893)" This reverts commit cc8b2db5efd08353424bafefbae868c6b12d93ba. commit cc8b2db5efd08353424bafefbae868c6b12d93ba Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 16 17:23:25 2026 -0500 fix(deps): update dependency pg to v8.17.1 (#13893) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 33c2de9c5b428b1c5c650256dd632ddcbfa80845 Author: Julian Lam Date: Fri Jan 16 15:12:16 2026 -0500 feat: opportunistic backfill, #13895 commit 4bab9fb44649bb590e2881fdecd52031692d8755 Author: Julian Lam Date: Thu Jan 15 15:39:46 2026 -0500 fix: export sendMessage as _sendMessage for use in ap jobs lib commit c595edb4c0e978d220fc0f01fa08c651a920337c Author: Julian Lam Date: Thu Jan 15 15:12:57 2026 -0500 refactor: move ap jobs to its own file commit 62498a3c1b303c32854e85baf40d360a9831d141 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 16 13:22:50 2026 -0500 fix(deps): update dependency pg-cursor to v2.16.1 (#13894) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5b5955d6fc88dc5a147f26c99038a3ca1921b48f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 16 13:22:41 2026 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.35 (#13896) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit dd6fda81529b2578fee6b7c9ee7f80291e4a7119 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 16 13:22:35 2026 -0500 fix(deps): update dependency satori to v0.19.1 (#13898) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e505e3699121bcc5b8d0b54347eb6681d27964d8 Author: Barış Soner Uşaklı Date: Fri Jan 16 13:17:02 2026 -0500 fix: make translator.unescape stricter like escape commit f7c5414d6c1dfbc3d9473cb184b0e4e4c70e339f Merge: 8b7d350e0a b2fa7304e9 Author: Barış Soner Uşaklı Date: Fri Jan 16 13:08:29 2026 -0500 Merge branch 'master' into develop commit b2fa7304e932af6a3e7e3a1ac29be34c7d86d513 Author: Barış Soner Uşaklı Date: Fri Jan 16 13:00:41 2026 -0500 fix: closes #13887, make translator.escape stricter only match [[namespace:key]] allow underscores,dashes and dots in namespace key add test commit 918bb044918e964c7c162549fd34d34a510b0816 Author: Barış Soner Uşaklı Date: Fri Jan 16 12:51:11 2026 -0500 test: add missing awaits, change error message commit fe4a447651ed4e90848bdf2436cc861bd282ad29 Author: Barış Soner Uşaklı Date: Fri Jan 16 12:36:17 2026 -0500 refactor: use async/await for group search commit 7d36c75790d4eb08540d5fef0ec2a35ece00ca8d Author: Barış Soner Uşaklı Date: Fri Jan 16 10:58:03 2026 -0500 fix: closes #13897, display group create errors properly fix typo in redirect after group deletion commit 639ea42d5a35913688582b411310aabdc60fe801 Author: Barış Soner Uşaklı Date: Thu Jan 15 16:47:28 2026 -0500 refactor: put alltime in query string for term commit 469a8ef9b4c99d9136c621b96c2c7a1835e551a4 Author: Barış Soner Uşaklı Date: Thu Jan 15 15:46:12 2026 -0500 chore: up dbsearch commit c1a92c472317bf87814ab59b1d8d5a0ecb972e86 Author: Barış Soner Uşaklı Date: Thu Jan 15 15:33:07 2026 -0500 chore: up harmony commit 317be96fb1c9fd98b3077523dec93f64249204df Author: Barış Soner Uşaklı Date: Thu Jan 15 15:18:20 2026 -0500 chore: up harmony commit 57a73c4854106913a258fceed8508f1da9e5aa7b Author: Barış Soner Uşaklı Date: Wed Jan 14 18:47:52 2026 -0500 refactor: crossposts.get to support multiple tids commit be5b36bcd19b59099a2d372b9708345a5afdf4f9 Author: Barış Soner Uşaklı Date: Wed Jan 14 18:14:49 2026 -0500 test: dont return cross posts commit c494d002ba24757061d75ef133edad0b32a4388b Author: Julian Lam Date: Wed Jan 14 15:01:33 2026 -0500 fix: consider crossposts when building teasers, fixes #13891 commit 98c0a3fedcfbc05d35d5687e4408f357f424c64b Author: Julian Lam Date: Wed Jan 14 14:18:02 2026 -0500 fix: #13892, logical flaw commit 8b7d350e0ae83d571fcdcb7108bfeb4ef77f8083 Author: Julian Lam Date: Wed Jan 14 14:18:02 2026 -0500 fix: #13892, logical flaw commit a9fbcf2aeb66d15242b67e733553b5ced749943c Author: Misty Release Bot Date: Wed Jan 14 17:54:34 2026 +0000 chore: update changelog for v4.8.0 commit 8d6a4ed8757b942b7f0d53f0c7394e6f29259a2d Author: Misty Release Bot Date: Wed Jan 14 17:54:33 2026 +0000 chore: incrementing version number - v4.8.0 commit a73ab8ee1e674d4b3b37ef58221b4869d5010946 Author: Julian Lam Date: Wed Jan 14 12:46:14 2026 -0500 fix: i18n fallbacks commit 9b1c32b1845ba8e72f7329d8221349dfa0162775 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jan 14 12:42:41 2026 -0500 fix(deps): update dependency spdx-license-list to v6.11.0 (#13890) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 974ab1f8bc226738dbc7bdbf04c611ced5351225 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jan 14 12:41:04 2026 -0500 fix(deps): update dependency diff to v8.0.3 (#13882) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0c75934adf4040ad83f10b454aa8dbc9bd7c2f80 Author: Julian Lam Date: Tue Jan 13 11:25:18 2026 -0500 fix: #13889, custom emoji from Piefed commit 6eea4df5efda5f86f0342a258ddf2f5549067c36 Author: Julian Lam Date: Tue Jan 13 10:36:03 2026 -0500 fix: #13888, decode html entities for AP category name and description commit d2f8af25f6d97b892659174ac235f7b52e69304c Merge: bcc204fa93 0e1ccfc988 Author: Barış Soner Uşaklı Date: Mon Jan 12 20:50:28 2026 -0500 Merge branch 'master' into develop commit 0e1ccfc988f6dcd3ba77fcd96dbd350f353875ea Author: Barış Soner Uşaklı Date: Mon Jan 12 20:49:31 2026 -0500 refactor: check if tid is truthy commit bcc204fa932822f063c2df944e1c91ba5b2a271c Author: Julian Lam Date: Mon Jan 12 15:42:33 2026 -0500 fix: derp commit a4c470ffa909c912b6d2a83870a71ef8ef281536 Author: Julian Lam Date: Mon Jan 12 14:10:31 2026 -0500 fix: bump themes commit 2f96eed4aff41beef9c74f447f668e736e41bbe2 Author: Julian Lam Date: Mon Jan 12 14:07:45 2026 -0500 fix: guard against negative uids crossposting commit 943b53b0bc0bb9613e0c2740ebf7cd13c8d3c800 Author: Julian Lam Date: Mon Jan 12 12:45:49 2026 -0500 fix: bump themes commit 82507c0fb1675b29fb97462e35f3da7088e592de Author: Julian Lam Date: Mon Jan 12 12:29:53 2026 -0500 fix: calling sortedSetRemove to remove multiple values, instead of baking it into sortedSetRemoveBulk commit b9b33f9f8d0dc845a73ac3da178b4f857a0c79d0 Author: Julian Lam Date: Thu Jan 8 16:47:00 2026 -0500 fix: unused values commit d20906b59228900eb1ca380c97efc3cb1de2ac92 Author: Julian Lam Date: Thu Jan 8 15:59:09 2026 -0500 tests: fix... tests commit 7465762d8731aff82cc3f3a538c6cd4fa7906ef4 Author: Julian Lam Date: Wed Jan 7 11:50:00 2026 -0500 fix: typo, client-side handling of crossposts as pertains to uncategorized topics commit 273bc68c468cdc6b174269939a414c6f35461b82 Author: Julian Lam Date: Wed Jan 7 10:48:22 2026 -0500 feat: user crossposts federate as:Announce commit 47e37ed5716207ba655fbf1a06c58b6fd4c9c1e5 Author: Julian Lam Date: Tue Jan 6 10:13:04 2026 -0500 test: intify uid/cid if they are numbers (when getting crossposts) commit 0677689a7541d4f88976d9b57fdb4e4b8104037a Author: Julian Lam Date: Mon Jan 5 15:07:50 2026 -0500 test: stop using partialDeepStrictEqual for now commit d81b644d7f177011cf40c04aa0904ddbe8c206f4 Author: Julian Lam Date: Mon Jan 5 12:24:00 2026 -0500 docs: update openapi schema for missing routes related to crossposting commit add163a42db9b103e8c85d4118ed367071fdccba Author: Julian Lam Date: Wed Dec 31 10:54:57 2025 -0500 test: ensure auto-cat and cat sync logic properly integrates with crossposts commit ea417b062b8ebff6d80abfb8f831c6eb31d4460f Author: Julian Lam Date: Wed Dec 31 10:08:12 2025 -0500 fix: client-side handling of category selector when cross-posting so only local cids are sent to backend commit e5ee52e5da5763c9c5e1dc2be18d6026e95068a9 Author: Julian Lam Date: Mon Dec 29 15:08:04 2025 -0500 fix: update category sync logic to utilise crossposts instead commit 28249efbe674093e987a0f86f8cdac0644e83e85 Author: Julian Lam Date: Mon Dec 29 15:07:47 2025 -0500 fix: remove old remote user to remote category migration logic + tests commit 148663c5367173fcc1f6faefbdaa80dddf2e6737 Author: Julian Lam Date: Mon Dec 29 14:57:47 2025 -0500 fix: update auto-categorization rules to also handle already-categorized topics via crosspost commit f6cc556d37bd2289709b79f9dd70212b8656342e Author: Julian Lam Date: Mon Dec 29 14:32:34 2025 -0500 fix: topic crosspost delete and purge handling commit 0a0a7da9ba3c76cdcd22e6a2b4b0635e8da3eef2 Author: Julian Lam Date: Mon Dec 29 14:20:25 2025 -0500 fix: bug where privileges users could not uncrosspost others' crossposts. Tests commit 6daaad810f89a8e79cfa526a75a593a5350cb6a9 Author: Julian Lam Date: Mon Dec 29 13:00:09 2025 -0500 fix: allow non-mods to crosspost, move crosspost button out of topic tools, in-modal state updates commit 38fd17984893825ec14bdb56e4b7389d20dd0e6f Author: Julian Lam Date: Mon Dec 29 11:49:06 2025 -0500 feat: add missing files, minor changes to crossposts list modal commit b981082dd706f508b9654bba01cd94439d0e3cc9 Author: Julian Lam Date: Tue Dec 16 14:21:51 2025 -0500 fix: removed ajaxify refresh on crosspost commit, dynamically update post stats in template, logic fix commit 947676efac025a60bcf96ea5ebb935392c0b1056 Author: Julian Lam Date: Tue Dec 16 11:43:53 2025 -0500 test: crossposting behaviour and logic tests commit 349b087502e30a90066956915f0012a5e96f419b Author: Julian Lam Date: Mon Dec 15 10:38:51 2025 -0500 refactor: crossposts.get to return limited category data (name, icon, etc.), fixed up crosspost modal to hide uncategorized and all categories options commit 1be88ca0eaeacf9f1b3022abf1c93eb8e824854f Author: Julian Lam Date: Fri Dec 12 13:56:08 2025 -0500 refactor: move crosspost methods into their own file in src/topics commit 0041cfe2eda78645d68944e2d4b0f0f155cd3cff Author: Julian Lam Date: Thu Dec 11 16:03:19 2025 -0500 feat: introduce new front-end UI button for cross-posting, hide move on topics in remote cids - Hide the ability to select remote cids in topic move category search - Add a new option to category search: 'localOnly'; pretty self descriptive. commit ea1e4c7dff4b60ff82467bfddeba7e940b8655b9 Author: Julian Lam Date: Thu Dec 11 15:32:18 2025 -0500 feat: disallow moving topics to and from remote categories, + basic tests for topic moving commit 3560b6a3d08ad27b7107b6a2bcad03e520f9d326 Author: Julian Lam Date: Wed Dec 10 12:08:16 2025 -0500 test: new test file for crossposts commit 74172ecc5d71647a0f7ab681c318d1707fc63b1b Author: Julian Lam Date: Mon Nov 3 14:43:51 2025 -0500 feat: API v3 calls to crosspost and uncrosspost a topic to and from a category commit 4f1fa2d15cbfc5578f63567e54a41694a7a4fffe Author: Julian Lam Date: Mon Nov 3 14:43:21 2025 -0500 test: additional logic to allow multi-typing in schema type commit 14aa2beea329250eadc3f853aa654ecc151bbeaa Author: Julian Lam Date: Mon Jan 12 12:54:47 2026 -0500 fix: nodeinfo route to publish federation.enabled in metadata section commit 81cac015255288ff383fc4a4a11e943e4196fd74 Author: Barış Soner Uşaklı Date: Sun Jan 11 14:43:24 2026 -0500 test: lowercase tags commit 00b9ca111e3ad5dcc4a4bce76cc062138f84b394 Author: Barış Uşaklı Date: Sun Jan 11 14:38:14 2026 -0500 Change owner rest route (#13881) * fix: dont use sass-embedded on freebsd, #13867 * fix: #13715, dont reduce hardcap if usersPerPage is < 50 * fix: closes #13872, use translator.compile for notification text so commas don't cause issues * fix: remove bidiControls from notification.bodyShort * refactor: move change owner call to rest api deprecate socket method * fix spec * test: one more fix * test: add 404 * test: fix tests :rage1: * test: update test to use new method commit 7b793527f94a7e58345b44235dfe4d551cc2d386 Author: Barış Uşaklı Date: Sun Jan 11 14:38:14 2026 -0500 Change owner rest route (#13881) * fix: dont use sass-embedded on freebsd, #13867 * fix: #13715, dont reduce hardcap if usersPerPage is < 50 * fix: closes #13872, use translator.compile for notification text so commas don't cause issues * fix: remove bidiControls from notification.bodyShort * refactor: move change owner call to rest api deprecate socket method * fix spec * test: one more fix * test: add 404 * test: fix tests :rage1: * test: update test to use new method commit 74e478200f58427ed9eefba0aba152d9ebc7c6df Author: Julian Lam Date: Fri Jan 9 14:42:04 2026 -0500 fix: bump link-preview again commit 486e77c76ec095d7e713f44b2554fc70b47d6a6e Author: Julian Lam Date: Fri Jan 9 13:16:12 2026 -0500 fix: bump link-preview commit ffc3d27903f995029ac202765069d241a8ca3318 Author: Julian Lam Date: Fri Jan 9 11:21:15 2026 -0500 fix: remove commented out require commit cc1649e00933afc2ecbdc722d9e52175bc249cb4 Author: Julian Lam Date: Fri Jan 9 11:19:37 2026 -0500 fix: bump link-preview commit be0d43cfb6417b309e7b94f937345b021e9aba09 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jan 9 11:03:47 2026 -0500 chore(deps): update dependency @stylistic/eslint-plugin to v5.7.0 (#13879) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9390ccb6b07c246dfca4ae6a5ae9c7d72e1bf426 Author: Julian Lam Date: Wed Jan 7 13:55:51 2026 -0500 fix: auto-enable post queue as default, adjust tests to compensate commit 5954015ed741b660c4eefb8adcc2250346e02dbe Author: Julian Lam Date: Wed Jan 7 13:28:17 2026 -0500 test: fix test to check for Secure in cookie string if test runner domain is https commit 47074b3c93843fd619a012ffc33173d235301831 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jan 8 17:56:10 2026 -0500 fix(deps): update dependency nodebb-theme-persona to v14.1.23 (#13878) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 125c8e58219abf6e5c09ccf42638cbdd7b0a6340 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jan 8 11:34:01 2026 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.31 (#13877) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c88ce519ee6c831cf1a084cb2c45629038dc055a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jan 8 11:33:52 2026 -0500 chore(deps): update commitlint monorepo to v20.3.1 (#13876) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bed6ed3c188142adea0a6f3c38192a994e149bf7 Author: Julian Lam Date: Wed Jan 7 12:51:04 2026 -0500 chore: bump profile max upload size default commit 874ffd7b266640abbff461d2f512cb58f4a8ff62 Author: Julian Lam Date: Wed Jan 7 10:39:03 2026 -0500 feat: refactor out.announce.topic to allow user announces, refactor tests to accommodate commit e717f00edd14053894865b8a7cf3e3d954a208b7 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jan 7 10:22:02 2026 -0500 fix(deps): update dependency body-parser to v2.2.2 (#13873) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b0679cadcf2f1daca6eaf85eb2936f50988e0a63 Author: Barış Soner Uşaklı Date: Tue Jan 6 12:48:09 2026 -0500 fix: remove bidiControls from notification.bodyShort commit cfdbbb048d976432919cc0f5731dda5519a8a065 Author: Julian Lam Date: Tue Jan 6 12:08:39 2026 -0500 test: more out.announce tests commit 27d511ff92c94183e3857f8144f50bfae93c90b1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jan 6 11:45:34 2026 -0500 chore(deps): update dependency sass-embedded to v1.97.2 (#13870) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5100cc4fe14689d85fd1e82d32ebcda01a6aefe7 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jan 6 11:45:23 2026 -0500 fix(deps): update dependency sass to v1.97.2 (#13871) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b05199d897a601f063b01e96958ff76fae4df993 Author: Julian Lam Date: Tue Jan 6 11:34:46 2026 -0500 fix: author of boosted content was not targeted in the activity commit 5a031d01e6da53acc165df013498d80695cbdcbc Author: Barış Soner Uşaklı Date: Tue Jan 6 11:34:43 2026 -0500 fix: closes #13872, use translator.compile for notification text so commas don't cause issues commit 67912dc989c0dd492a01b82b4760a3b2f3ecb454 Author: Julian Lam Date: Tue Jan 6 11:34:27 2026 -0500 test: basic tests for activitypub.out commit 41368ef83db279aebc38f1b989eada374e9c7799 Author: Julian Lam Date: Tue Jan 6 11:14:11 2026 -0500 test: update activitypub._sent to save targets as well, updated tests to accommodate format change commit 483ab083899aff5cf9d3d556d93cf72fe482ff53 Author: Julian Lam Date: Tue Jan 6 10:57:31 2026 -0500 test: test runs should not actually federate activities out commit a8c18f8a5a99c7f9e47889e5416438a4dfaf1cb2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jan 5 19:42:46 2026 -0500 fix(deps): update dependency nodebb-plugin-markdown to v13.2.3 (#13869) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 447cfd036ccd54aa3afe358fd82df8e0c97f241b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jan 5 19:39:55 2026 -0500 chore(deps): update commitlint monorepo to v20.3.0 (#13865) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 49379e2e330e39c11722edf95adb8e70edd15ef6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jan 5 19:39:46 2026 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.30 (#13863) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e4435e52918ffc4be81c0ac112ecd7b008daa31e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jan 5 19:39:36 2026 -0500 fix(deps): update dependency nodebb-theme-persona to v14.1.22 (#13864) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit cb31e70e8a7bb6e054daf63b14bac2113660f384 Author: Barış Soner Uşaklı Date: Mon Jan 5 10:32:46 2026 -0500 fix: #13715, dont reduce hardcap if usersPerPage is < 50 commit b323b5d83276a1e1689be043eabeb9ff9b8521d1 Author: Barış Soner Uşaklı Date: Sat Jan 3 18:11:48 2026 -0500 chore: up themes commit b7de0cc725a86ba3fe6c246540c2a7bd092b422c Author: Barış Soner Uşaklı Date: Fri Jan 2 08:56:25 2026 -0500 fix: dont use sass-embedded on freebsd, #13867 commit eb77c9bfc43d5dcc85d0b8de7c717c3d9a8b0ead Author: Barış Soner Uşaklı Date: Thu Jan 1 12:40:24 2026 -0500 chore: up markdown commit 89abdca1794647cd716fd7999c7e0403fb2b286d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 31 11:08:26 2025 -0500 fix(deps): update dependency @isaacs/ttlcache to v2.1.4 (#13861) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f35c77ddeec14a5c854a0bc43d7cd942f764c4a1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Dec 27 09:31:16 2025 -0500 chore(deps): update dependency smtp-server to v3.18.0 (#13858) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 37c052f4c6ce34218d00907d4bcfb7b6f2d9a41f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Dec 27 09:31:04 2025 -0500 chore(deps): update dependency jsdom to v27.4.0 (#13860) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 403230ccfd52fb488455b4cd7d5920564741845f Author: Barış Soner Uşaklı Date: Fri Dec 26 23:38:44 2025 -0500 refactor: silence if-function deprecation on prod commit 5a3cf50111c878c87ee824bcaa6518f20558afa2 Merge: c2e57061d9 648d9c78bb Author: Barış Soner Uşaklı Date: Fri Dec 26 23:33:01 2025 -0500 Merge branch 'master' into develop commit 648d9c78bbdc51917224a7f35553276f7f89ac9d Author: Barış Soner Uşaklı Date: Fri Dec 26 23:32:49 2025 -0500 chore: up mentions commit c2e57061d976645dc55934d54d8e60ca8a1006ef Merge: 6807f86048 2f0526b8a4 Author: Barış Soner Uşaklı Date: Fri Dec 26 23:21:55 2025 -0500 Merge branch 'master' into develop commit 6807f86048deccfd5c15a71eed1fc2c028b5fc3b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 24 23:33:28 2025 -0500 fix(deps): update socket.io packages to v4.8.3 (#13857) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7325b995fe00aedcb1db4297d3e959c5c2eeb0ee Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 24 23:32:17 2025 -0500 fix(deps): update dependency sass to v1.97.1 (#13856) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ded431589959939bdf9b56987201dd442ce5d61f Merge: 2a5bd6ef36 b8f68fb460 Author: Barış Soner Uşaklı Date: Wed Dec 24 23:27:07 2025 -0500 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 2a5bd6ef36d71c6e37429c4369ac30c0e69ae79b Merge: abcb2382ca 1f9f2dff2f Author: Barış Soner Uşaklı Date: Wed Dec 24 23:27:06 2025 -0500 Merge branch 'master' into develop commit 2f0526b8a4a4a7752adcc20067564dc9432eb4c5 Author: Misty Release Bot Date: Wed Dec 24 18:38:30 2025 +0000 chore: update changelog for v4.7.2 commit bab4304e04fe9ef6a19e5cc1b6ef3df4a2df98ba Author: Misty Release Bot Date: Wed Dec 24 18:38:30 2025 +0000 chore: incrementing version number - v4.7.2 commit b8f68fb460086c6fa17d000a1684d801a14d0c9b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 23 21:23:21 2025 -0500 fix(deps): update dependency nodebb-theme-persona to v14.1.20 (#13855) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f98fd6dc577ee1f78fb539f553771ac41441c633 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 23 21:23:11 2025 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.28 (#13854) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d28866abc8b1f46506daeab4527bfd235ed40862 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 23 21:20:49 2025 -0500 chore(deps): update dependency sass-embedded to v1.97.1 (#13850) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 160ce17f8543059b437b9461f03e7260f3c1139a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 23 21:20:38 2025 -0500 fix(deps): update dependency fs-extra to v11.3.3 (#13851) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f6ef041c18ddf9625697d865b4146d874ac6c74f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 23 21:20:20 2025 -0500 fix(deps): update dependency nodemailer to v7.0.12 (#13853) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1f9f2dff2fd1a00b425d3aba405dd17afbbcf82d Author: Barış Soner Uşaklı Date: Tue Dec 23 14:29:28 2025 -0500 fix: update data-isowner when changing is ownership fixes multiple ownership toggles commit abcb2382cad2dae8e609f8340eed300b75eaa10b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 22 16:35:03 2025 -0500 fix(deps): update dependency nodebb-plugin-2factor to v7.6.1 (#13852) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 2a10f9046afb196588d3dbbca3f2c4ff19c58bd2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 22 14:54:41 2025 -0500 fix(deps): update dependency validator to v13.15.26 (#13846) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b933d1a274c1257ec3b919aa5af3a309758cf340 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 22 14:54:03 2025 -0500 fix(deps): update dependency nodebb-theme-persona to v14.1.19 (#13849) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 61d8cba984da3c41c4f2d03facce015f266db3e5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 22 14:53:56 2025 -0500 fix(deps): update dependency nodebb-theme-harmony to v2.1.27 (#13848) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 59dd1ca6072b238cfd60f7d872b190cc5afbebf2 Author: Barış Soner Uşaklı Date: Fri Dec 19 17:38:19 2025 -0500 chore: up body-parser commit d03137128c1be08b6ca11eb833bf240f89a7a3a1 Author: Julian Lam Date: Thu Dec 18 11:39:31 2025 -0500 fix: bump 2factor commit a331f8da775a8fc6bb89ad9bb4b6a03365c5cc66 Author: Barış Soner Uşaklı Date: Thu Dec 18 10:47:55 2025 -0500 refactor: clear quick reply as soon as submitting bring back message on error commit b405a09bfd02b25171fa20638d7889459fc9968c Author: Barış Soner Uşaklı Date: Thu Dec 18 10:38:28 2025 -0500 Revert "test: check if tests pass without await" This reverts commit 5414cf473de85419c125ca9d9b76c2692f19e1fa. commit 5414cf473de85419c125ca9d9b76c2692f19e1fa Author: Barış Soner Uşaklı Date: Thu Dec 18 10:31:46 2025 -0500 test: check if tests pass without await commit bb5a90a3fe076f7d84bdb9e5b491bdb513fab638 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Dec 18 10:04:35 2025 -0500 fix(deps): update dependency webpack to v5.104.1 (#13847) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit da79582148a57540cc39b116e49b27873b35c5e7 Author: b2cc Date: Wed Dec 17 23:00:43 2025 +0100 * Docker: add function to entrypoint to auto-install plugins on reboot (fixes #13735) (#13749) * * Docker: add function to entrypoint to auto-install plugins on reboot (fixes #13735) Added a function to install additional NodeBB plugins if specified. This fixes #13735 * fix: case on --------- Co-authored-by: Jakub Bliźniuk commit 5844e393bdb596fc09609bd1cf22b9909600d66a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 17 16:56:29 2025 -0500 fix(deps): update dependency esbuild to v0.27.2 (#13842) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 550411fb58b07ad9741b810fd82eeefe6ce4448a Author: Barış Uşaklı Date: Wed Dec 17 16:56:07 2025 -0500 test: change redis connection (#13844) commit 2ffa43834eb7aebc36e6355bd55256c9d67e1ef3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 17 16:46:16 2025 -0500 fix(deps): update dependency nodebb-plugin-mentions to v4.8.4 (#13845) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1305faa8385b13e445976a626a96e516201fd63e Author: Barış Soner Uşaklı Date: Wed Dec 17 14:35:35 2025 -0500 test: add await to check tests commit d505301fa0d73555060a72e2120d3bb249ba9a8c Author: Barış Soner Uşaklı Date: Wed Dec 17 13:02:43 2025 -0500 chore: up mentions commit 9f8d50706ec859cc6fc1ae2012cc11fe11a2e625 Author: Barış Soner Uşaklı Date: Wed Dec 17 12:34:54 2025 -0500 test: add back logs for failing test commit 301b5386492bd9023da07f6662c5319b67505b95 Author: Barış Soner Uşaklı Date: Wed Dec 17 12:34:54 2025 -0500 test: add back logs for failing test commit e3ecc5436d5b73131e36ab0b2b9552725813556a Merge: f16eec3045 8668cfb38c Author: Barış Soner Uşaklı Date: Wed Dec 17 11:16:17 2025 -0500 Merge branch 'master' into develop commit f16eec3045b55b1f6bceb0ce0dabd8c265cd931a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 17 10:23:33 2025 -0500 fix(deps): update dependency webpack to v5.104.0 (#13839) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 168b6e630c80559b837dc078962c596e2c5d03cc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 17 10:23:21 2025 -0500 chore(deps): update dependency sass-embedded to v1.97.0 (#13837) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ab8dbb4158b867ca395cb663a187c1a8104a7c5b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 17 10:23:11 2025 -0500 fix(deps): update dependency sass to v1.97.0 (#13838) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d60db54419445ebdbd79b2731208b0e0273b9cfe Merge: 0ef5cbbbca b1fc5bfdaa Author: Barış Soner Uşaklı Date: Wed Dec 17 10:21:00 2025 -0500 Merge branch 'master' into develop commit 8668cfb38c96619a5ab0673375debbb6dde0911b Author: Misty Release Bot Date: Wed Dec 17 15:18:53 2025 +0000 chore: update changelog for v4.7.1 commit e6deb625f20898eaef8a6d76b07878d8f2b3ba05 Author: Misty Release Bot Date: Wed Dec 17 15:18:53 2025 +0000 chore: incrementing version number - v4.7.1 commit b1fc5bfdaab55d0b428767b9a2640c77fac5ca07 Author: Julian Lam Date: Thu Dec 11 11:10:29 2025 -0500 fix: wrong increment value commit 9f94a72117d6dcd661fd52e017c40c34efa15f54 Author: Julian Lam Date: Thu Dec 11 11:09:13 2025 -0500 fix: increment progress on upgrade script commit 9f729964161bb12b1b860b18c46379e24132d2f6 Author: Julian Lam Date: Thu Dec 11 10:56:57 2025 -0500 feat: stop extraneous vote and tids_read data from being saved for remote users commit 0ef5cbbbca94b1f7a8f0f92c6f913c05dd57ced2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 15 16:56:30 2025 -0500 fix(deps): update dependency fetch-cookie to v3.2.0 (#13836) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7c2e83303cb6d54b3f2990fbe2083544fb761acd Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 15 16:54:56 2025 -0500 fix(deps): update dependency autoprefixer to v10.4.23 (#13835) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5ae8d553ed1ba276b92afeb2b32b26623209d225 Author: Shlomo <78599753+ShlomoCode@users.noreply.github.com> Date: Mon Dec 15 20:16:38 2025 +0200 fix: disallow inline viewing of unsafe files (#13833) commit 90a151348e344b7002b8126aa8d235bda88879ce Author: Barış Soner Uşaklı Date: Sat Dec 13 17:19:16 2025 -0500 fix: moving topic to cid=-1 will remove it from list commit ad895efb61c9fb5386ee63ee266b7cf1c4f33b52 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Dec 13 10:36:23 2025 -0500 chore(deps): update dependency smtp-server to v3.17.1 (#13829) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 22fe83f0053c5ffaa762ab2d13773f39e3818dd2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Dec 13 10:36:13 2025 -0500 chore(deps): update dependency @eslint/js to v9.39.2 (#13830) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b16962186096290fa22c5f4df911eefa3d925582 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Dec 13 10:36:02 2025 -0500 chore(deps): update github artifact actions (#13831) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit da7c9b32b863551cecc44f0117eda1605145ad7e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Dec 12 07:56:00 2025 -0500 fix(deps): update dependency terser-webpack-plugin to v5.3.16 (#13827) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0fcc8543c6e687e36520142d6caa3f9daa74d918 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Dec 12 07:55:41 2025 -0500 chore(deps): update actions/cache action to v5 (#13828) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ed977c48b457a5fc992248ba3d22102f28d18c76 Merge: 20918b5281 f49f540bfa Author: Barış Soner Uşaklı Date: Thu Dec 11 21:25:56 2025 -0500 Merge branch 'master' into develop commit f49f540bfab071ba54a273300e52e1725c05a8c4 Author: Barış Soner Uşaklı Date: Thu Dec 11 21:25:42 2025 -0500 fix: show errors when saving settings commit 20918b5281a7b7d5fc9aee18ce36db22fd236218 Author: Julian Lam Date: Thu Dec 11 11:10:29 2025 -0500 fix: wrong increment value commit 8abe0dfa9feeb846007afc340c4aac1fcc40e840 Author: Julian Lam Date: Thu Dec 11 11:09:13 2025 -0500 fix: increment progress on upgrade script commit 097d0802b749de74c207fd23677401148394c0ab Author: Julian Lam Date: Thu Dec 11 10:56:57 2025 -0500 feat: stop extraneous vote and tids_read data from being saved for remote users commit 3adcbe0f7d16b11ea2b9a6abdffb63f3cf08ef85 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Dec 11 10:34:57 2025 -0500 chore(deps): update dependency smtp-server to v3.17.0 (#13824) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b992511bb981a65e0dfbd4aa026c1e994bdd0c41 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Dec 11 10:13:51 2025 -0500 chore(deps): update dependency sass-embedded to v1.96.0 (#13821) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d4f53a624287c9613b9567b9323c5dc890482b55 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Dec 11 10:13:35 2025 -0500 fix(deps): update dependency sass to v1.96.0 (#13822) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 528cd258c4a39d7f6a968bc5a81229b7dd47afb3 Author: Julian Lam Date: Wed Dec 10 12:22:44 2025 -0500 feat: support remote Dislike activity, federate out a Dislike on downvote, bwahahah commit a2f2c8c761634caebf86d073815e0dd7a06c0bd5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 9 20:04:25 2025 -0500 chore(deps): update dependency sass-embedded to v1.95.1 (#13817) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 81c232f1811caa658842b7e77e06d501d5fe1ca6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 9 19:47:59 2025 -0500 fix(deps): update dependency winston to v3.19.0 (#13812) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f077c4cab83af1481ce8ec1ea4e14283e3f9f8c9 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 9 19:46:55 2025 -0500 fix(deps): update dependency cron to v4.4.0 (#13818) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit adedb7b626bb7fc7521f7fba2f1a5a9a703bddd4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 9 19:46:11 2025 -0500 fix(deps): update dependency sass to v1.95.1 (#13816) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a35c326a6c655b1a57a9d0eae19a48f666fe3d27 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 9 19:25:06 2025 -0500 chore(deps): update dependency jsdom to v27.3.0 (#13814) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit eaa6e71a9947ea590aca41cb2c3aea00a078e72d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 9 19:24:47 2025 -0500 fix(deps): update dependency sass to v1.95.0 (#13815) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 011f8b24658747f27d0692a11ee7e39ae37d0bea Merge: 2c57cb13e4 b19281b061 Author: Barış Soner Uşaklı Date: Mon Dec 8 10:19:00 2025 -0500 Merge branch 'master' into develop commit b19281b0611328e4213259e9c3c0d686cfdd6cb0 Author: Barış Soner Uşaklı Date: Mon Dec 8 10:18:38 2025 -0500 revert: spec change commit 9d6665505e9d7930afadb752c691ed6661e25c29 Author: Barış Soner Uşaklı Date: Mon Dec 8 09:43:50 2025 -0500 chore: up widget-essentials commit 2c57cb13e43d8cb4e080c38c37460ef90d03acd4 Merge: f6fbb0226b 11b01dfccb Author: Barış Soner Uşaklı Date: Sat Dec 6 20:44:57 2025 -0500 Merge branch 'master' into develop commit 11b01dfccbf166a0cffa33098392e64f6b303533 Author: Barış Soner Uşaklı Date: Sat Dec 6 20:44:51 2025 -0500 test: fix tests commit f6fbb0226be70bc93f58f3a88bface64226b2784 Merge: 823c6cb340 193aaf55d5 Author: Barış Soner Uşaklı Date: Sat Dec 6 20:40:30 2025 -0500 Merge branch 'master' into develop commit 2e00c0ff428f0b078619889679fbddf598d4afe9 Author: Konrad Moskal Date: Sun Dec 7 02:12:27 2025 +0100 Modify delete post diff response format (#13761) * Modify delete post diff response format Updated the delete operation response to return JSON content. * fix: timestamp open api schema commit 193aaf55d5e2df41b6e5116f14098fbe55b5a166 Author: Barış Soner Uşaklı Date: Sat Dec 6 20:08:05 2025 -0500 fix: closes #13666, update category label on topic move if we are not on category page commit 823c6cb340ee88bb9ca46e67e1a13a9ff5bc40c7 Merge: e50edd52fc ebf2a2c5af Author: Barış Soner Uşaklı Date: Fri Dec 5 12:28:29 2025 -0500 Merge branch 'master' into develop commit ebf2a2c5afa31e69eb94901f8801620fe5f6fcd4 Author: Nephilim Date: Fri Dec 5 11:26:53 2025 -0600 fix: respect user pagination settings in infinite scroll (#13765) (#13788) - Changed hardcoded topicsPerPage value of 20 to use settings.topicsPerPage - Allows infinite scroll to respect user's configured page size preference - Consistent with pagination handling in other controllers (category.js, recent.js, etc) - Validates against admin's maxTopicsPerPage setting - Fixes issue where all users were limited to 20 topics per request regardless of settings commit e50edd52fca0b6735de6eee16c6e898bbc4a1553 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Dec 5 12:24:16 2025 -0500 chore(deps): update commitlint monorepo to v20.2.0 (#13810) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 10d2e929a1642b900fcd8246c27f52ab6275ffe2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Dec 5 12:24:04 2025 -0500 fix(deps): update dependency terser-webpack-plugin to v5.3.15 (#13811) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 695627041196b588171baf32dda335ec59414247 Author: Barış Uşaklı Date: Thu Dec 4 18:33:55 2025 -0500 test: add a test for set db.exists (#13809) * test: add a test for set db.exists * delete empty sets * test: psql commit 254370c5beca5062fc0307e4d1d4af09aea32bc5 Author: Jakub Bliźniuk Date: Fri Dec 5 00:11:04 2025 +0100 ci: drop ARM v7 from docker builds (#13808) Removed optional ARM v7 (32 bit) platform from the workflow due to lack of support from Node and very limited usefulness. As the platform had been flaky in the past, this part of the workflow was already optional and didn't cause it to fail. So this is just the next step here. commit c5292442297d4769ca3db6a9c814701df4ef0431 Author: Julian Lam Date: Thu Dec 4 16:03:28 2025 -0500 test: fix failing test by adjusting the tests commit f1d50c3510ba72a82ddeab224af94b2f1866eab4 Author: Julian Lam Date: Thu Dec 4 11:54:56 2025 -0500 fix: add join-lemmy context for outgoing category group actors context prop commit 6b1dcb4b90142425811b9de87e5bbfca260715e2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Dec 4 11:39:32 2025 -0500 fix(deps): update dependency esbuild to v0.27.1 (#13806) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7b734cfdc52e62fc63407dfaa2c65b39210afdf4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Dec 4 11:37:57 2025 -0500 fix(deps): update dependency jsonwebtoken to v9.0.3 (#13807) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6a56105037e76d8c54680b6c6344e4f8ff26e13c Author: Julian Lam Date: Thu Dec 4 11:00:41 2025 -0500 feat: expand postingRestrictedToMods mask testing, handle actor update for that prop commit 7d5402fe66d3c244be4e0d4103e9386a0c933f0f Author: Barış Uşaklı Date: Wed Dec 3 18:18:14 2025 -0500 feat: setAddBulk (#13805) * feat: setAddBulk add some tests * fix: sAdd with value array on redis commit d8e55d58de5f4a096f55441be0a98fe3501de420 Author: Julian Lam Date: Wed Dec 3 14:47:10 2025 -0500 fix: use setsAdd commit 4a6dcf1a21e7fd8bb8865d5ae324cf27553fee90 Author: Julian Lam Date: Wed Dec 3 14:35:07 2025 -0500 fix: missing await commit 29687722876749bca6e865867a9818e4069e8b97 Author: Julian Lam Date: Wed Dec 3 14:08:06 2025 -0500 chore: allow direct testing in test/categories.js commit 934e6be91198c76760621a554471375e98e87f08 Author: Julian Lam Date: Wed Dec 3 13:53:12 2025 -0500 test: privilege masking tests commit f0a7a442db0d05a9231fc0a1dec93976bf9feac6 Author: Julian Lam Date: Wed Dec 3 13:53:02 2025 -0500 feat: save privilege masking set when asserting group commit 7b194c69165489d21b1a83bbab03f5bcd770d4a5 Author: Julian Lam Date: Wed Dec 3 12:14:05 2025 -0500 fix: admin privilege overrides only apply to local categories commit 4020e1be3529bdff2ff2f98b67278f5f77b86dac Author: Julian Lam Date: Tue Dec 2 13:18:15 2025 -0500 feat: patch low-level privilege query calls to accept privilege masks at the cid level commit 76b6b3b259821d8e28cfbf4fefc175e28d83bd39 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Dec 3 10:14:33 2025 -0500 chore(deps): update dependency lint-staged to v16.2.7 (#13785) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4cdb56904c7474a68c36f363cd2dcc8f77ffbc88 Merge: 70169758ec 9fb41c6933 Author: Barış Soner Uşaklı Date: Wed Dec 3 10:14:13 2025 -0500 Merge branch 'master' into develop commit 9fb41c693351c566127248c2e424e620b97e3a93 Author: Barış Soner Uşaklı Date: Wed Dec 3 10:14:08 2025 -0500 lint: fix missing comma commit 70169758ec61ef29286634c0b1ded18551aaf560 Merge: 7f21a17175 ba85474dfb Author: Barış Soner Uşaklı Date: Wed Dec 3 09:49:22 2025 -0500 Merge branch 'master' into develop commit ba85474dfba92740ac877a531ee7af9ed7a5aef3 Author: Barış Soner Uşaklı Date: Wed Dec 3 09:49:16 2025 -0500 feat: add hreflang to buildLinkTag commit 7f21a17175e8e85cfd446dc347d92ac0e69f7ea0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 14:41:12 2025 -0500 chore(deps): update actions/checkout action to v6 (#13802) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 93057306f471e20bb1519cd5a1f8ca3a7fd06db5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 14:36:08 2025 -0500 fix(deps): update dependency ace-builds to v1.43.5 (#13797) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 731933a66bd50c4a137120b887c11e2e205284cb Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 14:35:46 2025 -0500 fix(deps): update dependency lru-cache to v11.2.4 (#13798) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 38321220f2bb9d37d02add7e2a9c1ea77594253d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 14:35:32 2025 -0500 fix(deps): update dependency express to v4.22.1 (#13800) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ad5cd27b377589a3c102334927cb456537f978cd Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 14:27:07 2025 -0500 fix(deps): update dependency ipaddr.js to v2.3.0 (#13801) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ecec1f45945efc66be7b213b5d266239528db5af Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 14:26:50 2025 -0500 fix(deps): update dependency nodemailer to v7.0.11 (#13799) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3b7bcba6c01780f66714fc4ba9b70b4946564cb0 Author: Julian Lam Date: Tue Dec 2 14:15:13 2025 -0500 fix: have notes.assert call out.announce.topic only if uid is set (so, if note assertion is called via search; manual pull) commit a82e1f441cda89dffa8400e30dcd26dd144c3048 Author: Julian Lam Date: Tue Dec 2 13:50:50 2025 -0500 debug: still broken... more debug logs commit 977a67f4cd8423c96af9a4c5bc81126e40a5c01d Author: Julian Lam Date: Tue Dec 2 13:42:06 2025 -0500 fix: deep clone activity prop before execution; feps.announce commit 8236b594af2553f3479245f6ef98ab74aaff881c Author: Julian Lam Date: Tue Dec 2 13:19:48 2025 -0500 debug: log mock results commit 22d3c52332cf428e2cec2eea1ba9a841595b1480 Author: Barış Soner Uşaklı Date: Tue Dec 2 12:59:28 2025 -0500 test: log label commit e39c91497fa58c4d3da786ed73adc8fdd76c2eaa Author: Barış Soner Uşaklı Date: Tue Dec 2 12:47:47 2025 -0500 test: log activities commit 841bd8252cdb62629ef62fd19cd6015133c72208 Author: Barış Soner Uşaklı Date: Tue Dec 2 11:54:14 2025 -0500 test: on test fail show activities commit 5ba6bea049333fc147f2a828b154c11a3407f30d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 11:44:46 2025 -0500 fix(deps): update dependency cron to v4.3.5 (#13796) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 624ef616553efd6983293998d5f91592c355ef3d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 11:44:29 2025 -0500 fix(deps): update dependency body-parser to v2.2.1 (#13795) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 53e22acffbfe7f3eb4edbbefeb94a73725520573 Author: Barış Soner Uşaklı Date: Tue Dec 2 11:12:05 2025 -0500 fix: remove hardcoded name for sentinel, #13794 commit 287b25695d4e5232a1a135a9b9ce23118c474ae8 Author: Barış Uşaklı Date: Tue Dec 2 11:08:20 2025 -0500 test: new mongodb deps (#13793) commit 5f55ca85e61c002f7ce140266e71dad5526b160f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Dec 2 09:50:11 2025 -0500 fix(deps): update dependency @isaacs/ttlcache to v2.1.3 (#13791) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1cb8b381d513c3f3d344e50d294afb2d5f60e27c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 1 18:53:29 2025 -0500 fix(deps): update dependency sass to v1.94.2 (#13786) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1bcfe3f09ef1084bf2360186eed8dfd7a6fba70b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Dec 1 18:53:11 2025 -0500 fix(deps): update dependency redis to v5.10.0 (#13787) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 2142b680d995b433b2cc1291d80b9995fe4f4d21 Author: Barış Soner Uşaklı Date: Mon Dec 1 17:47:46 2025 -0500 chore: remove log commit 5bd1f7b7ac95a3498f8e8d7543289ec467206a5d Author: Barış Soner Uşaklı Date: Mon Dec 1 17:46:01 2025 -0500 feat: #13790, allow ssl setup in psql commit 936dede44f9c1ca0a76a0a974cd2fe1f0ad0991f Merge: 3ab61615af 59f649b885 Author: Barış Soner Uşaklı Date: Mon Dec 1 11:15:23 2025 -0500 Merge branch 'master' into develop commit 59f649b8851f4a224b5b4133df170f8996da2588 Author: Barış Soner Uşaklı Date: Mon Dec 1 11:15:14 2025 -0500 chore: up harmony commit 3ab61615af14db54bfdbfe981de0aea21ccea414 Author: Julian Lam Date: Mon Dec 1 11:11:27 2025 -0500 feat: federate out topic removal activities when topic is deleted and purged from a local category commit 411baa21f42735cd75938711599b0b1c21457c84 Author: Julian Lam Date: Mon Dec 1 10:25:32 2025 -0500 fix: minor comment fix commit c365c1dc3e2aaee822c3f08a309ef6984211bc58 Author: Julian Lam Date: Wed Nov 26 12:29:48 2025 -0500 fix: publish `postingRestrictedToMods` property in group actor commit 1c0a43dc556464fb692db2c317f7ff8b0e01cc2f Author: Misty Release Bot Date: Wed Nov 26 16:59:43 2025 +0000 chore: update changelog for v4.7.0 commit 3d8638ed5cbb13642535a76f7956edbe58c9ba4e Author: Misty Release Bot Date: Wed Nov 26 16:59:42 2025 +0000 chore: incrementing version number - v4.7.0 commit 2bb6683f1613f06a64b1f2834cf420cf474c904d Merge: e3ac9ccf1b bdb452488b Author: Julian Lam Date: Wed Nov 26 10:53:44 2025 -0500 Merge remote-tracking branch 'origin/master' into develop commit e3ac9ccf1b3bbc89028f1435291a5ca7492e3043 Author: Julian Lam Date: Tue Nov 25 13:19:19 2025 -0500 fix(deps): bump mentions to fix #13637 commit 9d83a3d0da3ae6c86a04cf5ca1353473705e0a30 Author: Julian Lam Date: Mon Nov 24 15:48:38 2025 -0500 fix: null check on attachments property in assertPrivate commit 24e176831257688e4a5e71d05d0fb19d5670eadc Author: Julian Lam Date: Mon Nov 24 12:20:35 2025 -0500 fix: update announce and undo(announce) so that their IDs don't use timestamps commit 832477f8199f9abf8cf472f58fde46e362cc682b Author: Julian Lam Date: Mon Nov 24 11:53:59 2025 -0500 feat: federate out undo(announce) when moving topics commit 2b733e4a8ee610e8a959bee01c05b95c2cfd23db Author: Julian Lam Date: Fri Nov 21 15:20:44 2025 -0500 fix: incorrect topic event added when topic moved out of cid -1 (used to be a share by the user; since removed.) commit aa7e078fbff9d0f538148625f2bfdae628d27987 Author: Julian Lam Date: Thu Nov 20 14:15:43 2025 -0500 fix: #13654, improper OrderedCollectionPage ID commit 822f4edc4cd1d7c01c883d7efe8c941fa935938c Author: Julian Lam Date: Thu Nov 20 14:08:12 2025 -0500 feat: native image appending for remote private notes commit bdb452488b8c401eaa5dbff230f0ade65a4d45b8 Author: Barış Soner Uşaklı Date: Thu Nov 20 12:58:07 2025 -0500 fix: IS logic when body.height < window.height commit 6cd8a7c7400ae371781ee26c76c8429ee50d0ad6 Merge: 894f19882e 3fd193e373 Author: Barış Soner Uşaklı Date: Thu Nov 20 09:14:51 2025 -0500 Merge branch 'master' into develop commit 3fd193e3730e814584502bca049377d33c5a4425 Author: Misty Release Bot Date: Thu Nov 20 14:13:20 2025 +0000 chore: update changelog for v4.6.3 commit af7f78e9b6cb1ad192add9f3440c3da9c2eb833e Author: Misty Release Bot Date: Thu Nov 20 14:13:20 2025 +0000 chore: incrementing version number - v4.6.3 commit 894f19882e0e1e6f2ae38ad1faa786c4be031bd2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 20 09:07:58 2025 -0500 chore(deps): update dependency @stylistic/eslint-plugin to v5.6.1 (#13778) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5ab8f87741e3717a7391205f4141ba01a22bedc5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 20 09:07:38 2025 -0500 fix(deps): update dependency rimraf to v6.1.2 (#13784) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a34794b5ee3c7ba096eaee5dfa4d33d48f430107 Merge: af477d0c2d 76a07d5941 Author: Barış Soner Uşaklı Date: Thu Nov 20 09:02:22 2025 -0500 Merge branch 'master' of https://github.com/NodeBB/NodeBB commit af477d0c2d3361bb3274e431853bcedd4648744b Author: Barış Soner Uşaklı Date: Thu Nov 20 09:02:17 2025 -0500 fix: update validator dep. to get fix for CVE-2025-56200 https://nvd.nist.gov/vuln/detail/CVE-2025-56200 commit d4e3b423691ccbcbbebdbea0f4f6798fa57f46be Merge: cecc0fee54 76a07d5941 Author: Julian Lam Date: Wed Nov 19 15:17:08 2025 -0500 Merge branch 'master' into develop commit 76a07d5941fab8100864b5666969fc1c327585ee Author: Julian Lam Date: Wed Nov 19 15:16:51 2025 -0500 fix: missing logic in mocks.notes.private that precluded the use of emoji commit cecc0fee5402f0902f80a1a029c361a643e80150 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Nov 19 11:31:04 2025 -0500 fix(deps): update dependency @isaacs/ttlcache to v2.1.2 (#13780) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bfffb4b9e65657faae815c36099ce00fe8776fe6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Nov 19 11:29:41 2025 -0500 fix(deps): update dependency workerpool to v10.0.1 (#13781) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e24d8c1780f3ca84e15101a29610259816f1d93e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Nov 19 11:22:37 2025 -0500 chore(deps): update redis docker tag to v8.4.0 (#13782) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5acfd1844ce6c0d662b5a8915a75e5fc0d63f027 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Nov 19 11:22:27 2025 -0500 fix(deps): update dependency webpack to v5.103.0 (#13783) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 12dab84914c60f6a8f108819680918a958e9edef Author: Barış Soner Uşaklı Date: Wed Nov 19 11:03:44 2025 -0500 fix: tiny fix for IS when page is empty related https://github.com/NodeBB/NodeBB/commit/4aad6019675ef4a2123c5ac2211cf09a2b0d76a5 https://github.com/NodeBB/NodeBB/pull/9525 commit b0c9bb1eed0d13f674a8f12a800479c0972a69a0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Nov 19 10:37:18 2025 -0500 fix(deps): update dependency sass to v1.94.1 (#13777) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 958bb04769fef4dc498f85991d1ecb49b768b9f6 Merge: 88598886f0 8da3819c5f Author: Barış Soner Uşaklı Date: Wed Nov 19 10:32:58 2025 -0500 Merge branch 'master' into develop commit 8da3819c5f5977a2ac3ab5b82ecbf423cb8d064b Author: Misty Release Bot Date: Wed Nov 19 15:31:58 2025 +0000 chore: update changelog for v4.6.2 commit 065a87ad5c61f64dad2d5e26ab0f4e50799df7fe Author: Misty Release Bot Date: Wed Nov 19 15:31:57 2025 +0000 chore: incrementing version number - v4.6.2 commit 88598886f055d36b9e625c1ca0b5bf9b99a16c69 Merge: b00c08ce89 e300241193 Author: Barış Soner Uşaklı Date: Wed Nov 19 08:45:35 2025 -0500 Merge branch 'master' into develop commit e30024119334479c57e442a72ed72a07084fe1e9 Author: Barış Soner Uşaklı Date: Wed Nov 19 08:45:08 2025 -0500 fix: #13779, svg uploads if tmp file doesn't have extension stripEXIF was called for gifs/svgs commit b00c08ce8928439560d9191103954e3ff47f4392 Merge: 26795272fa abfb6d1365 Author: Barış Soner Uşaklı Date: Tue Nov 18 10:24:37 2025 -0500 Merge branch 'master' into develop commit abfb6d13654a3b87cfed3e9c40ec41f342b6b7c8 Author: Barış Soner Uşaklı Date: Tue Nov 18 10:23:49 2025 -0500 fix: #13776, if plugin is in install/package.json use latest version from there otherwise show latest version from nbbpm commit 26795272fa6c50b77ca86d6c10a61636763086e0 Merge: c51b7b650a dece0628bf Author: Barış Soner Uşaklı Date: Mon Nov 17 20:25:14 2025 -0500 Merge branch 'master' into develop commit dece0628bf59bfd533f54fe1d4ab797abd2aa60d Author: Barış Soner Uşaklı Date: Mon Nov 17 20:25:05 2025 -0500 fix: category labels showing up on infinite scroll on category page regression from cross posting tpl change commit aacd27ee3242afd164b0bb394743e73f9e39f0b2 Author: Barış Soner Uşaklı Date: Mon Nov 17 20:13:55 2025 -0500 refactor: remove unused share commit c51b7b650a25257b8c582e86d04cc23873837935 Author: Julian Lam Date: Mon Nov 17 14:34:35 2025 -0500 fix: update markdown and web-push to latest versions commit 2ce691cb774309808fb403fd2a0ce74f521a77fe Author: Julian Lam Date: Mon Nov 17 14:30:18 2025 -0500 fix: bump mentions to 4.8.2 commit e2f4884e1508c3c4166a923f8e79cdbc33da126e Merge: db4ba8c385 5bc5bb3d08 Author: Barış Soner Uşaklı Date: Mon Nov 17 13:01:58 2025 -0500 Merge branch 'master' into develop commit 5bc5bb3d0828392bdf7a8e8c73c67608c8212271 Author: Barış Soner Uşaklı Date: Mon Nov 17 13:01:37 2025 -0500 chore: up emoji commit db4ba8c385cec9d61644a26a62f7be7d71d5d11b Merge: 3ea029bdb2 f764b79118 Author: Barış Soner Uşaklı Date: Mon Nov 17 08:00:46 2025 -0500 Merge branch 'master' into develop commit f764b79118c427cfa6e2e718efb28582769c1a9b Author: Barış Soner Uşaklı Date: Mon Nov 17 07:59:49 2025 -0500 chore: up peace, closes #13774 commit 3ea029bdb24415efd732e0fe632c24e7d87b5c23 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:45:13 2025 -0500 chore(deps): update postgres docker tag to v18.1 (#13771) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 111ae163304ae486712df2f9c962f4ca94dd6485 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:45:04 2025 -0500 fix(deps): update dependency mongodb to v6.21.0 (#13772) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c95bfcbf5e08e9326cf1a207868fd6bb994cd927 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:44:53 2025 -0500 fix(deps): update dependency sass to v1.94.0 (#13773) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 899414f418bcec6fde4863c2ed750310cded9705 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:18:15 2025 -0500 chore(deps): update dependency jsdom to v27.2.0 (#13770) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 93c69f9d77e2abfe6b98f3ee6eaff11c29cc025e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:18:03 2025 -0500 fix(deps): update dependency validator to v13.15.23 (#13769) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e14d3ac14be9b8e22e9f69a5670f8d42d1558cb1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:01:52 2025 -0500 fix(deps): update dependency express-useragent to v2.0.2 (#13767) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9271e26786024789bc1beae32387638678d588ff Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Nov 15 12:01:17 2025 -0500 fix(deps): update dependency autoprefixer to v10.4.22 (#13768) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 50951d5db5bba6f5aec7da2db7c3d2d4c066c89f Merge: f24bb090b1 9900171f24 Author: Barış Soner Uşaklı Date: Thu Nov 13 12:25:24 2025 -0500 Merge branch 'master' into develop commit 9900171f24f26fa6d88237d109ce401926616843 Author: Barış Soner Uşaklı Date: Thu Nov 13 12:25:16 2025 -0500 fix: crash in resolveInboxes 2025-11-13T12:38:44.161Z [4568/2508892] - error: uncaughtException: Invalid URL TypeError: Invalid URL at new URL (node:internal/url:818:25) at /home/saas/nodebb/src/activitypub/index.js:123:25 at Array.filter () at ActivityPub.resolveInboxes (/home/saas/nodebb/src/activitypub/index.js:122:13) at ActivityPub.send (/home/saas/nodebb/src/activitypub/index.js:424:36) at Object.announce (/home/saas/nodebb/src/activitypub/feps.js:72:20) commit f24bb090b135b13bb7f36dceddfb3f016a53aeb1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 13 10:47:24 2025 -0500 fix(deps): update dependency @isaacs/ttlcache to v2.1.1 (#13763) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 63789ebb3b7ae51cf77fc5c25b8b1a15a38987e0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 13 10:46:53 2025 -0500 fix(deps): update dependency esbuild to v0.27.0 (#13766) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5d9da6035e3aa74c0e95f7c16d36c18deb690999 Author: Julian Lam Date: Mon Nov 10 11:55:19 2025 -0500 fix: log out user if session cookie resolves to non-existent uid commit 6ad93cd33265325eafc52f72574a03a4ed9eb9dc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 6 18:20:28 2025 -0500 fix(deps): update dependency cron to v4.3.4 (#13762) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bc64d27f735e2c6421703ec3fb3a80e4a68d6e08 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 6 17:33:59 2025 -0500 chore(deps): update dependency smtp-server to v3.16.1 (#13755) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c88ecd25eb76f53c711f089af429667c4a54ed33 Author: Misty Release Bot Date: Thu Nov 6 20:53:04 2025 +0000 Latest translations and fallbacks commit 3a81f9032201adfe355a1aa35cdf59bb67172f93 Author: Julian Lam Date: Thu Nov 6 15:51:23 2025 -0500 fix: make i18n test failure message easier to read commit 5be0a63054245c2da1eb2589aa024f1f2a195b31 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 6 15:35:13 2025 -0500 fix(deps): update dependency sharp to v0.34.5 (#13758) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit dfe53d293a4a8ff48c4e72fd7ca1da220d30c72b Author: Barış Soner Uşaklı Date: Thu Nov 6 12:36:26 2025 -0500 chore: up dbsearch commit 9bb8a955cf1daa97f12eefb5ea2a4e94e7f155ac Author: Julian Lam Date: Thu Nov 6 11:59:56 2025 -0500 fix: rename activitypub.out.announce.category, federate out Delete on topic move to cid -1 commit e6911be35db55b7ad46c10a0bc3c9ed83d1d7b9e Author: Julian Lam Date: Thu Nov 6 11:30:17 2025 -0500 refactor: deleteOrRestore internal method to federate out a Delete on delete, not just purge; better adheres to FEP 4f05 commit e1bf80dcefd05411c037da2891f6620b3810be75 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Nov 6 09:44:09 2025 -0500 chore(deps): update dependency mocha to v11.7.5 (#13754) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 62889d2d5c242534403229f0c4d8777073f9d6dc Author: Misty Release Bot Date: Thu Nov 6 09:21:54 2025 +0000 Latest translations and fallbacks commit c616e657ccd351c4831ccd880608c70fe2ce18c1 Author: Julian Lam Date: Wed Nov 5 13:50:47 2025 -0500 fix: bump harmony and persona for #13756 commit 2066727f3e325058008c5eff495fa347d1882251 Author: Julian Lam Date: Wed Nov 5 13:42:56 2025 -0500 fix: renderOverride to not clobber url if already set in template data commit 172aabcb57aa7b03e1b353b81e9e5a61d9633bc3 Author: Julian Lam Date: Wed Nov 5 13:42:20 2025 -0500 feat: add isNumber to client-side helpers commit 49567c72959d0fee3eacd1e2295d2203aaadd2f2 Author: Misty Release Bot Date: Wed Nov 5 17:55:34 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories commit ed83bc5b83f243e68faa56f1c1852b613aa08c2b Author: Julian Lam Date: Wed Nov 5 12:55:03 2025 -0500 revert: remove `federatedDescription` category field, closes #13757 commit a8e45587bc8015974d80a76d37e60ff026726827 Author: Misty Release Bot Date: Wed Nov 5 09:22:28 2025 +0000 Latest translations and fallbacks commit 4c5f7f6060c87a6528545a26f95cd05a79357638 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Nov 4 12:54:57 2025 -0500 chore(deps): update redis docker tag to v8.2.3 (#13750) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a34284df834b2fc5ea50655b528e05d80ca22cf0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Nov 4 12:44:03 2025 -0500 fix(deps): update dependency bcryptjs to v3.0.3 (#13751) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1921ccaa101baff4b777d995b3a686e56a9ab619 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Nov 4 12:43:34 2025 -0500 fix(deps): update dependency sitemap to v9 (#13752) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4e33c1dfd3a59e15f194659b8f810c258839a6e0 Author: Barış Soner Uşaklı Date: Tue Nov 4 12:42:08 2025 -0500 chore: up harmony, closes #13753 commit 13c23fddd7508388a54216f4bc63203bdf5625c9 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Nov 4 11:56:41 2025 -0500 chore(deps): update github artifact actions (#13730) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4e7867a95d7cca8b244773c897d3e94ffc08b1d6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Nov 4 11:56:01 2025 -0500 chore(deps): update dependency @eslint/js to v9.39.1 (#13747) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 090eb0884527b7a36a84985b04dc3aea40d3bd4b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Nov 4 11:55:48 2025 -0500 fix(deps): update dependency esbuild to v0.25.12 (#13748) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7fb9bb2f154365bcc766cfccc8c518e88125a79f Merge: 85d2667215 be4d0e811e Author: Julian Lam Date: Tue Nov 4 11:32:55 2025 -0500 Merge remote-tracking branch 'origin/master' into develop commit be4d0e811e925ff19a30afd912139f959e6afd14 Author: Julian Lam Date: Tue Nov 4 11:09:15 2025 -0500 fix: wrong auto-categorization if group actor is explicitly included in `audience` commit 85d2667215dd944405b0054ef39907416ad00b64 Author: Misty Release Bot Date: Sat Nov 1 09:20:27 2025 +0000 Latest translations and fallbacks commit a36d89fcdaa0759e31ee2a38fbbe2f04784ac6a6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 31 20:27:53 2025 -0400 fix(deps): update dependency rimraf to v6.1.0 (#13744) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ba1230735f7d23cb930a53b5e243bd9ba6b33b3b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 31 20:27:10 2025 -0400 fix(deps): update dependency sass to v1.93.3 (#13746) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit cb96701b470bb6bc95062b4e76a526629008841d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 31 20:27:01 2025 -0400 chore(deps): update dependency sass-embedded to v1.93.3 (#13745) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4ce4e773cb4c23dbacc6b5c26f9cad8f74799162 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 31 16:17:35 2025 -0400 chore(deps): update dependency jsdom to v27.1.0 (#13743) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 98a1101d40a52c778cc6e3d111d447b2552c89d2 Author: Julian Lam Date: Fri Oct 31 09:44:06 2025 -0400 test: update test for toPid logic to reflect that toPid stays even if parent is purged commit 9d3e8179600482ce7cb175466e14042378908ec3 Author: Julian Lam Date: Fri Oct 31 09:40:59 2025 -0400 fix: bump themes for cross-post support, #13396 commit 179440372aef747f824b91e5fa023d1e269f22eb Author: Barış Soner Uşaklı Date: Thu Oct 30 20:34:01 2025 -0400 refactor: get rid of post.exists check, if post doesnt exist content is falsy commit b5ea20898e40c93f4dc125a948ba2d016af755b1 Author: Barış Soner Uşaklı Date: Thu Oct 30 20:32:24 2025 -0400 chore: up express-useragent commit 425d2eb2954f6c32aec6cb8c3d58712965ab49e9 Author: Misty Release Bot Date: Thu Oct 30 09:20:53 2025 +0000 Latest translations and fallbacks commit 4858abe1498a6c75d3f3b7108484d32f7e7439c6 Author: Julian Lam Date: Wed Oct 29 15:18:13 2025 -0400 fix: add replies in parallel during note assertion commit 748cc5eecda87f2f0c4335f2318c00689cf52a04 Author: Julian Lam Date: Wed Oct 29 15:15:01 2025 -0400 fix: logic error in context generation commit 30b1212a0ae0e40e6c18cd7cf6af7ce37e9a80c5 Author: Julian Lam Date: Wed Oct 29 14:52:59 2025 -0400 fix: relax toPid assertion checks so that it only checks that it is a number or uri commit f6219d0026bd977f96dc021c8bf35707ce7059e8 Author: Julian Lam Date: Wed Oct 29 14:49:53 2025 -0400 fix: update logic so that purging a post does not remove toPid fields from children, updated addParentPosts so that post existence is checked commit 728184dab6b725e5a9b91899c3e430fcaae536b5 Merge: a0a10c8b5c 97e5aa1d18 Author: Barış Soner Uşaklı Date: Wed Oct 29 13:16:39 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit a0a10c8b5cd667e699b1e1aef15e6a67d3f2b6d7 Author: Barış Soner Uşaklı Date: Wed Oct 29 13:16:34 2025 -0400 chore: up ttlcache to 2.x commit 97e5aa1d1823ea1fae4cb8cd126687e654fcdefe Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 29 13:08:05 2025 -0400 chore(deps): update mongo docker tag to v8.2 (#13738) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b5c1e8e7f624aa5dc4991de64c722b115771ba66 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 29 13:07:55 2025 -0400 fix(deps): update dependency sitemap to v8.0.2 (#13736) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 07d169d29e2665fa26141661a09f04a8c7624070 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 29 13:07:34 2025 -0400 chore(deps): update dependency smtp-server to v3.16.0 (#13737) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5c3b126166e839c289453b9b6cad1dfb63356a8a Author: Julian Lam Date: Wed Oct 29 12:32:36 2025 -0400 fix(deps): update mentions commit 524df6e5485aa833fc7ec55333908e99e1a7a0b0 Author: Julian Lam Date: Wed Oct 29 12:32:21 2025 -0400 fix: update category mock to save full handle commit 6f448ce2f6d433ad22dd9d8158da1c3793333039 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 29 11:47:10 2025 -0400 fix(deps): update dependency validator to v13.15.20 (#13733) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 964a5388b72195381d0017ca15b1eca1a63926cc Author: Julian Lam Date: Tue Oct 28 13:40:35 2025 -0400 fix(deps): bump mentions to 4.8.0 commit a68b00ade9acc46b96d46c5c37f711668c71a624 Merge: d1a06ba020 5cfec5b1a9 Author: Julian Lam Date: Tue Oct 28 11:51:10 2025 -0400 Merge branch 'master' into develop commit 5cfec5b1a9003aae2ce851688336775b5d2932af Author: Julian Lam Date: Tue Oct 28 11:51:02 2025 -0400 fix: order of operations when updating category handle commit d1a06ba0200e65c4d05198593af917252c0d5981 Merge: a49efe49ea 07eb16150c Author: Barış Soner Uşaklı Date: Mon Oct 27 20:11:21 2025 -0400 Merge branch 'master' into develop commit 07eb16150c4e4d14757be1964faa5bafd28d58b7 Author: Barış Soner Uşaklı Date: Mon Oct 27 20:07:33 2025 -0400 center user count in chat, add commas to usercount, make last user image full width commit a49efe49ea7dbfb724e26a10ff66720893a2f328 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 27 10:57:57 2025 -0400 fix(deps): update dependency commander to v14.0.2 (#13731) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ff5f65bfa1cbdcab9738c8d488fb2ff601af2a07 Author: Misty Release Bot Date: Mon Oct 27 09:21:45 2025 +0000 Latest translations and fallbacks commit ab9154aa49a132b28b2a938b19c143e1a638b041 Author: Julian Lam Date: Fri Oct 24 13:32:04 2025 -0400 fix: logic error in out.remove.context commit 9ce84808315c7409c08b4a399b113986271df3a8 Merge: aa1ba4b59e 008e1ae4e4 Author: Barış Soner Uşaklı Date: Fri Oct 24 11:27:50 2025 -0400 Merge branch 'master' into develop commit 008e1ae4e497f2ea8aa1f1eeab8533567dbad586 Author: Barış Soner Uşaklı Date: Fri Oct 24 11:27:43 2025 -0400 lint: fix lint commit aa1ba4b59e27be1e1e51f783c0e458449cfa809e Merge: 418717fdff 430a3e8113 Author: Barış Soner Uşaklı Date: Fri Oct 24 11:13:11 2025 -0400 Merge branch 'master' into develop commit 430a3e81130ac6c1393f9854e1ac78de484e8c92 Author: Barış Soner Uşaklı Date: Fri Oct 24 11:12:20 2025 -0400 test: add test for #13729 commit 9410f466d80b30f52e5f926e5d17a513beec1084 Author: Barış Soner Uşaklı Date: Fri Oct 24 11:04:29 2025 -0400 fix: closes #13729, fix filename encoding commit 418717fdff7606651f9bc66ad9a597bd024a823d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 24 09:16:38 2025 -0400 fix(deps): update dependency redis to v5.9.0 (#13727) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 25c088b228c12aa233764e2bc96edde602736b68 Author: Misty Release Bot Date: Fri Oct 24 09:21:02 2025 +0000 Latest translations and fallbacks commit 8ca52c7e78e9eaa314e2fe84b4f073b3cd7ceb39 Author: Julian Lam Date: Thu Oct 23 12:15:36 2025 -0400 feat: handle Move(Context) activity commit 194cedb4d7e637b017c6c414a2f76e7dc795ab04 Author: Julian Lam Date: Thu Oct 23 12:02:59 2025 -0400 fix: cross-check remove(context) target prop against cid commit e3c55f76c1b6aacf31d1c9807ce79ad04f40907c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Oct 23 09:36:55 2025 -0400 chore(deps): update dependency lint-staged to v16.2.6 (#13725) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c1f6e52ba5c6fd16dff1b55afb0c214d8d3f4690 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Oct 23 09:36:33 2025 -0400 fix(deps): update dependency nodemailer to v7.0.10 (#13726) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4f2f872bf993313694701981e7d01b3d22fe671b Author: Julian Lam Date: Wed Oct 22 15:15:19 2025 -0400 fix: update logic re: federating out topic moves commit 22868d3f97be1807a53872d3cade8f735f83064d Author: Julian Lam Date: Wed Oct 22 15:05:06 2025 -0400 fix: bad var commit d02e188a5f7d6fb271d28653a82159ea9802d18c Author: Julian Lam Date: Wed Oct 22 15:04:47 2025 -0400 feat: update Remove(Context) to use target instead of origin, federate out Move(Context) on topic move between local cids commit 3ede64d8a12a1ecc1970f0d9218ea7bcae9100e1 Author: Julian Lam Date: Wed Oct 22 12:51:50 2025 -0400 refactor: move all methods in src/api/activitypub.js to src/activitypub.out.js commit bb34b8c7a3b2fd617bc7bb8000df38ae897baea0 Author: Misty Release Bot Date: Wed Oct 22 09:20:27 2025 +0000 Latest translations and fallbacks commit 5a6c209770c3439edf7564f6a18ed693a6b6b683 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Oct 21 20:23:22 2025 -0400 fix(deps): update dependency workerpool to v10 (#13723) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3df4970ce112970cba895253465186403c8e3849 Author: Julian Lam Date: Tue Oct 21 12:16:20 2025 -0400 fix: call api.topics method on topic move during note assertion, have category announce new topic on note assertion commit 34e95e6d46b670df2a3973c842d7b667bbe9d0ea Author: Julian Lam Date: Tue Oct 21 12:00:01 2025 -0400 feat: context removal logic (aka moving topics to uncategorized, and federating this to other NodeBBs) Squashed commit of the following: commit 3309117eb1c08f3a1bcfa2c56fa81a81427c0f0c Author: Julian Lam Date: Tue Oct 21 11:48:12 2025 -0400 fix: activitypubApi.remove.context to use oldCid instead of cid commit e90c5f79eb42fc17c5329c7083dcf5d462bb5d0a Author: Julian Lam Date: Tue Oct 21 11:41:05 2025 -0400 fix: parseInt cid in cid detection for api.topics.move commit ab6561e60f1d05e0010ae28868d62070d6857855 Author: Julian Lam Date: Mon Oct 20 14:03:45 2025 -0400 feat: inbox handler for Remove(Context) commit 30dc527cc0ec5bfbe4c0cfd459ef5a517a645871 Author: Julian Lam Date: Mon Oct 20 12:17:23 2025 -0400 feat: unwind announce(delete), federate out Remove(Context) on delete, but not on purge commit 83a172c9a49bb499e6bb84dd42bdf63829ad313d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Oct 21 10:13:58 2025 -0400 chore(deps): update dependency lint-staged to v16.2.5 (#13721) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e7498e8fb5d2c852752a0d1128279655985420e3 Author: Misty Release Bot Date: Tue Oct 21 14:11:49 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-uploads commit 97e59fbe0430f40ec28ee8df0668b2d1218b7fa4 Author: Barış Soner Uşaklı Date: Tue Oct 21 10:11:18 2025 -0400 feat: add new setting to control posts uploads being shown as thumbs commit 93d46c842ea4f94218783bb35e19da283b8c86ef Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 20 11:44:31 2025 -0400 chore(deps): update dependency @stylistic/eslint-plugin to v5.5.0 (#13717) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9d2b83f5636f88befa9f5a6a7e45bf0db16aca8d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 20 11:33:11 2025 -0400 chore(deps): update dependency jsdom to v27.0.1 (#13718) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1d9d7fc56b222ddd26e2661f5d0407fc8d99c9d6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 20 11:32:52 2025 -0400 fix(deps): update dependency sitemap to v8.0.1 (#13720) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7fd9e89495cebba1fca0aa63b1ba1585572129c0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 17 22:25:08 2025 -0400 chore(deps): update dependency @eslint/js to v9.38.0 (#13716) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 27a0dc731bddf58db8406a0bf9f619d5a69e056e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 17 22:10:24 2025 -0400 fix(deps): update dependency ace-builds to v1.43.4 (#13714) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f59b1c03b89c1aa9a91d6f37f7132aea8e9390c1 Merge: 52c56bc545 655c858b5d Author: Barış Soner Uşaklı Date: Fri Oct 17 22:03:10 2025 -0400 Merge branch 'master' into develop commit 52c56bc5453bd68d64082cf4ce5bfcd728a9cb1f Author: Barış Soner Uşaklı Date: Fri Oct 17 22:02:57 2025 -0400 chore: up themes commit 2425f3b671ef1f286740f21cac788c206cef8546 Author: Barış Soner Uşaklı Date: Fri Oct 17 16:23:50 2025 -0400 https://github.com/NodeBB/NodeBB/issues/13713 commit 655c858b5de68e5e439ecb96600ed0aa99bf6caf Author: Misty Release Bot Date: Fri Oct 17 15:21:57 2025 +0000 chore: update changelog for v4.6.1 commit 351c9abc6f3016a55b34b6c791b85a7bb7f9a8d7 Author: Misty Release Bot Date: Fri Oct 17 15:21:57 2025 +0000 chore: incrementing version number - v4.6.1 commit ecf95d1898347d3d8f0c6799d6c2ea9c92d4818b Author: Julian Lam Date: Fri Oct 17 11:11:04 2025 -0400 fix: do not include image or icon props if they are falsy values commit 603068aebb792dfbf4696c5e7a158bedf9484289 Author: Julian Lam Date: Fri Oct 17 11:11:04 2025 -0400 fix: do not include image or icon props if they are falsy values commit f98a7216a3feed00c80a91ee6276126bfc51158b Author: Julian Lam Date: Thu Oct 16 16:23:27 2025 -0400 feat: handle Delete(Context) as a move to cid -1 if the remote context still exists commit e09bb8b611acca5348a18bd19d3ed6c2a70129b9 Author: Julian Lam Date: Thu Oct 16 15:57:01 2025 -0400 refactor: user announces no longer occur on topic move. Instead, the new category announces. Only occurs when topic moved to local categories. commit 1d529473b4ca1123ddc73bd480044e32f63042d3 Author: Julian Lam Date: Thu Oct 16 12:17:52 2025 -0400 fix: rebroadcasting logic should only execute for local tids if the remote cid is not addressed already commit 2b2028e4469702547184784aa43568b820dae86f Author: Julian Lam Date: Thu Oct 16 11:27:21 2025 -0400 refactor: inbox announce(delete) handling to also handle context deletion, #13712 commit 4d5005b972664150ba1f38b652304d4c30b2856a Author: Julian Lam Date: Thu Oct 16 11:12:00 2025 -0400 feat: handle incoming Announce(Delete), closes #13712 commit fadac6165e362e4bbfd53ac364a9d18e32d27451 Author: Julian Lam Date: Wed Oct 15 15:02:23 2025 -0400 fix: move Announce(Delete) out of topics.move and into topics API method commit 3fa74d4cecc130fc584a6f9989fbaeb2da5a47dc Author: Julian Lam Date: Wed Oct 15 12:33:57 2025 -0400 fix: do not include actor from reflected activity when rebroadcasting remote cid commit d4695f1085e356507ab6a1b17329abc4b082cc72 Author: Julian Lam Date: Wed Oct 15 12:31:55 2025 -0400 fix: broken category urls in to, cc commit a45f6f9c4cd1014202dd4900051068d87c836cbb Author: Julian Lam Date: Wed Oct 15 12:24:42 2025 -0400 fix: update getPrivateKey to send application actor key when cid 0 commit 58a9e1c4f9ec6cba94d5422b36005cf508f7a09d Author: Julian Lam Date: Wed Oct 15 12:08:29 2025 -0400 fix: update targets in 1b12 rebroadcast when cid is remote commit 79d088536a590671765bebdf08f90218c28ba445 Author: Julian Lam Date: Wed Oct 15 12:03:26 2025 -0400 fix: update 1b12 rebroadcast logic to send as application actor if post is in remote cid commit c25c629023959e078630bcaa0d2fa618936b3fc8 Author: Julian Lam Date: Wed Oct 15 11:52:47 2025 -0400 fix(deps): bump dbsearch commit 9583f0d49b040d82130051f9958b64fb50c476a9 Author: Julian Lam Date: Wed Oct 15 11:24:08 2025 -0400 feat: execute 1b12 rebroadcast logic on all tids even if not posted to a local cid commit 41b7a91d8f3e02cf7836086aee8a189f8a35df3d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 15 09:10:55 2025 -0400 fix(deps): update dependency esbuild to v0.25.11 (#13710) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit febe0ae01aa14db94e2ac7c2f41f6ca99f90c5ae Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Oct 14 13:24:44 2025 -0400 chore(deps): update actions/setup-node action to v6 (#13708) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bf37c7bd77cd4a391d8b9c80d16915ba5d1f0608 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Oct 14 13:24:26 2025 -0400 fix(deps): update dependency chart.js to v4.5.1 (#13704) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit af5efbd71d6ad9bedbf1ad74e1ede162f3a7fe31 Author: Julian Lam Date: Tue Oct 14 11:21:39 2025 -0400 fix: regression caused by d3b3720915f5846e8f5a8e0bee9c17b3ff233902 commit c80cb51065dd898b1ecc86c8fe234dc3ec5ab348 Merge: 238600a0ec 499c50a485 Author: Barış Soner Uşaklı Date: Mon Oct 13 13:45:21 2025 -0400 Merge branch 'master' into develop commit 499c50a485eb6db4b8600f253846591caa909a93 Author: Barış Soner Uşaklı Date: Mon Oct 13 13:45:11 2025 -0400 fix: #13705, don't cover link if preview is opening up commit 238600a0ec85f45e3306c1877168d1b8ff92ebad Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 21:04:03 2025 -0400 chore(deps): update dependency smtp-server to v3.15.0 (#13702) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f608c7c7a78e85d8cec27ad118f06f5c3945decf Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 21:03:52 2025 -0400 chore(deps): update dependency lint-staged to v16.2.4 (#13699) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit fa18287d037759a91ba11adc499b36fc2a5e24f8 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 21:03:42 2025 -0400 fix(deps): update dependency nodebb-theme-persona to v14.1.15 (#13701) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 49a293259435b9db640737abfe278f8d6dae739e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 21:03:34 2025 -0400 fix(deps): update dependency nodebb-theme-harmony to v2.1.21 (#13700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6c2100684b7a1630e618560a13901e28a2750bc5 Author: Barış Soner Uşaklı Date: Sat Oct 11 20:54:00 2025 -0400 fix: crash in tests commit d7657538faeefbe70a237b61a1f6414bc018acf9 Author: Barış Soner Uşaklı Date: Sat Oct 11 20:39:14 2025 -0400 Revert "feat: auto-enable link-preview plugin on new installations" This reverts commit b153941cf389732b3a93ca30e5e8de65aaae2809. commit 5d3709f002c089679f148eeedd50e148d069f6d2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 16:02:42 2025 -0400 fix(deps): update dependency nodemailer to v7.0.9 (#13695) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a2892f60bc018d16bb6794f32398141fa80324d6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 16:02:30 2025 -0400 fix(deps): update dependency semver to v7.7.3 (#13697) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bb7b65eaa13f9ff6fc8a284f50161f7be2a97165 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Oct 11 16:02:20 2025 -0400 fix(deps): update dependency webpack to v5.102.1 (#13698) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b309a672a8f102265f430112c8321089ee518cd0 Author: Barış Soner Uşaklı Date: Fri Oct 10 12:19:58 2025 -0400 chore: up persona commit b153941cf389732b3a93ca30e5e8de65aaae2809 Author: Julian Lam Date: Thu Oct 9 14:01:08 2025 -0400 feat: auto-enable link-preview plugin on new installations commit e7bdf6bc31b7c7ae60e1cc38b78aec6c393e66bc Author: Julian Lam Date: Thu Oct 9 14:00:30 2025 -0400 feat: bundle link-preview plugin commit 07bed55e333805921463b1415f4317a4b1313e83 Author: Julian Lam Date: Thu Oct 9 13:57:21 2025 -0400 fix: add attachments to retrieved post data onNewPost commit d3b3720915f5846e8f5a8e0bee9c17b3ff233902 Author: Julian Lam Date: Thu Oct 9 13:56:59 2025 -0400 refactor: move post attachment handling directly into posts.create commit 623cec9d910c03dd67c94db3ce45bf334dafd869 Author: Julian Lam Date: Wed Oct 8 11:07:43 2025 -0400 fix: logic error in image mime type checking commit 79327e6cace72c280e00b2fdc03fec7d93714b9c Author: Barış Soner Uşaklı Date: Tue Oct 7 17:34:55 2025 -0400 chore: up harmony commit 4d24309a069cd04fe589fb493b40e230cf64f663 Author: Julian Lam Date: Tue Oct 7 11:35:36 2025 -0400 feat: federate topic deletion on topic deletion as well as purge commit e29a418cdde48de0a79ace92754d306d617323b3 Merge: 93b6cb5984 ec3998974c Author: Julian Lam Date: Mon Oct 6 22:17:45 2025 -0400 Merge branch 'master' into develop commit ec3998974c6484116852418254dbb61f9fcce6b1 Author: Julian Lam Date: Mon Oct 6 22:17:35 2025 -0400 fix: omg what. commit 93b6cb598402c5ba430a46712fc2459c654dcdd1 Author: Julian Lam Date: Mon Oct 6 13:45:40 2025 -0400 feat: federate Delete on post delete as well as purge, topic deletion federates Announce(Delete(Object)) commit 923ddbc1f1a464772a5f858b3318fae07106ce17 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 6 10:28:44 2025 -0400 chore(deps): update postgres docker tag to v18 (#13679) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d73892aedab0eb2e0d2e6a2bd5cc2a935c966cb8 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 6 10:13:40 2025 -0400 chore(deps): update dependency @eslint/js to v9.37.0 (#13693) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5dc9f2c5d45a0a11ef06b7f16feee71e0dc682e3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Oct 6 10:13:26 2025 -0400 fix(deps): update dependency nodemailer to v7.0.7 (#13694) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 66285ef53eba7aec0152eab5c642477ccde18094 Author: Misty Release Bot Date: Sun Oct 5 09:20:02 2025 +0000 Latest translations and fallbacks commit 9b6e9b2ac39520b51270fb1cc2ec92008bd3bef4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 3 16:50:50 2025 -0400 fix(deps): update dependency redis to v5.8.3 (#13691) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4640a63e4bbbfdc6e844be5a1ba9945eb8d772b0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 3 16:47:23 2025 -0400 chore(deps): update redis docker tag to v8.2.2 (#13692) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c7696667372858e0d6c65246c1360035b79715de Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 3 16:05:14 2025 -0400 chore(deps): update dependency mocha to v11.7.4 (#13685) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit eb06bda8d8d1c70040b8c8b1300ddf8089276a5a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 3 16:04:55 2025 -0400 chore(deps): update dependency @commitlint/cli to v20.1.0 (#13686) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 19dc1025d4cb7c7081948e55d1a62c3c83aa34b6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Oct 3 16:04:26 2025 -0400 fix(deps): update dependency winston to v3.18.3 (#13687) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c0d9bb0723713b5b0f1cc69e239d50322d41d6f3 Author: Misty Release Bot Date: Wed Oct 1 18:12:06 2025 +0000 chore: update changelog for v4.6.0 commit 367f66caa4bb2cb64fabc06c894c861e86d1bc8f Author: Misty Release Bot Date: Wed Oct 1 18:12:05 2025 +0000 chore: incrementing version number - v4.6.0 commit 39e5b8fec3bd5d284b843597e8c637d6b67388a2 Merge: 189fcfa694 5ed19ef8a9 Author: Julian Lam Date: Wed Oct 1 13:51:11 2025 -0400 Merge branch 'master' into develop commit 5ed19ef8a9372453a89c73140d10944bc46090a1 Author: Julian Lam Date: Wed Oct 1 13:51:04 2025 -0400 fix: login handler to handle if non-confirmed email is entered commit 189fcfa694c8bb72b2f51852c0e94ac9ec9a42dc Merge: 3fcaa678a1 56a9336611 Author: Julian Lam Date: Wed Oct 1 12:52:17 2025 -0400 Merge branch 'master' into develop commit 56a9336611cc19788686c63b5d2768e37859fd9e Author: Julian Lam Date: Wed Oct 1 12:52:09 2025 -0400 docs: update openapi schema to refer to try.nodebb.org instead of example.org commit 3fcaa678a1c7478644f924e5b6d1e6f8458f21ca Author: Julian Lam Date: Wed Oct 1 12:15:07 2025 -0400 chore: remove unneeded secureRandom require commit 675178aca4ed41f70c7be069526214a5a8fb300f Author: Julian Lam Date: Wed Oct 1 12:13:57 2025 -0400 fix: allow quote-inline class in mocks sanitizer so quote-post fallback elements can be detected and removed during title generation, fixes #13688 commit 9cee799937c369acb300c37e702a7779f8e80f9f Author: Julian Lam Date: Wed Oct 1 11:53:57 2025 -0400 fix: force outgoing page on direct access to `/ap` handler commit 954e7bc8e3034baaa159366c55482c42155809f3 Author: Julian Lam Date: Wed Oct 1 11:43:23 2025 -0400 fix: update outgoing page to match 404 design commit d7e93a5d757853f9e9d847e0b458545b9344cabf Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 1 11:31:16 2025 -0400 chore(deps): update dependency lint-staged to v16.2.3 (#13681) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 17dba0b0385e1156f413ed929a05a4051e559b83 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Oct 1 11:12:42 2025 -0400 fix(deps): update dependency webpack to v5.102.0 (#13683) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4776d012812f1b250fe24687c51128dc5c752ad8 Author: Julian Lam Date: Wed Oct 1 11:00:03 2025 -0400 sec: disallow checkHeader from returning a URL from a different origin than the passed-in URL commit c3df68f2ed34cca590946a19182bd29c924f5075 Author: Julian Lam Date: Tue Sep 30 11:05:42 2025 -0400 fix: don\'t begin processing local login if the passed-in username isn't even valid commit 9a596d67f34153cbd0cdaf8443fbcce7cbdee7d3 Author: Misty Release Bot Date: Mon Sep 29 14:04:08 2025 +0000 chore: update changelog for v4.5.2 commit 30ba8e82476e992dcaf228c41083a68f89d28314 Author: Misty Release Bot Date: Mon Sep 29 14:04:07 2025 +0000 chore: incrementing version number - v4.5.2 commit f644974a9b49fdd6b20514e11b9a3b99f5d3193a Author: Misty Release Bot Date: Sun Sep 28 09:20:03 2025 +0000 Latest translations and fallbacks commit 675bec331c4fe4749fb372d2fa49a034c46ccc84 Author: Misty Release Bot Date: Sat Sep 27 09:20:16 2025 +0000 Latest translations and fallbacks commit 160907d0fa843279c9b9c36884fd77af8011cf01 Author: Misty Release Bot Date: Fri Sep 26 09:20:35 2025 +0000 Latest translations and fallbacks commit 28a6256209a218eeb62263c39501c3842fb2a728 Merge: d6e7e168ba 30ca00002a Author: Barış Soner Uşaklı Date: Thu Sep 25 21:35:02 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit d6e7e168bae945c13fb7004cacc0e590918688c3 Author: Barış Soner Uşaklı Date: Thu Sep 25 21:35:01 2025 -0400 test: fix message commit 30ca00002ac3758979641d0983060d6aad271359 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 25 20:49:28 2025 -0400 chore(deps): update actions/download-artifact action to v5 (#13646) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a4d8619ba39329a0174275db6cfe2f635c637fdd Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 25 20:47:24 2025 -0400 chore(deps): update dependency @eslint/js to v9.36.0 (#13670) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6dab3f2e6365ee10632862fab04e7b15dd469ae5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 25 20:33:52 2025 -0400 chore(deps): update commitlint monorepo to v20 (#13678) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8061a53b3a19a2c75b75439e507f419497b4bca1 Merge: 8614d8258d 3370c06472 Author: Barış Soner Uşaklı Date: Thu Sep 25 19:20:21 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 8614d8258d2c2ca6cca9f5809cb21811773903c5 Author: Barış Soner Uşaklı Date: Thu Sep 25 19:20:17 2025 -0400 test: show tids on test fail commit 3370c06472c84fab5d36d4d0668c7d04e9b6ab8b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 25 19:18:05 2025 -0400 chore(deps): update dependency @stylistic/eslint-plugin to v5.4.0 (#13671) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 13ce106b213bf0b614385567f2d5f53aff980887 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 25 19:01:50 2025 -0400 chore(deps): update dependency lint-staged to v16.2.1 (#13672) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9b00ff1e5202383d0e3e266c76f0a1f3a2bcc43f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 25 18:42:27 2025 -0400 fix(deps): update dependency mongodb to v6.20.0 (#13665) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3bba9029320d88563986417d115d324f443e0d15 Author: Julian Lam Date: Thu Sep 25 15:29:10 2025 -0400 test: more fixes for note vs. article commit 158780870062f47e2db4be26f7a845e1f3a223fa Author: Julian Lam Date: Thu Sep 25 15:18:26 2025 -0400 test: short OPs create Notes again commit 051043b6820feecc77ea9a885aa24608252426dc Author: Julian Lam Date: Thu Sep 25 15:15:24 2025 -0400 doc: 'nickname' and 'descriptionParsed' use in categories controller commit 7184507be2b769269bd6846ed85e15f021c940b4 Author: Julian Lam Date: Thu Sep 25 15:12:52 2025 -0400 fix: #13667, record to instances:lastSeen instead of domains:lastSeen commit c7e84b8d92bbc8056d9c0b17af3cd6edbaa67b71 Merge: 15fdaba5f6 7abdfd86ac Author: Barış Soner Uşaklı Date: Thu Sep 25 12:38:02 2025 -0400 Merge branch 'master' into develop commit 7abdfd86ac87bcd4f5df7d17ae0cdf157177a991 Author: Julian Lam Date: Thu Sep 25 11:56:38 2025 -0400 fix: skip header checking during note assertion if test runner is active commit 0a2fa45da1768c175f1821ea79e9eebdfb83faab Author: Barış Soner Uşaklı Date: Thu Sep 25 11:02:12 2025 -0400 perf: update upgrade script to use bulk methods add missing progress.total commit 15fdaba5f6d0e88610ae73bfcc2ecf2e3b0ad08c Author: Misty Release Bot Date: Thu Sep 25 09:20:45 2025 +0000 Latest translations and fallbacks commit 32d0ee480844b350cd5502922bfe1109ad913d19 Author: Barış Soner Uşaklı Date: Thu Sep 25 02:03:14 2025 -0400 perf: update old upgrade scripts to use bulkSet/Add fix a missing await commit 2b987d09ce487bb97891c8c38d639a2e6c216537 Author: Barış Soner Uşaklı Date: Thu Sep 25 02:03:14 2025 -0400 perf: update old upgrade scripts to use bulkSet/Add fix a missing await commit 6055b345e1e55d33320e7b5690f51b6bcda23bd3 Author: Misty Release Bot Date: Wed Sep 24 17:49:25 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories commit cf3964be6f7a51355e543b4e92e26b7f02b76f7a Author: Julian Lam Date: Wed Sep 24 13:48:57 2025 -0400 chore: fix grammatical error in language string commit 8730073af14a605a73c2848075472da640666591 Author: Misty Release Bot Date: Wed Sep 24 15:25:46 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories commit bd80b77a7a06cacaeb527fe8e6896af7dffb56f9 Author: Julian Lam Date: Wed Sep 24 11:25:20 2025 -0400 feat: ability to nickname remote categories, closes #13677 commit 175dc2090654a70add7a7cca401688da93525c63 Author: Julian Lam Date: Wed Sep 24 10:42:16 2025 -0400 fix: #13676, bug where nested remote categories could not be removed commit 8c553b1854a65a0df02fb36f97e8118a99b6611d Author: Julian Lam Date: Wed Sep 24 10:00:57 2025 -0400 fix: regression 218f5ea from via, stricter check on whether the calling user is a remote uid commit d0921ea5a231d39d25b43b72027acc8a32399a46 Author: Misty Release Bot Date: Wed Sep 24 09:20:40 2025 +0000 Latest translations and fallbacks commit 00d80616d9d4c2e83c99babd55b768d3c8516bc3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Sep 23 20:17:28 2025 -0400 fix(deps): update dependency lru-cache to v11.2.2 (#13669) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit df9d637c139b8c6f35bc12c688786053168daf1b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Sep 23 20:16:38 2025 -0400 chore(deps): update dependency sass-embedded to v1.93.2 (#13673) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1b5804e1c9f118ebed6e88a19a3589f92840af0a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Sep 23 20:16:28 2025 -0400 fix(deps): update dependency sass to v1.93.2 (#13674) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6e84e35fc3ac3c02d5741d3255d5286b8906561c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Sep 23 19:40:03 2025 -0400 fix(deps): update fontsource monorepo (#13663) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 218f5eabe2a29a18089dae05526c7ca8b2bfca69 Author: Julian Lam Date: Tue Sep 23 10:58:00 2025 -0400 fix: #13668, privilege checking on topic create for remote users; was not properly checking against fediverse pseudo-user commit 33b56e810c14c63049d36c992f76868ffcafc06a Merge: be098e1207 d0c058263f Author: Julian Lam Date: Mon Sep 22 12:15:30 2025 -0400 Merge branch 'master' into develop commit d0c058263f5ffbdd7be821d15b3fd3bcbdb5fa12 Author: Julian Lam Date: Mon Sep 22 12:14:14 2025 -0400 fix: update note assertion topic members check to simpler posts.exists check The original logic checked that each member of the resolved chain was part of the resolved topic. That isn't always the case, especially when topics splinter due to network timeouts/unavailability. This ended up causing issues where already asserted posts were re-asserted but failed because they no longer served an _activitypub object since it was already asserted and the data was just pulled from the db. commit be098e12079ce4cff24cce213fe8a3eb6bae515a Merge: 3f8ff7139f 4d68e3fe14 Author: Julian Lam Date: Mon Sep 22 11:57:05 2025 -0400 Merge branch 'master' into develop commit 4d68e3fe145e49f38d1f3dc2b45d8409bb3945f6 Author: Julian Lam Date: Mon Sep 22 11:56:55 2025 -0400 fix: re-jig handling of ap tag values so that only hashtags are considered (not Piefed community tags, etc.) commit 3f8ff7139fc74518ae09576e84ee8cc58a491fe7 Author: Misty Release Bot Date: Sun Sep 21 09:20:01 2025 +0000 Latest translations and fallbacks commit 9deb576d1e09670a1e1c7c0b6f416fc248f4a615 Merge: d122bf4a98 f9edb13f62 Author: Julian Lam Date: Fri Sep 19 14:43:13 2025 -0400 Merge branch 'master' into develop commit f9edb13f6209b075d4a53c130d1bba166ae188fa Author: Julian Lam Date: Fri Sep 19 14:43:04 2025 -0400 fix: missing actor assertion on 1b12 announced upboat commit d122bf4a985e180e60196bdfaaa8e3bf035d1c12 Author: Julian Lam Date: Fri Sep 19 12:43:11 2025 -0400 fix: update logic as to whether a post is served as an article or not Now, if OP is less than 500 characters, it is just federated out as a Note instead. commit be9212b59fc97ecb5519efeadb803a3a77dd1486 Author: Julian Lam Date: Fri Sep 19 10:56:35 2025 -0400 fix: update activitypubFilterList logic so that it is also checked on resolveInbox and ActivityPub.get methods, updated instances.isAllowed to no longer return a promise commit 559155da6389ed648461b1dc68904c22c59d0082 Author: Julian Lam Date: Fri Sep 19 10:34:57 2025 -0400 refactor: notes.assert to add finally block, update assertPayload to update instances:lastSeen via method instead of direct db call commit d1f5060f11a257388690d1441726efd58ca88b5a Author: Julian Lam Date: Thu Sep 18 13:33:16 2025 -0400 fix(deps): bump 2factor to 7.6.0 commit 9b48bbd5019173c25f00514062b5a930a3940efc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 17 17:33:55 2025 -0400 fix(deps): update dependency esbuild to v0.25.10 (#13664) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c8680f300af316365f5e1a88f612fab390389d13 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 17 17:00:39 2025 -0400 fix(deps): update dependency sharp to v0.34.4 (#13662) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3238248eecde18c1f026ede7f4b7b91267dd4fb0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 17 11:41:57 2025 -0400 chore(deps): update dependency jsdom to v27 (#13653) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b2d91dc3199b6287d466d48b0babfa8971a58b44 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 17 11:41:33 2025 -0400 fix(deps): update dependency satori to v0.18.3 (#13660) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5beeedd67cc2fc08b6dda77f237ba7892b5329b1 Author: Barış Soner Uşaklı Date: Wed Sep 17 11:09:02 2025 -0400 Revert "lint: remove unused" This reverts commit a6674f67a1cfb92f6236e76447e5e9213b1b5710. commit 57a4ce800683913523be268b630c43c0be4771ef Merge: a6674f67a1 532653110c Author: Barış Soner Uşaklı Date: Wed Sep 17 10:58:31 2025 -0400 Merge branch 'master' of https://github.com/NodeBB/NodeBB commit a6674f67a1cfb92f6236e76447e5e9213b1b5710 Author: Barış Soner Uşaklı Date: Wed Sep 17 10:58:26 2025 -0400 lint: remove unused commit 532653110c9e0400967c42352415be6dccb8e6a4 Author: Julian Lam Date: Wed Sep 17 10:58:07 2025 -0400 Revert "fix: add pre-processing step to title generation logic so sbd doesn't fall over so badly" This reverts commit f7c47429879f757e08975b5cd003416db00f5568. commit 6cca55e37f0bce389c3094c5aae07ed1bbed3297 Author: Barış Soner Uşaklı Date: Wed Sep 17 10:50:35 2025 -0400 fix: use parameterized query for key lookup commit f7c47429879f757e08975b5cd003416db00f5568 Author: Julian Lam Date: Wed Sep 17 10:44:51 2025 -0400 fix: add pre-processing step to title generation logic so sbd doesn't fall over so badly commit b845aa48be5b2fe1ff4a28eccec5ca40981718fe Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Sep 16 21:26:59 2025 -0400 fix(deps): update dependency nodebb-theme-harmony to v2.1.20 (#13659) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8324be2d796301d004c03591dbe211cc3a066a62 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Sep 16 21:18:11 2025 -0400 fix(deps): update dependency fs-extra to v11.3.2 (#13658) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6cba6927e4d300f1f0ae94b8ad82fce7cc1c28ca Merge: b1e134b44e f7bbec7ccf Author: Barış Soner Uşaklı Date: Tue Sep 16 19:08:14 2025 -0400 Merge branch 'master' into develop commit b1e134b44ef09c1ce817df240c51fedf605b514c Author: Barış Soner Uşaklı Date: Tue Sep 16 19:08:10 2025 -0400 pass string to isUUID commit f7bbec7ccfee65cd21118d430f710a87ffcabcce Author: Barış Soner Uşaklı Date: Tue Sep 16 11:48:39 2025 -0400 fix: switch to action commit 9c18c6fe49be49975fb5ea0eef73045890544415 Author: Barış Soner Uşaklı Date: Tue Sep 16 11:24:14 2025 -0400 feat: add a term param to recent controller so it can be controller without req.query.term commit 68a8db856a9dcad42fb58e505aaacaa173a1401a Author: Barış Soner Uşaklı Date: Tue Sep 16 11:23:31 2025 -0400 feat: add a new hook to override generateUrl in navigator.js commit 36346dd8221cecd7d9d1315d2c139e1fc8d528e2 Merge: e2e916e200 b66c30a2a7 Author: Julian Lam Date: Mon Sep 15 14:10:07 2025 -0400 Merge branch 'master' into develop commit b66c30a2a73d06c45a6b6d97607b0d9378d56b87 Author: Julian Lam Date: Mon Sep 15 14:10:02 2025 -0400 fix: handle cases where incoming ap object tag can be a non-array commit e2e916e200dd27a674e961220689c0919de7686e Merge: 5f4790a48c f67942caec Author: Julian Lam Date: Mon Sep 15 14:01:08 2025 -0400 Merge remote-tracking branch 'origin/master' into develop commit 5f4790a48c5b30f6d0558aee2d089523b2c9fdce Author: Julian Lam Date: Mon Sep 15 14:01:00 2025 -0400 feat: allow activities to be addressed to as:Public or Public to be treated as public content commit f67942caecbefab7f062379f653615f33b074b2f Author: Julian Lam Date: Mon Sep 15 13:53:27 2025 -0400 fix: local pids not always converted to absolute URLs on topic actor controller commit 8a120b76a8a5bf3e8a027ae8ef6200033d11fce8 Merge: 52fec49310 225bf85e94 Author: Barış Soner Uşaklı Date: Mon Sep 15 12:57:34 2025 -0400 Merge branch 'master' of https://github.com/NodeBB/NodeBB commit 52fec49310db9957677c3ab3a3a98ae8ad08f903 Author: Barış Soner Uşaklı Date: Mon Sep 15 12:57:29 2025 -0400 chore: remove obsolete deprecation commit 225bf85e941c95dd0d724ac5b47467918661f806 Author: Julian Lam Date: Mon Sep 15 12:47:49 2025 -0400 fix: #13657, fix remote category data inconsistency in `sendNotificationToPostOwner` commit db89250982c6c67194bc08dd319831cd24fdfb6a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Sep 15 11:09:40 2025 -0400 fix(deps): update dependency @fontsource/inter to v5.2.7 (#13655) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b95c6e2d2da88a50ecf70c34d72e96fbee291854 Merge: 19f3919890 405d2172ac Author: Barış Soner Uşaklı Date: Mon Sep 15 09:33:12 2025 -0400 Merge branch 'master' into develop commit 405d2172acc49321f6ec0109bda6b28f5340d39d Author: Barış Soner Uşaklı Date: Mon Sep 15 09:32:05 2025 -0400 chore: up persona commit 19f391989001c228e5af04e2321525cf8daa9f0e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Sep 13 20:27:47 2025 -0400 fix(deps): update dependency commander to v14.0.1 (#13652) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 61b39316213c765eb16b1b7fe4927163440febff Merge: 8cb1cae1cc dfe19a98c1 Author: Barış Soner Uşaklı Date: Sat Sep 13 18:11:56 2025 -0400 Merge branch 'master' into develop commit dfe19a98c193bdfdd601f4f7e4be06e67f8431b4 Author: Barış Soner Uşaklı Date: Sat Sep 13 17:51:25 2025 -0400 fix: don't show votes on unread if rep system disabled add openapi spec commit 8cb1cae1cc9b982d420e6f2dcab9352a8ddb973f Merge: 1e82af66a6 8a786c717e Author: Barış Soner Uşaklı Date: Sat Sep 13 17:40:49 2025 -0400 Merge branch 'master' into develop commit 8a786c717e5366620d12c7b49d34fb0dffbe67a8 Author: Barış Soner Uşaklı Date: Sat Sep 13 17:40:09 2025 -0400 fix: if reputation is disabled hide votes on /recent they were only hidden on category page commit e2dc592c4f21a48bd5f6c10b58773d0b755e32bf Author: Barış Soner Uşaklı Date: Fri Sep 12 19:50:19 2025 -0400 fix: favicon path commit a37521b0167bf3c10c1b856ed317589f4a08bede Author: Barış Soner Uşaklı Date: Fri Sep 12 19:27:07 2025 -0400 lint: fix commit 56fad0be0d0da2c1f3a12562d07a8f2adab3bffa Author: Barış Soner Uşaklı Date: Fri Sep 12 19:19:52 2025 -0400 fix: check brand:touchIcon for correct path commit 1e82af66a6f2c57f5ef971d6f1b52b7a30f74b36 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Sep 12 11:36:55 2025 -0400 fix(deps): update dependency bootswatch to v5.3.8 (#13651) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f9ddbebacc76c2601d5dbb8adf97b44cb32395b7 Author: Barış Soner Uşaklı Date: Fri Sep 12 11:33:53 2025 -0400 fix: remove .auth call commit eecf9dda6498c649cd2cceaa57508abf5c8fb1ee Author: Misty Release Bot Date: Fri Sep 12 09:21:18 2025 +0000 Latest translations and fallbacks commit 15b0b54000d237045e0d4aa2dc4c12d6ce07b7d2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 11 18:58:44 2025 -0400 chore(deps): update dependency sass-embedded to v1.92.1 (#13638) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 10344c98a8730f89a0e16f5c535944016cf1fefc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 11 18:58:34 2025 -0400 fix(deps): update dependency sass to v1.92.1 (#13645) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7147a2e31aeb4711c9a8ff398db50f33810243e6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 11 17:46:24 2025 -0400 chore(deps): update dependency lint-staged to v16.1.6 (#13635) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f9688b36b67e1706cafacce69f784a9e8e5aeaa0 Author: Barış Soner Uşaklı Date: Thu Sep 11 17:44:34 2025 -0400 fix: port the try/catch for notes.assert from develop commit 9184a7a4cc8c68c094d9ddbe86dd0c23f578986e Author: Barış Soner Uşaklı Date: Thu Sep 11 17:28:56 2025 -0400 fix: add missing unlock in nested try/catch commit 95fb084ca49084d257e11b58cef9d30ed84394e1 Author: Julian Lam Date: Thu Sep 11 10:30:21 2025 -0400 fix: wrap majority of note assertion logic in try..catch to handle exceptions so that the lock is always released commit 4f5e770c5f55689f090a4d1867f69ca2b3a4443c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 11 09:24:29 2025 -0400 chore(deps): update actions/setup-node action to v5 (#13647) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6a1e9e8a110193a8540566bbd0adbaebaa047dce Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Sep 11 09:23:59 2025 -0400 fix(deps): update dependency workerpool to v9.3.4 (#13650) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 67fa433f1af91f9dff915b4f9002cb7a65a4502f Author: Misty Release Bot Date: Thu Sep 11 09:21:14 2025 +0000 Latest translations and fallbacks commit ac90ef8c9a8844dd7dcd9dc89a9692eee455a203 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 10 17:27:20 2025 -0400 chore(deps): update dependency mocha to v11.7.2 (#13636) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6adfbb2482e80504665ae552cb6ea48d16c18f04 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 10 17:26:21 2025 -0400 fix(deps): update dependency lru-cache to v11.2.1 (#13644) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f7d10e09ac97b0720ee4bda93370017ea7b4f8d4 Merge: feda629f82 953c051c2e Author: Julian Lam Date: Wed Sep 10 15:07:13 2025 -0400 Merge branch 'master' into develop commit 953c051c2e546364a8a28d4097ebceac1876bcac Author: Julian Lam Date: Wed Sep 10 14:59:13 2025 -0400 fix: perform Link header check on note assertion only when skipChecks is falsy commit feda629f821b284e0f16f8c3c4263116306a43d7 Author: Julian Lam Date: Wed Sep 10 14:48:24 2025 -0400 chore: remove formatApiResponse logging commit 8d4e46529f861e4514799ba2db3447ff7f8ddd17 Author: Misty Release Bot Date: Wed Sep 10 13:49:20 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories commit 160f4750291f13f73cee9b7eefa2ed173cf966e4 Merge: 8d6a0f0298 0311b98ed7 Author: Barış Soner Uşaklı Date: Wed Sep 10 09:48:49 2025 -0400 Merge branch 'master' into develop commit 0311b98ed7d926502f27a7d84b00c02b73712dc7 Author: Barış Soner Uşaklı Date: Wed Sep 10 09:46:39 2025 -0400 feat: add topic templates per category, closes #13649 commit 8d6a0f0298679a3c6e4c399decc9d58cb6684529 Author: Barış Soner Uşaklı Date: Tue Sep 9 11:28:29 2025 -0400 test: ap timeouts commit 10350ea6f6101d4b28efd29affc87ed55d42386a Author: Barış Soner Uşaklı Date: Tue Sep 9 11:20:03 2025 -0400 revert: post queue changes to fix tests commit a5ea4b405621909796cfb3aaf42525f7ea23952c Author: Barış Soner Uşaklı Date: Tue Sep 9 11:07:28 2025 -0400 chore: up eslint commit 3044f3829109aa791e28ad5b4ba41c581492b6fe Author: Misty Release Bot Date: Tue Sep 9 09:20:59 2025 +0000 Latest translations and fallbacks commit 8939010195babe649267c317b20666727ac12345 Author: Misty Release Bot Date: Mon Sep 8 19:37:21 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-activitypub commit 1d6a9fe738bdbe628306bbaa640fb30eaea86e45 Author: Julian Lam Date: Mon Sep 8 14:57:51 2025 -0400 feat: allow user auto-categorization rule commit 10f665e3e30f13062adad929230415b4f3464564 Merge: c43e85164d 527f27af29 Author: Julian Lam Date: Mon Sep 8 12:00:49 2025 -0400 Merge branch 'master' into develop commit 527f27af2948af908006af7fe3683a2aee207fc5 Author: Julian Lam Date: Mon Sep 8 12:00:32 2025 -0400 fix: make auto-categorization logic case-insensitive commit c43e85164dc202388350990fb0d32ebd8578d40c Merge: 5528c6eb19 b3ffa00789 Author: Barış Soner Uşaklı Date: Mon Sep 8 09:37:54 2025 -0400 Merge branch 'master' into develop commit b3ffa00789f17d7a33aade67ac2f0ee6b8d29a10 Author: Barış Soner Uşaklı Date: Mon Sep 8 09:29:32 2025 -0400 fix: closes #13641, log test email sending errors server side commit 5528c6eb1949bc72f6e450dfeb127059cadbf72c Author: Misty Release Bot Date: Mon Sep 8 09:20:47 2025 +0000 Latest translations and fallbacks commit 290a9395c095f2589804f954fc0f45bebb74b0ee Author: Barış Soner Uşaklı Date: Sat Sep 6 13:47:46 2025 -0400 fix: pass object to.auth commit 9bfce68b5eea7bc91edb24939761bbe82d434c69 Author: Julian Lam Date: Fri Sep 5 14:39:23 2025 -0400 test: disable post queue when testing posting logic commit 15f9fbaa5cec0708ad248cfc1ca3f6f5e44ec210 Author: Julian Lam Date: Fri Sep 5 13:11:52 2025 -0400 feat: add minor pre-processing step to better handle header elements in incoming html commit 2de200b31129632b146e84e09268f68afb47786f Author: Misty Release Bot Date: Fri Sep 5 09:20:19 2025 +0000 Latest translations and fallbacks commit 2ea624fc8e945a3bd44c4cb10848f21b2c733f63 Author: Julian Lam Date: Thu Sep 4 16:55:04 2025 -0400 fix: use newline_boundaries param for tokenizer during title and summary generation, attempt to serve HTML in summary generation commit a9fffd7ca07416184a2f6ec512b7cca95502e8b2 Author: Misty Release Bot Date: Thu Sep 4 16:02:47 2025 +0000 chore: update changelog for v4.5.1 commit 7a9e09a696589d64712e5f16ef4ee7d3c363752e Author: Misty Release Bot Date: Thu Sep 4 16:02:47 2025 +0000 chore: incrementing version number - v4.5.1 commit fcd9f1a9994ce148bb0c054a23b095f6347269d0 Merge: 99e067f19e 8d7e35378f Author: Julian Lam Date: Thu Sep 4 11:47:45 2025 -0400 Merge branch 'master' into develop commit 8d7e35378fef8d93c92c37a0e974256a40253ea5 Author: Julian Lam Date: Thu Sep 4 11:47:40 2025 -0400 fix: remove unused dependency commit 9221d34f01e8db62a88ae56b380c6c68016ad4c3 Author: Julian Lam Date: Thu Sep 4 11:45:33 2025 -0400 fix: remove test for 1b12 announce on topic move (as this no longer occurs) commit 99e067f19ef456cdfb5494258e44e20e08f61231 Merge: 58677c117a e6996846ac Author: Julian Lam Date: Thu Sep 4 11:20:26 2025 -0400 Merge branch 'master' into develop commit e6996846acd220bbb2974e0dbe765b684719caea Author: Julian Lam Date: Thu Sep 4 11:20:17 2025 -0400 fix: use existing id if checkHeader returns false commit 58677c117a32a922388848ccc77790df793cdb8b Merge: 2563255931 0c48e0e909 Author: Barış Soner Uşaklı Date: Thu Sep 4 10:48:59 2025 -0400 Merge branch 'master' into develop commit 0c48e0e909ada2c14d9b959970a4048acf6dd2cf Author: Barış Soner Uşaklı Date: Thu Sep 4 10:48:53 2025 -0400 feat: use _variables.scss overrides from acp in custom skins and bootswatch skins as well commit 256325593176a028aebb0ce0aa7122d2a4b73dba Merge: 3f00f250d9 86d9016f02 Author: Julian Lam Date: Thu Sep 4 10:32:48 2025 -0400 Merge branch 'master' into develop commit 86d9016f02cea7f0ae50a73fb3285133e1130fe6 Author: Julian Lam Date: Thu Sep 4 10:29:17 2025 -0400 fix: regression that caused Piefed (or potentially others) content to be dropped on receipt commit c07e81d2ab9c3eea95739936707cf4dd2eb3a601 Author: Barış Soner Uşaklı Date: Wed Sep 3 20:57:55 2025 -0400 chore: up dbsearch commit 3f00f250d981023288d21c3eac43ca9d486e4e76 Merge: 0f9015f050 7adfe39ea1 Author: Julian Lam Date: Wed Sep 3 16:56:46 2025 -0400 Merge remote-tracking branch 'origin/master' into develop commit 0f9015f050a42843770c9351d63fb2e158e4e4d9 Author: Julian Lam Date: Wed Sep 3 14:45:31 2025 -0400 fix: deprecated call to api.topics.move commit 7adfe39ea1ab349593e59ee6cccfdc7914805a65 Author: Julian Lam Date: Wed Sep 3 14:46:01 2025 -0400 fix: remove faulty code that tried to announce a remote object but couldn't as the ID was not a number commit b472dba244e0cfb4509386e52fc158d1fd8fda7a Merge: 5d6535719a 86d03b1e46 Author: Barış Soner Uşaklı Date: Wed Sep 3 14:12:23 2025 -0400 Merge branch 'master' into develop commit 5d6535719a2fc2594bcf62d4890c433b5703a206 Author: Julian Lam Date: Wed Sep 3 14:02:58 2025 -0400 chore: update default settings undoTimeout reduced to 0 post queue default enabled with minimum reputation to bypass set to 1 commit 86d03b1e463c275e8fc94e0ff5aadbf774a7dbbd Author: Misty Release Bot Date: Wed Sep 3 17:42:16 2025 +0000 chore: update changelog for v4.5.0 commit 8c4d68a728898db8c513d553444cd45215d1a3b3 Author: Misty Release Bot Date: Wed Sep 3 17:42:15 2025 +0000 chore: incrementing version number - v4.5.0 commit 2dc39f1e3e7e0f24ab03a819a213ac6f53677cbc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 3 11:27:55 2025 -0400 fix(deps): update dependency satori to v0.18.2 (#13628) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7adabd600d89349233ffc629313781725d311116 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 3 11:25:56 2025 -0400 fix(deps): update dependency ace-builds to v1.43.3 (#13633) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4ade6007855f159ac14c73d3ff851aa3d3a188be Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 3 11:25:35 2025 -0400 chore(deps): pin dependency @stylistic/eslint-plugin to 5.3.1 (#13634) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 07b9cd16bdd44a5069fdbb496c7a886f4e6dd471 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Sep 3 11:20:27 2025 -0400 fix(deps): update dependency nodemailer to v7.0.6 (#13630) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 70bbed93cedc6335ebb8070207388403875bda62 Author: Julian Lam Date: Wed Sep 3 11:12:43 2025 -0400 test: delete commented-out test commit 5c00c6a57957161acf2660852d0af318412cbe52 Merge: 2d5ad8b24e 931b7345e4 Author: Barış Soner Uşaklı Date: Wed Sep 3 11:10:31 2025 -0400 Merge branch 'master' into develop commit 2d5ad8b24e48e8be804a1c056439d4709febaa31 Merge: 19aa8a7168 b517e27d60 Author: Barış Soner Uşaklı Date: Sat Aug 30 13:24:34 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 19aa8a716868602b771a41e20299f188f789c254 Author: Barış Soner Uşaklı Date: Sat Aug 30 13:24:33 2025 -0400 fix: display proper id if lock fails commit b517e27d60ccf9a41e42045b314aa51439a6ff16 Author: Misty Release Bot Date: Sat Aug 30 09:19:56 2025 +0000 Latest translations and fallbacks commit 931b7345e4565720caba707612f54b53df0cf50a Author: Jakub Bliźniuk Date: Sat Aug 30 03:07:29 2025 +0200 ci: use native arm runners for building docker images (#13627) * ci: split docker runners * ci: don't tag initial image * ci: use lowercase image name * ci: remove qemu commit 9d4a9b83ccabb28da178bb041900d90a24951ac7 Author: Barış Soner Uşaklı Date: Fri Aug 29 21:02:14 2025 -0400 fix: closes #13624, update post fields before schedule code tldr when reschedule was called it was still using the timestamp in the future when adding to cid::pids causing that post to get stuck at the top of that zset, which led to the bug in this issue commit a4674578e429ed91fe459cdf3c886cf4d198286a Merge: f67265daa7 4ef605b1aa Author: Barış Soner Uşaklı Date: Fri Aug 29 15:23:24 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit f67265daa78b80095fe197637e2bd61400286795 Author: Barış Soner Uşaklı Date: Fri Aug 29 15:23:19 2025 -0400 refactor: revert, don't need to pass relative_path commit 4ef605b1aa222bce8b4f1b828f8642af3d15b196 Author: Julian Lam Date: Fri Aug 29 13:33:14 2025 -0400 fix: #13622, WordPress blog URLs not asserting properly commit 648c454303096a8b1d58abc7dbde572bda3784f6 Author: Barış Soner Uşaklı Date: Fri Aug 29 13:07:46 2025 -0400 refactor: leaner utils.params for relative path commit a0e78ff853c37b89862d29b5dddd1dd23e8b0176 Author: Barış Soner Uşaklı Date: Fri Aug 29 12:50:06 2025 -0400 fix: closes #13625, fix utils.params so it works with relative_paths commit 35641f377ca20882d43123241bef839641c68df9 Author: Julian Lam Date: Thu Aug 28 14:27:41 2025 -0400 feat: use sbd to more intelligently put together a sub-500 character summary based on existing sentences in post content The original behaviour was to just shove the entire post content (html and all) into summary. Summary _can_ include HTML, but it's a little harder to retain HTML but truncate the content based on sentences, without accidentally dropping tags. commit 826863223583653a2b58969d2b21ce58ce93eed4 Author: Julian Lam Date: Thu Aug 28 14:12:04 2025 -0400 feat: add sbd dependency to improve title generation (and for summary generation, later) commit a0be4a28da5c97f315b1a1b89d3140c21f7f5867 Author: Julian Lam Date: Thu Aug 28 12:45:46 2025 -0400 fix: remove webfinger error log commit b73ee309e0a39d16bdf3e13ca80486d4a2b7e55d Author: Barış Soner Uşaklı Date: Thu Aug 28 12:39:44 2025 -0400 refactor: remove invalid queued items catch invalid json in payload commit 5f7085f34d01062cc3eed78fd98b22a3d1d9ef1c Author: Julian Lam Date: Thu Aug 28 11:52:22 2025 -0400 fix: urlencoded param in openapi spec example commit cbdc90a43283d7071fa2c8bfea610bd73e128edf Author: Julian Lam Date: Thu Aug 28 09:55:13 2025 -0400 fix: re-ordering dependencies because raisins commit c67983cc501727b02e442bcdab3bab55951437e0 Author: Misty Release Bot Date: Thu Aug 28 09:20:35 2025 +0000 Latest translations and fallbacks commit 788301a56a7ab40821de59b207cb563118f61a95 Author: Julian Lam Date: Thu Aug 28 00:03:07 2025 -0400 fix: missed a tab character commit f83d2536ce905fb00dd82dd652776bea82c91926 Author: Barış Soner Uşaklı Date: Wed Aug 27 18:46:37 2025 -0400 refactor: braces commit 457908bdf488f4e363e35ba94871d02b30c059b5 Merge: 0f44034ec3 8a326a6e74 Author: Barış Soner Uşaklı Date: Wed Aug 27 18:43:00 2025 -0400 Merge branch 'master' into develop commit 8a326a6e7479e11e2bcc4bf462f55f008bd60719 Author: ledlamp Date: Wed Aug 27 15:42:30 2025 -0700 Allow setting value of Express 'trust proxy' from config (#13034) * Allow setting value of Express 'trust proxy' from config * Allow config to disable 'trust proxy' if port is 80/443 And show the value of trust_proxy in log * fix errors commit 0f44034ec32ef74d04bae68afad681625f67e51a Author: Julian Lam Date: Wed Aug 27 15:21:38 2025 -0400 docs: add missing routes to openapi schema commit 771b8dcb2db7ad5340d1149fae2d3dcc739d3ae1 Author: Julian Lam Date: Wed Aug 27 15:08:51 2025 -0400 fix: random hotkeys adding dependencies to my project smh commit 560cc2ebf928825733be56af3defade2ba1c32bd Author: Julian Lam Date: Wed Aug 27 14:21:41 2025 -0400 docs: openapi typo commit cb00fb3bccf91210db8e868ad4689ba200684117 Author: Misty Release Bot Date: Wed Aug 27 18:17:35 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-activitypub commit a9a12a9f08b6a4773fb0980d6757916a1c8ad5ec Author: Julian Lam Date: Wed Aug 27 14:16:24 2025 -0400 docs: update openapi schema for relays and rules commit 40973ca7d1d25eb6d0daa0c0d4f0586ad01d665f Author: Julian Lam Date: Wed Aug 27 13:52:45 2025 -0400 fix: parseAndTranslate bug commit aa26dfb372a285cf391fb302fa1ae826dff2daef Author: Julian Lam Date: Wed Aug 27 12:33:27 2025 -0400 feat: send local posts out to established relays commit 6576468e2e98ca34b60b077f679d01aea7e2cb47 Author: Julian Lam Date: Wed Aug 27 12:20:36 2025 -0400 fix: internationalize relay states commit 28b63891d43f3f979570ac1a7fda1b551ae37506 Author: Julian Lam Date: Tue Aug 26 14:11:51 2025 -0400 fix: minor fixes for yukimochi/Activity-Relay compatibility commit b1dbb19c10c08f6a6e54e1d57004ac34e4c64515 Author: Julian Lam Date: Tue Aug 26 13:53:51 2025 -0400 fix: inbox.announce to not reject activities from relays commit f4d1df7c66b1084da6599568a8071404e991fc58 Author: Julian Lam Date: Tue Aug 26 12:30:22 2025 -0400 feat: relay handshake logic, handle Follow/Accept, send back Accept. commit 4967492f4f65ae719de9c581d04928fd2a53c958 Author: Julian Lam Date: Tue Aug 26 11:53:27 2025 -0400 fix: handle webfinger responses with subject missing scheme commit 1e0fb20db4fb2cdbfef958d8c4c568e4886bc4c9 Author: Julian Lam Date: Mon Aug 25 16:50:18 2025 -0400 feat: adding and removing relays from AP settings page in ACP commit 6d856545ec4dd1e0cb44893697457a6b1390351d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 27 13:42:18 2025 -0400 fix(deps): update dependency mongodb to v6.19.0 (#13619) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit be53dbcbb8321c110d2243652bcbe6f4252158df Author: Barış Soner Uşaklı Date: Wed Aug 27 13:30:29 2025 -0400 remove logs commit 4ad7b592815894bc493d4fe04e672fa212f57a11 Author: Barış Soner Uşaklı Date: Wed Aug 27 13:28:26 2025 -0400 Update notes.js commit 5dab17450fb3835363f703a0dea7dfd104209c0f Author: Barış Soner Uşaklı Date: Wed Aug 27 13:27:36 2025 -0400 Revert "test: more logs for failing test" This reverts commit 79c6e72ce639e177779d3e4040a2cd6c34c27ce8. commit 8f7411c3aaeff614a17d56197efc191c21ee1738 Author: Barış Soner Uşaklı Date: Wed Aug 27 13:08:19 2025 -0400 test: add timeout to ap.helpers.query commit 8e160fe05eff2acd5c8f4ee4d167b9dc5073290b Author: Barış Soner Uşaklı Date: Wed Aug 27 12:49:42 2025 -0400 test: more logs commit f703a94b3145def82c7602a396c2bc6f2da6a6f7 Author: Barış Soner Uşaklı Date: Wed Aug 27 12:34:24 2025 -0400 test: add more logs commit 681ce8bf2f5e81e637494f550a2e8bff63273d76 Author: Barış Soner Uşaklı Date: Wed Aug 27 12:23:10 2025 -0400 test: add more logs commit 029da6c52e5e3d9a84dcecc7ff9dd898f9583cce Author: Barış Soner Uşaklı Date: Wed Aug 27 12:10:30 2025 -0400 test: debug timeout commit 79c6e72ce639e177779d3e4040a2cd6c34c27ce8 Author: Barış Soner Uşaklı Date: Wed Aug 27 11:29:43 2025 -0400 test: more logs for failing test commit bf279d71b02911b33d267810efc631c49bf3e2bd Author: Barış Soner Uşaklı Date: Wed Aug 27 11:02:12 2025 -0400 fix: closes #13501 add missing await commit 027d6f307c8ef7b5d6a6b6ef5c309b44e515d25b Author: Barış Soner Uşaklı Date: Wed Aug 27 00:06:32 2025 -0400 fix: closes #13620 commit 5ee1fd02bb2dca745f03ec27e982223b498662b5 Author: Barış Soner Uşaklı Date: Tue Aug 26 19:23:39 2025 -0400 refactor: add missing awaits fix error message, lock not using second param commit 567f453b79041c6bf8086c92052656bc8645b13f Author: Barış Soner Uşaklı Date: Tue Aug 26 14:09:03 2025 -0400 chore: enable dbsearch on new installs commit e79dfeb7c3cc0f86237808ff2c0b8c8937e978aa Author: Barış Soner Uşaklı Date: Tue Aug 26 13:56:47 2025 -0400 fix: rare crash if queued item is no longer in db but id is in post:queue commit 69a6c1502fd62f283951800ec848bad0f10ea67c Author: Barış Soner Uşaklı Date: Tue Aug 26 12:04:58 2025 -0400 test: catch error in failing test commit fa1985bb177f4a5afaaee3fab2a580a92ad415be Merge: 08ea56bd12 f5ad786240 Author: Barış Soner Uşaklı Date: Tue Aug 26 11:52:09 2025 -0400 Merge branch 'master' into develop commit f5ad786240e4f23e9a0d5bda13a287ec0fa07e18 Author: Barış Soner Uşaklı Date: Tue Aug 26 11:52:02 2025 -0400 fix: jquery selector on post edit commit 08ea56bd126b92ba32e218771908aa45fcc7ded2 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Aug 26 11:32:19 2025 -0400 fix(deps): update dependency sass to v1.91.0 (#13615) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e504ee348c7d54b8b450f870d16399b0cca24343 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Aug 26 11:32:11 2025 -0400 chore(deps): update dependency sass-embedded to v1.91.0 (#13614) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 29a7402fc9b5593bd56172ed80d341f29610f731 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Aug 26 11:21:33 2025 -0400 fix(deps): update dependency bootstrap to v5.3.8 (#13618) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit dfc558cdeb0ecb62cc1ffafaeb64dded51b97b2e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Aug 26 11:01:39 2025 -0400 chore(deps): update dependency @eslint/js to v9.34.0 (#13612) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a771b17fac34760e0c9d923281b707e942167f42 Author: Julian Lam Date: Mon Aug 25 23:51:33 2025 -0400 fix: relative paths in openapi schema commit e8401472c0f92eb54858228e94b56a2628a42cf0 Author: Julian Lam Date: Mon Aug 25 16:48:33 2025 -0400 fix: add missing routes to write.yaml commit d4bf5f0c2f3b682d4e62d911b5607768c949c802 Author: Julian Lam Date: Mon Aug 25 13:23:25 2025 -0400 lint: fix comma dangle commit 590eae2917fb30f5218691bbab83d5f83e8527e2 Merge: c0248ca52b 1ea10eff1c Author: Barış Soner Uşaklı Date: Mon Aug 25 12:17:23 2025 -0400 Merge branch 'master' into develop commit 1ea10eff1c3b678c21a52a21bed183b8d65a8da6 Author: Barış Soner Uşaklı Date: Mon Aug 25 12:08:55 2025 -0400 test: sharp invalid png commit c0248ca52b93c0854c2ba0fdc22d34c933c3ed90 Author: Julian Lam Date: Mon Aug 25 12:05:50 2025 -0400 docs: openapi schema fixes for auto-categorization commits commit 3cdf28bd2cff610255e94075548068fe89e839db Author: Barış Soner Uşaklı Date: Mon Aug 25 11:48:34 2025 -0400 test: latest sharp commit 165af50dc8297cf133def10e1edc6ab9a551a0d3 Author: Julian Lam Date: Mon Aug 25 11:47:01 2025 -0400 feat: apply auto-categorization logic commit 312df523933492d65e7262d76803e0fd373bbb7b Author: Barış Soner Uşaklı Date: Mon Aug 25 11:18:04 2025 -0400 fix: only process unique slugs commit 70d7e3292920fd07a5f9e02ea20d4d746e51bd72 Author: Julian Lam Date: Mon Aug 25 10:11:09 2025 -0400 fix: remove special-case logic that added a requested object to a topic if its defined context didn't actually contain it commit ae7fa6958d35df44cb180ef32bad5957882ec59e Author: Misty Release Bot Date: Sun Aug 24 09:19:45 2025 +0000 Latest translations and fallbacks commit 09898b94ecca86d961bbaf51761db0d8faf0ceca Author: Barış Soner Uşaklı Date: Fri Aug 22 11:06:47 2025 -0400 fix: return null if field is falsy fixes MongoServerError: FieldPath cannot be constructed with empty string error when getObjectField is called with a falsy value commit c16f9d649532ceb5187cd3c54b2e97258990edab Author: Barış Soner Uşaklı Date: Fri Aug 22 08:50:09 2025 -0400 fix: mark-all read notifications button commit 929ae61646156845550ba57fb4883f424c7bb79c Author: Misty Release Bot Date: Fri Aug 22 09:20:14 2025 +0000 Latest translations and fallbacks commit fdd0152ee4da48f6d9cd65ae63a562b543f48385 Author: Barış Soner Uşaklı Date: Thu Aug 21 21:32:10 2025 -0400 chore: up peace commit 6d60f9457a2b6fb614e76846bc18db8a1a10c143 Author: Barış Soner Uşaklı Date: Thu Aug 21 21:29:53 2025 -0400 chore: up harmony commit 9bdf24f08be55906f3251d64738ef873fbba8e9c Author: Barış Soner Uşaklı Date: Thu Aug 21 21:25:14 2025 -0400 fix: catch exceptions in assertPayload, closes #13611 commit 74cd68b865600cb20dc80757af5a615ae666a7ad Merge: 845e4cb8f3 5dfd241335 Author: Barış Soner Uşaklı Date: Thu Aug 21 11:01:15 2025 -0400 Merge branch 'master' into develop commit 5dfd2413356a2cdaa95bdf94571e40cd7e45b395 Author: Barış Soner Uşaklı Date: Thu Aug 21 10:49:13 2025 -0400 lint: fix lint issue commit 845e4cb8f32f5dbfcb0d96270ac76a5f8cd926b7 Merge: 2d415b5610 181aa9c2ed Author: Barış Soner Uşaklı Date: Thu Aug 21 10:48:35 2025 -0400 Merge branch 'master' into develop commit 2d415b5610103c5a6f4ddb5a7639f322c55a157c Merge: 20e2c8fe67 2f4cf26c59 Author: Barış Soner Uşaklı Date: Thu Aug 21 10:45:36 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 20e2c8fe676764de2acfb60b94bf7a585d13a38d Merge: 44c0413c75 82037dee00 Author: Barış Soner Uşaklı Date: Thu Aug 21 10:45:28 2025 -0400 Merge branch 'master' into develop commit 181aa9c2edf0b1494cf2a6d85c839abbedd11e09 Author: Marco Beyer Date: Thu Aug 21 16:45:06 2025 +0200 (fix) fixed typos in activitypub urls (#13610) commit 8bef68001554a6c57ad277640556257779ca3e47 Author: Marco Beyer Date: Thu Aug 21 16:44:28 2025 +0200 (fix) Return relative asset URL instead of absolute asset url (#13605) * Return relative asset URL instead of absolute asset url * fixed linter issues and repeating relative path commit 2f4cf26c599bbfe184036b738224a122d220ee88 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Aug 21 10:33:45 2025 -0400 fix(deps): update dependency nodebb-theme-harmony to v2.1.17 (#13607) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8af76f3cae535b82828b37dbb048faa70732ac3e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Aug 21 10:33:35 2025 -0400 fix(deps): update dependency nodebb-theme-peace to v2.2.47 (#13608) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f6e1a2e55cd6f9d079c1de0ffc61c32c5842e9c8 Author: Misty Release Bot Date: Thu Aug 21 09:19:42 2025 +0000 Latest translations and fallbacks commit 02228c04efd2918d5ead2f18aa124c9670011f38 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 20 14:14:31 2025 -0400 chore(deps): update redis docker tag to v8.2.1 (#13603) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 138c6753749cf0fb321c2d259035c7963def980d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 20 14:14:16 2025 -0400 fix(deps): update dependency redis to v5.8.2 (#13606) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 996740bdf9a97fe6b852c8eaea991fa90cf8ef0d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 20 13:49:29 2025 -0400 fix(deps): update dependency webpack to v5.101.3 (#13602) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 44c0413c753f309a19f9c88c65102b0acadb6f3b Author: Barış Soner Uşaklı Date: Wed Aug 20 11:48:19 2025 -0400 chore: use fontsource-utils/scss to get rid of deprecation warning closes #13520 commit 981d3c29f8decef737a007a36cfd81c08f98dfac Author: Misty Release Bot Date: Wed Aug 20 09:21:29 2025 +0000 Latest translations and fallbacks commit 057e3b790bed4bd69b453a9ff7fcb1d805c8ff14 Author: Julian Lam Date: Tue Aug 19 19:54:57 2025 -0400 fix: add missing files commit 10d84d0329e997775dbe1c423a39ce8cdb71555f Author: Misty Release Bot Date: Tue Aug 19 09:20:26 2025 +0000 Latest translations and fallbacks commit 40bda8fca4ae6543aa024046bfc72331e037a2f7 Author: Misty Release Bot Date: Mon Aug 18 20:09:26 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories, nodebb.admin-settings-activitypub commit bdcf28a3d9063e229d7b0aa39cf9c440832b7f1e Author: Julian Lam Date: Sun Aug 17 22:07:30 2025 -0400 feat: ability to add/remove auto-categorization rules for incoming federated content commit cb0b609289ad072d153481afb711c86d8a85b709 Author: Julian Lam Date: Tue Aug 12 15:38:49 2025 -0400 refactor: category listing logic to allow remote categories to be added, disabled, and re-arranged in main forum index commit 75639c86bd53e6bdf3294e1928a6a8bd00ceaeef Author: Julian Lam Date: Sun Aug 10 22:32:37 2025 -0400 feat: re-jigger 'add category' button to allow addition of remote category to main index commit 1515580940b343b8897e9497f6a15590262594e7 Author: Barış Soner Uşaklı Date: Sun Aug 17 11:17:47 2025 -0400 test: add logs for test that's timing out commit f4f7953ae3af04367e52010746782514500f8d51 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Aug 16 18:43:50 2025 -0400 chore(deps): update dependency lint-staged to v16.1.5 (#13585) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 90bddccbc5d5e884a98b83752c38c0480827b2b0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 15 10:02:33 2025 -0400 fix(deps): update dependency webpack to v5.101.2 (#13598) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f5b0444b1c03c716cfe4c81d89d122dd53af545a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 15 10:02:07 2025 -0400 fix(deps): update dependency nodebb-widget-essentials to v7.0.40 (#13597) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 62d15a0e05a7be87f123ae9643ebc90261d3cd78 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 15 10:01:56 2025 -0400 chore(deps): update postgres docker tag to v17.6 (#13599) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ceb65d138f67f904b77b8e51117225a70f3f5512 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 15 10:01:45 2025 -0400 fix(deps): update dependency tough-cookie to v6 (#13600) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0bb86b20b3981bb3a4f1c05cc212e2c89c18d323 Merge: 3a1ebae796 bfdf47b69e Author: Barış Soner Uşaklı Date: Thu Aug 14 19:05:30 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 3a1ebae79627dbb214c04af293ed8246eb1915c1 Author: Barış Soner Uşaklı Date: Thu Aug 14 19:05:25 2025 -0400 dont spam logs commit bfdf47b69eae3a6c36998c9e36c0e1eb07ee9baf Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Aug 14 18:49:27 2025 -0400 chore(deps): update dependency @eslint/js to v9.33.0 (#13589) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e079f8b291fe97b5df1d5fdb33f3f80e04ece1bd Author: Misty Release Bot Date: Thu Aug 14 09:20:43 2025 +0000 Latest translations and fallbacks commit ecab347b2d042cf0c52c63434e3d5fe43e3f520d Author: Julian Lam Date: Wed Aug 13 18:37:20 2025 -0400 fix: add missing file to ur language folder commit 076cc9e868119597a7c532e06e8507d9203c961a Author: Barış Soner Uşaklı Date: Wed Aug 13 17:36:55 2025 -0400 lint: remove unused url commit 311bbefa427db5f56e7f76a8bbe6215c912b758d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 13 17:35:28 2025 -0400 chore(deps): update actions/checkout action to v5 (#13590) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9ef4cfa2e2374b47bb7e33e09750af0837751635 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 13 17:35:05 2025 -0400 fix(deps): update dependency esbuild to v0.25.9 (#13593) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0f72b8cd6f53de5160e267a5502c799024e5173d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 13 17:34:49 2025 -0400 fix(deps): update dependency redis to v5.8.1 (#13594) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0481549734b50dc9e93ffe9609127cdb0d4d5aaf Author: Julian Lam Date: Wed Aug 13 15:26:32 2025 -0400 test: use protocol of test runner commit 06c38247402948b758c98697129d04899d2bc092 Author: Julian Lam Date: Wed Aug 13 14:41:44 2025 -0400 fix: regression caused by cc6fd49c4d2ddc6970ea23011dece5ba91517ec0 commit c67aa43f14fae778fd634a9b2544a3893bec5976 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 13 11:02:52 2025 -0400 fix(deps): update dependency webpack to v5.101.1 (#13588) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit cc6fd49c4d2ddc6970ea23011dece5ba91517ec0 Author: Julian Lam Date: Wed Aug 13 10:01:05 2025 -0400 fix: protocol-relative URLs being accidentally munged, #13592 commit 673896390fa15289a83602c2daef5d9cc9452bbd Author: Julian Lam Date: Wed Aug 13 10:00:39 2025 -0400 fix: cache lookup error when doing loopback calls commit 8c6992f525ce0d9e6f3edd00723da86b137484c6 Author: Julian Lam Date: Wed Aug 13 09:34:58 2025 -0400 feat: add Urdu localisation, thank you! commit 49de4f375e0646e7641c0daf041f93fa29aa3d19 Author: Misty Release Bot Date: Wed Aug 13 09:19:46 2025 +0000 Latest translations and fallbacks commit eeabc99092267b5216e634073b8f78862805f70a Author: Misty Release Bot Date: Tue Aug 12 21:07:22 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.social commit 82037dee0073fa4a3462bc8476798380f1277d8c Author: Barış Soner Uşaklı Date: Tue Aug 12 17:06:57 2025 -0400 feat: add wordpress commit c10656ec52753d335e584b1208703b34ae908860 Author: Barış Soner Uşaklı Date: Tue Aug 12 17:06:57 2025 -0400 feat: add wordpress commit e90b524b66f855f5e8d553b1adafb1c68f28dd30 Author: Misty Release Bot Date: Tue Aug 12 09:19:59 2025 +0000 Latest translations and fallbacks commit 18a6c98c9d3d7cc294d676ed3780c31d1d415f66 Author: Misty Release Bot Date: Mon Aug 11 09:20:05 2025 +0000 Latest translations and fallbacks commit f8733e06a783580ca88ee735eb317e0dafac427e Author: Barış Soner Uşaklı Date: Fri Aug 8 16:10:11 2025 -0400 refactor: show code/stack when dep check fails commit 900b04cbadae12a078425a0baaaf908ab9b4816a Merge: e68deaaca1 abf7dd74d0 Author: Barış Soner Uşaklı Date: Fri Aug 8 13:54:17 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit e68deaaca1f88a2ed9b3034948523d89793f8a69 Author: Barış Soner Uşaklı Date: Fri Aug 8 13:54:12 2025 -0400 chore: up eslibt commit abf7dd74d0138d3c76f3b82a346e39208186da12 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 8 13:51:20 2025 -0400 fix(deps): update dependency sass to v1.90.0 (#13582) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c8694333733b63bffdf5d44fa203bea2a2548901 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 8 13:51:09 2025 -0400 chore(deps): update dependency sass-embedded to v1.90.0 (#13581) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c8e3dc6b0b637b570ea7f442f2d06924d9fbc386 Merge: 88b40e1e9d e7b479954a Author: Barış Soner Uşaklı Date: Fri Aug 8 13:33:28 2025 -0400 Merge branch 'master' into develop commit e7b479954ac0044f576cb51a0eaaa018bf965397 Author: Barış Soner Uşaklı Date: Fri Aug 8 13:21:43 2025 -0400 chore: up widget essentials commit 88b40e1e9d79777cd153c2e6c587a95a01b06a39 Author: Misty Release Bot Date: Fri Aug 8 09:19:47 2025 +0000 Latest translations and fallbacks commit c305cc9069035f606b47139dc77d617c37b0fd70 Author: Misty Release Bot Date: Thu Aug 7 09:20:34 2025 +0000 Latest translations and fallbacks commit b4ff79061f8018e6d17a476567c256c88e5fa1ac Author: Julian Lam Date: Wed Aug 6 13:50:08 2025 -0400 fix: image handling when image url received is not a path with an extension commit 3895a0590c27e54e001cf32785db703d84df921e Author: Misty Release Bot Date: Wed Aug 6 17:48:34 2025 +0000 chore: update changelog for v4.4.6 commit bb913c152bc050567d4a4236fa5a9535b293d811 Author: Misty Release Bot Date: Wed Aug 6 17:48:33 2025 +0000 chore: incrementing version number - v4.4.6 commit 32de562e709410f20fd136c06d200c5912af2528 Author: Barış Soner Uşaklı Date: Wed Aug 6 13:38:42 2025 -0400 Revert "feat: add inspect argument" This reverts commit 955b27debc036199afa9e34f66812e83464572ac. commit 34ecdf2043d5d3bdade79af292cf7fb4e8f91bbb Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 6 13:12:31 2025 -0400 chore(deps): update dependency lint-staged to v16.1.4 (#13575) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5ce556d41f76649df62a97d3f24d2fd8bea16391 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 6 13:12:19 2025 -0400 fix(deps): update dependency fs-extra to v11.3.1 (#13579) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3c3e44860613aac0412d489faafc400b100bc12f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Aug 6 13:12:05 2025 -0400 fix(deps): update dependency redis to v5.8.0 (#13580) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 955b27debc036199afa9e34f66812e83464572ac Author: Barış Soner Uşaklı Date: Wed Aug 6 13:10:56 2025 -0400 feat: add inspect argument commit 25bc9ba00b859c3b43ac55adfd0a7dc99f45d087 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Aug 5 18:01:42 2025 -0400 chore(deps): update redis docker tag to v8.2.0 (#13577) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f8a0a7e194b0acd3242c00ec65a598b0de7d6ef0 Author: Barış Soner Uşaklı Date: Tue Aug 5 18:00:44 2025 -0400 test: fix notification tests commit 9d39ed512fa96a518f81d09f54bafa6f3e86b2ae Author: Barış Soner Uşaklı Date: Tue Aug 5 17:51:53 2025 -0400 feat: only mark notifications read that match current filter closes #13574 commit e1423636a5404f265e7a6d41c6d0afed74b0b8f5 Author: Barış Soner Uşaklı Date: Tue Aug 5 10:46:10 2025 -0400 feat: closes #13578, increase uniquevisitors on ap pageviews like normal pageviews commit 340618d3e0945a7a4487c8adf79941923e20f40a Author: Misty Release Bot Date: Mon Aug 4 09:19:55 2025 +0000 Latest translations and fallbacks commit 2b8f3a7891037b7acf9c668c2472e2797ad57987 Merge: c6889f0864 2a6e4b0a8d Author: Barış Soner Uşaklı Date: Sun Aug 3 06:43:36 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit c6889f0864c1c7e51c4fbc152311f5e9ef115f3a Author: Barış Soner Uşaklı Date: Sun Aug 3 06:43:31 2025 -0400 fix: readd retry items commit 2a6e4b0a8dd9c83f432d80b2929115d190602797 Author: Misty Release Bot Date: Sun Aug 3 09:19:18 2025 +0000 Latest translations and fallbacks commit fe1601608d2540e2994375035fa042bb9b385dba Author: Julian Lam Date: Sun Aug 3 02:33:54 2025 -0400 fix: set noindex tag on remote profiles as well commit c8ad086779eb4ff56cb2063f9ce0e99532c23571 Author: Julian Lam Date: Sat Aug 2 09:52:13 2025 -0400 fix: duplicate canonical link header commit 8ce5498f2351bf513f295d25441e20e56d9dd051 Author: Julian Lam Date: Sat Aug 2 09:48:59 2025 -0400 fix: add rel canonical to remote user profiles commit 27d60a19f9025231bb5d98f6ed54b17b4da81967 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 1 20:37:15 2025 -0400 fix(deps): update dependency redis to v5.7.0 (#13570) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0b4efa14a99f9d986522c74e1691cdf4766107fe Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Aug 1 20:37:04 2025 -0400 fix(deps): update dependency cron to v4.3.3 (#13573) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bc40d79cf8954cd724888eb5562b0ebc5b2b1e20 Author: Barış Soner Uşaklı Date: Fri Aug 1 18:46:27 2025 -0400 refactor: dont del if cache disabled commit 567ed8755b6783ef99619f2700b8b2a67095e70e Author: Barış Soner Uşaklı Date: Thu Jul 31 18:44:09 2025 -0400 feat: add new brite skin from bootswatch commit 8305a7425ad5cff79374c2d32247354a734f5a4d Author: Barış Soner Uşaklı Date: Fri Aug 1 12:26:38 2025 -0400 refactor: remove old arg commit b229488daf2f1322a50d68eccbfdaf7d6b29125d Merge: a8bf4ea069 d5f57af342 Author: Barış Soner Uşaklı Date: Fri Aug 1 12:21:25 2025 -0400 Merge branch 'master' into develop commit d5f57af3422babbc0df7698e848e9b1075c6dbea Author: Barış Soner Uşaklı Date: Fri Aug 1 12:21:17 2025 -0400 fix: pass max-memory expose-gc as process args commit a8bf4ea069347b22bbca554a392c31fee2c4002d Author: Barış Soner Uşaklı Date: Fri Aug 1 11:59:59 2025 -0400 fix: ap queue id to use payload.type payload.id commit 9a32bb56967813084074014c55cc21128cf0e20f Merge: d5f6d158f4 5c69c8bf9c Author: Barış Soner Uşaklı Date: Fri Aug 1 11:50:15 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit d5f6d158f49aab651cf89963aa895ef6846bb184 Author: Barış Soner Uşaklı Date: Fri Aug 1 11:50:13 2025 -0400 refactor: if user.delete fails in actor prune remove from ap sets/keys commit 5c69c8bf9c97870ceb5530c1a715faec0905a2c1 Author: Misty Release Bot Date: Fri Aug 1 09:20:24 2025 +0000 Latest translations and fallbacks commit e851a52390d766851c1bcf2012c1994c2286b7a0 Author: Barış Soner Uşaklı Date: Thu Jul 31 18:44:09 2025 -0400 feat: add new brite skin from bootswatch commit 5d737a78194b917887b6e3670754d6ed2ba10d52 Merge: 95f6688c04 de05dad251 Author: Barış Soner Uşaklı Date: Thu Jul 31 10:29:08 2025 -0400 Merge branch 'master' into develop commit de05dad2518b657acc0b854c86a7c8fc88484e0e Author: Misty Release Bot Date: Thu Jul 31 13:57:00 2025 +0000 chore: update changelog for v4.4.5 commit af95cde187782a7e206c8d5f7c04f241eadf2997 Author: Misty Release Bot Date: Thu Jul 31 13:57:00 2025 +0000 chore: incrementing version number - v4.4.5 commit 95f6688c04067e4b15c3471354ce680c80c8bac6 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:24:04 2025 -0400 test: one more fix commit 7393bdd4447a591ef260f5cf5922efd432aa4add Author: Barış Soner Uşaklı Date: Thu Jul 31 09:17:26 2025 -0400 test: fix spec commit 1071ac0cea6b00d669bae2e4afc4c20e982dd684 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:14:19 2025 -0400 test: fix openapi commit 472df3aa225897880db9ed8386f93b7fb7762cf8 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:00:40 2025 -0400 refactor: use promise.all commit 97d4994afbac711d8ee27c37b66cadb90bbc2c27 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:02:49 2025 -0400 feat: add filter:post.getDiffs commit 5f5a6972537fb5a614fb384c4894b8d8e792a78c Author: Barış Soner Uşaklı Date: Thu Jul 31 09:24:04 2025 -0400 test: one more fix commit 3b609316047004fb79721ad711cc7ba370116abc Author: Barış Soner Uşaklı Date: Thu Jul 31 09:17:26 2025 -0400 test: fix spec commit c7c83e0e4bc610a64ad77a3626760e2401ed0eb1 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:14:19 2025 -0400 test: fix openapi commit 90a6512970cad99abf764436eed7c658d26f5f94 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:02:49 2025 -0400 feat: add filter:post.getDiffs commit 7c00e814b711712e5e7d092e1647987c829968ef Author: Barış Soner Uşaklı Date: Thu Jul 31 09:00:40 2025 -0400 refactor: use promise.all commit bbb9a4601963d0283802ed0e9e268a2dc63f63da Author: Barış Soner Uşaklı Date: Thu Jul 31 09:02:49 2025 -0400 feat: add filter:post.getDiffs commit 6eab44a01d41900e39592d83c346e0ea5332f689 Author: Barış Soner Uşaklı Date: Thu Jul 31 09:00:40 2025 -0400 refactor: use promise.all commit 70d3a29c32a1d5386722ac728c1efb611ae7abd1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jul 30 10:54:26 2025 -0400 fix(deps): update dependency satori to v0.16.2 (#13569) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bba18e31027c82b64eb75787679a63b3a936aa86 Author: Barış Soner Uşaklı Date: Wed Jul 30 09:37:36 2025 -0400 feat: add expose-gc flag to loader commit 36d2929fa9cf62151df288cdcd6269bb0facd5e1 Merge: b3a4a128cd 5f696176b4 Author: Barış Soner Uşaklı Date: Wed Jul 30 09:34:36 2025 -0400 Merge branch 'master' into develop commit b3a4a128cdfc361949beeb0a6e9e7f30e486bb6a Author: Barış Uşaklı Date: Wed Jul 30 09:32:58 2025 -0400 refactor: move ap retry queue from lru cache to db (#13568) * refactor: move ap retry queue from lru cache to db get rid of the setTimeouts that were running for 2months retries will survive server restarts * refactor: reduce exp. backoff commit 6fc8dfa9408fc7087d5d059f28caa285108c5b55 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jul 29 11:45:02 2025 -0400 fix(deps): update dependency webpack to v5.101.0 (#13567) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5f696176b444e8836dcc09c08a59b3edf7690bac Author: Barış Soner Uşaklı Date: Sun Jul 27 10:35:17 2025 -0400 fix: clearTimeout if item is evicted from cache commit 0997fbfa4db471c1a6afe694067acc7b149f07a2 Author: Barış Soner Uşaklı Date: Sun Jul 27 10:35:17 2025 -0400 fix: clearTimeout if item is evicted from cache commit 2d1a5fea11ee7583e1ae16bad3e5f3f956155ea5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 26 17:44:14 2025 -0400 fix(deps): update dependency satori to v0.16.1 (#13560) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 637373e31a610187e1d6f563968f2238c61b2e92 Author: Barış Soner Uşaklı Date: Fri Jul 25 13:37:20 2025 -0400 chore: up eslint commit 9b566a55d9824a8ea0d0ef1fff11588030939d1a Merge: b20e486367 fe9b49e3d5 Author: Barış Soner Uşaklı Date: Fri Jul 25 10:57:45 2025 -0400 Merge branch 'master' into develop commit fe9b49e3d5dc8dadfea1b775095df63d001838e6 Author: Barış Soner Uşaklı Date: Fri Jul 25 10:57:30 2025 -0400 test: increase timeout of failing test commit b20e4863678bc3b9434e0c3ed12d237f720f41b7 Merge: 65364bfa0f b74c789849 Author: Barış Soner Uşaklı Date: Fri Jul 25 10:54:27 2025 -0400 Merge branch 'master' into develop commit b74c789849eb25bb9527d181d104837df940bfdb Author: Barış Soner Uşaklı Date: Fri Jul 25 10:49:52 2025 -0400 fix: use sharp to convert svg to png, closes #13534 commit 5a86415092c438bad4fb463a951886ca7b501213 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jul 25 10:28:40 2025 -0400 chore(config): migrate config renovate.json (#13565) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 65364bfa0f62fa589e4dc1d8ded1bf966b8fd510 Author: Barış Soner Uşaklı Date: Thu Jul 24 23:54:38 2025 -0400 fix: sometimes summary is null/undefined fixes TypeError: Cannot read properties of null (reading 'replace') at /home/saas/nodebb/src/activitypub/mocks.js:202:24 commit 1262aee843249946d9da351a9db0ad9fc355af2e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jul 24 12:19:56 2025 -0400 fix(deps): update dependency redis to v5.6.1 (#13564) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 947702fe0cd6379722a31d631c4e799387d8c6d1 Merge: dcdda2a8c8 5bcf078a75 Author: Barış Soner Uşaklı Date: Thu Jul 24 12:07:32 2025 -0400 Merge branch 'master' into develop commit 5bcf078a75ee484dc154821ca9ba34fd805118da Author: Barış Soner Uşaklı Date: Thu Jul 24 12:07:26 2025 -0400 fix: use filename to check for svg, tempPath doesn't always have extension commit dcdda2a8c8e8bf094cf63f93e225cfb161ad40eb Merge: 8e9d38430c a8f4c5e63a Author: Barış Soner Uşaklı Date: Thu Jul 24 11:55:39 2025 -0400 Merge branch 'master' into develop commit a8f4c5e63a93089ad334e9c927e9a0862e235394 Author: Barış Soner Uşaklı Date: Thu Jul 24 10:34:37 2025 -0400 fix: apply sanitizeSvg to regular uploads and uploads from manage uploads acp page commit 8e9d38430c676cc66fe96ade6152a4ef25f53e56 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jul 23 09:49:52 2025 -0400 fix(deps): update dependency mongodb to v6.18.0 (#13563) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit de71cc63104660a522ebad079cbe60d26c1515d7 Author: Barış Soner Uşaklı Date: Tue Jul 22 16:35:55 2025 -0400 refactor: log uid that failed commit f6ed7ec21c763395704b66564b2a08cd41f830c0 Author: Barış Soner Uşaklı Date: Tue Jul 22 16:28:37 2025 -0400 fix: don't translate text on admin logs page commit 1776bd1d7e732774a0a557b07d3d6a37483077a9 Author: Barış Soner Uşaklı Date: Tue Jul 22 10:58:17 2025 -0400 test: fix meta test commit 8eedb38a99d35d3009e46937d9813943c20b502a Author: Barış Soner Uşaklı Date: Tue Jul 22 10:51:54 2025 -0400 test: test fixes for default teaser change commit 8ba230a205edd3a2149f9bec612b451b65620306 Author: Barış Soner Uşaklı Date: Tue Jul 22 10:39:27 2025 -0400 refactor: change default teaser to last-post commit c43c3533507854740006e2cc4589a16b5d6b9c95 Author: Barış Soner Uşaklı Date: Mon Jul 21 21:22:40 2025 -0400 fix: change the client side reloginTimer to match setting when setting is changed restart timer closes #13561 commit 6a732e36166fd9ace0cd2c7692f23defea4a5fdc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jul 21 15:00:49 2025 -0400 fix(deps): update dependency esbuild to v0.25.8 (#13559) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 54fae3b12b778f11b09f8d3150a6a9cff6f4ee44 Author: Barış Soner Uşaklı Date: Sun Jul 20 13:38:31 2025 -0400 set max on upload rate limit commit eac3d0a043e66638cea34c78f10bb97fee32f66c Author: Barış Soner Uşaklı Date: Sun Jul 20 11:57:34 2025 -0400 fix: redis connect host/port commit e365cd5606f33ee251e99122f8406bf59c178860 Merge: 25c24298fb 1697e36f3a Author: Barış Soner Uşaklı Date: Sat Jul 19 17:21:01 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 25c24298fb277bd98891060ce09a05415b313de0 Author: Barış Soner Uşaklı Date: Sat Jul 19 17:20:59 2025 -0400 fix: closes #13558, override/extend json opts from config.json commit 1697e36f3abbcd36d31aa64a16b1dfd93cc4b016 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 19 13:34:52 2025 -0400 fix(deps): update dependency esbuild to v0.25.7 (#13557) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0eb0a67ae570ba5fe92059ad099aabb31a4c7a34 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 19 13:04:10 2025 -0400 fix(deps): update dependency express-session to v1.18.2 (#13554) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0e457f15858f341b05656bec2fa737968b923628 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 19 13:03:45 2025 -0400 fix(deps): update dependency morgan to v1.10.1 (#13555) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 35ca0e3b475e46adf33b58254b9ea2c0c5abab4f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 19 13:03:18 2025 -0400 fix(deps): update dependency multer to v2.0.2 (#13556) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3f520c33eff5d2753bcfc9afaa5fbdad09055147 Author: Barış Soner Uşaklı Date: Fri Jul 18 21:35:08 2025 -0400 fix: add missing cache name commit 12b9f4c743b4247e6b2b8361251a6bec9980a2ca Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jul 17 22:22:31 2025 -0400 fix(deps): update dependency compression to v1.8.1 (#13553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 57564190f3c2fd85a057ba68a893bf896336ba29 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jul 17 22:07:59 2025 -0400 fix(deps): update dependency ace-builds to v1.43.2 (#13548) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0b398bba4f0169e8eeaf4d14d44fec9d602626fb Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jul 17 22:07:44 2025 -0400 fix(deps): update dependency webpack to v5.100.2 (#13549) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e4a0160e085a067251339ff08229c262d90c5fb8 Author: Barış Soner Uşaklı Date: Thu Jul 17 21:34:14 2025 -0400 refactor: copy session/headers when building req commit 1d7c32a52f2927d59d04db9c902a0124cea856b0 Author: Barış Soner Uşaklı Date: Thu Jul 17 12:34:52 2025 -0400 refactor: show both days and hours commit 272008bb51084e688c6dc03e8df1c7c46a9f9ee6 Author: Barış Soner Uşaklı Date: Wed Jul 16 20:23:57 2025 -0400 refactor: add missing cache name commit 0fdde132082ff037da63f202c3af2459f70a8e2e Author: Barış Soner Uşaklı Date: Wed Jul 16 18:10:21 2025 -0400 refactor: another missing cache name commit a08551a5e1685aaff20f3ac449ad852952e07872 Author: Barış Soner Uşaklı Date: Wed Jul 16 17:42:23 2025 -0400 refactor: add names to caches, add max to request cache commit 1ad97ac19425a096b7720586b4753e61c90cdc6f Author: Barış Soner Uşaklı Date: Tue Jul 15 13:02:46 2025 -0400 refactor: closes #13547, process user uploads via batch reduce processed user count to 100 per batch commit 97a5d54387a174b028a74b40be67d6fae5fe682a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jul 14 10:25:17 2025 -0400 chore(deps): update dependency @eslint/js to v9.31.0 (#13545) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d8c26bec45d033b48e6ef3094d271729631dcce9 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jul 14 10:24:51 2025 -0400 fix(deps): update dependency webpack to v5.100.1 (#13544) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e838bb268f24e71a37b3ecec8064be39e73e1137 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jul 14 10:24:42 2025 -0400 fix(deps): update dependency cron to v4.3.2 (#13546) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e5de79ff7db451b9c9517b7336b7cb94046d5fae Author: Misty Release Bot Date: Mon Jul 14 09:19:32 2025 +0000 Latest translations and fallbacks commit 352f4a0c35252778399fe85d590154c710908df4 Author: Misty Release Bot Date: Sun Jul 13 09:19:24 2025 +0000 Latest translations and fallbacks commit 32e4db8ea8e18b5de6934d9aaf48e50045f4ebff Author: Misty Release Bot Date: Sat Jul 12 09:19:22 2025 +0000 Latest translations and fallbacks commit 01f2effcedc3f8800bcd65e26fce44d1b8174da3 Author: Barış Soner Uşaklı Date: Fri Jul 11 15:38:21 2025 -0400 fix: add missing ap pageview middleware commit 3ff7822d44576c8cd3cc0b95c0b0ec620a0f2c5f Merge: 020e0ad12e 5d16fdc93f Author: Barış Soner Uşaklı Date: Fri Jul 11 15:18:49 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 020e0ad12eee643725987457a67cda67fba719a6 Author: Barış Soner Uşaklı Date: Fri Jul 11 15:18:44 2025 -0400 test: add openapi spec commit 5d16fdc93f7469d000720c8accd2ebdbd4e82864 Author: Misty Release Bot Date: Fri Jul 11 19:10:21 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-dashboard commit 559a2d233de905ec56fe95eb9d51a2d260617dc4 Author: Barış Soner Uşaklı Date: Fri Jul 11 15:09:55 2025 -0400 feat: add ap pageviews analytics commit 59c1ce853f905c2f4a9af799a24b2fb76fc3bdf9 Author: Misty Release Bot Date: Fri Jul 11 14:23:04 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-development-info commit e74996fbb950765795bb58687a6b9f0398262ff7 Author: Barış Soner Uşaklı Date: Fri Jul 11 10:22:37 2025 -0400 revert: remove heapdump commit 27aab921910f575e06a94a1805907f1c902d48db Author: Barış Soner Uşaklı Date: Fri Jul 11 09:05:43 2025 -0400 test: try timeout again commit 3cc3b6760c932f77490e116405aeead6720a17f6 Merge: 930ff21f33 5b54e926f7 Author: Barış Soner Uşaklı Date: Fri Jul 11 09:01:39 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 930ff21f335de42d015985ae438bb96b009baef8 Author: Barış Soner Uşaklı Date: Fri Jul 11 09:01:33 2025 -0400 test: disable timeout commit 5b54e926f7402a7a3548e863732e23d52eaee79d Author: Misty Release Bot Date: Fri Jul 11 12:51:18 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-development-info commit f88329dbbe9a6ab99eec0eaee3a4b80efbcfe820 Author: Barış Soner Uşaklı Date: Fri Jul 11 08:50:53 2025 -0400 feat: add heap snapshot commit e4f56e8392e68e40076a3b2f577cd1529b479186 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jul 10 09:10:00 2025 -0400 fix(deps): update dependency nodebb-theme-peace to v2.2.46 (#13542) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4a5a4fe6bd387dbea4f1d0b9f42d7ce74740c5a1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jul 9 18:03:23 2025 -0400 fix(deps): update dependency webpack to v5.100.0 (#13541) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1b80910e806ea2a7348abcef71a57dbeaff89f4f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jul 8 14:09:15 2025 -0400 chore(deps): update redis docker tag to v8.0.3 (#13539) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit da1f2a9c9fd6062c916361f4df6bde67ea3d4bb2 Merge: dae81b76fb a6cb933bac Author: Barış Soner Uşaklı Date: Tue Jul 8 14:04:01 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit dae81b76fbe76acd4b669789903eb9d57068527b Author: Barış Soner Uşaklı Date: Tue Jul 8 14:03:56 2025 -0400 chore: up dbsearch commit a6cb933bac58220c60ccafd2ae2c13d3ffc8e28a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jul 8 13:55:29 2025 -0400 fix(deps): update dependency redis to v5.6.0 (#13540) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0ef98ec495c4d91f00fc5963cd7f4878039faa6f Author: Barış Soner Uşaklı Date: Tue Jul 8 13:34:41 2025 -0400 fix: set to empty string if undefined commit dbed2db992e80d12e031790923ee2892819596f2 Author: Barış Soner Uşaklı Date: Tue Jul 8 11:03:02 2025 -0400 fix: make clickable element anchor add rounded corners commit 8960fdb3a5f85c5d28100bae1e74a202a753f1b8 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jul 7 17:39:03 2025 -0400 fix(deps): update dependency esbuild to v0.25.6 (#13538) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c6f4148b214409fab97ec0e916062c6d3c253dc3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jul 7 17:38:43 2025 -0400 fix(deps): update dependency nodemailer to v7.0.5 (#13537) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 113607829f77b20b7841cbbdcc1c1b8146092bd2 Author: Barış Soner Uşaklı Date: Mon Jul 7 17:09:42 2025 -0400 remove log commit 329f98d5db866be9a624774e7c1430ed45d71cad Author: Barış Soner Uşaklı Date: Mon Jul 7 12:16:08 2025 -0400 fix: for attribute, remove upload trigger when click inputs user can input an absolute url in the inputs commit 72fec565c21d8bf03752e6fa2763d2609a8fd849 Author: Barış Soner Uşaklı Date: Mon Jul 7 11:28:22 2025 -0400 fix: check topic and thumbs commit 24e7cf4a0078dd91d637f449501df5e1fd1f3372 Author: Barış Uşaklı Date: Mon Jul 7 10:22:24 2025 -0400 refactor: move post uploads to post hash (#13533) * refactor: move post uploads to post hash * test: add uploads to api definition * refactor: move thumbs to topic hash * chore: up composer * refactor: dont use old zset commit bfcc36f7cbcdb297e6151f67a0b0fefdae6d3e83 Author: Misty Release Bot Date: Sun Jul 6 09:19:12 2025 +0000 Latest translations and fallbacks commit 991f518e2f615d654484cbe2444ef08ad378dbd1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jul 4 14:47:50 2025 -0400 fix(deps): update dependency nodebb-theme-peace to v2.2.45 (#13529) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 80fabdcb33bc6ae1be7624f5f01ea45f90e5b5d3 Author: Misty Release Bot Date: Thu Jul 3 09:20:10 2025 +0000 Latest translations and fallbacks commit 5a5ca8a5fb3d80e536f19cc52efa8145c5ae1247 Author: Barış Soner Uşaklı Date: Wed Jul 2 17:38:35 2025 -0400 fix: closes #13526, dont send multiple emails when user is invited commit ceae2aa1a81342ecb470a8a0bbbaa5f0ae541965 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jul 2 16:19:16 2025 -0400 fix(deps): update dependency nodebb-plugin-web-push to v0.7.5 (#13523) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6d7df13fdb899e5d129a73a1c2bbfd73298a5b70 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jul 2 16:19:07 2025 -0400 chore(deps): update dependency @eslint/js to v9.30.1 (#13524) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit aba2ddad949ac52879fbafa8592b18ab1d5e034d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jul 2 16:18:56 2025 -0400 fix(deps): update dependency ace-builds to v1.43.1 (#13525) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 37f0fa961ea4b39ebbe4b388f1a02d672559da39 Author: Barış Soner Uşaklı Date: Tue Jul 1 10:01:10 2025 -0400 Refactor hook call for filterSortedTids commit 18d6e5e1d64bb921415a331530a2540221f6a50c Author: Barış Soner Uşaklı Date: Mon Jun 30 20:33:16 2025 -0400 chore: up eslint-plugin commit f1fbea7b28fbd029602ced0add72d55a38a15049 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jun 30 20:25:03 2025 -0400 fix(deps): update dependency nodemailer to v7.0.4 (#13522) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 48071ebbb576cf4368af2f716de240e21fc53fd8 Author: Misty Release Bot Date: Sun Jun 29 09:19:19 2025 +0000 Latest translations and fallbacks commit 15ea123382fd529752a7b2c8e2180edbd6e21b78 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 27 22:04:00 2025 -0400 chore(deps): update dependency @eslint/js to v9.30.0 (#13519) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 22005b9ccf83f37498d6bf02afdfb1b7f60f6ba3 Author: Barış Soner Uşaklı Date: Fri Jun 27 16:17:06 2025 -0400 assign correct data commit 85e2d7d338009fa3fea8ba16e795bedfb8dba899 Author: Barış Soner Uşaklı Date: Fri Jun 27 16:08:51 2025 -0400 test: psql fix commit fd82919e5ab1c7acedbb3f9a71776e0fd4919d41 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 27 15:41:54 2025 -0400 fix(deps): update dependency pg to v8.16.3 (#13517) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 655a3bd3a305bf87175852341152c791dd472d8f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 27 15:41:45 2025 -0400 fix(deps): update dependency workerpool to v9.3.3 (#13518) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 59707df70be3d2cf4447ac0dc82708d747e38f56 Merge: c056bf5618 6e5083c263 Author: Barış Soner Uşaklı Date: Fri Jun 27 15:22:44 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit c056bf5618004db28c661860074b9785c0ef0ee0 Author: Barış Soner Uşaklı Date: Fri Jun 27 15:22:39 2025 -0400 chore: up eslint commit 6e5083c263318d4437d94c0fd2f001c26c3a3689 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 27 15:20:28 2025 -0400 fix(deps): update dependency pg-cursor to v2.15.3 (#13516) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 22d1972f83f87adc72baf3cb4b134fdb68ddf66d Author: Barış Soner Uşaklı Date: Fri Jun 27 15:13:16 2025 -0400 test: one more test fix commit 7acd63c2a0ab8f4535fbbd868d90869c49281273 Author: Barış Soner Uşaklı Date: Fri Jun 27 15:03:23 2025 -0400 test: fix test, add joinLeaveMessages to newRoom commit f5aca1144d776478f6b7a0821fe0684e9816a5c3 Author: Misty Release Bot Date: Fri Jun 27 18:19:19 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.modules commit 92a3859f7bdd68b52e44551b3ce62fd55ce6e834 Author: Barış Soner Uşaklı Date: Fri Jun 27 14:18:53 2025 -0400 feat: add option to toggle chat join/leave message closes #13508 commit a41d2c0b1a9e60c2765c883b7175a58a04138568 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 27 09:43:26 2025 -0400 chore(deps): update dependency smtp-server to v3.14.0 (#13515) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d2f0944eabd274967d6333fa855376eedbf6e2bf Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 20:29:27 2025 -0400 fix(deps): update dependency pg to v8.16.2 (#13505) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit bbacd8f6e420c1b8ddf0d216f854b3eda3e87156 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 20:29:07 2025 -0400 chore(deps): update dependency mocha to v11.7.1 (#13509) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 59090931039e25f6dcb9ee15a0e1b9bc6ab9f24a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 13:20:12 2025 -0400 fix(deps): update dependency nodebb-theme-peace to v2.2.44 (#13514) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4be2e82b5afe0e02769104dfa246e9402ea6806d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 13:20:02 2025 -0400 fix(deps): update dependency nodebb-theme-harmony to v2.1.16 (#13513) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit fa31ba0560b9f16452b1cbbc10b64bf67ff20456 Author: Barış Soner Uşaklı Date: Thu Jun 26 13:10:11 2025 -0400 test: increase timeout commit 1a85fafbaf7d5db4f900f1955add4abc9a244fa5 Author: Barış Soner Uşaklı Date: Thu Jun 26 13:01:28 2025 -0400 test: on more commit 82c8034cfbf475e9f3ce6af7f41fba2ca2d1978d Author: Barış Soner Uşaklı Date: Thu Jun 26 12:55:31 2025 -0400 test: testing timeout on failing test commit 1eefaf5cd819b30d58f2612a2dba8f1014265bc1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 12:43:41 2025 -0400 fix(deps): update dependency bootswatch to v5.3.7 (#13510) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 10f7b49be8502460491def251af3a13bdb63a7c3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 11:20:37 2025 -0400 fix(deps): update dependency pg-cursor to v2.15.2 (#13506) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e360f649b3f6dc8215ae841da7650458b0eb597b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 26 11:19:33 2025 -0400 fix(deps): update dependency ace-builds to v1.43.0 (#13507) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f9c6d24c73dd012835adbf2c5f80e5014a794fba Author: Misty Release Bot Date: Sat Jun 21 09:19:27 2025 +0000 Latest translations and fallbacks commit 3b364ba12046a2bffbc366367f19bd2af8c0c931 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 18 17:13:33 2025 -0400 fix(deps): update dependency pg-cursor to v2.15.1 (#13504) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0b9bfc1ce1bf381aba0170c1c860b8dd007357b1 Author: Barış Soner Uşaklı Date: Wed Jun 18 16:59:57 2025 -0400 refactor: parallel socket.io adapter commit 48621f5c6be45d9f015ba416f1b4908e2caeef9b Merge: a8faf2ba7c 3e961257ec Author: Barış Soner Uşaklı Date: Wed Jun 18 15:41:15 2025 -0400 Merge branch 'master' into develop commit a8faf2ba7cce9f0a559c30f2c4628ea2943f27a6 Merge: 39d243b04f 819e28052a Author: Barış Soner Uşaklı Date: Wed Jun 18 13:42:24 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 39d243b04f63a44670ac69211604bbbfba37caf2 Author: Barış Soner Uşaklı Date: Wed Jun 18 13:42:19 2025 -0400 test: remove ci env commit 819e28052aa672f565e0196c7036f5b778007ae5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 18 13:35:38 2025 -0400 fix(deps): update dependency pg to v8.16.1 (#13503) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0315e369411c3f18c3bdaac10f6817a8a8f23f3c Author: Barış Soner Uşaklı Date: Wed Jun 18 13:34:55 2025 -0400 chore: remove logs commit 3e961257ec0904dbc3b3c64dab3d4cbdffcfbbd7 Author: Barış Uşaklı Date: Wed Jun 18 13:25:36 2025 -0400 Update README.md commit a54dad932a69c0a6f27e72079c42380e95155afe Merge: 1fc91d5e75 0a0dd1c14d Author: Barış Soner Uşaklı Date: Wed Jun 18 13:21:23 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 1fc91d5e751d36bd10eb9fea49ab0c6119315efb Author: Barış Soner Uşaklı Date: Wed Jun 18 13:21:18 2025 -0400 test: add a null field test commit 0a0dd1c14dbce63c8360bee5cbe1472b1a6c87c6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 18 13:19:01 2025 -0400 chore(deps): update dependency mocha to v11.7.0 (#13502) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e84fc7393915438e88b9436f8b26bdc6f2f9a331 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 18 13:18:50 2025 -0400 fix(deps): update dependency bootstrap to v5.3.7 (#13499) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d3faff3680d19c8a634569d3d072702f49e88e65 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 18 13:18:36 2025 -0400 fix(deps): update dependency connect-redis to v9 (#13497) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f7f70468fd5dda007adff3b7d2f604b4ee342d3d Author: Barış Soner Uşaklı Date: Wed Jun 18 13:17:29 2025 -0400 fix: pubsub on node-redis commit 14043ab0fd6efc3070cdca0b2c4f5cbfef3d7f50 Author: Barış Uşaklı Date: Wed Jun 18 13:04:57 2025 -0400 Node redis (#13500) * refactor: start migrating to node-redis * few more zset fixes * fix: db.scan * fix: list methods * fix set methods * fix: hash methods * use hasOwn, remove cloning * sorted set fixes * fix: so data is converted to strings before saving otherwise node-redis throws below error TypeError: "arguments[2]" must be of type "string | Buffer", got number instead. * chore: remove comments * fix: zrank string param * use new close * chore: up dbsearch * test: add log * test: more log * test: log failing test * test: catch errors in formatApiResponse add await so exception goes to catch * tetst: add log * fix: dont set null/undefined values * test: more fixes commit 7b14e2677544938f3a121363783b06071e15a4f5 Author: Misty Release Bot Date: Wed Jun 18 14:20:41 2025 +0000 chore: update changelog for v4.4.4 commit 2490c312c97926d0b5975c15780db593b8d7ade6 Author: Misty Release Bot Date: Wed Jun 18 14:20:41 2025 +0000 chore: incrementing version number - v4.4.4 commit 3f7d415744f1cd9ffe53380f64ff68e8d59f23e3 Merge: 2046ca724a a3fed408e5 Author: Barış Soner Uşaklı Date: Tue Jun 17 10:00:05 2025 -0400 Merge branch 'master' into develop commit 2046ca724ad3617e27fcf68d8cecb576a0309335 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jun 17 09:36:26 2025 -0400 chore(deps): update dependency @eslint/js to v9.29.0 (#13491) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a3fed408e571b4f4be0dfcf75539dd77f95ae60b Author: Barış Soner Uşaklı Date: Tue Jun 17 09:21:00 2025 -0400 change default to perma ban commit 8c69c6a0c4399debac837469dbbb1a142d220679 Author: Barış Soner Uşaklı Date: Tue Jun 17 09:17:57 2025 -0400 feat: link to post in preview timestamp commit f36a5ac8928f1dbf33ca8aa86cb56a24f144607b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jun 17 08:10:55 2025 -0400 fix(deps): update dependency chart.js to v4.5.0 (#13495) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d6ba79302df38990b258c0aa9b734dc95cbc8177 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Jun 17 08:09:05 2025 -0400 chore(deps): update dependency lint-staged to v16.1.2 (#13492) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 703fcbbf36bac82f0cc0e2ef8a92fce92a445a5d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jun 16 20:02:18 2025 -0400 fix(deps): update dependency postcss to v8.5.6 (#13494) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c101d0d5afa70cab727f76f31a3f0c0faf678901 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 11 17:59:25 2025 -0400 fix(deps): update dependency postcss to v8.5.5 (#13490) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c1b47fbf4d34c1d496df5d6c45fe8ae0de55264e Merge: 442c6e71c0 da2597f81c Author: Barış Soner Uşaklı Date: Wed Jun 11 17:14:08 2025 -0400 Merge branch 'master' into develop commit da2597f81ce5c7df5e02a90d5215e2da087d1f47 Author: Barış Soner Uşaklı Date: Wed Jun 11 17:13:56 2025 -0400 fix: sanitize svg when uploading site-logo, default avatar and og:image commit dc37789b5dd4888332c6ef4c3ede65fedcdd2452 Author: Barış Soner Uşaklı Date: Wed Jun 11 13:16:52 2025 -0400 refactor: send single message commit 84d99a0fc775f01e1dc28b4c80ad78b58d539263 Author: Eli Sheinfeld Date: Wed Jun 11 20:13:23 2025 +0300 feat: Add live reload functionality with Grunt watch and Socket.IO (#13489) - Added livereload event to Grunt watch tasks for instant browser refresh - Integrated Socket.IO WebSocket communication for real-time updates - Enhanced development workflow with immediate file change detection - Improved developer experience with automatic browser reload on file changes Changes: - Gruntfile.js: Send livereload message when files change - src/start.js: Handle livereload events and broadcast via Socket.IO commit 442c6e71c0d0fb9c0b575be6bf6a6c4e721b1f96 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 11 11:04:53 2025 -0400 fix(deps): update dependency sass to v1.89.2 (#13487) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit efcbbf29d161290ab4f14b40ecac34279eb94500 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 11 11:02:43 2025 -0400 fix(deps): update dependency nodebb-plugin-emoji to v6.0.3 (#13486) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d2a7eecb28ec267b6ec9abb2dd150328d98ad202 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 11 11:02:25 2025 -0400 fix(deps): update dependency serve-favicon to v2.5.1 (#13488) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c04bd7cc6e6569440b82f86218e1f3b3ff559248 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 11 10:38:12 2025 -0400 fix(deps): update dependency @fontsource/inter to v5.2.6 (#13477) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f56517878288cac09b12f27c354e4a4bf2002c97 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 11 10:37:52 2025 -0400 chore(deps): update dependency sass-embedded to v1.89.2 (#13482) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6c5b22684bdb30598dd7166f8fd4104ab7bd177b Author: cliffmccarthy <16453869+cliffmccarthy@users.noreply.github.com> Date: Wed Jun 11 08:52:36 2025 -0500 fix: Revise package hash check in Docker entrypoint.sh (#13483) - In the build_forum() function, the file install_hash.md5 is intended to track the content of package.json and detect changes that imply the need to run 'nodebb upgrade'. - The check to compare the current checksum of package.json to the one saved in install_hash.md5 is reversed. The "package.json was updated" branch is taken when the hashes are the same, not when they are different. - When install_hash.md5 does not exist, the comparison value becomes the null string, which never matches the checksum of package.json. As a result, the code always takes the "No changes in package.json" branch and returns from the function without creating install_hash.md5. As a result, install_hash.md5 never gets created on a new installation. - Revised build_forum() to use "not equals" when comparing the two checksums. This causes it to run 'nodebb upgrade' when the checksums are different, and also when install_hash.md5 does not yet exist. If the checksum saved in install_hash.md5 matches the current package.json checksum, it proceeds to either the "Build before start" case or the "No changes" case. commit 95ae8b5f1a109996f2122ae7a51a9fbadd670279 Author: Misty Release Bot Date: Wed Jun 11 09:19:40 2025 +0000 Latest translations and fallbacks commit afa366407039cf18232285fd6b965ffec0123d36 Merge: 2280ea88f2 32faaba0e5 Author: Barış Soner Uşaklı Date: Tue Jun 10 13:37:00 2025 -0400 Merge branch 'master' into develop commit 32faaba0e5a53da8c6ba4eb9192c6dd157a252ce Author: Barış Soner Uşaklı Date: Tue Jun 10 13:36:23 2025 -0400 fix: more edge cases commit 2280ea88f24ff82a8d1ec1347dade0c525b41f70 Author: Barış Soner Uşaklı Date: Tue Jun 10 12:46:07 2025 -0400 fix: typo commit fca90e66ce86e6542d6e466f6598bdffa6fb79f4 Merge: 6a5c2a43ed 0ebb31fe87 Author: Barış Soner Uşaklı Date: Tue Jun 10 12:39:57 2025 -0400 Merge branch 'master' into develop commit 0ebb31fe87750e6b50a2cccc389c0170543af493 Author: Barış Soner Uşaklı Date: Tue Jun 10 12:39:49 2025 -0400 fix: #13484, clear tooltip if cursor leaves link and doesn't enter tooltip commit 6a5c2a43eda1b750e02ef0f6bcfd23d29c7d586d Merge: 341b570d0d 8ab034d8f0 Author: Barış Soner Uşaklı Date: Tue Jun 10 10:53:01 2025 -0400 Merge branch 'master' into develop commit 8ab034d8f05c90e03fef0f719c2110061d0de01c Author: Barış Soner Uşaklı Date: Tue Jun 10 10:52:55 2025 -0400 lint: fix lint commit 341b570d0d1b5c95f1e0bc8d53a6c321b54d3585 Merge: 78ebe2988b 14e30c4bf8 Author: Barış Soner Uşaklı Date: Tue Jun 10 10:47:22 2025 -0400 Merge branch 'master' into develop commit 14e30c4bf8b0c3064cd1b3789badf32f50e22cfb Author: Barış Soner Uşaklı Date: Tue Jun 10 10:47:14 2025 -0400 feat: closes #13484, post preview changes don't close preview when mouse leaves the anchor close preview on click outside close preview when mouseleaves preview open the preview to the top if there isn't enough space add scrollbar to post preview commit 78ebe2988bb6ed76c95120116f2319ea986469e4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jun 9 11:55:37 2025 -0400 fix(deps): update dependency satori to v0.15.2 (#13481) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 89b637af4483a4719dfdf0d9ca2511702fdbf53d Merge: 61870b76bb 0c9297f81c Author: Barış Soner Uşaklı Date: Mon Jun 9 11:37:59 2025 -0400 Merge branch 'master' into develop commit 0c9297f81cd1e62df4c6cecaf36fe66fc4016472 Author: Misty Release Bot Date: Mon Jun 9 15:26:59 2025 +0000 chore: update changelog for v4.4.3 commit 3d88cb8696cc9a5d206575646ae952352e4d06f1 Author: Misty Release Bot Date: Mon Jun 9 15:26:58 2025 +0000 chore: incrementing version number - v4.4.3 commit 5f51dfc4356a70493a789d419dec41866b8003f3 Author: Barış Soner Uşaklı Date: Mon Jun 9 11:10:07 2025 -0400 chore: up composer commit 61870b76bb65c05793fe43afff348a8b512e71ff Merge: 9b4082dcfb b02eb57d06 Author: Barış Soner Uşaklı Date: Mon Jun 9 11:08:32 2025 -0400 Merge branch 'master' into develop commit 9b4082dcfbec77e72437975e76b2ba2de4e3572b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Jun 9 10:54:01 2025 -0400 chore(deps): update dependency mocha to v11.6.0 (#13479) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b02eb57d067105c4aedae09cf1eea5413db566e5 Author: Barış Soner Uşaklı Date: Mon Jun 9 10:23:00 2025 -0400 fix: escape, query params commit f157cfa7e8225dd6fd904cd1c2c6ff4160a3dae1 Author: Misty Release Bot Date: Sun Jun 8 09:19:19 2025 +0000 Latest translations and fallbacks commit 29afcd36b51946f6267f4ba3a1e5f49c89bf3a39 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 6 13:18:57 2025 -0400 fix(deps): update dependency satori to v0.14.0 (#13476) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 536ae9d6a577926aef93c3bf601c4914f6f9f3d1 Author: Barış Soner Uşaklı Date: Fri Jun 6 11:26:02 2025 -0400 chore: up eslint commit d239125f438e6ba8a514524f0278898896002fc5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 6 11:08:54 2025 -0400 chore(deps): update dependency smtp-server to v3.13.8 (#13464) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6b33b1f457a06416881437a814c1df2999daa080 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 6 11:08:44 2025 -0400 fix(deps): update dependency workerpool to v9.3.2 (#13452) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 166aaa7ab9379fc2ac2fb9ad6b908e21aceacaa4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 6 11:08:25 2025 -0400 chore(deps): update redis docker tag to v8.0.2 (#13465) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b3170c9c8ba4172f2401839179731050ebb7b503 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Jun 6 11:08:13 2025 -0400 chore(deps): update dependency @eslint/js to v9.28.0 (#13469) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 01b10170aafd03961e9c6a5b950587901f7e00aa Author: Misty Release Bot Date: Fri Jun 6 09:20:17 2025 +0000 Latest translations and fallbacks commit 44d1a17bc59a15eb74d35055b85b3a1774e16995 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 11:46:25 2025 -0400 fix(deps): update dependency satori to v0.13.2 (#13468) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d3a2dcf074bcc6ad31d11405133a8bfe83006f16 Merge: 6478532bf5 806e54bf5a Author: Barış Soner Uşaklı Date: Thu Jun 5 11:46:05 2025 -0400 Merge branch 'master' into develop commit 806e54bf5a0d6a6cefb2f9e0ba57346e77aef8a6 Author: Barış Soner Uşaklı Date: Thu Jun 5 11:42:29 2025 -0400 fix: closes #13475, don't store escaped username when updating profile commit 6478532bf5e8e02050f31147f9f4665dd0e882d5 Author: Julian Lam Date: Thu Jun 5 11:28:47 2025 -0400 fix: ensure check returns false if no addresses are looked up, fix bug where cached value got changed accidentally commit 32f13162dc65cf1e49378ca9413a9b8a114012f9 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 07:19:56 2025 -0400 chore(deps): update dependency sass-embedded to v1.89.1 (#13463) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1c432925cdd83476e2e196f401a56cac31ef161c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 07:19:30 2025 -0400 fix(deps): update dependency postcss to v8.5.4 (#13453) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d0060e5d7119f53ecb5600a50e61ec8cfe425e83 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 07:19:13 2025 -0400 fix(deps): update dependency multer to v2.0.1 (#13466) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 602417d0f91526e41d9d15625db09c41b45ba4a7 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 07:17:56 2025 -0400 fix(deps): update dependency sass to v1.89.1 (#13467) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c363b84e90bbad19ae33356dfbc119a20a5d857d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 07:17:34 2025 -0400 fix(deps): update dependency ace-builds to v1.42.0 (#13470) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a3cc99a2f07de51d33231dff75ded8504b833f89 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Jun 5 07:17:11 2025 -0400 fix(deps): update dependency mongodb to v6.17.0 (#13471) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit efb14ead1d4d2e2ba7370eea179b044ee192686f Author: Misty Release Bot Date: Thu Jun 5 11:16:26 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.error commit 677d6dd060f27ffadb336b46daef30f9fab159cb Merge: 3694f6555b 4fbcfae8b1 Author: Barış Soner Uşaklı Date: Thu Jun 5 07:16:00 2025 -0400 Merge branch 'master' into develop commit 4fbcfae8b15e4ce5d132c408bca69ebb9cf146ed Author: Barış Uşaklı Date: Thu Jun 5 07:15:45 2025 -0400 Post queue write api (#13473) * move post queue from socket.io to rest api * move harmony post-queue to core add canEdit, allow users to edit their queued posts * fix: openapi spec * lint: whitespace commit 3694f6555bca1ce0f16c75096eaced0260f83e33 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Jun 4 13:54:37 2025 -0400 fix(deps): update dependency cron to v4.3.1 (#13457) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 010113a9a00280663822b17696c56f3f8d1227ec Author: Julian Lam Date: Wed Jun 4 13:19:29 2025 -0400 fix: wrap cached returns for dns lookups in nextTick commit ea91dc00cd7359a0c81ed7af29e990ada4a6b989 Author: Misty Release Bot Date: Wed Jun 4 09:20:16 2025 +0000 Latest translations and fallbacks commit 0ccfe1dfe9a5c11bbfac6156ec67adee781fdead Author: Misty Release Bot Date: Tue Jun 3 09:20:10 2025 +0000 Latest translations and fallbacks commit 6411c19765050f12864bfb04678adb8fbe271be1 Author: Julian Lam Date: Mon Jun 2 11:58:54 2025 -0400 fix: #13459, unread indicators for remote categories commit 6d40a2118c7244ff8e2cb7f4aad8b52341aab0b9 Author: Misty Release Bot Date: Mon Jun 2 15:06:29 2025 +0000 chore: update changelog for v4.4.2 commit 9c7cbbe2e4f5043ed99036ccf39576eed070657b Author: Misty Release Bot Date: Mon Jun 2 15:06:29 2025 +0000 chore: incrementing version number - v4.4.2 commit e1eb76feba9ff1aba8ba2a032651c5de9e79e371 Author: Misty Release Bot Date: Mon Jun 2 15:06:01 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.error commit a8e613e13ac6f3eb8cc048bc085beb31f2594270 Author: Julian Lam Date: Sat May 24 22:12:48 2025 -0400 fix: further guard against DNS rebinding attack commit 70c04f0cb25e15f150084b1d87d3d8af3efb44a1 Author: Julian Lam Date: Fri May 23 13:57:25 2025 -0400 fix: undefined check, allow plugins to append to allow list commit df36021628c47f584d94b88f69dbcd6e3fdba29a Author: Julian Lam Date: Thu May 22 15:36:22 2025 -0400 fix: simplify dns to use .lookup instead of .resolve4 and .resolve6, automatically allow requests to own hostname commit 9d3b8c3abcd60aa5a6d85ff804008e7d8345a95b Author: Julian Lam Date: Thu May 22 14:14:53 2025 -0400 feat: add protection mechanism to request lib so that network requests to reserved IP ranges throw an error commit 524a1e8bfe403fa240e804f076943871264caf2f Author: Julian Lam Date: Sun Jun 1 12:40:37 2025 -0400 fix: return 200 for non-implemented activities instead of 501 commit b1022566da98b5b58f08a1efc76daba345eac232 Author: Barış Soner Uşaklı Date: Mon Jun 2 09:55:20 2025 -0400 fix: closes #13458, check if plugin is system plugin before activate/deactive/install/uninstall commit fcb3bfbc35221f207a83b197766edc06d0f05cdb Author: Julian Lam Date: Sun Jun 1 12:40:37 2025 -0400 fix: return 200 for non-implemented activities instead of 501 commit ff00829b3f9cad8ee726eab84b231e9dcc10c953 Author: Misty Release Bot Date: Sun Jun 1 09:19:27 2025 +0000 Latest translations and fallbacks commit 3d88f70680a32dff1656ee03bf7bb1115ff7eed0 Merge: f34930f5e8 cc92702620 Author: Julian Lam Date: Sun Jun 1 00:32:05 2025 -0400 Merge branch 'master' into develop commit cc9270262074b154fd6d3a5df7d1f354f3b4cb37 Author: Julian Lam Date: Sun Jun 1 00:31:58 2025 -0400 fix: add try..catch around topics.post in note assertion logic commit f34930f5e8bbe9989a88248ef0db7c87db9150fc Merge: 033e6e8f8b 83a55f6adc Author: Julian Lam Date: Sat May 31 22:47:06 2025 -0400 Merge branch 'master' into develop commit 83a55f6adcd246920ba08415dcdf46505503c4a4 Author: Julian Lam Date: Sat May 31 22:46:47 2025 -0400 fix: don't throw on unknown post on Undo(Like) commit 033e6e8f8b31aa3e3a31a061ecdde590b258b40e Merge: 0d595008b0 4d44456ff9 Author: Barış Soner Uşaklı Date: Fri May 30 17:12:56 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 0d595008b0bc08dfe52449fe43015257c0f71fed Author: Barış Soner Uşaklı Date: Fri May 30 17:12:54 2025 -0400 chore: eslint config commit 4d44456ff9efc9e016deb1c3c8893a3c4ebedcfa Merge: 57a5de2682 629eec7b5b Author: Julian Lam Date: Fri May 30 16:49:24 2025 -0400 Merge branch 'master' into develop commit 629eec7b5b17c55984dd690b281224d9139a57d4 Author: Julian Lam Date: Fri May 30 16:49:15 2025 -0400 fix: add try..catch wrapper around Announce(Like) call to internal method so as to not return a 500 — just drop the Like activity commit ebb88c1277945887062c1e669f5e4a93cbffd2ed Author: Barış Soner Uşaklı Date: Fri May 30 11:45:04 2025 -0400 feat: add action:post-queue.save fires after a post is added to the post queue commit 57a5de26827693f86b5804b2e389930dac16c0dc Author: Barış Soner Uşaklı Date: Fri May 30 11:15:02 2025 -0400 refactor: use strings for cids commit 28c021a01bf86abaf8404261a9d161ad99328a54 Author: Barış Soner Uşaklı Date: Fri May 30 11:11:45 2025 -0400 fix: remove null categories commit 8d16367ad4d61e31789496e8ed69ea48032a6ba3 Merge: a80edfa1f1 390f642850 Author: Barış Soner Uşaklı Date: Fri May 30 11:02:56 2025 -0400 Merge branch 'master' into develop commit 390f6428506f98458045fe07b6f594508141cc4c Author: Barış Soner Uşaklı Date: Fri May 30 11:00:08 2025 -0400 fix: browser title translation commit 78de8c6da12919e60660142bff46ebde5ad23b1f Author: Barış Soner Uşaklı Date: Fri May 30 09:22:06 2025 -0400 fix: allow guests to load topic tools if they have privilege to view them display errors from topics.loadTopicTools commit a80edfa1f11823bd6e0047b7b51ebbc0493d83c3 Author: Julian Lam Date: Thu May 29 15:15:06 2025 -0400 fix: patch ap .probe() so that it does not execute on requests for its own resources commit 0c1a61839efc6bfb57b945e223584c5c70c69177 Author: Julian Lam Date: Thu May 29 12:49:56 2025 -0400 test: fix groups:find webfinger test commit a9348e3607da9c86961d0eeb69675fc1d0930683 Merge: 20abeade41 72417d82bd Author: Barış Soner Uşaklı Date: Thu May 29 11:37:02 2025 -0400 Merge branch 'master' into develop commit 72417d82bd05c26dd8e38220fd9dc05792a07fdb Author: Barış Soner Uşaklı Date: Thu May 29 11:36:46 2025 -0400 fix: closes #13454, align dropdowns to opposite side on rtl commit 20abeade4182186daca541b0f59847e0c228eb2f Merge: 36f0cf250f 49b5268e52 Author: Julian Lam Date: Wed May 28 14:53:38 2025 -0400 Merge branch 'master' into develop commit 49b5268e529a403ca929797361f853c3c40f301d Author: Julian Lam Date: Wed May 28 14:53:32 2025 -0400 fix: send actor in undo(follow) commit b20a6ed0d700f0a02cdf5d9e42d6f0fd42b6f4e0 Author: Julian Lam Date: Wed May 28 12:31:53 2025 -0400 fix: missed handling zset on ap unfollow commit 36f0cf250fc2c5e04d13a4709d2131e5232e3878 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 28 09:04:04 2025 -0400 fix(deps): update dependency validator to v13.15.15 (#13451) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6efe3fdd02f9359d77c97eddf86179737b5792d1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 27 17:36:42 2025 -0400 chore(deps): update dependency lint-staged to v16.1.0 (#13449) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6a5bbe9204092434ba446521cbde74ae0f12cef1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 27 09:09:21 2025 -0400 fix(deps): update dependency esbuild to v0.25.5 (#13447) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a888b868c70361b2298c2fa1eb4770085c2c6689 Author: Julian Lam Date: Mon May 26 14:49:48 2025 -0400 fix: additional tests for remote privileges, enforcing privileges for remote edits and deletes commit e16420a4eb23bbe535be8deae8fae1d807269ba3 Merge: aeeda7c3be fd2ae7261e Author: Barış Soner Uşaklı Date: Sun May 25 19:04:33 2025 -0400 Merge branch 'master' into develop commit fd2ae7261e0e8378c10e7b8863c368ce864d5edb Author: Barış Soner Uşaklı Date: Sun May 25 19:04:01 2025 -0400 chore: up eslint stylistic commit aeeda7c3be86c0941b94323df274d6af64ee2041 Author: Misty Release Bot Date: Sun May 25 09:19:33 2025 +0000 Latest translations and fallbacks commit e2de0ec212bda29ae419f81fc92cad122da99e4b Author: Barış Soner Uşaklı Date: Sat May 24 16:50:53 2025 -0400 chore: up dbsearch commit 3ca6a9bcfa89a36069ea506857f1d3e41b38400c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat May 24 11:54:27 2025 -0400 fix(deps): update dependency nodebb-plugin-dbsearch to v6.2.18 (#13445) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 30aa0fe6d25c0b5aac9df422818911e909c178aa Author: Barış Soner Uşaklı Date: Sat May 24 11:49:49 2025 -0400 chore: up dbsearch commit e3a7fb5ccb05b95cc82eaee209e0b35f57a9c146 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat May 24 06:11:41 2025 -0400 fix(deps): update dependency bootbox to v6.0.4 (#13443) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c18464757885aed98ccf481808b84c8c6b59dc39 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri May 23 19:51:45 2025 -0400 chore(deps): update dependency mocha to v11.5.0 (#13442) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 777ecdf2c14b7882f550cb9e4140b6ccda566c00 Author: Misty Release Bot Date: Fri May 23 09:20:20 2025 +0000 Latest translations and fallbacks commit e70e990a1aa932a3f994b2205bacc2f35730aa01 Author: Julian Lam Date: Thu May 22 14:13:41 2025 -0400 feat: restrict access to ap.probe method to registered users, add rate limiting protection commit 76a624b9ca2004f76f101300922ca6dfa17f4fee Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu May 22 11:31:52 2025 -0400 fix(deps): update dependency diff to v8.0.2 (#13440) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f633f57d52f460312e0c7fa2edc8ccc504a63cf7 Merge: f9541f17dc 99234b3f97 Author: Barış Soner Uşaklı Date: Thu May 22 11:16:20 2025 -0400 Merge branch 'master' into develop commit 99234b3f97af00c136d126fd748e8c6cbb1ff989 Author: Barış Soner Uşaklı Date: Thu May 22 11:16:14 2025 -0400 chore: up harmony commit f9541f17dc6d23cf29e4ce88349690b752005e8e Merge: 1d624aadbe a16bc7382c Author: Barış Soner Uşaklı Date: Thu May 22 11:01:12 2025 -0400 Merge branch 'master' into develop commit a16bc7382cee1fe5c278ca05bd1c014203de8ff5 Author: Barış Soner Uşaklı Date: Thu May 22 11:01:05 2025 -0400 chore: up harmony commit 1d624aadbe8aefe54badaf48602f2a09956259fe Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 20 10:57:23 2025 -0400 fix(deps): update dependency commander to v14 (#13434) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 136e88140f689a4133894fdd2771f911e4e212fe Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 20 10:57:09 2025 -0400 chore(deps): update dependency smtp-server to v3.13.7 (#13437) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 314a4ff047cdf1a3e1e2b681f35719ec95c41bcc Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 20 10:56:59 2025 -0400 fix(deps): update dependency webpack to v5.99.9 (#13438) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 385f4f12be4dbd41832bebbe043288ee524a1bee Author: Barış Uşaklı Date: Tue May 20 10:45:56 2025 -0400 replace connect-multiparty with Multer (#13439) * post upload route * more multer changes keep name and type fields in file objects so we dont break all plugins using these * remove log * fix: thumbs delete * test: add array check commit 3c09e6247f0783c6a8bdef833005b34c24ba03be Merge: 2e02d3f673 a686cf2062 Author: Barış Soner Uşaklı Date: Tue May 20 09:26:58 2025 -0400 Merge branch 'master' into develop commit 2e02d3f6730ebfb1a891ebf9a468fa407d8fffe2 Author: Misty Release Bot Date: Tue May 20 09:19:53 2025 +0000 Latest translations and fallbacks commit ee8e223f2032fd3d0aa788615c69bd88e4ffb9a3 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 17:11:51 2025 -0400 fix(deps): update dependency connect-redis to v8.1.0 (#13433) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit aa9772822afb260df4fa9cb22274e61dcebc412e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 17:11:40 2025 -0400 chore(deps): update dependency sass-embedded to v1.89.0 (#13425) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5d017710bda84ca268c9ddb93b44dcdb46de43af Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 17:10:26 2025 -0400 chore(deps): update dependency mocha to v11.4.0 (#13435) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 42f16da501dcfd74c5f7874e4bc73e11b9dedf4c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 13:43:21 2025 -0400 fix(deps): update dependency nodebb-plugin-dbsearch to v6.2.17 (#13432) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 650eeac9087c24117bf0ca98514d0a50a57a8126 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 13:15:23 2025 -0400 chore(deps): update dependency mocha to v11.3.0 (#13426) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 2417a79b5fa8acc07f605db44f53a50078cbd024 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 13:15:06 2025 -0400 fix(deps): update dependency sass to v1.89.0 (#13427) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 475b0704b9f5b950a435a74eb1b3dc0d15d249d1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 19 13:14:48 2025 -0400 chore(deps): update dependency @eslint/js to v9.27.0 (#13429) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0fe1e53cf9f52b4060359d44ed215597c3fa1cdf Author: Misty Release Bot Date: Sun May 18 09:19:19 2025 +0000 Latest translations and fallbacks commit a686cf20624dfebf2077ddfb86054252deef7061 Author: Misty Release Bot Date: Fri May 16 16:37:49 2025 +0000 chore: update changelog for v4.4.1 commit 672dcc5d142e34f36cb9621dca1e05c7d41ee1ea Author: Misty Release Bot Date: Fri May 16 16:37:49 2025 +0000 chore: incrementing version number - v4.4.1 commit 0b9c760092aefe7dac48350e8953e9b677b8b9e1 Merge: f71c10ae42 948bfe46f1 Author: Julian Lam Date: Fri May 16 11:43:32 2025 -0400 Merge branch 'master' into develop commit 948bfe46f1c0fdb5c9d7e56dbcf26f40586ce330 Author: Julian Lam Date: Fri May 16 11:43:26 2025 -0400 test: fix tests to account for a460a55064e1280f36a0021e0510c7c557251030 commit f71c10ae420d34199eb571e16357cc80d4bd00d1 Merge: 4602b6b7c8 ce5ef1ab6e Author: Julian Lam Date: Fri May 16 10:04:51 2025 -0400 Merge branch 'master' into develop commit ce5ef1ab6e98c2c8e91735beab5eb6ee9fec6ca5 Author: Julian Lam Date: Fri May 16 10:04:39 2025 -0400 fix: openapi schema to handle additional `attachments` field in postsobject commit 4602b6b7c8f58993c1b22d6ce174910587d91a31 Author: Misty Release Bot Date: Fri May 16 09:20:24 2025 +0000 Latest translations and fallbacks commit 61a63851d4f26386494756c65c4385e09da61815 Author: Barış Soner Uşaklı Date: Thu May 15 18:25:10 2025 -0400 chore: up themes commit 0a574d72404f885e46e2a2783a24f129f5bfd3cc Author: Barış Soner Uşaklı Date: Thu May 15 18:23:38 2025 -0400 fix: group edit url commit a463495fb0f96245dc3e66b46222cb83e9f4a586 Merge: 8f933459cd 8f9f377121 Author: Julian Lam Date: Thu May 15 16:57:17 2025 -0400 Merge branch 'master' into develop commit 8f9f377121d38a1bb4aff121d35236b12bf75ebc Author: Julian Lam Date: Thu May 15 16:57:05 2025 -0400 fix: add attachments to getpostsummaries call in search, #13324 commit a460a55064e1280f36a0021e0510c7c557251030 Author: Julian Lam Date: Thu May 15 15:38:57 2025 -0400 fix: bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in `attributedTo` commit 8f933459cded56711406f5008cd812294188e38c Author: Julian Lam Date: Thu May 15 15:38:57 2025 -0400 fix: bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in `attributedTo` commit c55f12214f721c49798c1dc81beeeea5f181b29b Merge: ab6ed11155 3674fa5783 Author: Julian Lam Date: Thu May 15 14:00:20 2025 -0400 Merge branch 'master' into develop commit 3674fa578346483dac1f4e5922bf7d545a689785 Author: Julian Lam Date: Thu May 15 13:56:31 2025 -0400 feat: save width and height values into post attachment commit 45a11d45fc2cd09943934b52bd8bef155306fa65 Author: Julian Lam Date: Thu May 15 12:01:45 2025 -0400 fix: #13419, handle remote content with mediaType text/markdown commit 6c3e2a8e2291dc15163dfd4ec2b2034df5d9d9e1 Author: Barış Soner Uşaklı Date: Thu May 15 09:42:55 2025 -0400 refactor: create date once per digest.send commit 3faae559a8c39c398a6f9df50cebdd5479e78665 Merge: 3d96afb2d1 09cc91d5a0 Author: Barış Soner Uşaklı Date: Thu May 15 09:38:49 2025 -0400 Merge branch 'master' of https://github.com/NodeBB/NodeBB commit 3d96afb2d1bbb16c397e537133fd3fea21f9ffaa Author: Barış Soner Uşaklı Date: Thu May 15 09:38:43 2025 -0400 feat: use local date string for digest subject closes #13420 commit ab6ed111554c617bd0a556794b7b0c64aaab0af8 Author: Misty Release Bot Date: Thu May 15 09:19:48 2025 +0000 Latest translations and fallbacks commit 09cc91d5a06b34d70187ac23857254e8705f4c9c Author: Misty Release Bot Date: Wed May 14 20:36:36 2025 +0000 chore: update changelog for v4.4.0 commit b31d769d9c0171cd87281d3deec1198503de8998 Author: Misty Release Bot Date: Wed May 14 20:36:35 2025 +0000 chore: incrementing version number - v4.4.0 commit 799b08db3a6f74bf32b4fc45fbb4e94441e3892d Author: Julian Lam Date: Wed May 14 15:22:58 2025 -0400 fix: adjust Peertube-specific handling to shove mp4 into post attachments, #13324 commit 919d62ab4e3eb70808264e36f0afd42c62d8dd98 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 14:23:24 2025 -0400 fix(deps): update dependency diff to v8 (#13409) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3e18af1e2576ce4cbaef842674f80e15c60a9a4c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 14:22:45 2025 -0400 fix(deps): update dependency sanitize-html to v2.17.0 (#13418) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d5865613e3ca132227150be1be81e9545259ecf3 Author: Julian Lam Date: Wed May 14 14:14:06 2025 -0400 fix: #13081, don't add mention when you are replying to yourself commit f176d6b2c5cc76ee8ee749c6ed6b3b1f1430b89e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 13:45:21 2025 -0400 fix(deps): update dependency satori to v0.13.1 (#13408) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7320a858968af80f508aeaeccf731d199f59112f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 13:45:10 2025 -0400 fix(deps): update dependency pg-cursor to v2.15.0 (#13414) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 84b8ecc7a0bcf20ceae6c5d30865843e952b3534 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 13:45:01 2025 -0400 fix(deps): update dependency nodebb-plugin-markdown to v13.2.1 (#13416) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 366651d6e1651b0c5265ec21b1395463d3962f2f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 12:40:46 2025 -0400 fix(deps): update dependency semver to v7.7.2 (#13410) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0825c569aa72d1e0a3ea526dfe2b3fad6a9404dd Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 12:40:24 2025 -0400 fix(deps): update dependency pg to v8.16.0 (#13411) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit fbe97b4e914a31cdd1b66f90651ba77305e6a56f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 12:38:22 2025 -0400 chore(deps): update redis docker tag to v8.0.1 (#13415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 383a7ce507b7650804520a98e9543ec5b2689b4f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 14 12:38:00 2025 -0400 fix(deps): update dependency nodebb-plugin-mentions to v4.7.6 (#13417) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0f576a42195b42a225eba94213299244e0fc1dfa Author: Julian Lam Date: Wed May 14 12:16:06 2025 -0400 fix: add `announces` to postdataobject schema commit 61f6806b6a6939d56733e9c5ee9a3a1900e9aaa8 Author: Julian Lam Date: Wed May 14 11:49:12 2025 -0400 test: a few additional tests for announce handling commit 5b118904c9544b8cfb53f38b9df0ed6aab888eec Author: Julian Lam Date: Wed May 14 11:05:10 2025 -0400 test: fix regression from 5802c7ddd9506a4e296f6dbdf2d9a32621c7f4ef commit 9dc91f11a4ab57b8312d4c973894e0a4067c2249 Author: Julian Lam Date: Wed May 14 11:00:53 2025 -0400 test: fix broken test due to adjusted note assertion relation logic commit 7dc690a14ab1471ce6832e9ea84534cce903a3f7 Author: Misty Release Bot Date: Wed May 14 09:19:59 2025 +0000 Latest translations and fallbacks commit fe13c75549ff2c9263635a8f1444eac1eedc5287 Author: Julian Lam Date: Tue May 13 13:59:34 2025 -0400 fix: #13375, plus additional tests commit 30db15836be674643fbc1915a1d634028e6fcef0 Merge: a9c02acae2 0aa9c187f7 Author: Barış Soner Uşaklı Date: Mon May 12 21:23:19 2025 -0400 Merge branch 'master' into develop commit a9c02acae27358e76f3b8bea85e2c2489d784faf Merge: 1b0b1da6b9 5802c7ddd9 Author: Barış Soner Uşaklı Date: Mon May 12 17:48:47 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit 1b0b1da6b98c5183bdfd645aba24ab5eafda3040 Author: Barış Soner Uşaklı Date: Mon May 12 17:48:46 2025 -0400 refactor: use a single until commit 5802c7ddd9506a4e296f6dbdf2d9a32621c7f4ef Author: Julian Lam Date: Mon May 12 14:59:57 2025 -0400 fix: missing awaits, more comprehensive 1b12 tests commit 0aa9c187f71606ac0e395b51755b4c0394103193 Author: Misty Release Bot Date: Mon May 12 14:53:40 2025 +0000 chore: update changelog for v4.3.2 commit f60748906030c1e2f475ab5b8edcf951f99cc7b3 Author: Misty Release Bot Date: Mon May 12 14:53:39 2025 +0000 chore: incrementing version number - v4.3.2 commit f88f99b7a2939f82024fab7b564cdeb8aaf599d8 Merge: 5b6c34bfcf 00668bdc34 Author: Barış Soner Uşaklı Date: Mon May 12 10:29:45 2025 -0400 Merge branch 'master' into develop commit 00668bdc342f22b1ef263ca2d6003b12bbb194af Author: Barış Soner Uşaklı Date: Mon May 12 10:29:32 2025 -0400 refactor: wrap ap routes in try/catch commit dfa213298b7dce55845c18eee657ad4d32145ef5 Author: Barış Soner Uşaklı Date: Mon May 12 10:28:26 2025 -0400 refactor: call verify if request is POST commit 16504bad8100aa25327dd8b8b26483df9e087b69 Author: Barış Soner Uşaklı Date: Mon May 12 10:02:59 2025 -0400 fix: sql injection in sortedSetScan commit 285d438cb3f98a729765a1bde6b12efa535a3940 Author: Barış Soner Uşaklı Date: Mon May 12 09:30:33 2025 -0400 fix: escape flag filters commit 31be083e86060b5903d9f3ba6cba65f6c86a9c1d Author: Barış Soner Uşaklı Date: Mon May 12 09:12:51 2025 -0400 fix: #13407, don't restart user jobs if jobsDisabled=true on that process commit 5b6c34bfcfb5eb78d28719721e921f28447e215d Merge: 23374fd7e9 fcf9e8b796 Author: Barış Soner Uşaklı Date: Mon May 12 09:02:18 2025 -0400 Merge branch 'master' into develop commit fcf9e8b796d81db1c7e50561f25b78e5ebfe3bf4 Author: Barış Soner Uşaklı Date: Mon May 12 09:01:32 2025 -0400 chore: up mentions commit 23374fd7e9b6594b1cbcdaa8a65efa95182bdaf5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:33:49 2025 -0400 fix(deps): update dependency lru-cache to v11 (#12685) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6a4ffe02159d133e260fc99036db65aa63ad4d59 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:33:25 2025 -0400 fix(deps): update dependency rimraf to v6 (#12686) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1df7313c99611733b432e6ba5f53afa2bd159530 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:32:58 2025 -0400 chore(deps): update redis docker tag to v8 (#13387) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit d319b0aaadeee8224ac8788ad0563d72f1360cde Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:32:45 2025 -0400 chore(deps): update postgres docker tag to v17.5 (#13398) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7a7cf830c317d69bf05559d93832136e2dd7217a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:32:37 2025 -0400 fix(deps): update dependency bootswatch to v5.3.6 (#13400) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ecce9998186d453d3562f5062ec12be5469535a4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:32:27 2025 -0400 fix(deps): update dependency csrf-sync to v4.2.1 (#13401) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 694c79bc9a0af5b96ff4f1455b79fcd3e4056905 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:32:18 2025 -0400 chore(deps): update dependency sass-embedded to v1.88.0 (#13402) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7ffba2186c144434048dfdb48026343eae86a463 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:31:59 2025 -0400 fix(deps): update dependency sass to v1.88.0 (#13403) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9d877481bdb6036effd3996cbe20d96d0c727dc6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun May 11 22:31:50 2025 -0400 chore(deps): update dependency lint-staged to v16 (#13404) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8174578c5bfc45057be71b99425a9b93decaec98 Author: Barış Soner Uşaklı Date: Sun May 11 22:31:00 2025 -0400 fix: closes #13405, catch errors in ap.verify commit bbf69e4093a7326a1b135d1769e2983d6f337823 Merge: d7cc637675 20ab90694c Author: Julian Lam Date: Fri May 9 10:34:25 2025 -0400 Merge branch 'master' into develop commit 20ab90694c47ec3dde3afccde22b3ac2f89ca85b Author: Julian Lam Date: Fri May 9 10:34:19 2025 -0400 fix: send proper accept header for outgoing webfinger requests commit d7cc637675e06313c8a11949b08ea59273517993 Merge: af3afba0f8 64fdf91b6b Author: Julian Lam Date: Fri May 9 10:17:50 2025 -0400 Merge branch 'master' into develop commit 64fdf91b6b02c291edd29f2a7de0dbe85591fd79 Author: Julian Lam Date: Fri May 9 10:16:33 2025 -0400 fix: wrap generateCollection calls in try..catch to send 404 if thrown commit af3afba0f8fb495a529e6a61718b855ced50434c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu May 8 20:21:13 2025 -0400 fix(deps): update dependency nodemailer to v7.0.3 (#13395) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6bfe4e627d8bd396131442b59ee8c1fade8162b2 Author: Julian Lam Date: Thu May 8 16:34:13 2025 -0400 fix: another case commit e042201f4bc662cc294ba19f63e218aa0860d82c Author: Julian Lam Date: Thu May 8 16:32:21 2025 -0400 fix: handle missing orderedItems property in followers route commit d5349b39f5c8f857fccbe669e4f6c7d7ce70ac0f Merge: 7a7a4f0ab7 26e6a22278 Author: Julian Lam Date: Thu May 8 14:12:33 2025 -0400 Merge branch 'master' into develop commit 26e6a22278c426802e8f80e70cefcffc73653a25 Author: Julian Lam Date: Thu May 8 13:55:42 2025 -0400 fix: #13397, null values in category sync list commit 401ff797c91217c4887b86f013f038b7d4e09b13 Author: Julian Lam Date: Thu May 8 13:55:17 2025 -0400 fix: #13392, regression from c6f2c87, unable to unfollow from pending follows commit a9a5ab5e4bfef9acde8d45d04322081f44a3da47 Author: Julian Lam Date: Thu May 8 13:41:43 2025 -0400 fix: #13397, update getCidByHandle to work with remote categories, fix sync with handles causing issues with null entries commit 7a7a4f0ab7b901806b4c2e3e87323701c1f7755d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu May 8 09:40:53 2025 -0400 chore(deps): update commitlint monorepo to v19.8.1 (#13394) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4245575e09013253e72e607dc5b185aefb87cc90 Merge: 0b4d403c61 10077d0f89 Author: Barış Soner Uşaklı Date: Wed May 7 19:06:01 2025 -0400 Merge branch 'master' into develop commit 0b4d403c619fbfd00e8fe8d8f95930cd71c7a720 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 7 18:59:04 2025 -0400 fix(deps): update dependency nodemailer to v7 (#13381) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 4f0f67a45f815a93e0aab2a337d36814ef5b3791 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 7 18:58:52 2025 -0400 fix(deps): update dependency csrf-sync to v4.2.0 (#13364) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 10077d0f89b4e3779f2e9217d5061d7d55f84240 Author: Opliko Date: Thu May 8 00:57:40 2025 +0200 fix: correct stage name in dev dockerfile (#13393) Co-authored-by: ThisIsMissEm commit c7a164aef559676d4f3e4188ef0aceda78f91b0d Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed May 7 13:02:39 2025 -0400 fix(deps): update dependency webpack to v5.99.8 (#13390) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit a819d39c31f5f0e843701c5e59cf87de3aa9f294 Author: Barış Soner Uşaklı Date: Wed May 7 12:42:22 2025 -0400 test: update filter:router.page tests to response:router.page commit 2310a7b835438db75c288282c6651dec7f75c0e7 Author: Misty Release Bot Date: Wed May 7 15:38:18 2025 +0000 chore: update changelog for v4.3.1 commit 130b93eca91f305cc2c7b95ec818ae637f76a4dc Author: Misty Release Bot Date: Wed May 7 15:38:18 2025 +0000 chore: incrementing version number - v4.3.1 commit 9324a1937be24d8eb7ad057ee922e2dff37370eb Author: Barış Uşaklı Date: Wed May 7 11:28:33 2025 -0400 Update README.md commit ebe40f960c52aa6596a5e7bfc6fa9b97e8155d9d Author: Misty Release Bot Date: Wed May 7 09:20:10 2025 +0000 Latest translations and fallbacks commit 96dc5c89a462cc5a289762a6ea618a2192e01eba Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 17:26:01 2025 -0400 chore(deps): update dependency lint-staged to v15.5.2 (#13383) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e6a196127435752cd27127290e02e7ad3bd9c17b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 17:25:50 2025 -0400 fix(deps): update dependency bootstrap to v5.3.6 (#13384) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b6f4de5bffe9f9282e6fb720aed66d3156730fc5 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 17:25:40 2025 -0400 fix(deps): update dependency esbuild to v0.25.4 (#13385) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 52df41b9060123390fe3da8f8ba123acb5dc6f4e Author: Julian Lam Date: Tue May 6 15:13:29 2025 -0400 test: adjustment for now-removed labels property commit 860ac8953e753ef29fcc2b8abcb0130b07cb624f Author: Julian Lam Date: Tue May 6 15:08:21 2025 -0400 docs: remove since-removed `labels` property from api commit 15b6a2c117a9ab13afb0eeef631e12b7651be428 Author: Julian Lam Date: Tue Dec 5 10:06:36 2023 -0500 chore: remove unused require commit 8ea377a4011d9973c2980bdae9657a6fca20927e Author: Julian Lam Date: Mon Dec 4 14:21:05 2023 -0500 breaking: removal of deprecated privilege hooks * filter:privileges.global.list * filter:privileges.global.groups.list * filter:privileges.global.list_human * filter:privileges.global.groups.list_human * filter:privileges.list * filter:privileges.groups.list * filter:privileges.list_human * filter:privileges.groups.list_human * filter:privileges.admin.list * filter:privileges.admin.groups.list * filter:privileges.admin.list_human * filter:privileges.admin.groups.list_human commit 547fb482eb0bc82905956a5440410bcd118795bf Author: Julian Lam Date: Mon Dec 4 14:04:55 2023 -0500 breaking: removal of `filter:flags.getFilters` commit 7e25946cd7e18e17db8f64144fda6fe4d3ae0e28 Author: Julian Lam Date: Mon Dec 4 14:04:18 2023 -0500 breaking: removal of `filter:user.verify.code` commit df5c1a938de3db394928a2f24d729d8f9a8eb561 Author: Julian Lam Date: Mon Dec 4 14:03:19 2023 -0500 breaking: removal of `filter:post.purge` commit c84b72fb37bbb6991aa9ff80e8313a24552fa48d Author: Julian Lam Date: Mon Dec 4 14:02:31 2023 -0500 breaking: removal of `filter:post.purge` commit 9d8061eab9f018dc8478867d637ac711cbb23ba8 Author: Julian Lam Date: Mon Dec 4 14:00:48 2023 -0500 breaking: removal of `filter:router.page` commit b73a8d3e1dd9fe674c164e2bc500fe3a707de539 Author: Julian Lam Date: Mon Dec 4 13:56:25 2023 -0500 breaking: removal of `filter:email.send` commit 651ebaaf6c3e62e7ea02775a51ea5b1de4b6fb81 Author: Julian Lam Date: Tue May 6 13:24:58 2025 -0400 fix: missing await commit 53bb0bbc26ed4993a841f0179f35815f90697cc2 Author: Julian Lam Date: Tue May 6 12:30:43 2025 -0400 fix: handle missing orderedItems commit f83b1fbf68a4d5530b9fb0e1e258e90ce31f7cce Author: Julian Lam Date: Tue May 6 12:27:27 2025 -0400 fix: extra `orderedItems` property in generated paginated OrderedCollection, #13153 commit a2de7aaecfacc284a57f0b9389d978ea85216885 Author: Julian Lam Date: Tue May 6 12:09:33 2025 -0400 fix: #13153, follower and following collections to use generateCollection helper commit 7f59238d3a17c8dd077e9e296b15a431c45ae9ed Author: Julian Lam Date: Tue May 6 12:09:07 2025 -0400 refactor: Helpers.generateCollection so that total count and a bound function can be passed in, #13153 commit 450ce3b85ce86fd1e2411cf033398be90ecd79af Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 11:29:19 2025 -0400 chore(deps): update dependency @eslint/js to v9.26.0 (#13371) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit e2a8cf98f3f6b95596a24b1991fcd58a1334b2d7 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 11:17:35 2025 -0400 fix(deps): update dependency @fontsource/poppins to v5.2.6 (#13376) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b4338489712f44286f52d0a3ea726c83801cde56 Author: Julian Lam Date: Tue May 6 10:44:47 2025 -0400 fix: #13374, updates to posts.edit to handle remote content updates better commit 625ce96f94cc3197ed94186610d418ed32627dd0 Author: Julian Lam Date: Tue May 6 10:03:27 2025 -0400 fix: leftover `handle` var commit 2c0aba02d325af8ec49fe76e70aaf0b20a4dccad Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 09:59:17 2025 -0400 fix(deps): update dependency nodebb-plugin-mentions to v4.7.5 (#13386) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 7f757615e5fc08d686a8580af7df2b32d2c5a22c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 09:37:52 2025 -0400 fix(deps): update dependency nodebb-widget-essentials to v7.0.38 (#13380) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 954aa541ac5783c3c345139cc13076caa2039860 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 09:35:30 2025 -0400 fix(deps): update dependency nodebb-theme-persona to v14.1.11 (#13379) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 2aa0bfc5f660956abf0cffec2016307c3e8ac490 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 09:32:50 2025 -0400 fix(deps): update dependency nodebb-theme-peace to v2.2.42 (#13378) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 72b3a21539da1111b5fc9770f2bdb4f6d103a4c1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue May 6 09:32:32 2025 -0400 fix(deps): update dependency nodebb-theme-harmony to v2.1.12 (#13377) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 92af41582951ceda4a263d0fdcd300b69fc55ce4 Author: Misty Release Bot Date: Tue May 6 09:20:36 2025 +0000 Latest translations and fallbacks commit f8d012c81c7d9fa1141eed8940db828ee9af9861 Author: Julian Lam Date: Mon May 5 17:01:07 2025 -0400 fix: AP inbox update handling for non-note objects commit 9f80d10d09a5a6bc9ae040c3119b6063de39b65c Author: Julian Lam Date: Mon May 5 16:50:44 2025 -0400 fix: 1b12 creates being dropped commit 7cf61ab0809b5e7ffd8ebfb5bc4e8ab8c93736b7 Author: Julian Lam Date: Mon May 5 16:35:12 2025 -0400 fix: update AP api (un)follow ids to be url encoded id instead of handle commit 31af05c75aeec775385c94ca842a3b2b981adc78 Author: Barış Soner Uşaklı Date: Mon May 5 11:09:53 2025 -0400 test: fix android test commit 25979294e1ae59efb4d9a644aa73095c38914520 Author: Barış Soner Uşaklı Date: Mon May 5 11:00:30 2025 -0400 test: fix android test commit 7ef79981ddfdfebee8cea52799688bda0bebd57d Author: Barış Soner Uşaklı Date: Mon May 5 10:57:43 2025 -0400 test: fix a test commit 800426d68b91f0a8aeac9383f9728c9fb1588ea9 Author: Barış Soner Uşaklı Date: Mon May 5 10:46:04 2025 -0400 chore: node 18 eol commit 2d15555e5c687dc00af1c39b8c30287f18ddadbb Author: Barış Soner Uşaklı Date: Mon May 5 10:43:56 2025 -0400 update tests to node 20/22 commit 4b78710b46f1bbe230d816f79da1c91e390aaba0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon May 5 09:06:41 2025 -0400 fix(deps): update dependency ace-builds to v1.41.0 (#13372) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ee2f91ad988f256312c2a156f2b358fe4a8aaf0c Author: Barış Soner Uşaklı Date: Sat May 3 14:36:20 2025 -0400 chore: up widgets commit 18867fb14a810e261e89f4409e15864c40ae12da Author: Barış Soner Uşaklı Date: Fri May 2 19:49:56 2025 -0400 chore: up themes commit d35aad317d1afeb3e7ecaf1d1ef3c1b5934f2aab Author: Barış Soner Uşaklı Date: Fri May 2 19:48:35 2025 -0400 https://github.com/NodeBB/NodeBB/issues/13367 commit 39953ee16ba34926fc3f03a93deff1402f45c22c Author: Barış Soner Uşaklı Date: Fri May 2 19:48:35 2025 -0400 https://github.com/NodeBB/NodeBB/issues/13367 commit e958010f40658ffd2756dbb30a2a427013230b56 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri May 2 19:41:55 2025 -0400 chore(deps): update dependency mocha to v11.2.2 (#13366) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f3bd8590e9741751dcf1804ed4568859a6b1c87a Author: Julian Lam Date: Fri May 2 14:07:56 2025 -0400 fix(deps): bump markdown commit 227bfabb8bef2df5b8a5ba6cbf48cafee432b35a Author: Misty Release Bot Date: Fri May 2 09:19:56 2025 +0000 Latest translations and fallbacks commit cf62da5c3e5213b559d4c05a68ef2f9823239c9d Merge: 6ada76f401 343f13e1c1 Author: Barış Soner Uşaklı Date: Thu May 1 13:06:16 2025 -0400 Merge branch 'master' into develop commit 343f13e1c133b4fa536cda035c249f30f01f8067 Author: Barış Soner Uşaklı Date: Thu May 1 10:55:00 2025 -0400 chore: update bundled plugins to use eslint9 commit 6ada76f401e7111bfdf19d53a5a7e42a3ff365fa Author: Misty Release Bot Date: Thu May 1 09:19:38 2025 +0000 Latest translations and fallbacks commit 76c03019f80ddfae181c2c00cf851b801698bdd5 Author: Misty Release Bot Date: Thu May 1 04:13:40 2025 +0000 chore: update changelog for v4.3.0 commit 7b43b1b80e7bc96b21860349cf17603a226b20f2 Author: Misty Release Bot Date: Thu May 1 04:13:39 2025 +0000 chore: incrementing version number - v4.3.0 commit 1c07eab66be6d20879cc6cc35ff7a66bb7a3ecc8 Merge: b32b7fccff ed92ffaf08 Author: Julian Lam Date: Thu May 1 00:06:47 2025 -0400 Merge remote-tracking branch 'origin/master' into develop commit b32b7fccffd63fa12f12d5e1bfcb97d7a3c14927 Author: Julian Lam Date: Wed Apr 30 15:18:05 2025 -0400 docs: update openapi spec with new (missing) properties commit 2bf2e55664952b7c835aad86720e35f804168fa7 Author: Julian Lam Date: Wed Apr 30 15:11:48 2025 -0400 fix: bump harmony commit 2524d4ce127db34336611888f36ebd2cab0bb266 Author: Misty Release Bot Date: Wed Apr 30 09:20:22 2025 +0000 Latest translations and fallbacks commit 28b7a203396865e076ab5ad992248eba600ff43d Author: Julian Lam Date: Tue Apr 29 15:13:21 2025 -0400 lint: yup. commit 2827498d76901206f445a6b1b46226b6ccb91fd1 Author: Misty Release Bot Date: Tue Apr 29 19:06:06 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.category, nodebb.world commit 5aee2f2661b7f6ae6feeb057fe3e28c15a8e6e99 Author: Julian Lam Date: Tue Apr 29 11:52:59 2025 -0400 feat: upgrade script to remote duplicate remote users and categories as per #13352 commit 2572cbf5d5395079a7a78ac056fbb0c6ef6d0d30 Author: Julian Lam Date: Tue Apr 29 11:18:24 2025 -0400 fix: #13352, also do the webfinger backreference check when calling assertGroup commit e9b3306e7924fafe0fdb020255ddb22966ebade8 Author: Julian Lam Date: Mon Apr 28 14:55:21 2025 -0400 fix: regression that caused non-public content with source.content to fail parsing commit decc9cf19604bc1c228ea479f9977129ee945d64 Author: Julian Lam Date: Mon Apr 28 13:48:26 2025 -0400 feat: add new mixin clamp-fade, and handler for expanding it in category description closes #13322 commit 537a74289868354048644a3281ed2f4a376c499b Author: Barış Soner Uşaklı Date: Fri Apr 25 16:29:40 2025 -0400 fix: closes #13360, catch error in buildAccountData middleware commit e3e78445bafb7fb6a82bff8cae689a765ccc598c Author: Julian Lam Date: Wed Apr 23 13:34:08 2025 -0400 chore: v4.3.0-beta.2 commit 044736696e6ce5154900463942d3dfaf2da3f8c1 Author: Julian Lam Date: Wed Apr 23 13:17:10 2025 -0400 feat: handle Announce(Update(Note)) as well, #13320 commit 74e32a170f0f10f93f2d7226b3099f3bc27109ae Author: Julian Lam Date: Wed Apr 23 12:47:16 2025 -0400 feat: #13255, proper handling of upvotes shared by group actors fixes #13320 commit 5c5fd3d44fac48474f0fe0dc26e200f9ff871f7d Author: Julian Lam Date: Tue Apr 22 15:20:52 2025 -0400 feat: send the whole post content in `summary` as well commit c2a3ef817d441485cc4b717c9924b9254988048c Author: Julian Lam Date: Tue Apr 22 15:12:56 2025 -0400 fix: bug where disparate ids all claiming to be the same handle were causing duplicate remote users due to collisions, #13352 commit 3e508d6c65fc8ef895d44e65f40b55373a3ad20d Author: Julian Lam Date: Thu Apr 17 16:41:00 2025 -0400 test: article for new topic, note for replies commit a0a8c4624f530d831e2e209e60deb67a83ef2707 Author: Julian Lam Date: Wed Apr 16 11:21:26 2025 -0400 fix: posts incorrectly excluded from results if result pid is in a remote category commit d020e33422c2eb6ac110e45bbf97090b14267b0d Author: Julian Lam Date: Wed Apr 16 10:52:07 2025 -0400 fix: ap helpers.makeSet to handle undefined property values commit 512f889ec2f7eb4b635d97cb23ead7d6743626b0 Author: Julian Lam Date: Tue Apr 15 10:12:53 2025 -0400 test: missing clear ap send cache commit 0689da8120fbb00c77ab347043b2b541a89343e0 Author: Julian Lam Date: Wed Apr 9 14:25:15 2025 -0400 chore: v4.3.0-beta.1 commit 804208b7b5a9ca4e2fc22ce01bbd8dfccc6ea472 Author: Julian Lam Date: Wed Apr 9 11:50:24 2025 -0400 feat: show/hide categories on world page, #13255 commit d58d5861d93a803a8437d6a33b63d6a7d2fafe5e Author: Julian Lam Date: Wed Apr 9 10:55:32 2025 -0400 fix: add back localCategories to categorySearch when defaultCategories is supplied commit 93a5b35f33070fa76a625431d7c6758a3e1892f0 Author: Julian Lam Date: Tue Apr 8 14:16:49 2025 -0400 feat: notice on remote categories that have no local followers, #13255 commit f02d96614a7d8bd2d22e0390ffa9287b3cf37309 Author: Julian Lam Date: Tue Apr 8 14:00:24 2025 -0400 fix: remote bare hash for remote users on prune as well commit a487d5f6f2c56be0e5c2e1069aa91aff71e2513d Author: Julian Lam Date: Tue Apr 8 13:31:23 2025 -0400 feat: add new option to categorySearch module, `defaultCategories`, use to populate the category list when you don't want to poll backend for the main category list commit 695312f17f7296b56169c9f91811a643424a8f94 Author: Julian Lam Date: Tue Apr 8 11:43:11 2025 -0400 fix: missing teasers for remote categories on /world commit 0fab4255cceca5649b4e4464191704f84c588ca3 Author: Julian Lam Date: Tue Apr 8 10:57:46 2025 -0400 fix: remove superfluous privilege filter in markAllRead commit 2e3e675be4ad4333a28fcdb76aea26d13a4b5930 Author: Julian Lam Date: Mon Apr 7 15:44:40 2025 -0400 chore: cut 4.3.0-alpha.3 commit 17909516594ab0e329378e3642820a74290d7c57 Author: Julian Lam Date: Mon Apr 7 15:14:39 2025 -0400 feat: category quick search on world page, theme version updates, #13255 commit dabcefafd402b565ff0c1850c0a8de7b67f6f24f Author: Julian Lam Date: Thu Apr 3 11:52:01 2025 -0400 fix: reversed image and icon for remote categories, omit fa icon if remote category has icon property set, #13255 commit 34ab677174e98685a74ea91b0d1f0c6c8f5d83a9 Author: Julian Lam Date: Wed Apr 2 15:14:04 2025 -0400 feat: show tracked/watched remote categories in world page, #13255 commit f1d1d0820ac651dad1c9a863286b112a03fedb4f Author: Barış Soner Uşaklı Date: Wed Apr 2 09:22:30 2025 -0400 fix: closes #13289, id can be null commit 4a7111d042968cb008be6b438020e50d1ed712ca Author: Julian Lam Date: Tue Apr 1 14:40:58 2025 -0400 fix: marking remote category topics as read commit b0236735f2972e0699a4a0f53a801b7d25f676b2 Author: Julian Lam Date: Tue Apr 1 14:13:51 2025 -0400 fix: markAllRead to get tids based on same logic as unread page, instead of marking all recent posts read commit 39fc9bae892366b813d088b1c068531dc5784d56 Author: Julian Lam Date: Tue Apr 1 14:02:16 2025 -0400 test: additional test for ensuring handle:uid is continually set even after re-assertion commit c4690392cd3d28fd5453b4d5b5514eb8a8956d9c Author: Julian Lam Date: Mon Mar 31 16:09:03 2025 -0400 chore: cut v4.3.0-alpha.2 commit 6dee3e56e687f2681cb1a40d9901a8de235a52bf Author: Julian Lam Date: Mon Mar 31 15:17:25 2025 -0400 fix: key ownership cross-check to also work with remote categories, #13255 commit 4379df68f5f837e2150852cb6bfc6ed1ca257cf3 Author: Julian Lam Date: Fri Mar 28 12:27:34 2025 -0400 chore: cut v4.3.0-alpha commit 1f04678210e397227940191f68f0476011af0058 Author: Julian Lam Date: Wed Mar 26 14:44:22 2025 -0400 fix: #13255, assert all recipients of the main post when asserting a note, so that remote categories can be discovered commit 4d1d7c3dcaa0c6709f4f9be0fc380b3ecd3d6474 Author: Julian Lam Date: Wed Mar 26 14:08:26 2025 -0400 fix: remote categories should not show up in a user's follow lists commit 3213da1c77c8a193c9706d08159303f50bda9eff Author: Julian Lam Date: Wed Mar 26 12:28:10 2025 -0400 fix: #13255, remote user-to-category migration should not move shares that are already in an existing cid commit f2e0ba2165fad8ca459739b7eb144fb4e8dd99bc Author: Julian Lam Date: Wed Mar 26 12:00:55 2025 -0400 fix: proper handling of actors.qualify response commit 2cb6d10d9eaf03eeff0ea63e045bc23de090fe85 Author: Julian Lam Date: Tue Mar 25 10:44:39 2025 -0400 fix: missing dep commit c2f77cee04de061f00747c6f5cadfdd6d88424de Author: Julian Lam Date: Tue Mar 25 10:44:39 2025 -0400 test: additional test for remote category topic assertion when ignoring category commit c4274a3dca1688d57d435564997840b9a52fea84 Author: Julian Lam Date: Tue Mar 25 10:44:08 2025 -0400 fix: topics in remote categories showing up in /recent commit 0246c14643ee74b878f98d3d75007d1670652dfa Author: Julian Lam Date: Tue Mar 25 10:20:50 2025 -0400 fix: regression that caused resolveInboxes to always return empty, added tests for resolveInboxes commit 97a232e9d5d1b48c0edba7a4a9510bc3fc957d7f Author: Barış Soner Uşaklı Date: Mon Mar 24 16:02:28 2025 -0400 dont make db call if ap disabled commit 74661381d858c782032962c9909c10b916e73ea6 Author: Barış Soner Uşaklı Date: Mon Mar 24 16:01:08 2025 -0400 refactor: use promise.all commit c1b719642941bccbf0db01aeceff9faa3a55d39b Author: Barış Soner Uşaklı Date: Mon Mar 24 15:48:01 2025 -0400 fix: spread fail, @julianlam add ap check commit ac7b7f81b3d267db677760d47ae635ccd6bb87be Author: Julian Lam Date: Mon Mar 24 15:15:48 2025 -0400 feat: remote user to category migration should also migrate local user follows into category watches commit 309deb0d7a5758a69156392aea4ffa44ac6967b3 Author: Julian Lam Date: Mon Mar 24 14:29:26 2025 -0400 fix: filter out non-asserted targets when sending ap messages, diff. getter method when passed-in ID is a remote category commit c5901e0d244650f37de6075265c0a3865d19e79b Author: Julian Lam Date: Mon Mar 24 14:15:37 2025 -0400 fix: tag whitelist check socket call for remote categories commit 23b3148c841ffcd4e27178c2ac2aca4fe88cace4 Author: Julian Lam Date: Mon Mar 24 14:05:40 2025 -0400 feat: allowing manual group assertion via category search input commit ee34396c71be518ae72d7bfb3423d2aa07040909 Author: Julian Lam Date: Mon Mar 24 13:55:14 2025 -0400 fix: migrate topics as system user instead of uid 0 commit 6e374200e59d5a37ec1d796fc3fb2b213fb53bfc Author: Julian Lam Date: Mon Mar 24 12:00:08 2025 -0400 send ap follow/undo-follow if remote category watch state changes commit d19f692b8df556ba4d629962ce4587369f819d4c Author: Julian Lam Date: Mon Mar 24 11:53:39 2025 -0400 feat: remote group actors migrated to categories if they were previous asserted as remote users commit c6f2c874787fc10e86113d20f73ca7d3f8a4cbbe Author: Julian Lam Date: Mon Mar 24 11:52:09 2025 -0400 fix: do not send out ap (undo:)follow if local user or category is (not)already following commit 85e7c1a20d152757aa460caf6d560b07b8ac5963 Author: Julian Lam Date: Fri Mar 21 14:22:22 2025 -0400 test: #13255, reply to topic in remote category addresses remote category commit b8c531d53d9123354f741da2e974acb6fd2fe855 Author: Julian Lam Date: Fri Mar 21 14:16:33 2025 -0400 feat: #13255 new topics in remote category addresses remote category, tests, fixes to tests commit 0b333fb7d43128d2823e3fcaa939bcfdd08430dd Author: Julian Lam Date: Thu Mar 20 14:48:09 2025 -0400 fix: allow category controller to respond also by remote category id commit 9c1d5cd36e062f7b6eb60ee719a46e50f4de9026 Author: Julian Lam Date: Thu Mar 20 13:02:30 2025 -0400 feat: #13255, deliver asserted topics to remote category followers commit 6e23de46d6770f13df09e208b1ee2d54760f5477 Author: Julian Lam Date: Wed Mar 19 23:16:48 2025 -0400 fix: #13255, update category search logic to allow for remote categories commit 876d1b0414f45465a9e7f58696db43b27017be82 Author: Julian Lam Date: Wed Mar 19 23:04:43 2025 -0400 feat: #13255, add category name and handle to category search zset commit bfc7daf255379914fe6bf44b52d55fe3b79e0924 Author: Julian Lam Date: Wed Mar 19 22:18:47 2025 -0400 refactor: categories.sortTidsBySet to not take cid, retrieve from tids themselves re: ##13255, this fixes the issue with topics outside of cid -1 in /world being sorted incorrectly commit 53dc79a1bd837338ec60f06e6369ce6cf735c011 Author: Julian Lam Date: Wed Mar 19 11:02:48 2025 -0400 test: remote user pruning tests commit 9b5855f79ddabdb4fb0cee91103db2374e14e779 Author: Julian Lam Date: Wed Mar 19 10:53:37 2025 -0400 feat: integrate remote category pruning into actor pruning logic commit 4be0f73ace64bd320df73b209c3d60c262d85b12 Author: Julian Lam Date: Tue Mar 18 14:50:04 2025 -0400 feat: migration of group-as-user to group-as-category, remote category purging, more tests commit 7ccd6b73aebdbb26426d17e8ba6d793e06f67879 Author: Julian Lam Date: Tue Mar 18 11:15:31 2025 -0400 fix: delete shares zset on account deletion commit 4f7481582c774816a995f70df1dc3c7f9afa8d5a Author: Julian Lam Date: Tue Mar 18 11:06:10 2025 -0400 test: introduce overrides into person and group mocks commit 80069a198c7cf96cbe48afe6df4d4e508a84c11d Author: Julian Lam Date: Tue Mar 18 10:18:38 2025 -0400 test: have ap helper mocks for person and group auto-save to ap cache commit afc476435810f4f809723c80f183e3be2d799ed5 Author: Julian Lam Date: Tue Mar 18 10:16:40 2025 -0400 test: add failing tests for actor/group assertion via wrong method, remote user to category migration commit f483e883a7d0f9f706c8897425bda3f132a516b0 Author: Julian Lam Date: Mon Mar 17 14:52:52 2025 -0400 feat: asserted topics and posts to remote categories will notify and add to unread based on remote category watch state commit 804052f272f709c718c2f1e6b402e666b46af6bd Author: Julian Lam Date: Mon Mar 17 12:02:43 2025 -0400 test: add tests for topics slotting into remote categories if addressed commit ca9a5b6dfba05f34ca1a3cfb948b31a2d1d4b71c Author: Julian Lam Date: Mon Mar 17 11:44:32 2025 -0400 test: group actor assertion tests commit 0fa98237af0a955b7d03ac264c5dc6c288022364 Author: Julian Lam Date: Fri Mar 14 15:26:59 2025 -0400 refactor: allow topics to be asserted directly into a remote category, or -1 otherwise commit f73f727d90e6901627a9571de8dc68b5a51c8c2e Author: Julian Lam Date: Thu Mar 13 16:06:06 2025 -0400 feat: also include category in `to` field when mocking post for federation commit 1f40995f79f36eec087ed7c0a4921433e60ea8c2 Author: Julian Lam Date: Thu Mar 13 15:50:44 2025 -0400 refactor: ability to browse to remote categories, group actor assertion logic, etc. -- no logic to assign topics to remote categories yet commit 55c89969edc84b6db010f8e6b34d8459f27e8281 Author: Julian Lam Date: Wed Mar 12 11:52:07 2025 -0400 revert: use of vanity domains, needs rethinking. Originally added in 709a02d97ae7acbab08c7fa1fecfd01e0dcadcc7 commit 85fc16780098d5afe155260efbd7614e56abba9e Author: Misty Release Bot Date: Tue Apr 29 09:20:03 2025 +0000 Latest translations and fallbacks commit 4111512841b12c55b35205ec6d4afec61fed4595 Author: Misty Release Bot Date: Sun Apr 27 09:19:32 2025 +0000 Latest translations and fallbacks commit ea9f7903eff54382b2dba1ac36c9d7717095b78f Author: Barış Soner Uşaklı Date: Sat Apr 26 20:44:35 2025 -0400 fix: persona tooltip so it doesn't appear when dropdowns are open commit 7f533167eecd3a6006206373e134ff2fa8e26a3f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Apr 26 14:37:21 2025 -0400 fix(deps): update dependency pg to v8.15.6 (#13362) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f7aaabaadb77991b1b5bd95826beb0fda4f37dc3 Author: Misty Release Bot Date: Sat Apr 26 09:19:33 2025 +0000 Latest translations and fallbacks commit 03e06784d461d0b1e498c6d4d4d1f43d660c18c6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 25 16:18:47 2025 -0400 fix(deps): update dependency pg-cursor to v2.14.6 (#13363) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit c889d60c5d33e143162d3c247e55f54d77800894 Author: Misty Release Bot Date: Fri Apr 25 16:18:30 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.error commit 4277765b64a21bd3cdb977cb596e624cc7a87e6b Author: Barış Soner Uşaklı Date: Fri Apr 25 12:17:26 2025 -0400 fix: lang keys commit d3409b40b1d7e5e0e64837b2c2a14d756ba16d48 Author: Misty Release Bot Date: Fri Apr 25 15:54:52 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.admin-settings-user, nodebb.user commit a5afad27e52fd336163063ba40dcadc80233ae10 Author: Barış Soner Uşaklı Date: Fri Apr 25 11:54:11 2025 -0400 feat: chat allow/deny list, closes #13359 commit 7800016f2f1b89d2d3cfea6a7da7c77096b7b927 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 25 09:04:15 2025 -0400 chore(deps): update redis docker tag to v7.4.3 (#13358) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 0a3e4d61d84232f8a4ed80feed4bdf8b79ddb04b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 25 09:04:06 2025 -0400 fix(deps): update dependency webpack to v5.99.7 (#13361) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit be7959e5ebb8b2bdca66b1996bd365df956eddfc Author: Barış Soner Uşaklı Date: Thu Apr 24 11:38:48 2025 -0400 refactor: remove datepicker using datetime-local now https://github.com/NodeBB/NodeBB/blob/master/src/views/admin/partials/widget-settings.tpl#L14-L20 commit 74558b0fc76eae0b753335b5b5dad145c8d5c023 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Apr 24 10:20:12 2025 -0400 fix(deps): update dependency pg to v8.15.5 (#13356) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8ffbc35923cc8f9a785a8edb80d52991a885ec22 Author: Barış Soner Uşaklı Date: Thu Apr 24 09:50:52 2025 -0400 refactor: add sping/ping into openapi change getObject to getSortedSetRange so db is always checked. getObject calls are cached commit 7eb2f12751cda32ad3aab2cfbb761478b65d96cb Author: Misty Release Bot Date: Thu Apr 24 09:19:59 2025 +0000 Latest translations and fallbacks commit 4eec053a7715056b9fbbf1c1e5128e460e20ccf4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Apr 23 20:01:22 2025 -0400 fix(deps): update dependency ace-builds to v1.40.1 (#13354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 43d7d47fa62e4d43cf55af6bcbef697956b530eb Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Apr 23 20:01:12 2025 -0400 fix(deps): update dependency esbuild to v0.25.3 (#13355) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 00be573d4f8aec89950e4f0d97bd85599074173f Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Apr 23 20:00:56 2025 -0400 fix(deps): update dependency pg-cursor to v2.14.5 (#13350) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit b49436de0a3915ab9cc5342a1c675b3ffae927eb Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Wed Apr 23 14:21:47 2025 -0400 fix(deps): update dependency pg to v8.15.2 (#13349) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit ed92ffaf0833a84228a9a1b21d8554b8993dd1c5 Author: Barış Soner Uşaklı Date: Wed Apr 23 06:50:40 2025 -0400 fix: closes #13353, don't use index for finding plugin data commit e0235a1e94fed02e4f7d171220ac50dd0416be12 Author: Barış Soner Uşaklı Date: Tue Apr 22 17:22:01 2025 -0400 test: shorter test commit f864a5a4aced228d93e28514d52023b6abab051d Author: Barış Soner Uşaklı Date: Tue Apr 22 17:15:48 2025 -0400 test: eslint for tests commit b57ce29dc633d687a21b0e884f1fd6c4c10232cf Author: Barış Uşaklı Date: Tue Apr 22 13:32:41 2025 -0400 chore: up pg, pg-cursor (#13351) * chore: up pg, pg-cursor * test: check file directly commit 1a3e669b6fe97e54db6ade709ccc74e43f2d7513 Merge: 71cd46e132 ce196589f5 Author: Barış Soner Uşaklı Date: Tue Apr 22 12:38:25 2025 -0400 Merge branch 'master' into develop commit ce196589f5568c76e6d7b59f8ac6eb7d31c4afd0 Author: Misty Release Bot Date: Tue Apr 22 16:33:51 2025 +0000 chore: update changelog for v4.2.2 commit a8bb46996bdb1334149e660b12d02fd06f8f87f9 Author: Misty Release Bot Date: Tue Apr 22 16:33:51 2025 +0000 chore: incrementing version number - v4.2.2 commit 71cd46e132722c04afeb8f437c028c18674027a1 Merge: e2543abb59 42a5a127b6 Author: Barış Soner Uşaklı Date: Tue Apr 22 11:46:43 2025 -0400 Merge branch 'master' into develop commit 42a5a127b6a55483c800a7f459b12fc62c63c6a5 Author: Barış Soner Uşaklı Date: Tue Apr 22 11:46:03 2025 -0400 fix: escape displayname in topic events commit e2543abb5955bfcf88eb4c93b0dfc6ae2144041e Merge: 65b2042ffb bee79784cf Author: Barış Soner Uşaklı Date: Tue Apr 22 11:24:58 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit bee79784cfd098e01cbbed9c3b7072c860afb5db Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Apr 22 09:52:10 2025 -0400 chore(deps): update dependency sass-embedded to v1.87.0 (#13347) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 50a58bbc03c91e45b9eec4c7676e4007fcccd3a9 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Apr 22 09:51:59 2025 -0400 fix(deps): update dependency sass to v1.87.0 (#13348) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 92f7f2305f6200b71fc603ebed85e051b4290ebd Author: Misty Release Bot Date: Tue Apr 22 09:19:47 2025 +0000 Latest translations and fallbacks commit 25e4e844d9ec3e40f733e0e65be2e861f90c0dcf Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Apr 21 18:54:15 2025 -0400 chore(deps): update dependency @eslint/js to v9.25.1 (#13344) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 8d84206f5bc0f4ffc99124fdb6d013fdbda7a9b1 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Apr 21 18:54:05 2025 -0400 fix(deps): update dependency connect-redis to v8.0.3 (#13345) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 3c24810d73455f5486912496d5d55f77304c8771 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon Apr 21 18:53:57 2025 -0400 fix(deps): update dependency mongodb to v6.16.0 (#13346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 65b2042ffb796807948656c1706370a26f819aa9 Merge: e184c9108a d687fe60a1 Author: Barış Soner Uşaklı Date: Mon Apr 21 09:05:39 2025 -0400 Merge branch 'develop' of https://github.com/NodeBB/NodeBB into develop commit e184c9108a9f0c2ef530b0c20b9cc370489be727 Author: Barış Soner Uşaklı Date: Mon Apr 21 09:05:35 2025 -0400 refactor: moved these rules to nodebb-config commit d687fe60a1e78e271611983f9e6324b6eeeb8ec7 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sun Apr 20 20:49:23 2025 -0400 chore(deps): update dependency eslint-config-nodebb to v1.1.3 (#13343) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 71642f5cedef53d06b16c61e10ce8fbb82c1791c Author: Misty Release Bot Date: Sun Apr 20 09:19:23 2025 +0000 Latest translations and fallbacks commit 0cc492c6df42d64b624eebeaa69fc99577436adf Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Apr 19 20:30:57 2025 -0400 chore(deps): update dependency @eslint/js to v9.25.0 (#13342) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 853244a13a8fe18c0170bef8144b5bf39d8e4be5 Author: Barış Soner Uşaklı Date: Fri Apr 18 22:17:30 2025 -0400 chore: up eslint-nodebb commit 3f01b719c48760911b93ab09afa0ebe40660b3ea Author: Barış Soner Uşaklı Date: Fri Apr 18 22:10:35 2025 -0400 remove unused import, up eslint-nodebb commit 92d6e0220b67d98a376689168a701a75187917be Author: Barış Soner Uşaklı Date: Fri Apr 18 21:57:12 2025 -0400 refactor: switch eslint configs to esm add rules from https://eslint.style/ refactor for in loops to use Object.entries commit 0c5ef0e866d2238ee343f3be49a20f1423bbaec6 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 15:03:37 2025 -0400 fix(deps): update dependency chart.js to v4.4.9 (#13328) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6f8c7aba50056c0f2513f05d1db35ee67ab9ceac Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 15:03:15 2025 -0400 fix(deps): update dependency nconf to v0.13.0 (#13333) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 5d461f041776fa1cab9f5b708f1a69f6cff57404 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 15:00:13 2025 -0400 fix(deps): update dependency ace-builds to v1.40.0 (#13331) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6fbb2b4bc440abab75d55f1c6a78af93a10cbb17 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 14:59:48 2025 -0400 fix(deps): update dependency nodemailer to v6.10.1 (#13329) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 74d9806d28ade1d1a548470b63427adac906741c Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 12:00:22 2025 -0400 fix(deps): update dependency ioredis to v5.6.1 (#13318) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 9046aceaa63e71fd4163386d58d14feb5ecd5798 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 12:00:13 2025 -0400 chore(deps): update dependency lint-staged to v15.5.1 (#13319) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit f96ce25a8493e6b42aa52c815117674f94497058 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 12:00:01 2025 -0400 fix(deps): update dependency cron to v4.3.0 (#13332) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 24a5f407ec3a77a6562b6e21ad84207165b25fb4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 11:44:59 2025 -0400 fix(deps): update dependency sanitize-html to v2.16.0 (#13339) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit cac1c37b6b24b10625111e76d20e7daec3998ef4 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 11:44:42 2025 -0400 fix(deps): update dependency webpack to v5.99.6 (#13341) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 321defb989febc83252b7ad30a922ecf730e1e8a Author: Barış Soner Uşaklı Date: Fri Apr 18 11:16:02 2025 -0400 test: fix tests commit 8f784bb3487f1e31cd4537069f75378789dc6861 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Fri Apr 18 11:00:19 2025 -0400 fix(deps): update dependency bootbox to v6.0.3 (#13327) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 40767c8bcb8b6b892c11571a0e0d29c87091fd2a Author: Misty Release Bot Date: Fri Apr 18 09:19:34 2025 +0000 Latest translations and fallbacks commit 4301bf979736c39378e94a7c3e2abc3df84c6bf1 Author: Barış Soner Uşaklı Date: Thu Apr 17 13:20:37 2025 -0400 chore: up themes commit 9f93cc9bd0957ee5295ce015360438fb41be2c90 Author: Barış Soner Uşaklı Date: Thu Apr 17 13:19:48 2025 -0400 refactor: move topic/post menu lists to core commit 87aacc8943fe7cfc05dcadfd9b3a9b5a54c93987 Author: Barış Soner Uşaklı Date: Thu Apr 17 12:02:52 2025 -0400 refactor: show topic tools if plugins add them previously regular users couldn't see topic tools if it was something that didnt require privileges commit b73fb67b3383a0694bea6a914da4baca96b21fc1 Author: Barış Soner Uşaklı Date: Thu Apr 17 09:32:55 2025 -0400 refactor: remove reply icons commit 13884e43c05c6d75001348195f3c8644a6c9a7ff Author: Misty Release Bot Date: Thu Apr 17 09:19:56 2025 +0000 Latest translations and fallbacks commit be1abcc95783e10209c8f47ba70720690010bcf7 Merge: 83245e4abf 6832541c02 Author: Barış Soner Uşaklı Date: Wed Apr 16 10:31:41 2025 -0400 Merge branch 'master' into develop commit 6832541c020ce7742019606c105e7218388d3e4b Author: Barış Soner Uşaklı Date: Wed Apr 16 10:31:28 2025 -0400 lint: fix semi commit 4f13eb0338aaa397e6b12328e323d00e0d7b1a34 Author: Barış Soner Uşaklı Date: Wed Apr 16 10:23:25 2025 -0400 fix: closes #13336, allow main post deletion from "delete posts" tool commit 83245e4abf54723cfbdd83aa457c2d9a9e5485c5 Author: Misty Release Bot Date: Wed Apr 16 09:19:59 2025 +0000 Latest translations and fallbacks commit 3f000ed6ec7fadb6aca0f8ec1b33612212930d7b Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Tue Apr 15 17:57:02 2025 -0400 chore(deps): update dependency jsdom to v26.1.0 (#13330) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 41252197163d07959c7a0d4528134579ef6efacf Author: Julian Lam Date: Tue Apr 15 14:15:06 2025 -0400 fix: regression on search query that is a url, via 3526c937ccec843d4637efa894f49efc9bac5493 commit 6c163f7c1372f7838118f10d581ca1399c7f45f0 Author: Misty Release Bot Date: Tue Apr 15 14:08:09 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.global commit bf2d4c46f8b002a256537e76cc9e8eaa990359fc Author: Barış Soner Uşaklı Date: Tue Apr 15 10:07:45 2025 -0400 feat: show topic follower counts (#13326) fix upgrade script dates add upgrade script to count topic followers for each topic commit 5d94f2cad45e8295519121bb326a65c37e08dc8c Author: Julian Lam Date: Tue Apr 15 10:06:04 2025 -0400 test: fix test expecting Note when it is now Article commit 3c4be7738c8d4485cfe49a90aa939068c7ca5729 Author: Julian Lam Date: Mon Apr 14 13:56:49 2025 -0400 feat: federate out as:Article with `preview` for root-level posts in a topic, instead of `as:Note` commit 2a98a9b33428cb94aa101409c2a24dab1ed661b7 Author: Julian Lam Date: Mon Apr 14 13:35:22 2025 -0400 fix: bug where generateHandle would throw when passed in an invalid slug commit 38b46fb488f9f74d719cfecde9ab960dc08e715c Author: Misty Release Bot Date: Sun Apr 13 09:19:23 2025 +0000 Latest translations and fallbacks commit dcf34e3da2310f88b3dd56aded3684cbf972a4a9 Author: Misty Release Bot Date: Sat Apr 12 18:17:23 2025 +0000 chore(i18n): fallback strings for new resources: nodebb.notifications commit 73c8dbfe00a4959cd9b64d9395f3adeeb8014ab6 Merge: 1bd1262247 d59a5728df Author: Barış Soner Uşaklı Date: Sat Apr 12 14:16:58 2025 -0400 Merge branch 'master' into develop commit d59a5728dfc977f44533186ace531248c2917516 Author: Barış Soner Uşaklı Date: Fri Apr 11 19:58:48 2025 -0400 lint: fix missing comma commit 46ed56cf96bd979b6b5b45831136514a03e279f6 Author: Barış Soner Uşaklı Date: Fri Apr 11 19:51:18 2025 -0400 refactor: use sortedSetsCard commit 4cee37b98e5ded06f79ba8f80e9877808b51f29a Author: Misty Release Bot Date: Thu Apr 10 14:03:46 2025 +0000 chore: update changelog for v4.2.1 commit 59bc2b0d4b4c1c10dca002f030404fd2fb47891e Author: Misty Release Bot Date: Thu Apr 10 14:03:46 2025 +0000 chore: incrementing version number - v4.2.1 commit 1bd1262247f3e9d82d54aa9aeb522e0d4bc06e4e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Apr 10 09:54:21 2025 -0400 fix(deps): update dependency nodebb-theme-harmony to v2.1.6 (#13314) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 6de89952f5016c3946beca80d69779a3dd9725e0 Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Thu Apr 10 09:54:06 2025 -0400 fix(deps): update dependency nodebb-theme-persona to v14.1.5 (#13316) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 33d50637a33814f10bb5e8d78b8e314a39bea417 Author: Barış Soner Uşaklı Date: Thu Apr 10 09:53:20 2025 -0400 fix: closes #13317, fix email confirm for changing email commit 59bcb6ee7701881881ddcd8fde1afc0c078fd3b3 Author: Misty Release Bot Date: Thu Apr 10 09:20:24 2025 +0000 Latest translations and fallbacks --- .github/workflows/docker.yml | 119 +- .github/workflows/test.yaml | 16 +- .tx/config | 130 ++ CHANGELOG.md | 2061 +++++++++++++++++ Gruntfile.js | 5 +- README.md | 4 +- app.js | 1 - dev.Dockerfile | 4 +- docker-compose-pgsql.yml | 4 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 4 +- eslint.config.cjs => eslint.config.mjs | 42 +- install/data/defaults.json | 13 +- install/docker/entrypoint.sh | 32 +- install/package.json | 172 +- loader.js | 12 +- public/language/ar/admin/advanced/cache.json | 4 - public/language/ar/admin/dashboard.json | 1 + .../language/ar/admin/development/info.json | 2 +- .../language/ar/admin/manage/categories.json | 15 +- .../ar/admin/manage/custom-reasons.json | 16 + .../language/ar/admin/manage/privileges.json | 1 + public/language/ar/admin/manage/users.json | 3 + .../ar/admin/settings/activitypub.json | 22 + public/language/ar/admin/settings/chat.json | 2 - public/language/ar/admin/settings/email.json | 8 +- .../ar/admin/settings/notifications.json | 4 +- .../language/ar/admin/settings/uploads.json | 6 + public/language/ar/admin/settings/user.json | 1 + .../ar/admin/settings/web-crawler.json | 1 + public/language/ar/aria.json | 3 +- public/language/ar/category.json | 5 +- public/language/ar/error.json | 7 + public/language/ar/global.json | 2 + public/language/ar/groups.json | 2 + public/language/ar/modules.json | 3 +- public/language/ar/notifications.json | 38 +- public/language/ar/social.json | 4 +- public/language/ar/themes/harmony.json | 2 + public/language/ar/topic.json | 14 +- public/language/ar/user.json | 4 + public/language/ar/world.json | 13 +- public/language/az/admin/advanced/cache.json | 4 - public/language/az/admin/dashboard.json | 1 + .../language/az/admin/development/info.json | 2 +- .../language/az/admin/manage/categories.json | 15 +- .../az/admin/manage/custom-reasons.json | 16 + .../language/az/admin/manage/privileges.json | 1 + public/language/az/admin/manage/users.json | 3 + .../az/admin/settings/activitypub.json | 22 + public/language/az/admin/settings/chat.json | 2 - public/language/az/admin/settings/email.json | 8 +- .../az/admin/settings/notifications.json | 4 +- .../language/az/admin/settings/uploads.json | 6 + public/language/az/admin/settings/user.json | 1 + .../az/admin/settings/web-crawler.json | 1 + public/language/az/aria.json | 3 +- public/language/az/category.json | 5 +- public/language/az/error.json | 11 +- public/language/az/global.json | 2 + public/language/az/groups.json | 2 + public/language/az/modules.json | 3 +- public/language/az/notifications.json | 44 +- public/language/az/social.json | 4 +- public/language/az/themes/harmony.json | 2 + public/language/az/topic.json | 14 +- public/language/az/user.json | 4 + public/language/az/world.json | 13 +- public/language/bg/admin/advanced/cache.json | 4 - public/language/bg/admin/dashboard.json | 1 + .../language/bg/admin/development/info.json | 2 +- .../language/bg/admin/manage/categories.json | 15 +- .../bg/admin/manage/custom-reasons.json | 16 + .../language/bg/admin/manage/privileges.json | 1 + public/language/bg/admin/manage/users.json | 3 + .../bg/admin/settings/activitypub.json | 22 + public/language/bg/admin/settings/chat.json | 2 - public/language/bg/admin/settings/email.json | 8 +- .../bg/admin/settings/notifications.json | 4 +- .../language/bg/admin/settings/uploads.json | 6 + public/language/bg/admin/settings/user.json | 1 + .../bg/admin/settings/web-crawler.json | 1 + public/language/bg/aria.json | 3 +- public/language/bg/category.json | 5 +- public/language/bg/error.json | 7 + public/language/bg/global.json | 2 + public/language/bg/groups.json | 2 + public/language/bg/modules.json | 5 +- public/language/bg/notifications.json | 34 +- public/language/bg/social.json | 4 +- public/language/bg/themes/harmony.json | 2 + public/language/bg/topic.json | 14 +- public/language/bg/user.json | 4 + public/language/bg/world.json | 13 +- public/language/bn/admin/advanced/cache.json | 4 - public/language/bn/admin/dashboard.json | 1 + .../language/bn/admin/development/info.json | 2 +- .../language/bn/admin/manage/categories.json | 15 +- .../bn/admin/manage/custom-reasons.json | 16 + .../language/bn/admin/manage/privileges.json | 1 + public/language/bn/admin/manage/users.json | 3 + .../bn/admin/settings/activitypub.json | 22 + public/language/bn/admin/settings/chat.json | 2 - public/language/bn/admin/settings/email.json | 8 +- .../bn/admin/settings/notifications.json | 4 +- .../language/bn/admin/settings/uploads.json | 6 + public/language/bn/admin/settings/user.json | 1 + .../bn/admin/settings/web-crawler.json | 1 + public/language/bn/aria.json | 3 +- public/language/bn/category.json | 5 +- public/language/bn/error.json | 7 + public/language/bn/global.json | 2 + public/language/bn/groups.json | 2 + public/language/bn/modules.json | 3 +- public/language/bn/notifications.json | 36 +- public/language/bn/social.json | 4 +- public/language/bn/themes/harmony.json | 2 + public/language/bn/topic.json | 14 +- public/language/bn/user.json | 4 + public/language/bn/world.json | 13 +- public/language/cs/admin/advanced/cache.json | 4 - public/language/cs/admin/dashboard.json | 1 + .../language/cs/admin/development/info.json | 6 +- .../language/cs/admin/manage/categories.json | 15 +- .../cs/admin/manage/custom-reasons.json | 16 + .../language/cs/admin/manage/privileges.json | 1 + public/language/cs/admin/manage/users.json | 39 +- public/language/cs/admin/menu.json | 4 +- .../cs/admin/settings/activitypub.json | 22 + .../language/cs/admin/settings/advanced.json | 2 +- public/language/cs/admin/settings/chat.json | 6 +- public/language/cs/admin/settings/email.json | 12 +- .../cs/admin/settings/notifications.json | 4 +- public/language/cs/admin/settings/post.json | 4 +- .../language/cs/admin/settings/uploads.json | 10 +- public/language/cs/admin/settings/user.json | 1 + .../cs/admin/settings/web-crawler.json | 1 + public/language/cs/aria.json | 15 +- public/language/cs/category.json | 17 +- public/language/cs/error.json | 61 +- public/language/cs/global.json | 30 +- public/language/cs/groups.json | 2 + public/language/cs/modules.json | 43 +- public/language/cs/notifications.json | 78 +- public/language/cs/pages.json | 4 +- public/language/cs/register.json | 6 +- public/language/cs/search.json | 4 +- public/language/cs/social.json | 4 +- public/language/cs/tags.json | 4 +- public/language/cs/themes/harmony.json | 26 +- public/language/cs/themes/persona.json | 2 +- public/language/cs/topic.json | 154 +- public/language/cs/unread.json | 2 +- public/language/cs/user.json | 16 +- public/language/cs/users.json | 2 +- public/language/cs/world.json | 35 +- public/language/da/admin/advanced/cache.json | 4 - public/language/da/admin/dashboard.json | 1 + .../language/da/admin/development/info.json | 2 +- .../language/da/admin/manage/categories.json | 15 +- .../da/admin/manage/custom-reasons.json | 16 + .../language/da/admin/manage/privileges.json | 1 + public/language/da/admin/manage/users.json | 3 + .../da/admin/settings/activitypub.json | 22 + public/language/da/admin/settings/chat.json | 2 - public/language/da/admin/settings/email.json | 8 +- .../da/admin/settings/notifications.json | 4 +- .../language/da/admin/settings/uploads.json | 6 + public/language/da/admin/settings/user.json | 1 + .../da/admin/settings/web-crawler.json | 1 + public/language/da/aria.json | 3 +- public/language/da/category.json | 5 +- public/language/da/error.json | 7 + public/language/da/global.json | 2 + public/language/da/groups.json | 2 + public/language/da/modules.json | 3 +- public/language/da/notifications.json | 40 +- public/language/da/social.json | 4 +- public/language/da/themes/harmony.json | 2 + public/language/da/topic.json | 14 +- public/language/da/user.json | 4 + public/language/da/world.json | 13 +- public/language/de/admin/advanced/cache.json | 4 - public/language/de/admin/dashboard.json | 1 + .../language/de/admin/development/info.json | 2 +- .../language/de/admin/manage/categories.json | 15 +- .../de/admin/manage/custom-reasons.json | 16 + .../language/de/admin/manage/privileges.json | 1 + public/language/de/admin/manage/users.json | 3 + .../de/admin/settings/activitypub.json | 54 +- public/language/de/admin/settings/chat.json | 6 +- public/language/de/admin/settings/email.json | 8 +- .../de/admin/settings/notifications.json | 4 +- .../language/de/admin/settings/uploads.json | 6 + public/language/de/admin/settings/user.json | 1 + .../de/admin/settings/web-crawler.json | 1 + public/language/de/aria.json | 3 +- public/language/de/category.json | 5 +- public/language/de/error.json | 55 +- public/language/de/global.json | 2 + public/language/de/groups.json | 2 + public/language/de/modules.json | 3 +- public/language/de/notifications.json | 44 +- public/language/de/social.json | 4 +- public/language/de/themes/harmony.json | 2 + public/language/de/topic.json | 36 +- public/language/de/user.json | 4 + public/language/de/world.json | 15 +- public/language/el/admin/advanced/cache.json | 4 - public/language/el/admin/dashboard.json | 1 + .../language/el/admin/development/info.json | 2 +- .../language/el/admin/manage/categories.json | 15 +- .../el/admin/manage/custom-reasons.json | 16 + .../language/el/admin/manage/privileges.json | 1 + public/language/el/admin/manage/users.json | 3 + .../el/admin/settings/activitypub.json | 22 + public/language/el/admin/settings/chat.json | 2 - public/language/el/admin/settings/email.json | 8 +- .../el/admin/settings/notifications.json | 4 +- .../language/el/admin/settings/uploads.json | 6 + public/language/el/admin/settings/user.json | 1 + .../el/admin/settings/web-crawler.json | 1 + public/language/el/aria.json | 3 +- public/language/el/category.json | 5 +- public/language/el/error.json | 7 + public/language/el/global.json | 2 + public/language/el/groups.json | 2 + public/language/el/modules.json | 3 +- public/language/el/notifications.json | 36 +- public/language/el/social.json | 4 +- public/language/el/themes/harmony.json | 2 + public/language/el/topic.json | 14 +- public/language/el/user.json | 4 + public/language/el/world.json | 13 +- .../language/en-GB/admin/advanced/cache.json | 4 - public/language/en-GB/admin/dashboard.json | 1 + .../en-GB/admin/development/info.json | 2 +- .../en-GB/admin/manage/categories.json | 15 +- .../en-GB/admin/manage/custom-reasons.json | 16 + .../en-GB/admin/manage/privileges.json | 1 + public/language/en-GB/admin/manage/users.json | 3 + .../en-GB/admin/settings/activitypub.json | 22 + .../language/en-GB/admin/settings/chat.json | 2 - .../language/en-GB/admin/settings/email.json | 8 +- .../en-GB/admin/settings/notifications.json | 4 +- .../en-GB/admin/settings/uploads.json | 6 + .../language/en-GB/admin/settings/user.json | 1 + .../en-GB/admin/settings/web-crawler.json | 1 + public/language/en-GB/aria.json | 3 +- public/language/en-GB/category.json | 5 +- public/language/en-GB/error.json | 9 + public/language/en-GB/global.json | 2 + public/language/en-GB/groups.json | 2 + public/language/en-GB/modules.json | 3 +- public/language/en-GB/notifications.json | 36 +- public/language/en-GB/social.json | 4 +- public/language/en-GB/themes/harmony.json | 2 + public/language/en-GB/topic.json | 17 +- public/language/en-GB/user.json | 4 + public/language/en-GB/world.json | 13 +- .../language/en-US/admin/advanced/cache.json | 4 - public/language/en-US/admin/dashboard.json | 1 + .../en-US/admin/development/info.json | 2 +- .../en-US/admin/manage/categories.json | 15 +- .../en-US/admin/manage/custom-reasons.json | 16 + .../en-US/admin/manage/privileges.json | 1 + public/language/en-US/admin/manage/users.json | 3 + .../en-US/admin/settings/activitypub.json | 22 + .../language/en-US/admin/settings/chat.json | 2 - .../language/en-US/admin/settings/email.json | 8 +- .../en-US/admin/settings/notifications.json | 4 +- .../en-US/admin/settings/uploads.json | 6 + .../language/en-US/admin/settings/user.json | 1 + .../en-US/admin/settings/web-crawler.json | 1 + public/language/en-US/aria.json | 3 +- public/language/en-US/category.json | 5 +- public/language/en-US/error.json | 7 + public/language/en-US/global.json | 2 + public/language/en-US/groups.json | 2 + public/language/en-US/modules.json | 3 +- public/language/en-US/notifications.json | 36 +- public/language/en-US/social.json | 4 +- public/language/en-US/themes/harmony.json | 2 + public/language/en-US/topic.json | 14 +- public/language/en-US/user.json | 4 + public/language/en-US/world.json | 13 +- .../en-x-pirate/admin/advanced/cache.json | 4 - .../language/en-x-pirate/admin/dashboard.json | 1 + .../en-x-pirate/admin/development/info.json | 2 +- .../en-x-pirate/admin/manage/categories.json | 15 +- .../admin/manage/custom-reasons.json | 16 + .../en-x-pirate/admin/manage/privileges.json | 1 + .../en-x-pirate/admin/manage/users.json | 3 + .../admin/settings/activitypub.json | 22 + .../en-x-pirate/admin/settings/chat.json | 2 - .../en-x-pirate/admin/settings/email.json | 8 +- .../admin/settings/notifications.json | 4 +- .../en-x-pirate/admin/settings/uploads.json | 6 + .../en-x-pirate/admin/settings/user.json | 1 + .../admin/settings/web-crawler.json | 1 + public/language/en-x-pirate/aria.json | 3 +- public/language/en-x-pirate/category.json | 5 +- public/language/en-x-pirate/error.json | 7 + public/language/en-x-pirate/global.json | 2 + public/language/en-x-pirate/groups.json | 2 + public/language/en-x-pirate/modules.json | 3 +- .../language/en-x-pirate/notifications.json | 36 +- public/language/en-x-pirate/social.json | 4 +- .../language/en-x-pirate/themes/harmony.json | 2 + public/language/en-x-pirate/topic.json | 14 +- public/language/en-x-pirate/user.json | 4 + public/language/en-x-pirate/world.json | 13 +- public/language/es/admin/advanced/cache.json | 4 - public/language/es/admin/dashboard.json | 1 + .../language/es/admin/development/info.json | 2 +- .../language/es/admin/manage/categories.json | 15 +- .../es/admin/manage/custom-reasons.json | 16 + .../language/es/admin/manage/privileges.json | 1 + .../es/admin/manage/user-custom-fields.json | 44 +- public/language/es/admin/manage/users.json | 3 + .../es/admin/settings/activitypub.json | 22 + public/language/es/admin/settings/chat.json | 2 - public/language/es/admin/settings/email.json | 8 +- .../es/admin/settings/notifications.json | 4 +- .../language/es/admin/settings/uploads.json | 6 + public/language/es/admin/settings/user.json | 1 + .../es/admin/settings/web-crawler.json | 1 + public/language/es/aria.json | 3 +- public/language/es/category.json | 5 +- public/language/es/error.json | 7 + public/language/es/global.json | 2 + public/language/es/groups.json | 2 + public/language/es/modules.json | 3 +- public/language/es/notifications.json | 40 +- public/language/es/social.json | 4 +- public/language/es/themes/harmony.json | 2 + public/language/es/topic.json | 14 +- public/language/es/user.json | 4 + public/language/es/world.json | 13 +- public/language/et/admin/advanced/cache.json | 4 - public/language/et/admin/dashboard.json | 1 + .../language/et/admin/development/info.json | 2 +- .../language/et/admin/manage/categories.json | 15 +- .../et/admin/manage/custom-reasons.json | 16 + .../language/et/admin/manage/privileges.json | 1 + public/language/et/admin/manage/users.json | 3 + .../et/admin/settings/activitypub.json | 22 + public/language/et/admin/settings/chat.json | 2 - public/language/et/admin/settings/email.json | 8 +- .../et/admin/settings/notifications.json | 4 +- .../language/et/admin/settings/uploads.json | 6 + public/language/et/admin/settings/user.json | 1 + .../et/admin/settings/web-crawler.json | 1 + public/language/et/aria.json | 3 +- public/language/et/category.json | 5 +- public/language/et/error.json | 7 + public/language/et/global.json | 2 + public/language/et/groups.json | 2 + public/language/et/modules.json | 3 +- public/language/et/notifications.json | 40 +- public/language/et/social.json | 4 +- public/language/et/themes/harmony.json | 2 + public/language/et/topic.json | 14 +- public/language/et/user.json | 4 + public/language/et/world.json | 13 +- .../language/fa-IR/admin/advanced/cache.json | 4 - public/language/fa-IR/admin/dashboard.json | 1 + .../fa-IR/admin/development/info.json | 2 +- .../fa-IR/admin/manage/categories.json | 15 +- .../fa-IR/admin/manage/custom-reasons.json | 16 + .../fa-IR/admin/manage/privileges.json | 1 + public/language/fa-IR/admin/manage/users.json | 3 + .../fa-IR/admin/settings/activitypub.json | 22 + .../language/fa-IR/admin/settings/chat.json | 2 - .../language/fa-IR/admin/settings/email.json | 8 +- .../fa-IR/admin/settings/notifications.json | 4 +- .../fa-IR/admin/settings/uploads.json | 6 + .../language/fa-IR/admin/settings/user.json | 1 + .../fa-IR/admin/settings/web-crawler.json | 1 + public/language/fa-IR/aria.json | 3 +- public/language/fa-IR/category.json | 5 +- public/language/fa-IR/error.json | 7 + public/language/fa-IR/global.json | 2 + public/language/fa-IR/groups.json | 2 + public/language/fa-IR/modules.json | 3 +- public/language/fa-IR/notifications.json | 40 +- public/language/fa-IR/social.json | 4 +- public/language/fa-IR/themes/harmony.json | 2 + public/language/fa-IR/topic.json | 14 +- public/language/fa-IR/user.json | 4 + public/language/fa-IR/world.json | 13 +- public/language/fi/admin/advanced/cache.json | 4 - public/language/fi/admin/dashboard.json | 1 + .../language/fi/admin/development/info.json | 2 +- .../language/fi/admin/manage/categories.json | 15 +- .../fi/admin/manage/custom-reasons.json | 16 + .../language/fi/admin/manage/privileges.json | 1 + public/language/fi/admin/manage/users.json | 3 + .../fi/admin/settings/activitypub.json | 34 +- public/language/fi/admin/settings/chat.json | 2 - public/language/fi/admin/settings/email.json | 8 +- .../fi/admin/settings/notifications.json | 4 +- .../language/fi/admin/settings/uploads.json | 6 + public/language/fi/admin/settings/user.json | 1 + .../fi/admin/settings/web-crawler.json | 1 + public/language/fi/aria.json | 3 +- public/language/fi/category.json | 5 +- public/language/fi/error.json | 7 + public/language/fi/global.json | 2 + public/language/fi/groups.json | 2 + public/language/fi/modules.json | 11 +- public/language/fi/notifications.json | 36 +- public/language/fi/recent.json | 2 +- public/language/fi/social.json | 4 +- public/language/fi/themes/harmony.json | 2 + public/language/fi/topic.json | 14 +- public/language/fi/unread.json | 2 +- public/language/fi/user.json | 22 +- public/language/fi/world.json | 21 +- public/language/fr/admin/advanced/cache.json | 4 - public/language/fr/admin/dashboard.json | 1 + .../language/fr/admin/development/info.json | 2 +- .../language/fr/admin/manage/categories.json | 15 +- .../fr/admin/manage/custom-reasons.json | 16 + .../language/fr/admin/manage/privileges.json | 1 + .../fr/admin/manage/user-custom-fields.json | 8 +- public/language/fr/admin/manage/users.json | 5 +- .../fr/admin/settings/activitypub.json | 22 + public/language/fr/admin/settings/chat.json | 6 +- public/language/fr/admin/settings/email.json | 8 +- .../fr/admin/settings/notifications.json | 4 +- .../language/fr/admin/settings/uploads.json | 6 + public/language/fr/admin/settings/user.json | 1 + .../fr/admin/settings/web-crawler.json | 1 + public/language/fr/aria.json | 5 +- public/language/fr/category.json | 5 +- public/language/fr/error.json | 13 +- public/language/fr/global.json | 6 +- public/language/fr/groups.json | 2 + public/language/fr/modules.json | 3 +- public/language/fr/notifications.json | 52 +- public/language/fr/social.json | 4 +- public/language/fr/themes/harmony.json | 2 + public/language/fr/topic.json | 54 +- public/language/fr/user.json | 4 + public/language/fr/world.json | 35 +- public/language/gl/admin/advanced/cache.json | 4 - public/language/gl/admin/dashboard.json | 1 + .../language/gl/admin/development/info.json | 2 +- .../language/gl/admin/manage/categories.json | 15 +- .../gl/admin/manage/custom-reasons.json | 16 + .../language/gl/admin/manage/privileges.json | 1 + public/language/gl/admin/manage/users.json | 3 + .../gl/admin/settings/activitypub.json | 22 + public/language/gl/admin/settings/chat.json | 2 - public/language/gl/admin/settings/email.json | 8 +- .../gl/admin/settings/notifications.json | 4 +- .../language/gl/admin/settings/uploads.json | 6 + public/language/gl/admin/settings/user.json | 1 + .../gl/admin/settings/web-crawler.json | 1 + public/language/gl/aria.json | 3 +- public/language/gl/category.json | 5 +- public/language/gl/error.json | 7 + public/language/gl/global.json | 2 + public/language/gl/groups.json | 2 + public/language/gl/modules.json | 3 +- public/language/gl/notifications.json | 40 +- public/language/gl/social.json | 4 +- public/language/gl/themes/harmony.json | 2 + public/language/gl/topic.json | 14 +- public/language/gl/user.json | 4 + public/language/gl/world.json | 13 +- public/language/he/admin/advanced/cache.json | 4 - .../language/he/admin/advanced/database.json | 2 +- public/language/he/admin/advanced/errors.json | 2 +- public/language/he/admin/advanced/events.json | 8 +- public/language/he/admin/advanced/logs.json | 4 +- .../he/admin/appearance/customise.json | 6 +- .../language/he/admin/appearance/skins.json | 8 +- .../language/he/admin/appearance/themes.json | 10 +- public/language/he/admin/dashboard.json | 29 +- .../language/he/admin/development/info.json | 6 +- .../language/he/admin/development/logger.json | 2 +- public/language/he/admin/extend/rewards.json | 10 +- public/language/he/admin/extend/widgets.json | 16 +- .../language/he/admin/manage/categories.json | 33 +- .../he/admin/manage/custom-reasons.json | 16 + .../language/he/admin/manage/privileges.json | 1 + public/language/he/admin/manage/users.json | 3 + public/language/he/admin/menu.json | 6 +- .../he/admin/settings/activitypub.json | 22 + public/language/he/admin/settings/chat.json | 2 - public/language/he/admin/settings/email.json | 8 +- .../he/admin/settings/notifications.json | 4 +- public/language/he/admin/settings/sounds.json | 4 +- .../language/he/admin/settings/uploads.json | 6 + public/language/he/admin/settings/user.json | 3 +- .../he/admin/settings/web-crawler.json | 1 + public/language/he/aria.json | 3 +- public/language/he/category.json | 5 +- public/language/he/email.json | 50 +- public/language/he/error.json | 47 +- public/language/he/global.json | 8 +- public/language/he/groups.json | 22 +- public/language/he/modules.json | 3 +- public/language/he/notifications.json | 44 +- public/language/he/search.json | 2 +- public/language/he/social.json | 4 +- public/language/he/themes/harmony.json | 4 +- public/language/he/topic.json | 14 +- public/language/he/uploads.json | 4 +- public/language/he/user.json | 6 +- public/language/he/world.json | 13 +- public/language/hr/admin/advanced/cache.json | 4 - public/language/hr/admin/dashboard.json | 1 + .../language/hr/admin/development/info.json | 2 +- .../language/hr/admin/manage/categories.json | 15 +- .../hr/admin/manage/custom-reasons.json | 16 + .../language/hr/admin/manage/privileges.json | 1 + public/language/hr/admin/manage/users.json | 3 + .../hr/admin/settings/activitypub.json | 22 + public/language/hr/admin/settings/chat.json | 2 - public/language/hr/admin/settings/email.json | 8 +- .../hr/admin/settings/notifications.json | 4 +- .../language/hr/admin/settings/uploads.json | 6 + public/language/hr/admin/settings/user.json | 1 + .../hr/admin/settings/web-crawler.json | 1 + public/language/hr/aria.json | 3 +- public/language/hr/category.json | 7 +- public/language/hr/error.json | 7 + public/language/hr/global.json | 2 + public/language/hr/groups.json | 2 + public/language/hr/modules.json | 3 +- public/language/hr/notifications.json | 40 +- public/language/hr/register.json | 2 +- public/language/hr/social.json | 4 +- public/language/hr/themes/harmony.json | 4 +- public/language/hr/topic.json | 20 +- public/language/hr/user.json | 16 +- public/language/hr/world.json | 13 +- public/language/hu/admin/advanced/cache.json | 4 - public/language/hu/admin/dashboard.json | 1 + .../language/hu/admin/development/info.json | 2 +- .../language/hu/admin/manage/categories.json | 15 +- .../hu/admin/manage/custom-reasons.json | 16 + .../language/hu/admin/manage/privileges.json | 1 + public/language/hu/admin/manage/users.json | 3 + .../hu/admin/settings/activitypub.json | 22 + public/language/hu/admin/settings/chat.json | 2 - public/language/hu/admin/settings/email.json | 8 +- .../hu/admin/settings/notifications.json | 4 +- .../language/hu/admin/settings/uploads.json | 6 + public/language/hu/admin/settings/user.json | 1 + .../hu/admin/settings/web-crawler.json | 1 + public/language/hu/aria.json | 3 +- public/language/hu/category.json | 5 +- public/language/hu/error.json | 7 + public/language/hu/global.json | 2 + public/language/hu/groups.json | 2 + public/language/hu/modules.json | 3 +- public/language/hu/notifications.json | 40 +- public/language/hu/social.json | 4 +- public/language/hu/themes/harmony.json | 2 + public/language/hu/topic.json | 14 +- public/language/hu/user.json | 4 + public/language/hu/world.json | 13 +- public/language/hy/admin/advanced/cache.json | 4 - public/language/hy/admin/dashboard.json | 1 + .../language/hy/admin/development/info.json | 2 +- .../language/hy/admin/manage/categories.json | 15 +- .../hy/admin/manage/custom-reasons.json | 16 + .../language/hy/admin/manage/privileges.json | 1 + public/language/hy/admin/manage/users.json | 3 + .../hy/admin/settings/activitypub.json | 22 + public/language/hy/admin/settings/chat.json | 2 - public/language/hy/admin/settings/email.json | 8 +- .../hy/admin/settings/notifications.json | 4 +- .../language/hy/admin/settings/uploads.json | 6 + public/language/hy/admin/settings/user.json | 1 + .../hy/admin/settings/web-crawler.json | 1 + public/language/hy/aria.json | 3 +- public/language/hy/category.json | 5 +- public/language/hy/error.json | 7 + public/language/hy/global.json | 2 + public/language/hy/groups.json | 2 + public/language/hy/modules.json | 3 +- public/language/hy/notifications.json | 44 +- public/language/hy/social.json | 4 +- public/language/hy/themes/harmony.json | 2 + public/language/hy/topic.json | 14 +- public/language/hy/user.json | 4 + public/language/hy/world.json | 13 +- public/language/id/admin/advanced/cache.json | 4 - public/language/id/admin/dashboard.json | 1 + .../language/id/admin/development/info.json | 2 +- .../language/id/admin/manage/categories.json | 15 +- .../id/admin/manage/custom-reasons.json | 16 + .../language/id/admin/manage/privileges.json | 1 + public/language/id/admin/manage/users.json | 3 + .../id/admin/settings/activitypub.json | 22 + public/language/id/admin/settings/chat.json | 2 - public/language/id/admin/settings/email.json | 8 +- .../id/admin/settings/notifications.json | 4 +- .../language/id/admin/settings/uploads.json | 6 + public/language/id/admin/settings/user.json | 1 + .../id/admin/settings/web-crawler.json | 1 + public/language/id/aria.json | 3 +- public/language/id/category.json | 5 +- public/language/id/error.json | 7 + public/language/id/global.json | 2 + public/language/id/groups.json | 2 + public/language/id/modules.json | 3 +- public/language/id/notifications.json | 38 +- public/language/id/social.json | 4 +- public/language/id/themes/harmony.json | 2 + public/language/id/topic.json | 14 +- public/language/id/user.json | 4 + public/language/id/world.json | 13 +- public/language/it/admin/advanced/cache.json | 4 - public/language/it/admin/dashboard.json | 1 + .../language/it/admin/development/info.json | 2 +- .../language/it/admin/manage/categories.json | 47 +- .../it/admin/manage/custom-reasons.json | 16 + .../language/it/admin/manage/privileges.json | 1 + public/language/it/admin/manage/users.json | 5 +- .../it/admin/settings/activitypub.json | 66 +- public/language/it/admin/settings/chat.json | 6 +- public/language/it/admin/settings/email.json | 12 +- .../language/it/admin/settings/general.json | 4 +- .../it/admin/settings/notifications.json | 4 +- .../language/it/admin/settings/uploads.json | 6 + public/language/it/admin/settings/user.json | 1 + .../it/admin/settings/web-crawler.json | 1 + public/language/it/aria.json | 3 +- public/language/it/category.json | 7 +- public/language/it/error.json | 29 +- public/language/it/flags.json | 10 +- public/language/it/global.json | 2 + public/language/it/groups.json | 2 + public/language/it/modules.json | 3 +- public/language/it/notifications.json | 52 +- public/language/it/recent.json | 2 +- public/language/it/social.json | 4 +- public/language/it/themes/harmony.json | 12 +- public/language/it/topic.json | 28 +- public/language/it/user.json | 8 +- public/language/it/world.json | 35 +- public/language/ja/admin/advanced/cache.json | 4 - public/language/ja/admin/dashboard.json | 1 + .../language/ja/admin/development/info.json | 2 +- .../language/ja/admin/manage/categories.json | 15 +- .../ja/admin/manage/custom-reasons.json | 16 + .../language/ja/admin/manage/privileges.json | 1 + public/language/ja/admin/manage/users.json | 3 + .../ja/admin/settings/activitypub.json | 22 + public/language/ja/admin/settings/chat.json | 2 - public/language/ja/admin/settings/email.json | 8 +- .../ja/admin/settings/notifications.json | 4 +- .../language/ja/admin/settings/uploads.json | 6 + public/language/ja/admin/settings/user.json | 1 + .../ja/admin/settings/web-crawler.json | 1 + public/language/ja/aria.json | 3 +- public/language/ja/category.json | 5 +- public/language/ja/error.json | 7 + public/language/ja/global.json | 2 + public/language/ja/groups.json | 2 + public/language/ja/modules.json | 3 +- public/language/ja/notifications.json | 40 +- public/language/ja/social.json | 4 +- public/language/ja/themes/harmony.json | 2 + public/language/ja/topic.json | 14 +- public/language/ja/user.json | 4 + public/language/ja/world.json | 13 +- public/language/ko/admin/advanced/cache.json | 4 - public/language/ko/admin/dashboard.json | 1 + .../language/ko/admin/development/info.json | 2 +- .../language/ko/admin/manage/categories.json | 15 +- .../ko/admin/manage/custom-reasons.json | 16 + .../language/ko/admin/manage/privileges.json | 1 + public/language/ko/admin/manage/users.json | 3 + .../ko/admin/settings/activitypub.json | 22 + public/language/ko/admin/settings/chat.json | 2 - public/language/ko/admin/settings/email.json | 8 +- .../ko/admin/settings/notifications.json | 4 +- .../language/ko/admin/settings/uploads.json | 6 + public/language/ko/admin/settings/user.json | 1 + .../ko/admin/settings/web-crawler.json | 1 + public/language/ko/aria.json | 3 +- public/language/ko/category.json | 5 +- public/language/ko/error.json | 7 + public/language/ko/global.json | 2 + public/language/ko/groups.json | 2 + public/language/ko/modules.json | 3 +- public/language/ko/notifications.json | 44 +- public/language/ko/social.json | 4 +- public/language/ko/themes/harmony.json | 2 + public/language/ko/topic.json | 14 +- public/language/ko/user.json | 4 + public/language/ko/world.json | 13 +- public/language/lt/admin/advanced/cache.json | 4 - public/language/lt/admin/dashboard.json | 1 + .../language/lt/admin/development/info.json | 2 +- .../language/lt/admin/manage/categories.json | 15 +- .../lt/admin/manage/custom-reasons.json | 16 + .../language/lt/admin/manage/privileges.json | 1 + public/language/lt/admin/manage/users.json | 3 + .../lt/admin/settings/activitypub.json | 22 + public/language/lt/admin/settings/chat.json | 2 - public/language/lt/admin/settings/email.json | 8 +- .../lt/admin/settings/notifications.json | 4 +- .../language/lt/admin/settings/uploads.json | 6 + public/language/lt/admin/settings/user.json | 1 + .../lt/admin/settings/web-crawler.json | 1 + public/language/lt/aria.json | 3 +- public/language/lt/category.json | 5 +- public/language/lt/error.json | 7 + public/language/lt/global.json | 2 + public/language/lt/groups.json | 2 + public/language/lt/modules.json | 3 +- public/language/lt/notifications.json | 38 +- public/language/lt/social.json | 4 +- public/language/lt/themes/harmony.json | 2 + public/language/lt/topic.json | 14 +- public/language/lt/user.json | 4 + public/language/lt/world.json | 13 +- public/language/lv/admin/advanced/cache.json | 4 - public/language/lv/admin/dashboard.json | 1 + .../language/lv/admin/development/info.json | 2 +- .../language/lv/admin/manage/categories.json | 15 +- .../lv/admin/manage/custom-reasons.json | 16 + .../language/lv/admin/manage/privileges.json | 1 + public/language/lv/admin/manage/users.json | 3 + .../lv/admin/settings/activitypub.json | 22 + public/language/lv/admin/settings/chat.json | 2 - public/language/lv/admin/settings/email.json | 8 +- .../lv/admin/settings/notifications.json | 4 +- .../language/lv/admin/settings/uploads.json | 6 + public/language/lv/admin/settings/user.json | 1 + .../lv/admin/settings/web-crawler.json | 1 + public/language/lv/aria.json | 3 +- public/language/lv/category.json | 5 +- public/language/lv/error.json | 7 + public/language/lv/global.json | 2 + public/language/lv/groups.json | 2 + public/language/lv/modules.json | 3 +- public/language/lv/notifications.json | 40 +- public/language/lv/social.json | 4 +- public/language/lv/themes/harmony.json | 2 + public/language/lv/topic.json | 14 +- public/language/lv/user.json | 4 + public/language/lv/world.json | 13 +- public/language/ms/admin/advanced/cache.json | 4 - public/language/ms/admin/dashboard.json | 1 + .../language/ms/admin/development/info.json | 2 +- .../language/ms/admin/manage/categories.json | 15 +- .../ms/admin/manage/custom-reasons.json | 16 + .../language/ms/admin/manage/privileges.json | 1 + public/language/ms/admin/manage/users.json | 3 + .../ms/admin/settings/activitypub.json | 22 + public/language/ms/admin/settings/chat.json | 2 - public/language/ms/admin/settings/email.json | 8 +- .../ms/admin/settings/notifications.json | 4 +- .../language/ms/admin/settings/uploads.json | 6 + public/language/ms/admin/settings/user.json | 1 + .../ms/admin/settings/web-crawler.json | 1 + public/language/ms/aria.json | 3 +- public/language/ms/category.json | 5 +- public/language/ms/error.json | 7 + public/language/ms/global.json | 2 + public/language/ms/groups.json | 2 + public/language/ms/modules.json | 3 +- public/language/ms/notifications.json | 40 +- public/language/ms/social.json | 4 +- public/language/ms/themes/harmony.json | 2 + public/language/ms/topic.json | 14 +- public/language/ms/user.json | 4 + public/language/ms/world.json | 13 +- public/language/nb/admin/advanced/cache.json | 4 - public/language/nb/admin/advanced/logs.json | 2 +- public/language/nb/admin/dashboard.json | 1 + .../language/nb/admin/development/info.json | 2 +- .../language/nb/admin/manage/categories.json | 15 +- .../nb/admin/manage/custom-reasons.json | 16 + .../language/nb/admin/manage/privileges.json | 5 +- public/language/nb/admin/manage/users.json | 3 + public/language/nb/admin/menu.json | 4 +- .../nb/admin/settings/activitypub.json | 24 +- public/language/nb/admin/settings/chat.json | 2 - public/language/nb/admin/settings/email.json | 8 +- .../nb/admin/settings/notifications.json | 4 +- public/language/nb/admin/settings/post.json | 4 +- .../nb/admin/settings/reputation.json | 22 +- .../language/nb/admin/settings/uploads.json | 6 + public/language/nb/admin/settings/user.json | 1 + .../nb/admin/settings/web-crawler.json | 1 + public/language/nb/aria.json | 3 +- public/language/nb/category.json | 15 +- public/language/nb/error.json | 37 +- public/language/nb/flags.json | 8 +- public/language/nb/global.json | 16 +- public/language/nb/groups.json | 2 + public/language/nb/login.json | 2 +- public/language/nb/modules.json | 27 +- public/language/nb/notifications.json | 70 +- public/language/nb/pages.json | 16 +- public/language/nb/recent.json | 4 +- public/language/nb/search.json | 14 +- public/language/nb/social.json | 4 +- public/language/nb/success.json | 2 +- public/language/nb/tags.json | 4 +- public/language/nb/themes/harmony.json | 2 + public/language/nb/topic.json | 86 +- public/language/nb/unread.json | 2 +- public/language/nb/user.json | 28 +- public/language/nb/users.json | 4 +- public/language/nb/world.json | 21 +- public/language/nl/admin/advanced/cache.json | 4 - public/language/nl/admin/dashboard.json | 1 + .../language/nl/admin/development/info.json | 2 +- .../language/nl/admin/manage/categories.json | 15 +- .../nl/admin/manage/custom-reasons.json | 16 + .../language/nl/admin/manage/privileges.json | 1 + public/language/nl/admin/manage/users.json | 3 + .../nl/admin/settings/activitypub.json | 22 + public/language/nl/admin/settings/chat.json | 2 - public/language/nl/admin/settings/email.json | 8 +- .../nl/admin/settings/notifications.json | 4 +- .../language/nl/admin/settings/uploads.json | 6 + public/language/nl/admin/settings/user.json | 1 + .../nl/admin/settings/web-crawler.json | 1 + public/language/nl/aria.json | 3 +- public/language/nl/category.json | 5 +- public/language/nl/error.json | 7 + public/language/nl/global.json | 2 + public/language/nl/groups.json | 2 + public/language/nl/modules.json | 3 +- public/language/nl/notifications.json | 40 +- public/language/nl/social.json | 4 +- public/language/nl/themes/harmony.json | 2 + public/language/nl/topic.json | 14 +- public/language/nl/user.json | 4 + public/language/nl/world.json | 13 +- .../language/nn-NO/admin/advanced/cache.json | 4 - public/language/nn-NO/admin/dashboard.json | 3 +- .../nn-NO/admin/development/info.json | 2 +- .../nn-NO/admin/manage/categories.json | 25 +- .../nn-NO/admin/manage/custom-reasons.json | 16 + .../nn-NO/admin/manage/privileges.json | 3 +- public/language/nn-NO/admin/manage/users.json | 7 +- .../nn-NO/admin/settings/activitypub.json | 24 +- .../language/nn-NO/admin/settings/chat.json | 2 - .../language/nn-NO/admin/settings/email.json | 8 +- .../nn-NO/admin/settings/general.json | 2 +- .../nn-NO/admin/settings/notifications.json | 4 +- .../language/nn-NO/admin/settings/post.json | 4 +- .../nn-NO/admin/settings/reputation.json | 14 +- .../language/nn-NO/admin/settings/sounds.json | 2 +- .../nn-NO/admin/settings/uploads.json | 6 + .../language/nn-NO/admin/settings/user.json | 7 +- .../nn-NO/admin/settings/web-crawler.json | 1 + public/language/nn-NO/aria.json | 3 +- public/language/nn-NO/category.json | 25 +- public/language/nn-NO/email.json | 2 +- public/language/nn-NO/error.json | 37 +- public/language/nn-NO/flags.json | 2 +- public/language/nn-NO/global.json | 28 +- public/language/nn-NO/groups.json | 2 + public/language/nn-NO/ip-blacklist.json | 2 +- public/language/nn-NO/modules.json | 27 +- public/language/nn-NO/notifications.json | 88 +- public/language/nn-NO/pages.json | 18 +- public/language/nn-NO/recent.json | 4 +- public/language/nn-NO/search.json | 12 +- public/language/nn-NO/social.json | 4 +- public/language/nn-NO/tags.json | 8 +- public/language/nn-NO/themes/harmony.json | 2 + public/language/nn-NO/topic.json | 52 +- public/language/nn-NO/unread.json | 4 +- public/language/nn-NO/user.json | 44 +- public/language/nn-NO/users.json | 6 +- public/language/nn-NO/world.json | 23 +- public/language/pl/admin/advanced/cache.json | 4 - public/language/pl/admin/dashboard.json | 1 + .../language/pl/admin/development/info.json | 2 +- .../language/pl/admin/manage/categories.json | 15 +- .../pl/admin/manage/custom-reasons.json | 16 + .../language/pl/admin/manage/privileges.json | 1 + public/language/pl/admin/manage/users.json | 3 + .../pl/admin/settings/activitypub.json | 22 + public/language/pl/admin/settings/chat.json | 2 - public/language/pl/admin/settings/email.json | 8 +- .../pl/admin/settings/notifications.json | 4 +- .../language/pl/admin/settings/uploads.json | 6 + public/language/pl/admin/settings/user.json | 1 + .../pl/admin/settings/web-crawler.json | 1 + public/language/pl/aria.json | 3 +- public/language/pl/category.json | 5 +- public/language/pl/error.json | 11 +- public/language/pl/global.json | 2 + public/language/pl/groups.json | 2 + public/language/pl/modules.json | 3 +- public/language/pl/notifications.json | 44 +- public/language/pl/social.json | 4 +- public/language/pl/themes/harmony.json | 2 + public/language/pl/topic.json | 14 +- public/language/pl/user.json | 4 + public/language/pl/world.json | 13 +- .../language/pt-BR/admin/advanced/cache.json | 4 - .../pt-BR/admin/advanced/database.json | 2 +- public/language/pt-BR/admin/dashboard.json | 1 + .../pt-BR/admin/development/info.json | 2 +- .../pt-BR/admin/manage/categories.json | 15 +- .../pt-BR/admin/manage/custom-reasons.json | 16 + .../pt-BR/admin/manage/privileges.json | 1 + public/language/pt-BR/admin/manage/users.json | 3 + .../pt-BR/admin/settings/activitypub.json | 22 + .../language/pt-BR/admin/settings/chat.json | 2 - .../language/pt-BR/admin/settings/email.json | 8 +- .../pt-BR/admin/settings/notifications.json | 4 +- .../pt-BR/admin/settings/uploads.json | 6 + .../language/pt-BR/admin/settings/user.json | 1 + .../pt-BR/admin/settings/web-crawler.json | 1 + public/language/pt-BR/aria.json | 3 +- public/language/pt-BR/category.json | 5 +- public/language/pt-BR/error.json | 7 + public/language/pt-BR/global.json | 6 +- public/language/pt-BR/groups.json | 2 + public/language/pt-BR/modules.json | 3 +- public/language/pt-BR/notifications.json | 42 +- public/language/pt-BR/pages.json | 2 +- public/language/pt-BR/social.json | 4 +- public/language/pt-BR/themes/harmony.json | 2 + public/language/pt-BR/topic.json | 14 +- public/language/pt-BR/user.json | 4 + public/language/pt-BR/world.json | 13 +- .../language/pt-PT/admin/advanced/cache.json | 4 - public/language/pt-PT/admin/dashboard.json | 1 + .../pt-PT/admin/development/info.json | 2 +- .../pt-PT/admin/manage/categories.json | 15 +- .../pt-PT/admin/manage/custom-reasons.json | 16 + .../pt-PT/admin/manage/privileges.json | 1 + public/language/pt-PT/admin/manage/users.json | 3 + .../pt-PT/admin/settings/activitypub.json | 22 + .../language/pt-PT/admin/settings/chat.json | 2 - .../language/pt-PT/admin/settings/email.json | 8 +- .../pt-PT/admin/settings/notifications.json | 4 +- .../pt-PT/admin/settings/uploads.json | 6 + .../language/pt-PT/admin/settings/user.json | 1 + .../pt-PT/admin/settings/web-crawler.json | 1 + public/language/pt-PT/aria.json | 3 +- public/language/pt-PT/category.json | 5 +- public/language/pt-PT/error.json | 7 + public/language/pt-PT/global.json | 2 + public/language/pt-PT/groups.json | 2 + public/language/pt-PT/modules.json | 3 +- public/language/pt-PT/notifications.json | 40 +- public/language/pt-PT/social.json | 4 +- public/language/pt-PT/themes/harmony.json | 2 + public/language/pt-PT/topic.json | 14 +- public/language/pt-PT/user.json | 4 + public/language/pt-PT/world.json | 13 +- public/language/ro/admin/admin.json | 26 +- public/language/ro/admin/advanced/cache.json | 4 - .../language/ro/admin/advanced/database.json | 2 +- public/language/ro/admin/dashboard.json | 5 +- .../language/ro/admin/development/info.json | 6 +- .../language/ro/admin/manage/categories.json | 49 +- .../ro/admin/manage/custom-reasons.json | 16 + .../language/ro/admin/manage/privileges.json | 3 +- .../ro/admin/manage/user-custom-fields.json | 52 +- public/language/ro/admin/manage/users.json | 3 + public/language/ro/admin/menu.json | 2 +- .../ro/admin/settings/activitypub.json | 64 +- public/language/ro/admin/settings/chat.json | 6 +- public/language/ro/admin/settings/email.json | 12 +- .../language/ro/admin/settings/general.json | 4 +- .../ro/admin/settings/navigation.json | 2 +- .../ro/admin/settings/notifications.json | 4 +- public/language/ro/admin/settings/post.json | 6 +- .../language/ro/admin/settings/uploads.json | 10 +- public/language/ro/admin/settings/user.json | 1 + .../ro/admin/settings/web-crawler.json | 1 + public/language/ro/aria.json | 3 +- public/language/ro/category.json | 5 +- public/language/ro/error.json | 7 + public/language/ro/global.json | 2 + public/language/ro/groups.json | 2 + public/language/ro/modules.json | 3 +- public/language/ro/notifications.json | 38 +- public/language/ro/pages.json | 6 +- public/language/ro/post-queue.json | 8 +- public/language/ro/recent.json | 4 +- public/language/ro/search.json | 2 +- public/language/ro/social.json | 6 +- public/language/ro/tags.json | 2 +- public/language/ro/themes/harmony.json | 2 + public/language/ro/topic.json | 48 +- public/language/ro/unread.json | 2 +- public/language/ro/user.json | 4 + public/language/ro/users.json | 2 +- public/language/ro/world.json | 35 +- public/language/ru/admin/advanced/cache.json | 4 - public/language/ru/admin/advanced/events.json | 8 +- public/language/ru/admin/dashboard.json | 1 + .../language/ru/admin/development/info.json | 2 +- .../language/ru/admin/manage/categories.json | 15 +- .../ru/admin/manage/custom-reasons.json | 16 + .../language/ru/admin/manage/privileges.json | 1 + .../ru/admin/manage/user-custom-fields.json | 44 +- public/language/ru/admin/manage/users.json | 3 + .../ru/admin/settings/activitypub.json | 22 + public/language/ru/admin/settings/chat.json | 2 - public/language/ru/admin/settings/email.json | 8 +- .../ru/admin/settings/notifications.json | 4 +- .../language/ru/admin/settings/uploads.json | 6 + public/language/ru/admin/settings/user.json | 1 + .../ru/admin/settings/web-crawler.json | 1 + public/language/ru/aria.json | 3 +- public/language/ru/category.json | 5 +- public/language/ru/error.json | 9 +- public/language/ru/global.json | 12 +- public/language/ru/groups.json | 2 + public/language/ru/modules.json | 3 +- public/language/ru/notifications.json | 40 +- public/language/ru/social.json | 4 +- public/language/ru/themes/harmony.json | 2 + public/language/ru/topic.json | 18 +- public/language/ru/user.json | 4 + public/language/ru/world.json | 35 +- public/language/rw/admin/advanced/cache.json | 4 - public/language/rw/admin/dashboard.json | 1 + .../language/rw/admin/development/info.json | 2 +- .../language/rw/admin/manage/categories.json | 15 +- .../rw/admin/manage/custom-reasons.json | 16 + .../language/rw/admin/manage/privileges.json | 1 + public/language/rw/admin/manage/users.json | 3 + .../rw/admin/settings/activitypub.json | 22 + public/language/rw/admin/settings/chat.json | 2 - public/language/rw/admin/settings/email.json | 8 +- .../rw/admin/settings/notifications.json | 4 +- .../language/rw/admin/settings/uploads.json | 6 + public/language/rw/admin/settings/user.json | 1 + .../rw/admin/settings/web-crawler.json | 1 + public/language/rw/aria.json | 3 +- public/language/rw/category.json | 5 +- public/language/rw/error.json | 7 + public/language/rw/global.json | 2 + public/language/rw/groups.json | 2 + public/language/rw/modules.json | 3 +- public/language/rw/notifications.json | 38 +- public/language/rw/social.json | 4 +- public/language/rw/themes/harmony.json | 2 + public/language/rw/topic.json | 14 +- public/language/rw/user.json | 4 + public/language/rw/world.json | 13 +- public/language/sc/admin/advanced/cache.json | 4 - public/language/sc/admin/dashboard.json | 1 + .../language/sc/admin/development/info.json | 2 +- .../language/sc/admin/manage/categories.json | 15 +- .../sc/admin/manage/custom-reasons.json | 16 + .../language/sc/admin/manage/privileges.json | 1 + public/language/sc/admin/manage/users.json | 3 + .../sc/admin/settings/activitypub.json | 22 + public/language/sc/admin/settings/chat.json | 2 - public/language/sc/admin/settings/email.json | 8 +- .../sc/admin/settings/notifications.json | 4 +- .../language/sc/admin/settings/uploads.json | 6 + public/language/sc/admin/settings/user.json | 1 + .../sc/admin/settings/web-crawler.json | 1 + public/language/sc/aria.json | 3 +- public/language/sc/category.json | 5 +- public/language/sc/error.json | 7 + public/language/sc/global.json | 2 + public/language/sc/groups.json | 2 + public/language/sc/modules.json | 3 +- public/language/sc/notifications.json | 36 +- public/language/sc/social.json | 4 +- public/language/sc/themes/harmony.json | 2 + public/language/sc/topic.json | 14 +- public/language/sc/user.json | 4 + public/language/sc/world.json | 13 +- public/language/sk/admin/advanced/cache.json | 4 - public/language/sk/admin/dashboard.json | 1 + .../language/sk/admin/development/info.json | 2 +- .../language/sk/admin/manage/categories.json | 15 +- .../sk/admin/manage/custom-reasons.json | 16 + .../language/sk/admin/manage/privileges.json | 1 + public/language/sk/admin/manage/users.json | 3 + .../sk/admin/settings/activitypub.json | 22 + public/language/sk/admin/settings/chat.json | 2 - public/language/sk/admin/settings/email.json | 8 +- .../sk/admin/settings/notifications.json | 4 +- .../language/sk/admin/settings/uploads.json | 6 + public/language/sk/admin/settings/user.json | 1 + .../sk/admin/settings/web-crawler.json | 1 + public/language/sk/aria.json | 3 +- public/language/sk/category.json | 5 +- public/language/sk/error.json | 7 + public/language/sk/global.json | 2 + public/language/sk/groups.json | 2 + public/language/sk/modules.json | 3 +- public/language/sk/notifications.json | 40 +- public/language/sk/social.json | 4 +- public/language/sk/themes/harmony.json | 2 + public/language/sk/topic.json | 14 +- public/language/sk/user.json | 4 + public/language/sk/world.json | 13 +- public/language/sl/admin/advanced/cache.json | 4 - public/language/sl/admin/dashboard.json | 1 + .../language/sl/admin/development/info.json | 2 +- .../language/sl/admin/manage/categories.json | 15 +- .../sl/admin/manage/custom-reasons.json | 16 + .../language/sl/admin/manage/privileges.json | 1 + public/language/sl/admin/manage/users.json | 3 + .../sl/admin/settings/activitypub.json | 22 + public/language/sl/admin/settings/chat.json | 2 - public/language/sl/admin/settings/email.json | 8 +- .../sl/admin/settings/notifications.json | 4 +- .../language/sl/admin/settings/uploads.json | 6 + public/language/sl/admin/settings/user.json | 1 + .../sl/admin/settings/web-crawler.json | 1 + public/language/sl/aria.json | 3 +- public/language/sl/category.json | 5 +- public/language/sl/error.json | 7 + public/language/sl/global.json | 2 + public/language/sl/groups.json | 2 + public/language/sl/modules.json | 3 +- public/language/sl/notifications.json | 40 +- public/language/sl/social.json | 4 +- public/language/sl/themes/harmony.json | 2 + public/language/sl/topic.json | 14 +- public/language/sl/user.json | 4 + public/language/sl/world.json | 13 +- .../language/sq-AL/admin/advanced/cache.json | 4 - public/language/sq-AL/admin/dashboard.json | 1 + .../sq-AL/admin/development/info.json | 2 +- .../sq-AL/admin/manage/categories.json | 15 +- .../sq-AL/admin/manage/custom-reasons.json | 16 + .../sq-AL/admin/manage/privileges.json | 1 + public/language/sq-AL/admin/manage/users.json | 3 + .../sq-AL/admin/settings/activitypub.json | 22 + .../language/sq-AL/admin/settings/chat.json | 2 - .../language/sq-AL/admin/settings/email.json | 8 +- .../sq-AL/admin/settings/notifications.json | 4 +- .../sq-AL/admin/settings/uploads.json | 6 + .../language/sq-AL/admin/settings/user.json | 1 + .../sq-AL/admin/settings/web-crawler.json | 1 + public/language/sq-AL/aria.json | 3 +- public/language/sq-AL/category.json | 5 +- public/language/sq-AL/error.json | 7 + public/language/sq-AL/global.json | 2 + public/language/sq-AL/groups.json | 2 + public/language/sq-AL/modules.json | 3 +- public/language/sq-AL/notifications.json | 40 +- public/language/sq-AL/social.json | 4 +- public/language/sq-AL/themes/harmony.json | 2 + public/language/sq-AL/topic.json | 14 +- public/language/sq-AL/user.json | 4 + public/language/sq-AL/world.json | 13 +- public/language/sr/admin/advanced/cache.json | 4 - public/language/sr/admin/dashboard.json | 1 + .../language/sr/admin/development/info.json | 2 +- .../language/sr/admin/manage/categories.json | 15 +- .../sr/admin/manage/custom-reasons.json | 16 + .../language/sr/admin/manage/privileges.json | 1 + public/language/sr/admin/manage/users.json | 3 + .../sr/admin/settings/activitypub.json | 22 + public/language/sr/admin/settings/chat.json | 2 - public/language/sr/admin/settings/email.json | 8 +- .../sr/admin/settings/notifications.json | 4 +- .../language/sr/admin/settings/uploads.json | 6 + public/language/sr/admin/settings/user.json | 1 + .../sr/admin/settings/web-crawler.json | 1 + public/language/sr/aria.json | 3 +- public/language/sr/category.json | 5 +- public/language/sr/error.json | 7 + public/language/sr/global.json | 2 + public/language/sr/groups.json | 2 + public/language/sr/modules.json | 3 +- public/language/sr/notifications.json | 44 +- public/language/sr/social.json | 4 +- public/language/sr/themes/harmony.json | 2 + public/language/sr/topic.json | 14 +- public/language/sr/user.json | 4 + public/language/sr/world.json | 13 +- public/language/sv/admin/advanced/cache.json | 4 - public/language/sv/admin/dashboard.json | 1 + .../language/sv/admin/development/info.json | 2 +- .../language/sv/admin/manage/categories.json | 15 +- .../sv/admin/manage/custom-reasons.json | 16 + .../language/sv/admin/manage/privileges.json | 1 + public/language/sv/admin/manage/users.json | 3 + .../sv/admin/settings/activitypub.json | 22 + public/language/sv/admin/settings/chat.json | 2 - public/language/sv/admin/settings/email.json | 8 +- .../sv/admin/settings/notifications.json | 4 +- .../language/sv/admin/settings/uploads.json | 6 + public/language/sv/admin/settings/user.json | 1 + .../sv/admin/settings/web-crawler.json | 1 + public/language/sv/aria.json | 3 +- public/language/sv/category.json | 5 +- public/language/sv/error.json | 7 + public/language/sv/global.json | 2 + public/language/sv/groups.json | 2 + public/language/sv/modules.json | 3 +- public/language/sv/notifications.json | 40 +- public/language/sv/social.json | 4 +- public/language/sv/themes/harmony.json | 2 + public/language/sv/topic.json | 14 +- public/language/sv/user.json | 4 + public/language/sv/world.json | 13 +- public/language/th/admin/advanced/cache.json | 4 - public/language/th/admin/dashboard.json | 1 + .../language/th/admin/development/info.json | 2 +- .../language/th/admin/manage/categories.json | 15 +- .../th/admin/manage/custom-reasons.json | 16 + .../language/th/admin/manage/privileges.json | 1 + public/language/th/admin/manage/users.json | 3 + .../th/admin/settings/activitypub.json | 22 + public/language/th/admin/settings/chat.json | 2 - public/language/th/admin/settings/email.json | 8 +- .../th/admin/settings/notifications.json | 4 +- .../language/th/admin/settings/uploads.json | 6 + public/language/th/admin/settings/user.json | 1 + .../th/admin/settings/web-crawler.json | 1 + public/language/th/aria.json | 3 +- public/language/th/category.json | 5 +- public/language/th/error.json | 7 + public/language/th/global.json | 2 + public/language/th/groups.json | 2 + public/language/th/modules.json | 3 +- public/language/th/notifications.json | 44 +- public/language/th/social.json | 4 +- public/language/th/themes/harmony.json | 2 + public/language/th/topic.json | 14 +- public/language/th/user.json | 4 + public/language/th/world.json | 13 +- public/language/tr/admin/advanced/cache.json | 4 - public/language/tr/admin/dashboard.json | 1 + .../language/tr/admin/development/info.json | 2 +- .../language/tr/admin/manage/categories.json | 15 +- .../tr/admin/manage/custom-reasons.json | 16 + .../language/tr/admin/manage/privileges.json | 1 + public/language/tr/admin/manage/users.json | 3 + .../tr/admin/settings/activitypub.json | 22 + public/language/tr/admin/settings/chat.json | 2 - public/language/tr/admin/settings/email.json | 8 +- .../tr/admin/settings/notifications.json | 4 +- .../language/tr/admin/settings/uploads.json | 6 + public/language/tr/admin/settings/user.json | 1 + .../tr/admin/settings/web-crawler.json | 1 + public/language/tr/aria.json | 3 +- public/language/tr/category.json | 5 +- public/language/tr/error.json | 7 + public/language/tr/global.json | 2 + public/language/tr/groups.json | 2 + public/language/tr/modules.json | 3 +- public/language/tr/notifications.json | 44 +- public/language/tr/social.json | 4 +- public/language/tr/themes/harmony.json | 2 + public/language/tr/topic.json | 14 +- public/language/tr/user.json | 4 + public/language/tr/world.json | 13 +- public/language/uk/admin/advanced/cache.json | 4 - public/language/uk/admin/dashboard.json | 1 + .../language/uk/admin/development/info.json | 2 +- .../language/uk/admin/manage/categories.json | 15 +- .../uk/admin/manage/custom-reasons.json | 16 + .../language/uk/admin/manage/privileges.json | 1 + public/language/uk/admin/manage/users.json | 3 + .../uk/admin/settings/activitypub.json | 22 + public/language/uk/admin/settings/chat.json | 2 - public/language/uk/admin/settings/email.json | 8 +- .../uk/admin/settings/notifications.json | 4 +- .../language/uk/admin/settings/uploads.json | 6 + public/language/uk/admin/settings/user.json | 1 + .../uk/admin/settings/web-crawler.json | 1 + public/language/uk/aria.json | 3 +- public/language/uk/category.json | 5 +- public/language/uk/error.json | 7 + public/language/uk/global.json | 2 + public/language/uk/groups.json | 2 + public/language/uk/modules.json | 3 +- public/language/uk/notifications.json | 40 +- public/language/uk/social.json | 4 +- public/language/uk/themes/harmony.json | 2 + public/language/uk/topic.json | 14 +- public/language/uk/user.json | 4 + public/language/uk/world.json | 13 +- public/language/ur/_DO_NOT_EDIT_FILES_HERE.md | 3 + public/language/ur/admin/admin.json | 18 + public/language/ur/admin/advanced/cache.json | 6 + .../language/ur/admin/advanced/database.json | 52 + public/language/ur/admin/advanced/errors.json | 15 + public/language/ur/admin/advanced/events.json | 17 + public/language/ur/admin/advanced/logs.json | 7 + .../ur/admin/appearance/customise.json | 20 + .../language/ur/admin/appearance/skins.json | 18 + .../language/ur/admin/appearance/themes.json | 13 + public/language/ur/admin/dashboard.json | 102 + .../language/ur/admin/development/info.json | 26 + .../language/ur/admin/development/logger.json | 13 + public/language/ur/admin/extend/plugins.json | 58 + public/language/ur/admin/extend/rewards.json | 17 + public/language/ur/admin/extend/widgets.json | 37 + .../language/ur/admin/manage/admins-mods.json | 13 + .../language/ur/admin/manage/categories.json | 131 ++ .../ur/admin/manage/custom-reasons.json | 16 + public/language/ur/admin/manage/digest.json | 22 + public/language/ur/admin/manage/groups.json | 49 + .../language/ur/admin/manage/privileges.json | 67 + .../ur/admin/manage/registration.json | 20 + public/language/ur/admin/manage/tags.json | 20 + public/language/ur/admin/manage/uploads.json | 12 + .../ur/admin/manage/user-custom-fields.json | 28 + public/language/ur/admin/manage/users.json | 155 ++ public/language/ur/admin/menu.json | 93 + .../ur/admin/settings/activitypub.json | 48 + .../language/ur/admin/settings/advanced.json | 47 + public/language/ur/admin/settings/api.json | 29 + public/language/ur/admin/settings/chat.json | 15 + .../language/ur/admin/settings/cookies.json | 13 + public/language/ur/admin/settings/email.json | 60 + .../language/ur/admin/settings/general.json | 63 + public/language/ur/admin/settings/group.json | 13 + .../ur/admin/settings/navigation.json | 26 + .../ur/admin/settings/notifications.json | 9 + .../ur/admin/settings/pagination.json | 12 + public/language/ur/admin/settings/post.json | 64 + .../ur/admin/settings/reputation.json | 43 + .../language/ur/admin/settings/sockets.json | 6 + public/language/ur/admin/settings/sounds.json | 9 + public/language/ur/admin/settings/tags.json | 13 + .../language/ur/admin/settings/uploads.json | 52 + public/language/ur/admin/settings/user.json | 98 + .../ur/admin/settings/web-crawler.json | 11 + public/language/ur/aria.json | 10 + public/language/ur/category.json | 30 + public/language/ur/email.json | 61 + public/language/ur/error.json | 269 +++ public/language/ur/flags.json | 101 + public/language/ur/global.json | 155 ++ public/language/ur/groups.json | 68 + public/language/ur/ip-blacklist.json | 19 + public/language/ur/language.json | 5 + public/language/ur/login.json | 12 + public/language/ur/modules.json | 135 ++ public/language/ur/notifications.json | 106 + public/language/ur/pages.json | 71 + public/language/ur/post-queue.json | 43 + public/language/ur/recent.json | 13 + public/language/ur/register.json | 33 + public/language/ur/reset_password.json | 18 + public/language/ur/rewards.json | 10 + public/language/ur/search.json | 110 + public/language/ur/social.json | 14 + public/language/ur/success.json | 7 + public/language/ur/tags.json | 17 + public/language/ur/themes/harmony.json | 25 + public/language/ur/themes/persona.json | 10 + public/language/ur/top.json | 4 + public/language/ur/topic.json | 240 ++ public/language/ur/unread.json | 16 + public/language/ur/uploads.json | 9 + public/language/ur/user.json | 234 ++ public/language/ur/users.json | 26 + public/language/ur/world.json | 25 + public/language/vi/admin/admin.json | 2 +- public/language/vi/admin/advanced/cache.json | 4 - public/language/vi/admin/dashboard.json | 1 + .../language/vi/admin/development/info.json | 2 +- .../language/vi/admin/manage/admins-mods.json | 2 +- .../language/vi/admin/manage/categories.json | 17 +- .../vi/admin/manage/custom-reasons.json | 16 + .../language/vi/admin/manage/privileges.json | 3 +- public/language/vi/admin/manage/users.json | 5 +- .../vi/admin/settings/activitypub.json | 22 + public/language/vi/admin/settings/api.json | 2 +- public/language/vi/admin/settings/chat.json | 2 - public/language/vi/admin/settings/email.json | 8 +- .../language/vi/admin/settings/general.json | 4 +- .../vi/admin/settings/notifications.json | 4 +- .../language/vi/admin/settings/uploads.json | 6 + public/language/vi/admin/settings/user.json | 5 +- .../vi/admin/settings/web-crawler.json | 1 + public/language/vi/aria.json | 3 +- public/language/vi/category.json | 5 +- public/language/vi/error.json | 25 +- public/language/vi/flags.json | 2 +- public/language/vi/global.json | 16 +- public/language/vi/groups.json | 22 +- public/language/vi/modules.json | 15 +- public/language/vi/notifications.json | 48 +- public/language/vi/pages.json | 20 +- public/language/vi/post-queue.json | 2 +- public/language/vi/reset_password.json | 4 +- public/language/vi/search.json | 8 +- public/language/vi/social.json | 4 +- public/language/vi/themes/harmony.json | 2 + public/language/vi/topic.json | 34 +- public/language/vi/user.json | 8 +- public/language/vi/users.json | 2 +- public/language/vi/world.json | 13 +- .../language/zh-CN/admin/advanced/cache.json | 4 - public/language/zh-CN/admin/dashboard.json | 1 + .../zh-CN/admin/development/info.json | 2 +- .../zh-CN/admin/manage/categories.json | 15 +- .../zh-CN/admin/manage/custom-reasons.json | 16 + .../zh-CN/admin/manage/privileges.json | 1 + public/language/zh-CN/admin/manage/users.json | 3 + .../zh-CN/admin/settings/activitypub.json | 22 + .../language/zh-CN/admin/settings/chat.json | 10 +- .../language/zh-CN/admin/settings/email.json | 8 +- .../zh-CN/admin/settings/notifications.json | 4 +- .../zh-CN/admin/settings/uploads.json | 12 +- .../language/zh-CN/admin/settings/user.json | 1 + .../zh-CN/admin/settings/web-crawler.json | 1 + public/language/zh-CN/aria.json | 13 +- public/language/zh-CN/category.json | 19 +- public/language/zh-CN/email.json | 22 +- public/language/zh-CN/error.json | 21 +- public/language/zh-CN/global.json | 18 +- public/language/zh-CN/groups.json | 4 +- public/language/zh-CN/modules.json | 41 +- public/language/zh-CN/notifications.json | 44 +- public/language/zh-CN/post-queue.json | 2 +- public/language/zh-CN/register.json | 2 +- public/language/zh-CN/search.json | 18 +- public/language/zh-CN/social.json | 6 +- public/language/zh-CN/themes/harmony.json | 2 + public/language/zh-CN/themes/persona.json | 2 +- public/language/zh-CN/topic.json | 20 +- public/language/zh-CN/user.json | 16 +- public/language/zh-CN/world.json | 13 +- .../language/zh-TW/admin/advanced/cache.json | 4 - public/language/zh-TW/admin/dashboard.json | 1 + .../zh-TW/admin/development/info.json | 2 +- .../zh-TW/admin/manage/categories.json | 15 +- .../zh-TW/admin/manage/custom-reasons.json | 16 + .../zh-TW/admin/manage/privileges.json | 1 + public/language/zh-TW/admin/manage/users.json | 3 + public/language/zh-TW/admin/menu.json | 26 +- .../zh-TW/admin/settings/activitypub.json | 46 +- .../language/zh-TW/admin/settings/chat.json | 2 - .../language/zh-TW/admin/settings/email.json | 8 +- .../zh-TW/admin/settings/notifications.json | 4 +- .../zh-TW/admin/settings/uploads.json | 6 + .../language/zh-TW/admin/settings/user.json | 1 + .../zh-TW/admin/settings/web-crawler.json | 1 + public/language/zh-TW/aria.json | 3 +- public/language/zh-TW/category.json | 5 +- public/language/zh-TW/error.json | 7 + public/language/zh-TW/global.json | 2 + public/language/zh-TW/groups.json | 2 + public/language/zh-TW/modules.json | 3 +- public/language/zh-TW/notifications.json | 40 +- public/language/zh-TW/social.json | 4 +- public/language/zh-TW/themes/harmony.json | 2 + public/language/zh-TW/topic.json | 14 +- public/language/zh-TW/user.json | 4 + public/language/zh-TW/world.json | 13 +- .../components/schemas/CategoryObject.yaml | 3 + public/openapi/components/schemas/Chats.yaml | 8 + .../components/schemas/CrosspostObject.yaml | 52 + .../components/schemas/PostObject.yaml | 6 + .../components/schemas/PostsObject.yaml | 13 +- .../components/schemas/SettingsObj.yaml | 18 +- .../components/schemas/TopicObject.yaml | 17 +- .../components/schemas/UserObject.yaml | 4 + .../components/schemas/admin/relays.yaml | 18 + .../components/schemas/admin/rules.yaml | 23 + public/openapi/read.yaml | 6 + public/openapi/read/admin/analytics.yaml | 4 + public/openapi/read/admin/config.yaml | 2 + .../openapi/read/admin/development/info.yaml | 7 +- .../openapi/read/admin/manage/categories.yaml | 8 + .../read/admin/manage/privileges/cid.yaml | 13 - .../admin/manage/users/custom-reasons.yaml | 28 + .../read/admin/settings/activitypub.yaml | 4 + public/openapi/read/admin/settings/email.yaml | 4 + public/openapi/read/ap.yaml | 6 +- public/openapi/read/category/category_id.yaml | 3 + public/openapi/read/config.yaml | 2 + public/openapi/read/confirm/code.yaml | 3 - public/openapi/read/ping.yaml | 13 + public/openapi/read/popular.yaml | 2 + public/openapi/read/post-queue.yaml | 4 +- public/openapi/read/recent.yaml | 2 + public/openapi/read/sping.yaml | 13 + public/openapi/read/top.yaml | 2 + public/openapi/read/topic/topic_id.yaml | 4 + public/openapi/read/unread.yaml | 11 + .../read/user/userslug/categories.yaml | 2 + .../read/user/userslug/chats/roomid.yaml | 6 + public/openapi/read/world.yaml | 143 +- public/openapi/write.yaml | 20 + .../write/admin/activitypub/relays.yaml | 28 + .../write/admin/activitypub/relays/url.yaml | 25 + .../write/admin/activitypub/rules.yaml | 36 + .../write/admin/activitypub/rules/order.yaml | 30 + .../write/admin/activitypub/rules/rid.yaml | 25 + .../write/categories/cid/moderator/uid.yaml | 19 +- .../write/categories/cid/privileges.yaml | 15 - .../categories/cid/privileges/privilege.yaml | 30 - .../openapi/write/categories/cid/topics.yaml | 1 - .../write/chats/roomId/messages/mid.yaml | 6 +- .../write/chats/roomId/messages/mid/ip.yaml | 2 +- public/openapi/write/posts/owner.yaml | 39 + public/openapi/write/posts/pid/diffs.yaml | 6 + public/openapi/write/posts/pid/owner.yaml | 39 + public/openapi/write/posts/queue/id.yaml | 92 + public/openapi/write/posts/queue/notify.yaml | 36 + .../openapi/write/topics/tid/crossposts.yaml | 104 + public/openapi/write/topics/tid/thumbs.yaml | 49 - .../write/topics/tid/thumbs/order.yaml | 4 +- public/scss/admin/fonts.scss | 28 +- public/scss/generics.scss | 42 +- public/scss/jquery-ui.scss | 1 - public/scss/mixins.scss | 22 + public/scss/modules/bottom-sheet.scss | 1 + public/scss/skins.scss | 9 +- public/src/admin/admin.js | 46 +- public/src/admin/advanced/cache.js | 40 + public/src/admin/dashboard.js | 74 +- public/src/admin/extend/plugins.js | 5 +- public/src/admin/extend/rewards.js | 18 +- public/src/admin/extend/widgets.js | 38 +- public/src/admin/manage/categories.js | 70 +- public/src/admin/manage/group.js | 2 +- public/src/admin/manage/groups.js | 9 +- public/src/admin/manage/privileges.js | 1 + public/src/admin/manage/users.js | 75 +- .../src/admin/manage/users/custom-reasons.js | 97 + public/src/admin/modules/relogin-timer.js | 36 + public/src/admin/settings.js | 20 +- public/src/admin/settings/activitypub.js | 193 ++ public/src/admin/settings/cookies.js | 6 +- public/src/admin/settings/email.js | 32 +- public/src/admin/settings/general.js | 6 +- public/src/ajaxify.js | 99 +- public/src/app.js | 2 - public/src/client/account/settings.js | 89 +- public/src/client/category.js | 23 +- public/src/client/category/tools.js | 20 +- public/src/client/chats.js | 166 +- public/src/client/chats/events.js | 117 + public/src/client/chats/manage.js | 4 + public/src/client/chats/messages.js | 113 +- public/src/client/flags/list.js | 6 +- public/src/client/groups/details.js | 1 + public/src/client/groups/list.js | 37 +- public/src/client/header.js | 28 +- public/src/client/infinitescroll.js | 2 +- public/src/client/notifications.js | 2 +- public/src/client/post-queue.js | 176 +- public/src/client/register.js | 11 +- public/src/client/topic.js | 144 +- public/src/client/topic/change-owner.js | 11 +- public/src/client/topic/crosspost.js | 153 ++ public/src/client/topic/delete-posts.js | 2 + public/src/client/topic/events.js | 20 +- public/src/client/topic/move.js | 32 +- public/src/client/topic/postTools.js | 4 +- public/src/client/topic/threadTools.js | 8 +- public/src/client/unread.js | 14 +- public/src/client/world.js | 183 +- public/src/modules/accounts/moderate.js | 61 +- public/src/modules/autocomplete.js | 5 +- public/src/modules/categorySearch.js | 8 +- public/src/modules/chat.js | 120 +- public/src/modules/helpers.common.js | 52 +- public/src/modules/iconSelect.js | 4 - public/src/modules/messages.js | 20 +- public/src/modules/navigator.js | 10 +- public/src/modules/notifications.js | 21 +- public/src/modules/pictureCropper.js | 10 +- public/src/modules/quickreply.js | 11 +- public/src/modules/scrollStop.js | 6 +- public/src/modules/search.js | 82 +- public/src/modules/settings.js | 13 +- public/src/modules/settings/array.js | 17 +- public/src/modules/settings/object.js | 58 +- public/src/modules/settings/sorted-list.js | 1 - public/src/modules/share.js | 6 +- public/src/modules/slugify.js | 16 +- public/src/modules/sort.js | 6 +- public/src/modules/topicList.js | 13 +- public/src/modules/topicSelect.js | 4 +- public/src/modules/topicThumbs.js | 78 +- public/src/modules/translator.common.js | 4 +- public/src/modules/uploadHelpers.js | 83 +- public/src/modules/uploader.js | 16 +- public/src/utils.common.js | 43 +- renovate.json | 5 +- src/activitypub/actors.js | 439 +++- src/activitypub/contexts.js | 35 +- src/activitypub/feps.js | 62 +- src/activitypub/helpers.js | 107 +- src/activitypub/inbox.js | 332 ++- src/activitypub/index.js | 456 ++-- src/activitypub/instances.js | 2 +- src/activitypub/jobs.js | 114 + src/activitypub/mocks.js | 352 ++- src/activitypub/notes.js | 557 +++-- src/activitypub/out.js | 619 +++++ src/activitypub/relays.js | 126 + src/activitypub/rules.js | 52 + src/admin/search.js | 24 +- src/analytics.js | 24 +- src/api/activitypub.js | 441 ---- src/api/categories.js | 12 +- src/api/chats.js | 12 +- src/api/helpers.js | 67 +- src/api/index.js | 1 - src/api/posts.js | 159 +- src/api/search.js | 10 +- src/api/topics.js | 44 +- src/api/users.js | 13 +- src/cache/lru.js | 8 + src/cache/tracker.js | 59 + src/cache/ttl.js | 12 +- src/categories/create.js | 11 +- src/categories/data.js | 42 +- src/categories/delete.js | 7 +- src/categories/icon.js | 8 +- src/categories/index.js | 37 +- src/categories/recentreplies.js | 30 +- src/categories/search.js | 22 +- src/categories/topics.js | 46 +- src/categories/update.js | 16 +- src/categories/watch.js | 7 +- src/cli/index.js | 17 +- src/controllers/accounts/edit.js | 2 +- src/controllers/accounts/helpers.js | 7 - src/controllers/accounts/profile.js | 24 +- src/controllers/accounts/settings.js | 11 + src/controllers/activitypub/actors.js | 64 +- src/controllers/activitypub/index.js | 98 +- src/controllers/activitypub/topics.js | 126 +- src/controllers/admin/cache.js | 53 +- src/controllers/admin/categories.js | 73 +- src/controllers/admin/dashboard.js | 4 +- src/controllers/admin/events.js | 9 +- src/controllers/admin/info.js | 13 +- src/controllers/admin/logs.js | 3 +- src/controllers/admin/settings.js | 18 +- src/controllers/admin/uploads.js | 93 +- src/controllers/admin/users.js | 7 +- src/controllers/api.js | 1 + src/controllers/authentication.js | 48 +- src/controllers/category.js | 42 +- src/controllers/helpers.js | 8 +- src/controllers/home.js | 8 +- src/controllers/index.js | 6 - src/controllers/mods.js | 6 +- src/controllers/ping.js | 2 +- src/controllers/recent.js | 5 +- src/controllers/topics.js | 55 +- src/controllers/unread.js | 1 + src/controllers/uploads.js | 19 +- src/controllers/write/admin.js | 41 + src/controllers/write/categories.js | 15 +- src/controllers/write/posts.js | 29 + src/controllers/write/topics.js | 37 +- src/controllers/write/users.js | 13 +- src/database/helpers.js | 2 +- src/database/mongo.js | 2 +- src/database/mongo/hash.js | 8 +- src/database/mongo/sets.js | 41 +- src/database/mongo/sorted.js | 2 +- src/database/postgres.js | 4 +- src/database/postgres/connection.js | 13 + src/database/postgres/hash.js | 2 +- src/database/postgres/main.js | 18 +- src/database/postgres/sets.js | 26 + src/database/postgres/sorted.js | 6 +- src/database/redis.js | 9 +- src/database/redis/connection.js | 51 +- src/database/redis/hash.js | 50 +- src/database/redis/helpers.js | 40 +- src/database/redis/list.js | 22 +- src/database/redis/main.js | 22 +- src/database/redis/pubsub.js | 7 +- src/database/redis/sets.js | 44 +- src/database/redis/sorted.js | 241 +- src/database/redis/sorted/add.js | 24 +- src/database/redis/sorted/intersect.js | 30 +- src/database/redis/sorted/remove.js | 8 +- src/database/redis/sorted/union.js | 26 +- src/emailer.js | 91 +- src/events.js | 3 +- src/file.js | 40 + src/flags.js | 24 +- src/groups/data.js | 85 +- src/groups/invite.js | 1 - src/image.js | 5 +- src/install.js | 16 +- src/messaging/data.js | 5 +- src/messaging/delete.js | 4 +- src/messaging/edit.js | 4 +- src/messaging/index.js | 18 +- src/messaging/notifications.js | 4 +- src/messaging/rooms.js | 57 +- src/meta/css.js | 14 +- src/meta/dependencies.js | 2 +- src/meta/errors.js | 2 +- src/meta/minifier.js | 5 +- src/meta/tags.js | 4 +- src/middleware/activitypub.js | 38 +- src/middleware/index.js | 87 +- src/middleware/multer.js | 17 + src/middleware/render.js | 11 +- src/middleware/uploads.js | 4 +- src/middleware/user.js | 12 +- src/notifications.js | 74 +- src/plugins/hooks.js | 106 +- src/plugins/index.js | 8 +- src/plugins/install.js | 20 +- src/plugins/usage.js | 2 +- src/posts/cache.js | 2 +- src/posts/create.js | 43 +- src/posts/data.js | 24 +- src/posts/delete.js | 24 +- src/posts/edit.js | 38 +- src/posts/parse.js | 14 +- src/posts/queue.js | 19 +- src/posts/summary.js | 2 +- src/posts/uploads.js | 42 +- src/posts/votes.js | 33 +- src/prestart.js | 18 +- src/privileges/admin.js | 10 +- src/privileges/categories.js | 24 +- src/privileges/global.js | 13 +- src/privileges/helpers.js | 40 +- src/privileges/posts.js | 2 +- src/privileges/topics.js | 11 +- src/request.js | 111 +- src/routes/activitypub.js | 49 +- src/routes/admin.js | 14 +- src/routes/api.js | 7 +- src/routes/authentication.js | 18 +- src/routes/feeds.js | 80 +- src/routes/helpers.js | 4 +- src/routes/well-known.js | 3 + src/routes/write/admin.js | 6 + src/routes/write/posts.js | 8 + src/routes/write/topics.js | 17 +- src/search.js | 10 +- src/sitemap.js | 16 +- src/socket.io/admin.js | 5 + src/socket.io/admin/analytics.js | 17 +- src/socket.io/admin/cache.js | 29 +- src/socket.io/admin/email.js | 120 +- src/socket.io/admin/plugins.js | 6 + src/socket.io/admin/user.js | 19 +- src/socket.io/helpers.js | 17 +- src/socket.io/notifications.js | 5 +- src/socket.io/posts.js | 91 +- src/socket.io/posts/tools.js | 22 +- src/socket.io/topics/move.js | 2 +- src/socket.io/topics/tags.js | 8 +- src/socket.io/topics/tools.js | 11 +- src/socket.io/topics/unread.js | 5 + src/socket.io/user.js | 8 + src/start.js | 27 +- src/topics/create.js | 19 +- src/topics/crossposts.js | 173 ++ src/topics/data.js | 43 +- src/topics/delete.js | 14 +- src/topics/events.js | 27 +- src/topics/follow.js | 25 +- src/topics/index.js | 4 +- src/topics/posts.js | 32 +- src/topics/recent.js | 3 +- src/topics/scheduled.js | 4 +- src/topics/sorted.js | 43 +- src/topics/tags.js | 4 +- src/topics/teaser.js | 2 +- src/topics/thumbs.js | 276 ++- src/topics/tools.js | 8 +- src/topics/unread.js | 31 +- src/translator.js | 2 +- src/upgrades/1.10.0/view_deleted_privilege.js | 1 + .../1.10.2/fix_category_topic_zsets.js | 24 +- src/upgrades/1.10.2/upgrade_bans_to_hashes.js | 34 +- src/upgrades/1.10.2/username_email_history.js | 45 +- .../1.12.1/clear_username_email_history.js | 53 +- .../1.12.1/moderation_notes_refactor.js | 10 +- src/upgrades/1.13.0/clean_post_topic_hash.js | 3 +- .../1.6.2/topics_lastposttime_zset.js | 35 +- src/upgrades/1.7.1/notification-settings.js | 35 +- src/upgrades/1.7.3/topic_votes.js | 56 +- src/upgrades/1.8.1/diffs_zset_to_listhash.js | 63 +- .../1.9.0/refresh_post_upload_associations.js | 19 +- src/upgrades/2.8.7/fix-email-sorted-sets.js | 2 +- src/upgrades/3.7.0/category-read-by-uid.js | 2 +- src/upgrades/4.3.0/chat_allow_list.js | 44 + src/upgrades/4.3.0/fix_duplicate_handles.js | 89 + .../4.3.0/normalize_thumbs_uploads.js | 44 +- src/upgrades/4.3.0/topic_follower_counts.js | 35 + .../4.3.2/fix_category_sync_null_values.js | 12 + src/upgrades/4.5.0/post-uploads-to-hash.js | 39 + src/upgrades/4.5.0/topic-thumbs-to-hash.js | 39 + .../4.7.1/remove_extraneous_ap_data.js | 24 + .../4.8.1/clean_ap_tids_from_topic_zsets.js | 48 + src/upgrades/4.9.0/crosspost_privilege.js | 18 + src/user/admin.js | 10 +- src/user/approval.js | 58 +- src/user/bans.js | 34 +- src/user/blocks.js | 2 +- src/user/categories.js | 17 +- src/user/create.js | 49 +- src/user/data.js | 98 +- src/user/delete.js | 12 +- src/user/digest.js | 71 +- src/user/email.js | 2 +- src/user/follow.js | 13 +- src/user/index.js | 13 +- src/user/invite.js | 6 + src/user/profile.js | 30 +- src/user/search.js | 20 +- src/user/settings.js | 34 +- src/utils.js | 20 +- src/views/admin/advanced/cache.tpl | 110 +- src/views/admin/development/info.tpl | 6 +- src/views/admin/manage/categories.tpl | 11 +- src/views/admin/manage/category.tpl | 8 +- src/views/admin/manage/group.tpl | 4 +- src/views/admin/manage/registration.tpl | 5 +- src/views/admin/manage/users.tpl | 16 +- .../admin/manage/users/custom-fields.tpl | 2 +- .../admin/manage/users/custom-reasons.tpl | 51 + .../admin/partials/activitypub/relays.tpl | 11 + .../admin/partials/activitypub/rules.tpl | 27 + src/views/admin/partials/categories/add.tpl | 12 + .../partials/categories/category-rows.tpl | 11 +- .../partials/categories/select-category.tpl | 6 +- .../category/selector-dropdown-content.tpl | 13 +- .../category/selector-dropdown-left.tpl | 2 +- .../category/selector-dropdown-right.tpl | 2 +- .../admin/partials/create_group_modal.tpl | 1 - .../partials/manage-custom-reasons-modal.tpl | 22 + .../admin/partials/manage_user_groups.tpl | 2 +- src/views/admin/settings/activitypub.tpl | 71 + src/views/admin/settings/api.tpl | 186 +- src/views/admin/settings/chat.tpl | 6 - src/views/admin/settings/email.tpl | 18 +- src/views/admin/settings/general.tpl | 8 +- src/views/admin/settings/notifications.tpl | 6 +- src/views/admin/settings/uploads.tpl | 15 + src/views/admin/settings/user.tpl | 4 +- src/views/admin/settings/web-crawler.tpl | 5 + src/views/chat.tpl | 2 +- src/views/chats.tpl | 5 +- src/views/confirm.tpl | 7 +- src/views/emails/banned.tpl | 4 +- src/views/flags/detail.tpl | 4 +- src/views/modals/crosspost-topic.tpl | 16 + src/views/modals/crossposts.tpl | 10 + src/views/modals/manage-room.tpl | 8 +- src/views/modals/temporary-ban.tpl | 23 +- src/views/modals/temporary-mute.tpl | 37 +- src/views/modals/topic-thumbs-view.tpl | 8 +- src/views/modals/topic-thumbs.tpl | 6 +- src/views/outgoing.tpl | 23 +- .../category/filter-dropdown-content.tpl | 2 + .../category/filter-dropdown-left.tpl | 2 +- .../category/filter-dropdown-right.tpl | 2 +- .../category/selector-dropdown-left.tpl | 2 +- .../category/selector-dropdown-right.tpl | 2 +- src/views/partials/category/sort.tpl | 2 +- .../category/tools-dropdown-content.tpl | 10 +- .../partials/category/tools-dropdown-left.tpl | 2 +- .../category/tools-dropdown-right.tpl | 2 +- src/views/partials/category/watch.tpl | 2 +- src/views/partials/chats/message-window.tpl | 2 +- src/views/partials/chats/options.tpl | 6 +- src/views/partials/chats/parent.tpl | 1 - src/views/partials/custom-reason.tpl | 20 + src/views/partials/feed/item.tpl | 96 + src/views/partials/flags/filters.tpl | 10 +- .../partials/tags/filter-dropdown-left.tpl | 2 +- .../partials/tags/filter-dropdown-right.tpl | 2 +- src/views/partials/tags/watch.tpl | 2 +- src/views/partials/topic/guest-cta.tpl | 22 + src/views/partials/topic/post-menu-list.tpl | 156 ++ src/views/partials/topic/post-parent.tpl | 3 +- src/views/partials/topic/post-preview.tpl | 15 +- src/views/partials/topic/topic-menu-list.tpl | 76 + src/views/post-queue.tpl | 299 ++- src/webserver.js | 27 +- test/activitypub.js | 50 - test/activitypub/actors.js | 437 +++- test/activitypub/analytics.js | 6 +- test/activitypub/feps.js | 254 +- test/activitypub/helpers.js | 183 +- test/activitypub/notes.js | 682 +++++- test/activitypub/out.js | 154 ++ test/activitypub/privileges.js | 443 ++++ test/api.js | 35 +- test/authentication.js | 30 +- test/categories.js | 9 +- test/controllers-admin.js | 2 +- test/controllers.js | 102 +- test/database/hash.js | 16 + test/database/sets.js | 103 +- test/database/sorted.js | 79 +- test/emailer.js | 5 +- test/files/dirty.svg | 4 + test/files/测试.jpg | Bin 0 -> 1010 bytes test/flags.js | 11 +- test/groups.js | 32 +- test/helpers/index.js | 2 +- test/i18n.js | 2 +- test/messaging.js | 62 +- test/meta.js | 13 +- test/mocks/databasemock.js | 27 +- test/notifications.js | 37 +- test/posts.js | 68 +- test/posts/uploads.js | 22 +- test/slugify.js | 164 ++ test/socket.io.js | 6 +- test/template-helpers.js | 4 +- test/topics.js | 94 +- test/topics/crossposts.js | 604 +++++ test/topics/thumbs.js | 172 +- test/topics/tools.js | 109 + test/translator.js | 34 +- test/uploads.js | 24 +- test/user.js | 101 +- test/user/custom-fields.js | 8 + test/user/reset.js | 14 +- test/utils.js | 96 +- webpack.common.js | 3 +- 1934 files changed, 28782 insertions(+), 9016 deletions(-) rename eslint.config.cjs => eslint.config.mjs (56%) create mode 100644 public/language/ar/admin/manage/custom-reasons.json create mode 100644 public/language/az/admin/manage/custom-reasons.json create mode 100644 public/language/bg/admin/manage/custom-reasons.json create mode 100644 public/language/bn/admin/manage/custom-reasons.json create mode 100644 public/language/cs/admin/manage/custom-reasons.json create mode 100644 public/language/da/admin/manage/custom-reasons.json create mode 100644 public/language/de/admin/manage/custom-reasons.json create mode 100644 public/language/el/admin/manage/custom-reasons.json create mode 100644 public/language/en-GB/admin/manage/custom-reasons.json create mode 100644 public/language/en-US/admin/manage/custom-reasons.json create mode 100644 public/language/en-x-pirate/admin/manage/custom-reasons.json create mode 100644 public/language/es/admin/manage/custom-reasons.json create mode 100644 public/language/et/admin/manage/custom-reasons.json create mode 100644 public/language/fa-IR/admin/manage/custom-reasons.json create mode 100644 public/language/fi/admin/manage/custom-reasons.json create mode 100644 public/language/fr/admin/manage/custom-reasons.json create mode 100644 public/language/gl/admin/manage/custom-reasons.json create mode 100644 public/language/he/admin/manage/custom-reasons.json create mode 100644 public/language/hr/admin/manage/custom-reasons.json create mode 100644 public/language/hu/admin/manage/custom-reasons.json create mode 100644 public/language/hy/admin/manage/custom-reasons.json create mode 100644 public/language/id/admin/manage/custom-reasons.json create mode 100644 public/language/it/admin/manage/custom-reasons.json create mode 100644 public/language/ja/admin/manage/custom-reasons.json create mode 100644 public/language/ko/admin/manage/custom-reasons.json create mode 100644 public/language/lt/admin/manage/custom-reasons.json create mode 100644 public/language/lv/admin/manage/custom-reasons.json create mode 100644 public/language/ms/admin/manage/custom-reasons.json create mode 100644 public/language/nb/admin/manage/custom-reasons.json create mode 100644 public/language/nl/admin/manage/custom-reasons.json create mode 100644 public/language/nn-NO/admin/manage/custom-reasons.json create mode 100644 public/language/pl/admin/manage/custom-reasons.json create mode 100644 public/language/pt-BR/admin/manage/custom-reasons.json create mode 100644 public/language/pt-PT/admin/manage/custom-reasons.json create mode 100644 public/language/ro/admin/manage/custom-reasons.json create mode 100644 public/language/ru/admin/manage/custom-reasons.json create mode 100644 public/language/rw/admin/manage/custom-reasons.json create mode 100644 public/language/sc/admin/manage/custom-reasons.json create mode 100644 public/language/sk/admin/manage/custom-reasons.json create mode 100644 public/language/sl/admin/manage/custom-reasons.json create mode 100644 public/language/sq-AL/admin/manage/custom-reasons.json create mode 100644 public/language/sr/admin/manage/custom-reasons.json create mode 100644 public/language/sv/admin/manage/custom-reasons.json create mode 100644 public/language/th/admin/manage/custom-reasons.json create mode 100644 public/language/tr/admin/manage/custom-reasons.json create mode 100644 public/language/uk/admin/manage/custom-reasons.json create mode 100644 public/language/ur/_DO_NOT_EDIT_FILES_HERE.md create mode 100644 public/language/ur/admin/admin.json create mode 100644 public/language/ur/admin/advanced/cache.json create mode 100644 public/language/ur/admin/advanced/database.json create mode 100644 public/language/ur/admin/advanced/errors.json create mode 100644 public/language/ur/admin/advanced/events.json create mode 100644 public/language/ur/admin/advanced/logs.json create mode 100644 public/language/ur/admin/appearance/customise.json create mode 100644 public/language/ur/admin/appearance/skins.json create mode 100644 public/language/ur/admin/appearance/themes.json create mode 100644 public/language/ur/admin/dashboard.json create mode 100644 public/language/ur/admin/development/info.json create mode 100644 public/language/ur/admin/development/logger.json create mode 100644 public/language/ur/admin/extend/plugins.json create mode 100644 public/language/ur/admin/extend/rewards.json create mode 100644 public/language/ur/admin/extend/widgets.json create mode 100644 public/language/ur/admin/manage/admins-mods.json create mode 100644 public/language/ur/admin/manage/categories.json create mode 100644 public/language/ur/admin/manage/custom-reasons.json create mode 100644 public/language/ur/admin/manage/digest.json create mode 100644 public/language/ur/admin/manage/groups.json create mode 100644 public/language/ur/admin/manage/privileges.json create mode 100644 public/language/ur/admin/manage/registration.json create mode 100644 public/language/ur/admin/manage/tags.json create mode 100644 public/language/ur/admin/manage/uploads.json create mode 100644 public/language/ur/admin/manage/user-custom-fields.json create mode 100644 public/language/ur/admin/manage/users.json create mode 100644 public/language/ur/admin/menu.json create mode 100644 public/language/ur/admin/settings/activitypub.json create mode 100644 public/language/ur/admin/settings/advanced.json create mode 100644 public/language/ur/admin/settings/api.json create mode 100644 public/language/ur/admin/settings/chat.json create mode 100644 public/language/ur/admin/settings/cookies.json create mode 100644 public/language/ur/admin/settings/email.json create mode 100644 public/language/ur/admin/settings/general.json create mode 100644 public/language/ur/admin/settings/group.json create mode 100644 public/language/ur/admin/settings/navigation.json create mode 100644 public/language/ur/admin/settings/notifications.json create mode 100644 public/language/ur/admin/settings/pagination.json create mode 100644 public/language/ur/admin/settings/post.json create mode 100644 public/language/ur/admin/settings/reputation.json create mode 100644 public/language/ur/admin/settings/sockets.json create mode 100644 public/language/ur/admin/settings/sounds.json create mode 100644 public/language/ur/admin/settings/tags.json create mode 100644 public/language/ur/admin/settings/uploads.json create mode 100644 public/language/ur/admin/settings/user.json create mode 100644 public/language/ur/admin/settings/web-crawler.json create mode 100644 public/language/ur/aria.json create mode 100644 public/language/ur/category.json create mode 100644 public/language/ur/email.json create mode 100644 public/language/ur/error.json create mode 100644 public/language/ur/flags.json create mode 100644 public/language/ur/global.json create mode 100644 public/language/ur/groups.json create mode 100644 public/language/ur/ip-blacklist.json create mode 100644 public/language/ur/language.json create mode 100644 public/language/ur/login.json create mode 100644 public/language/ur/modules.json create mode 100644 public/language/ur/notifications.json create mode 100644 public/language/ur/pages.json create mode 100644 public/language/ur/post-queue.json create mode 100644 public/language/ur/recent.json create mode 100644 public/language/ur/register.json create mode 100644 public/language/ur/reset_password.json create mode 100644 public/language/ur/rewards.json create mode 100644 public/language/ur/search.json create mode 100644 public/language/ur/social.json create mode 100644 public/language/ur/success.json create mode 100644 public/language/ur/tags.json create mode 100644 public/language/ur/themes/harmony.json create mode 100644 public/language/ur/themes/persona.json create mode 100644 public/language/ur/top.json create mode 100644 public/language/ur/topic.json create mode 100644 public/language/ur/unread.json create mode 100644 public/language/ur/uploads.json create mode 100644 public/language/ur/user.json create mode 100644 public/language/ur/users.json create mode 100644 public/language/ur/world.json create mode 100644 public/language/vi/admin/manage/custom-reasons.json create mode 100644 public/language/zh-CN/admin/manage/custom-reasons.json create mode 100644 public/language/zh-TW/admin/manage/custom-reasons.json create mode 100644 public/openapi/components/schemas/CrosspostObject.yaml create mode 100644 public/openapi/components/schemas/admin/relays.yaml create mode 100644 public/openapi/components/schemas/admin/rules.yaml create mode 100644 public/openapi/read/admin/manage/users/custom-reasons.yaml create mode 100644 public/openapi/read/ping.yaml create mode 100644 public/openapi/read/sping.yaml create mode 100644 public/openapi/write/admin/activitypub/relays.yaml create mode 100644 public/openapi/write/admin/activitypub/relays/url.yaml create mode 100644 public/openapi/write/admin/activitypub/rules.yaml create mode 100644 public/openapi/write/admin/activitypub/rules/order.yaml create mode 100644 public/openapi/write/admin/activitypub/rules/rid.yaml create mode 100644 public/openapi/write/posts/owner.yaml create mode 100644 public/openapi/write/posts/pid/owner.yaml create mode 100644 public/openapi/write/posts/queue/id.yaml create mode 100644 public/openapi/write/posts/queue/notify.yaml create mode 100644 public/openapi/write/topics/tid/crossposts.yaml create mode 100644 public/src/admin/manage/users/custom-reasons.js create mode 100644 public/src/admin/modules/relogin-timer.js create mode 100644 public/src/admin/settings/activitypub.js create mode 100644 public/src/client/chats/events.js create mode 100644 public/src/client/topic/crosspost.js create mode 100644 src/activitypub/jobs.js create mode 100644 src/activitypub/out.js create mode 100644 src/activitypub/relays.js create mode 100644 src/activitypub/rules.js delete mode 100644 src/api/activitypub.js create mode 100644 src/cache/tracker.js create mode 100644 src/middleware/multer.js create mode 100644 src/topics/crossposts.js create mode 100644 src/upgrades/4.3.0/chat_allow_list.js create mode 100644 src/upgrades/4.3.0/fix_duplicate_handles.js create mode 100644 src/upgrades/4.3.0/topic_follower_counts.js create mode 100644 src/upgrades/4.3.2/fix_category_sync_null_values.js create mode 100644 src/upgrades/4.5.0/post-uploads-to-hash.js create mode 100644 src/upgrades/4.5.0/topic-thumbs-to-hash.js create mode 100644 src/upgrades/4.7.1/remove_extraneous_ap_data.js create mode 100644 src/upgrades/4.8.1/clean_ap_tids_from_topic_zsets.js create mode 100644 src/upgrades/4.9.0/crosspost_privilege.js create mode 100644 src/views/admin/manage/users/custom-reasons.tpl create mode 100644 src/views/admin/partials/activitypub/relays.tpl create mode 100644 src/views/admin/partials/activitypub/rules.tpl create mode 100644 src/views/admin/partials/categories/add.tpl create mode 100644 src/views/admin/partials/manage-custom-reasons-modal.tpl create mode 100644 src/views/modals/crosspost-topic.tpl create mode 100644 src/views/modals/crossposts.tpl create mode 100644 src/views/partials/custom-reason.tpl create mode 100644 src/views/partials/feed/item.tpl create mode 100644 src/views/partials/topic/guest-cta.tpl create mode 100644 src/views/partials/topic/post-menu-list.tpl create mode 100644 src/views/partials/topic/topic-menu-list.tpl create mode 100644 test/activitypub/out.js create mode 100644 test/activitypub/privileges.js create mode 100644 test/files/dirty.svg create mode 100644 test/files/测试.jpg create mode 100644 test/slugify.js create mode 100644 test/topics/crossposts.js create mode 100644 test/topics/tools.js diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 742f443f9c..883daf8e40 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -16,14 +16,25 @@ permissions: packages: write jobs: - release: - runs-on: ubuntu-latest + build: + strategy: + matrix: + include: + - os: ubuntu-latest + platforms: linux/amd64 + required: true + - os: ubuntu-24.04-arm + platforms: linux/arm64 + required: true + continue-on-error: ${{ !matrix.required }} + runs-on: ${{ matrix.os }} steps: - - - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Prepare + run: | + platform=${{ matrix.platforms }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV + - uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -34,15 +45,73 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Get current date in NST - run: echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV - - name: Docker meta id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/${{ github.repository }} + images: ${{ env.IMAGE }} + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v5 + with: + path: var-cache-node-modules + key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }} + + - name: Build and push Docker images + id: build + uses: docker/build-push-action@v6 + with: + cache-from: type=gha + cache-to: type=gha,mode=min + context: . + file: ./Dockerfile + platforms: ${{ matrix.platforms }} + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ env.IMAGE }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v6 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Prepare + run: | + echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV + echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV + - name: Download digests + uses: actions/download-artifact@v7 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} @@ -52,21 +121,11 @@ jobs: type=raw,value=${{ env.CURRENT_DATE_NST }} flavor: | latest=true - - - name: Cache node_modules - id: cache-node-modules - uses: actions/cache@v4 - with: - path: var-cache-node-modules - key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }} - - - name: Build and push Docker images - uses: docker/build-push-action@v6 - with: - cache-from: type=gha - cache-to: type=gha,mode=min - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64,linux/arm/v7 - push: true - tags: ${{ steps.meta.outputs.tags }} + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.IMAGE }}@sha256:%s ' *) + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 724a49d584..87fa06e735 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -27,19 +27,19 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - node: [18, 20] + node: [20, 22] database: [mongo-dev, mongo, redis, postgres] include: # only run coverage once - os: ubuntu-latest - node: 18 + node: 22 coverage: true # test under development once - database: mongo-dev test_env: development # only run eslint once - os: ubuntu-latest - node: 18 + node: 22 database: mongo-dev lint: true runs-on: ${{ matrix.os }} @@ -48,7 +48,7 @@ jobs: services: postgres: - image: 'postgres:17-alpine' + image: 'postgres:18-alpine' env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:7.4.2' + image: 'redis:8.6.0' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" @@ -75,18 +75,18 @@ jobs: - 6379:6379 mongo: - image: 'mongo:8.0' + image: 'mongo:8.2' ports: # Maps port 27017 on service container to the host - 27017:27017 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: cp install/package.json package.json - name: Install Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} diff --git a/.tx/config b/.tx/config index 681e9a6709..293681c974 100644 --- a/.tx/config +++ b/.tx/config @@ -51,6 +51,7 @@ trans.sv = public/language/sv/admin/admin.json trans.th = public/language/th/admin/admin.json trans.tr = public/language/tr/admin/admin.json trans.uk = public/language/uk/admin/admin.json +trans.ur = public/language/ur/admin/admin.json trans.vi = public/language/vi/admin/admin.json trans.zh_CN = public/language/zh-CN/admin/admin.json trans.zh_TW = public/language/zh-TW/admin/admin.json @@ -105,6 +106,7 @@ trans.sv = public/language/sv/admin/advanced/cache.json trans.th = public/language/th/admin/advanced/cache.json trans.tr = public/language/tr/admin/advanced/cache.json trans.uk = public/language/uk/admin/advanced/cache.json +trans.ur = public/language/ur/admin/advanced/cache.json trans.vi = public/language/vi/admin/advanced/cache.json trans.zh_CN = public/language/zh-CN/admin/advanced/cache.json trans.zh_TW = public/language/zh-TW/admin/advanced/cache.json @@ -159,6 +161,7 @@ trans.sv = public/language/sv/admin/advanced/database.json trans.th = public/language/th/admin/advanced/database.json trans.tr = public/language/tr/admin/advanced/database.json trans.uk = public/language/uk/admin/advanced/database.json +trans.ur = public/language/ur/admin/advanced/database.json trans.vi = public/language/vi/admin/advanced/database.json trans.zh_CN = public/language/zh-CN/admin/advanced/database.json trans.zh_TW = public/language/zh-TW/admin/advanced/database.json @@ -213,6 +216,7 @@ trans.sv = public/language/sv/admin/advanced/errors.json trans.th = public/language/th/admin/advanced/errors.json trans.tr = public/language/tr/admin/advanced/errors.json trans.uk = public/language/uk/admin/advanced/errors.json +trans.ur = public/language/ur/admin/advanced/errors.json trans.vi = public/language/vi/admin/advanced/errors.json trans.zh_CN = public/language/zh-CN/admin/advanced/errors.json trans.zh_TW = public/language/zh-TW/admin/advanced/errors.json @@ -267,6 +271,7 @@ trans.sv = public/language/sv/admin/advanced/events.json trans.th = public/language/th/admin/advanced/events.json trans.tr = public/language/tr/admin/advanced/events.json trans.uk = public/language/uk/admin/advanced/events.json +trans.ur = public/language/ur/admin/advanced/events.json trans.vi = public/language/vi/admin/advanced/events.json trans.zh_CN = public/language/zh-CN/admin/advanced/events.json trans.zh_TW = public/language/zh-TW/admin/advanced/events.json @@ -321,6 +326,7 @@ trans.sv = public/language/sv/admin/advanced/logs.json trans.th = public/language/th/admin/advanced/logs.json trans.tr = public/language/tr/admin/advanced/logs.json trans.uk = public/language/uk/admin/advanced/logs.json +trans.ur = public/language/ur/admin/advanced/logs.json trans.vi = public/language/vi/admin/advanced/logs.json trans.zh_CN = public/language/zh-CN/admin/advanced/logs.json trans.zh_TW = public/language/zh-TW/admin/advanced/logs.json @@ -375,6 +381,7 @@ trans.sv = public/language/sv/admin/appearance/customise.json trans.th = public/language/th/admin/appearance/customise.json trans.tr = public/language/tr/admin/appearance/customise.json trans.uk = public/language/uk/admin/appearance/customise.json +trans.ur = public/language/ur/admin/appearance/customise.json trans.vi = public/language/vi/admin/appearance/customise.json trans.zh_CN = public/language/zh-CN/admin/appearance/customise.json trans.zh_TW = public/language/zh-TW/admin/appearance/customise.json @@ -429,6 +436,7 @@ trans.sv = public/language/sv/admin/appearance/skins.json trans.th = public/language/th/admin/appearance/skins.json trans.tr = public/language/tr/admin/appearance/skins.json trans.uk = public/language/uk/admin/appearance/skins.json +trans.ur = public/language/ur/admin/appearance/skins.json trans.vi = public/language/vi/admin/appearance/skins.json trans.zh_CN = public/language/zh-CN/admin/appearance/skins.json trans.zh_TW = public/language/zh-TW/admin/appearance/skins.json @@ -483,6 +491,7 @@ trans.sv = public/language/sv/admin/appearance/themes.json trans.th = public/language/th/admin/appearance/themes.json trans.tr = public/language/tr/admin/appearance/themes.json trans.uk = public/language/uk/admin/appearance/themes.json +trans.ur = public/language/ur/admin/appearance/themes.json trans.vi = public/language/vi/admin/appearance/themes.json trans.zh_CN = public/language/zh-CN/admin/appearance/themes.json trans.zh_TW = public/language/zh-TW/admin/appearance/themes.json @@ -537,6 +546,7 @@ trans.sv = public/language/sv/admin/dashboard.json trans.th = public/language/th/admin/dashboard.json trans.tr = public/language/tr/admin/dashboard.json trans.uk = public/language/uk/admin/dashboard.json +trans.ur = public/language/ur/admin/dashboard.json trans.vi = public/language/vi/admin/dashboard.json trans.zh_CN = public/language/zh-CN/admin/dashboard.json trans.zh_TW = public/language/zh-TW/admin/dashboard.json @@ -591,6 +601,7 @@ trans.sv = public/language/sv/admin/development/info.json trans.th = public/language/th/admin/development/info.json trans.tr = public/language/tr/admin/development/info.json trans.uk = public/language/uk/admin/development/info.json +trans.ur = public/language/ur/admin/development/info.json trans.vi = public/language/vi/admin/development/info.json trans.zh_CN = public/language/zh-CN/admin/development/info.json trans.zh_TW = public/language/zh-TW/admin/development/info.json @@ -645,6 +656,7 @@ trans.sv = public/language/sv/admin/development/logger.json trans.th = public/language/th/admin/development/logger.json trans.tr = public/language/tr/admin/development/logger.json trans.uk = public/language/uk/admin/development/logger.json +trans.ur = public/language/ur/admin/development/logger.json trans.vi = public/language/vi/admin/development/logger.json trans.zh_CN = public/language/zh-CN/admin/development/logger.json trans.zh_TW = public/language/zh-TW/admin/development/logger.json @@ -699,6 +711,7 @@ trans.sv = public/language/sv/admin/extend/plugins.json trans.th = public/language/th/admin/extend/plugins.json trans.tr = public/language/tr/admin/extend/plugins.json trans.uk = public/language/uk/admin/extend/plugins.json +trans.ur = public/language/ur/admin/extend/plugins.json trans.vi = public/language/vi/admin/extend/plugins.json trans.zh_CN = public/language/zh-CN/admin/extend/plugins.json trans.zh_TW = public/language/zh-TW/admin/extend/plugins.json @@ -753,6 +766,7 @@ trans.sv = public/language/sv/admin/extend/rewards.json trans.th = public/language/th/admin/extend/rewards.json trans.tr = public/language/tr/admin/extend/rewards.json trans.uk = public/language/uk/admin/extend/rewards.json +trans.ur = public/language/ur/admin/extend/rewards.json trans.vi = public/language/vi/admin/extend/rewards.json trans.zh_CN = public/language/zh-CN/admin/extend/rewards.json trans.zh_TW = public/language/zh-TW/admin/extend/rewards.json @@ -807,6 +821,7 @@ trans.sv = public/language/sv/admin/extend/widgets.json trans.th = public/language/th/admin/extend/widgets.json trans.tr = public/language/tr/admin/extend/widgets.json trans.uk = public/language/uk/admin/extend/widgets.json +trans.ur = public/language/ur/admin/extend/widgets.json trans.vi = public/language/vi/admin/extend/widgets.json trans.zh_CN = public/language/zh-CN/admin/extend/widgets.json trans.zh_TW = public/language/zh-TW/admin/extend/widgets.json @@ -861,6 +876,7 @@ trans.sv = public/language/sv/admin/manage/admins-mods.json trans.th = public/language/th/admin/manage/admins-mods.json trans.tr = public/language/tr/admin/manage/admins-mods.json trans.uk = public/language/uk/admin/manage/admins-mods.json +trans.ur = public/language/ur/admin/manage/admins-mods.json trans.vi = public/language/vi/admin/manage/admins-mods.json trans.zh_CN = public/language/zh-CN/admin/manage/admins-mods.json trans.zh_TW = public/language/zh-TW/admin/manage/admins-mods.json @@ -915,6 +931,7 @@ trans.sv = public/language/sv/admin/manage/categories.json trans.th = public/language/th/admin/manage/categories.json trans.tr = public/language/tr/admin/manage/categories.json trans.uk = public/language/uk/admin/manage/categories.json +trans.ur = public/language/ur/admin/manage/categories.json trans.vi = public/language/vi/admin/manage/categories.json trans.zh_CN = public/language/zh-CN/admin/manage/categories.json trans.zh_TW = public/language/zh-TW/admin/manage/categories.json @@ -969,6 +986,7 @@ trans.sv = public/language/sv/admin/manage/digest.json trans.th = public/language/th/admin/manage/digest.json trans.tr = public/language/tr/admin/manage/digest.json trans.uk = public/language/uk/admin/manage/digest.json +trans.ur = public/language/ur/admin/manage/digest.json trans.vi = public/language/vi/admin/manage/digest.json trans.zh_CN = public/language/zh-CN/admin/manage/digest.json trans.zh_TW = public/language/zh-TW/admin/manage/digest.json @@ -1023,6 +1041,7 @@ trans.sv = public/language/sv/admin/manage/groups.json trans.th = public/language/th/admin/manage/groups.json trans.tr = public/language/tr/admin/manage/groups.json trans.uk = public/language/uk/admin/manage/groups.json +trans.ur = public/language/ur/admin/manage/groups.json trans.vi = public/language/vi/admin/manage/groups.json trans.zh_CN = public/language/zh-CN/admin/manage/groups.json trans.zh_TW = public/language/zh-TW/admin/manage/groups.json @@ -1077,6 +1096,7 @@ trans.sv = public/language/sv/admin/manage/privileges.json trans.th = public/language/th/admin/manage/privileges.json trans.tr = public/language/tr/admin/manage/privileges.json trans.uk = public/language/uk/admin/manage/privileges.json +trans.ur = public/language/ur/admin/manage/privileges.json trans.vi = public/language/vi/admin/manage/privileges.json trans.zh_CN = public/language/zh-CN/admin/manage/privileges.json trans.zh_TW = public/language/zh-TW/admin/manage/privileges.json @@ -1131,6 +1151,7 @@ trans.sv = public/language/sv/admin/manage/registration.json trans.th = public/language/th/admin/manage/registration.json trans.tr = public/language/tr/admin/manage/registration.json trans.uk = public/language/uk/admin/manage/registration.json +trans.ur = public/language/ur/admin/manage/registration.json trans.vi = public/language/vi/admin/manage/registration.json trans.zh_CN = public/language/zh-CN/admin/manage/registration.json trans.zh_TW = public/language/zh-TW/admin/manage/registration.json @@ -1185,6 +1206,7 @@ trans.sv = public/language/sv/admin/manage/tags.json trans.th = public/language/th/admin/manage/tags.json trans.tr = public/language/tr/admin/manage/tags.json trans.uk = public/language/uk/admin/manage/tags.json +trans.ur = public/language/ur/admin/manage/tags.json trans.vi = public/language/vi/admin/manage/tags.json trans.zh_CN = public/language/zh-CN/admin/manage/tags.json trans.zh_TW = public/language/zh-TW/admin/manage/tags.json @@ -1239,10 +1261,66 @@ trans.sv = public/language/sv/admin/manage/uploads.json trans.th = public/language/th/admin/manage/uploads.json trans.tr = public/language/tr/admin/manage/uploads.json trans.uk = public/language/uk/admin/manage/uploads.json +trans.ur = public/language/ur/admin/manage/uploads.json trans.vi = public/language/vi/admin/manage/uploads.json trans.zh_CN = public/language/zh-CN/admin/manage/uploads.json trans.zh_TW = public/language/zh-TW/admin/manage/uploads.json +[o:nodebb:p:nodebb:r:admin-manage-user-custom-reasons] +file_filter = public/language//admin/manage/custom-reasons.json +source_file = public/language/en-GB/admin/manage/custom-reasons.json +source_lang = en_GB +type = KEYVALUEJSON +trans.ar = public/language/ar/admin/manage/custom-reasons.json +trans.az = public/language/az/admin/manage/custom-reasons.json +trans.bg = public/language/bg/admin/manage/custom-reasons.json +trans.bn = public/language/bn/admin/manage/custom-reasons.json +trans.cs = public/language/cs/admin/manage/custom-reasons.json +trans.da = public/language/da/admin/manage/custom-reasons.json +trans.de = public/language/de/admin/manage/custom-reasons.json +trans.el = public/language/el/admin/manage/custom-reasons.json +trans.en_US = public/language/en-US/admin/manage/custom-reasons.json +trans.en@pirate = public/language/en-x-pirate/admin/manage/custom-reasons.json +trans.es = public/language/es/admin/manage/custom-reasons.json +trans.et = public/language/et/admin/manage/custom-reasons.json +trans.fa_IR = public/language/fa-IR/admin/manage/custom-reasons.json +trans.fi = public/language/fi/admin/manage/custom-reasons.json +trans.fr = public/language/fr/admin/manage/custom-reasons.json +trans.gl = public/language/gl/admin/manage/custom-reasons.json +trans.he = public/language/he/admin/manage/custom-reasons.json +trans.hr = public/language/hr/admin/manage/custom-reasons.json +trans.hu = public/language/hu/admin/manage/custom-reasons.json +trans.hy = public/language/hy/admin/manage/custom-reasons.json +trans.id = public/language/id/admin/manage/custom-reasons.json +trans.it = public/language/it/admin/manage/custom-reasons.json +trans.ja = public/language/ja/admin/manage/custom-reasons.json +trans.ko = public/language/ko/admin/manage/custom-reasons.json +trans.lt = public/language/lt/admin/manage/custom-reasons.json +trans.lv = public/language/lv/admin/manage/custom-reasons.json +trans.ms = public/language/ms/admin/manage/custom-reasons.json +trans.nb = public/language/nb/admin/manage/custom-reasons.json +trans.nl = public/language/nl/admin/manage/custom-reasons.json +trans.nn_NO = public/language/nn-NO/admin/manage/custom-reasons.json +trans.pl = public/language/pl/admin/manage/custom-reasons.json +trans.pt_BR = public/language/pt-BR/admin/manage/custom-reasons.json +trans.pt_PT = public/language/pt-PT/admin/manage/custom-reasons.json +trans.ro = public/language/ro/admin/manage/custom-reasons.json +trans.ru = public/language/ru/admin/manage/custom-reasons.json +trans.rw = public/language/rw/admin/manage/custom-reasons.json +trans.sc = public/language/sc/admin/manage/custom-reasons.json +trans.sk = public/language/sk/admin/manage/custom-reasons.json +trans.sl = public/language/sl/admin/manage/custom-reasons.json +trans.sq_AL = public/language/sq-AL/admin/manage/custom-reasons.json +trans.sr = public/language/sr/admin/manage/custom-reasons.json +trans.sv = public/language/sv/admin/manage/custom-reasons.json +trans.th = public/language/th/admin/manage/custom-reasons.json +trans.tr = public/language/tr/admin/manage/custom-reasons.json +trans.uk = public/language/uk/admin/manage/custom-reasons.json +trans.ur = public/language/ur/admin/manage/custom-reasons.json +trans.vi = public/language/vi/admin/manage/custom-reasons.json +trans.zh_CN = public/language/zh-CN/admin/manage/custom-reasons.json +trans.zh_TW = public/language/zh-TW/admin/manage/custom-reasons.json + [o:nodebb:p:nodebb:r:admin-manage-user-custom-fields] file_filter = public/language//admin/manage/user-custom-fields.json source_file = public/language/en-GB/admin/manage/user-custom-fields.json @@ -1293,6 +1371,7 @@ trans.sv = public/language/sv/admin/manage/user-custom-fields.json trans.th = public/language/th/admin/manage/user-custom-fields.json trans.tr = public/language/tr/admin/manage/user-custom-fields.json trans.uk = public/language/uk/admin/manage/user-custom-fields.json +trans.ur = public/language/ur/admin/manage/user-custom-fields.json trans.vi = public/language/vi/admin/manage/user-custom-fields.json trans.zh_CN = public/language/zh-CN/admin/manage/user-custom-fields.json trans.zh_TW = public/language/zh-TW/admin/manage/user-custom-fields.json @@ -1347,6 +1426,7 @@ trans.sv = public/language/sv/admin/manage/users.json trans.th = public/language/th/admin/manage/users.json trans.tr = public/language/tr/admin/manage/users.json trans.uk = public/language/uk/admin/manage/users.json +trans.ur = public/language/ur/admin/manage/users.json trans.vi = public/language/vi/admin/manage/users.json trans.zh_CN = public/language/zh-CN/admin/manage/users.json trans.zh_TW = public/language/zh-TW/admin/manage/users.json @@ -1401,6 +1481,7 @@ trans.sv = public/language/sv/admin/menu.json trans.th = public/language/th/admin/menu.json trans.tr = public/language/tr/admin/menu.json trans.uk = public/language/uk/admin/menu.json +trans.ur = public/language/ur/admin/menu.json trans.vi = public/language/vi/admin/menu.json trans.zh_CN = public/language/zh-CN/admin/menu.json trans.zh_TW = public/language/zh-TW/admin/menu.json @@ -1455,6 +1536,7 @@ trans.sv = public/language/sv/admin/settings/advanced.json trans.th = public/language/th/admin/settings/advanced.json trans.tr = public/language/tr/admin/settings/advanced.json trans.uk = public/language/uk/admin/settings/advanced.json +trans.ur = public/language/ur/admin/settings/advanced.json trans.vi = public/language/vi/admin/settings/advanced.json trans.zh_CN = public/language/zh-CN/admin/settings/advanced.json trans.zh_TW = public/language/zh-TW/admin/settings/advanced.json @@ -1509,6 +1591,7 @@ trans.sv = public/language/sv/admin/settings/activitypub.json trans.th = public/language/th/admin/settings/activitypub.json trans.tr = public/language/tr/admin/settings/activitypub.json trans.uk = public/language/uk/admin/settings/activitypub.json +trans.ur = public/language/ur/admin/settings/activitypub.json trans.vi = public/language/vi/admin/settings/activitypub.json trans.zh_CN = public/language/zh-CN/admin/settings/activitypub.json trans.zh_TW = public/language/zh-TW/admin/settings/activitypub.json @@ -1563,6 +1646,7 @@ trans.sv = public/language/sv/admin/settings/api.json trans.th = public/language/th/admin/settings/api.json trans.tr = public/language/tr/admin/settings/api.json trans.uk = public/language/uk/admin/settings/api.json +trans.ur = public/language/ur/admin/settings/api.json trans.vi = public/language/vi/admin/settings/api.json trans.zh_CN = public/language/zh-CN/admin/settings/api.json trans.zh_TW = public/language/zh-TW/admin/settings/api.json @@ -1617,6 +1701,7 @@ trans.sv = public/language/sv/admin/settings/chat.json trans.th = public/language/th/admin/settings/chat.json trans.tr = public/language/tr/admin/settings/chat.json trans.uk = public/language/uk/admin/settings/chat.json +trans.ur = public/language/ur/admin/settings/chat.json trans.vi = public/language/vi/admin/settings/chat.json trans.zh_CN = public/language/zh-CN/admin/settings/chat.json trans.zh_TW = public/language/zh-TW/admin/settings/chat.json @@ -1671,6 +1756,7 @@ trans.sv = public/language/sv/admin/settings/cookies.json trans.th = public/language/th/admin/settings/cookies.json trans.tr = public/language/tr/admin/settings/cookies.json trans.uk = public/language/uk/admin/settings/cookies.json +trans.ur = public/language/ur/admin/settings/cookies.json trans.vi = public/language/vi/admin/settings/cookies.json trans.zh_CN = public/language/zh-CN/admin/settings/cookies.json trans.zh_TW = public/language/zh-TW/admin/settings/cookies.json @@ -1725,6 +1811,7 @@ trans.sv = public/language/sv/admin/settings/email.json trans.th = public/language/th/admin/settings/email.json trans.tr = public/language/tr/admin/settings/email.json trans.uk = public/language/uk/admin/settings/email.json +trans.ur = public/language/ur/admin/settings/email.json trans.vi = public/language/vi/admin/settings/email.json trans.zh_CN = public/language/zh-CN/admin/settings/email.json trans.zh_TW = public/language/zh-TW/admin/settings/email.json @@ -1779,6 +1866,7 @@ trans.sv = public/language/sv/admin/settings/general.json trans.th = public/language/th/admin/settings/general.json trans.tr = public/language/tr/admin/settings/general.json trans.uk = public/language/uk/admin/settings/general.json +trans.ur = public/language/ur/admin/settings/general.json trans.vi = public/language/vi/admin/settings/general.json trans.zh_CN = public/language/zh-CN/admin/settings/general.json trans.zh_TW = public/language/zh-TW/admin/settings/general.json @@ -1833,6 +1921,7 @@ trans.sv = public/language/sv/admin/settings/group.json trans.th = public/language/th/admin/settings/group.json trans.tr = public/language/tr/admin/settings/group.json trans.uk = public/language/uk/admin/settings/group.json +trans.ur = public/language/ur/admin/settings/group.json trans.vi = public/language/vi/admin/settings/group.json trans.zh_CN = public/language/zh-CN/admin/settings/group.json trans.zh_TW = public/language/zh-TW/admin/settings/group.json @@ -1887,6 +1976,7 @@ trans.sv = public/language/sv/admin/settings/navigation.json trans.th = public/language/th/admin/settings/navigation.json trans.tr = public/language/tr/admin/settings/navigation.json trans.uk = public/language/uk/admin/settings/navigation.json +trans.ur = public/language/ur/admin/settings/navigation.json trans.vi = public/language/vi/admin/settings/navigation.json trans.zh_CN = public/language/zh-CN/admin/settings/navigation.json trans.zh_TW = public/language/zh-TW/admin/settings/navigation.json @@ -1941,6 +2031,7 @@ trans.sv = public/language/sv/admin/settings/notifications.json trans.th = public/language/th/admin/settings/notifications.json trans.tr = public/language/tr/admin/settings/notifications.json trans.uk = public/language/uk/admin/settings/notifications.json +trans.ur = public/language/ur/admin/settings/notifications.json trans.vi = public/language/vi/admin/settings/notifications.json trans.zh_CN = public/language/zh-CN/admin/settings/notifications.json trans.zh_TW = public/language/zh-TW/admin/settings/notifications.json @@ -1995,6 +2086,7 @@ trans.sv = public/language/sv/admin/settings/pagination.json trans.th = public/language/th/admin/settings/pagination.json trans.tr = public/language/tr/admin/settings/pagination.json trans.uk = public/language/uk/admin/settings/pagination.json +trans.ur = public/language/ur/admin/settings/pagination.json trans.vi = public/language/vi/admin/settings/pagination.json trans.zh_CN = public/language/zh-CN/admin/settings/pagination.json trans.zh_TW = public/language/zh-TW/admin/settings/pagination.json @@ -2049,6 +2141,7 @@ trans.sv = public/language/sv/admin/settings/post.json trans.th = public/language/th/admin/settings/post.json trans.tr = public/language/tr/admin/settings/post.json trans.uk = public/language/uk/admin/settings/post.json +trans.ur = public/language/ur/admin/settings/post.json trans.vi = public/language/vi/admin/settings/post.json trans.zh_CN = public/language/zh-CN/admin/settings/post.json trans.zh_TW = public/language/zh-TW/admin/settings/post.json @@ -2103,6 +2196,7 @@ trans.sv = public/language/sv/admin/settings/reputation.json trans.th = public/language/th/admin/settings/reputation.json trans.tr = public/language/tr/admin/settings/reputation.json trans.uk = public/language/uk/admin/settings/reputation.json +trans.ur = public/language/ur/admin/settings/reputation.json trans.vi = public/language/vi/admin/settings/reputation.json trans.zh_CN = public/language/zh-CN/admin/settings/reputation.json trans.zh_TW = public/language/zh-TW/admin/settings/reputation.json @@ -2157,6 +2251,7 @@ trans.sv = public/language/sv/admin/settings/sockets.json trans.th = public/language/th/admin/settings/sockets.json trans.tr = public/language/tr/admin/settings/sockets.json trans.uk = public/language/uk/admin/settings/sockets.json +trans.ur = public/language/ur/admin/settings/sockets.json trans.vi = public/language/vi/admin/settings/sockets.json trans.zh_CN = public/language/zh-CN/admin/settings/sockets.json trans.zh_TW = public/language/zh-TW/admin/settings/sockets.json @@ -2211,6 +2306,7 @@ trans.sv = public/language/sv/admin/settings/sounds.json trans.th = public/language/th/admin/settings/sounds.json trans.tr = public/language/tr/admin/settings/sounds.json trans.uk = public/language/uk/admin/settings/sounds.json +trans.ur = public/language/ur/admin/settings/sounds.json trans.vi = public/language/vi/admin/settings/sounds.json trans.zh_CN = public/language/zh-CN/admin/settings/sounds.json trans.zh_TW = public/language/zh-TW/admin/settings/sounds.json @@ -2265,6 +2361,7 @@ trans.sv = public/language/sv/admin/settings/tags.json trans.th = public/language/th/admin/settings/tags.json trans.tr = public/language/tr/admin/settings/tags.json trans.uk = public/language/uk/admin/settings/tags.json +trans.ur = public/language/ur/admin/settings/tags.json trans.vi = public/language/vi/admin/settings/tags.json trans.zh_CN = public/language/zh-CN/admin/settings/tags.json trans.zh_TW = public/language/zh-TW/admin/settings/tags.json @@ -2319,6 +2416,7 @@ trans.sv = public/language/sv/admin/settings/uploads.json trans.th = public/language/th/admin/settings/uploads.json trans.tr = public/language/tr/admin/settings/uploads.json trans.uk = public/language/uk/admin/settings/uploads.json +trans.ur = public/language/ur/admin/settings/uploads.json trans.vi = public/language/vi/admin/settings/uploads.json trans.zh_CN = public/language/zh-CN/admin/settings/uploads.json trans.zh_TW = public/language/zh-TW/admin/settings/uploads.json @@ -2373,6 +2471,7 @@ trans.sv = public/language/sv/admin/settings/user.json trans.th = public/language/th/admin/settings/user.json trans.tr = public/language/tr/admin/settings/user.json trans.uk = public/language/uk/admin/settings/user.json +trans.ur = public/language/ur/admin/settings/user.json trans.vi = public/language/vi/admin/settings/user.json trans.zh_CN = public/language/zh-CN/admin/settings/user.json trans.zh_TW = public/language/zh-TW/admin/settings/user.json @@ -2427,6 +2526,7 @@ trans.sv = public/language/sv/admin/settings/web-crawler.json trans.th = public/language/th/admin/settings/web-crawler.json trans.tr = public/language/tr/admin/settings/web-crawler.json trans.uk = public/language/uk/admin/settings/web-crawler.json +trans.ur = public/language/ur/admin/settings/web-crawler.json trans.vi = public/language/vi/admin/settings/web-crawler.json trans.zh_CN = public/language/zh-CN/admin/settings/web-crawler.json trans.zh_TW = public/language/zh-TW/admin/settings/web-crawler.json @@ -2481,6 +2581,7 @@ trans.sv = public/language/sv/themes/harmony.json trans.th = public/language/th/themes/harmony.json trans.tr = public/language/tr/themes/harmony.json trans.uk = public/language/uk/themes/harmony.json +trans.ur = public/language/ur/themes/harmony.json trans.vi = public/language/vi/themes/harmony.json trans.zh_CN = public/language/zh-CN/themes/harmony.json trans.zh_TW = public/language/zh-TW/themes/harmony.json @@ -2535,6 +2636,7 @@ trans.sv = public/language/sv/themes/persona.json trans.th = public/language/th/themes/persona.json trans.tr = public/language/tr/themes/persona.json trans.uk = public/language/uk/themes/persona.json +trans.ur = public/language/ur/themes/persona.json trans.vi = public/language/vi/themes/persona.json trans.zh_CN = public/language/zh-CN/themes/persona.json trans.zh_TW = public/language/zh-TW/themes/persona.json @@ -2589,6 +2691,7 @@ trans.sv = public/language/sv/aria.json trans.th = public/language/th/aria.json trans.tr = public/language/tr/aria.json trans.uk = public/language/uk/aria.json +trans.ur = public/language/ur/aria.json trans.vi = public/language/vi/aria.json trans.zh_CN = public/language/zh-CN/aria.json trans.zh_TW = public/language/zh-TW/aria.json @@ -2643,6 +2746,7 @@ trans.sv = public/language/sv/category.json trans.th = public/language/th/category.json trans.tr = public/language/tr/category.json trans.uk = public/language/uk/category.json +trans.ur = public/language/ur/category.json trans.vi = public/language/vi/category.json trans.zh_CN = public/language/zh-CN/category.json trans.zh_TW = public/language/zh-TW/category.json @@ -2697,6 +2801,7 @@ trans.sv = public/language/sv/email.json trans.th = public/language/th/email.json trans.tr = public/language/tr/email.json trans.uk = public/language/uk/email.json +trans.ur = public/language/ur/email.json trans.vi = public/language/vi/email.json trans.zh_CN = public/language/zh-CN/email.json trans.zh_TW = public/language/zh-TW/email.json @@ -2751,6 +2856,7 @@ trans.sv = public/language/sv/error.json trans.th = public/language/th/error.json trans.tr = public/language/tr/error.json trans.uk = public/language/uk/error.json +trans.ur = public/language/ur/error.json trans.vi = public/language/vi/error.json trans.zh_CN = public/language/zh-CN/error.json trans.zh_TW = public/language/zh-TW/error.json @@ -2858,6 +2964,7 @@ trans.sv = public/language/sv/global.json trans.th = public/language/th/global.json trans.tr = public/language/tr/global.json trans.uk = public/language/uk/global.json +trans.ur = public/language/ur/global.json trans.vi = public/language/vi/global.json trans.zh_CN = public/language/zh-CN/global.json trans.zh_TW = public/language/zh-TW/global.json @@ -2912,6 +3019,7 @@ trans.sv = public/language/sv/groups.json trans.th = public/language/th/groups.json trans.tr = public/language/tr/groups.json trans.uk = public/language/uk/groups.json +trans.ur = public/language/ur/groups.json trans.vi = public/language/vi/groups.json trans.zh_CN = public/language/zh-CN/groups.json trans.zh_TW = public/language/zh-TW/groups.json @@ -2966,6 +3074,7 @@ trans.sv = public/language/sv/ip-blacklist.json trans.th = public/language/th/ip-blacklist.json trans.tr = public/language/tr/ip-blacklist.json trans.uk = public/language/uk/ip-blacklist.json +trans.ur = public/language/ur/ip-blacklist.json trans.vi = public/language/vi/ip-blacklist.json trans.zh_CN = public/language/zh-CN/ip-blacklist.json trans.zh_TW = public/language/zh-TW/ip-blacklist.json @@ -3020,6 +3129,7 @@ trans.sv = public/language/sv/language.json trans.th = public/language/th/language.json trans.tr = public/language/tr/language.json trans.uk = public/language/uk/language.json +trans.ur = public/language/ur/language.json trans.vi = public/language/vi/language.json trans.zh_CN = public/language/zh-CN/language.json trans.zh_TW = public/language/zh-TW/language.json @@ -3074,6 +3184,7 @@ trans.sv = public/language/sv/login.json trans.th = public/language/th/login.json trans.tr = public/language/tr/login.json trans.uk = public/language/uk/login.json +trans.ur = public/language/ur/login.json trans.vi = public/language/vi/login.json trans.zh_CN = public/language/zh-CN/login.json trans.zh_TW = public/language/zh-TW/login.json @@ -3128,6 +3239,7 @@ trans.sv = public/language/sv/modules.json trans.th = public/language/th/modules.json trans.tr = public/language/tr/modules.json trans.uk = public/language/uk/modules.json +trans.ur = public/language/ur/modules.json trans.vi = public/language/vi/modules.json trans.zh_CN = public/language/zh-CN/modules.json trans.zh_TW = public/language/zh-TW/modules.json @@ -3182,6 +3294,7 @@ trans.sv = public/language/sv/notifications.json trans.th = public/language/th/notifications.json trans.tr = public/language/tr/notifications.json trans.uk = public/language/uk/notifications.json +trans.ur = public/language/ur/notifications.json trans.vi = public/language/vi/notifications.json trans.zh_CN = public/language/zh-CN/notifications.json trans.zh_TW = public/language/zh-TW/notifications.json @@ -3236,6 +3349,7 @@ trans.sv = public/language/sv/pages.json trans.th = public/language/th/pages.json trans.tr = public/language/tr/pages.json trans.uk = public/language/uk/pages.json +trans.ur = public/language/ur/pages.json trans.vi = public/language/vi/pages.json trans.zh_CN = public/language/zh-CN/pages.json trans.zh_TW = public/language/zh-TW/pages.json @@ -3290,6 +3404,7 @@ trans.sv = public/language/sv/post-queue.json trans.th = public/language/th/post-queue.json trans.tr = public/language/tr/post-queue.json trans.uk = public/language/uk/post-queue.json +trans.ur = public/language/ur/post-queue.json trans.vi = public/language/vi/post-queue.json trans.zh_CN = public/language/zh-CN/post-queue.json trans.zh_TW = public/language/zh-TW/post-queue.json @@ -3344,6 +3459,7 @@ trans.sv = public/language/sv/recent.json trans.th = public/language/th/recent.json trans.tr = public/language/tr/recent.json trans.uk = public/language/uk/recent.json +trans.ur = public/language/ur/recent.json trans.vi = public/language/vi/recent.json trans.zh_CN = public/language/zh-CN/recent.json trans.zh_TW = public/language/zh-TW/recent.json @@ -3398,6 +3514,7 @@ trans.sv = public/language/sv/register.json trans.th = public/language/th/register.json trans.tr = public/language/tr/register.json trans.uk = public/language/uk/register.json +trans.ur = public/language/ur/register.json trans.vi = public/language/vi/register.json trans.zh_CN = public/language/zh-CN/register.json trans.zh_TW = public/language/zh-TW/register.json @@ -3452,6 +3569,7 @@ trans.sv = public/language/sv/reset_password.json trans.th = public/language/th/reset_password.json trans.tr = public/language/tr/reset_password.json trans.uk = public/language/uk/reset_password.json +trans.ur = public/language/ur/reset_password.json trans.vi = public/language/vi/reset_password.json trans.zh_CN = public/language/zh-CN/reset_password.json trans.zh_TW = public/language/zh-TW/reset_password.json @@ -3506,6 +3624,7 @@ trans.sv = public/language/sv/rewards.json trans.th = public/language/th/rewards.json trans.tr = public/language/tr/rewards.json trans.uk = public/language/uk/rewards.json +trans.ur = public/language/ur/rewards.json trans.vi = public/language/vi/rewards.json trans.zh_CN = public/language/zh-CN/rewards.json trans.zh_TW = public/language/zh-TW/rewards.json @@ -3560,6 +3679,7 @@ trans.sv = public/language/sv/search.json trans.th = public/language/th/search.json trans.tr = public/language/tr/search.json trans.uk = public/language/uk/search.json +trans.ur = public/language/ur/search.json trans.vi = public/language/vi/search.json trans.zh_CN = public/language/zh-CN/search.json trans.zh_TW = public/language/zh-TW/search.json @@ -3614,6 +3734,7 @@ trans.sv = public/language/sv/social.json trans.th = public/language/th/social.json trans.tr = public/language/tr/social.json trans.uk = public/language/uk/social.json +trans.ur = public/language/ur/social.json trans.vi = public/language/vi/social.json trans.zh_CN = public/language/zh-CN/social.json trans.zh_TW = public/language/zh-TW/social.json @@ -3668,6 +3789,7 @@ trans.sv = public/language/sv/success.json trans.th = public/language/th/success.json trans.tr = public/language/tr/success.json trans.uk = public/language/uk/success.json +trans.ur = public/language/ur/success.json trans.vi = public/language/vi/success.json trans.zh_CN = public/language/zh-CN/success.json trans.zh_TW = public/language/zh-TW/success.json @@ -3722,6 +3844,7 @@ trans.sv = public/language/sv/tags.json trans.th = public/language/th/tags.json trans.tr = public/language/tr/tags.json trans.uk = public/language/uk/tags.json +trans.ur = public/language/ur/tags.json trans.vi = public/language/vi/tags.json trans.zh_CN = public/language/zh-CN/tags.json trans.zh_TW = public/language/zh-TW/tags.json @@ -3776,6 +3899,7 @@ trans.sv = public/language/sv/top.json trans.th = public/language/th/top.json trans.tr = public/language/tr/top.json trans.uk = public/language/uk/top.json +trans.ur = public/language/ur/top.json trans.vi = public/language/vi/top.json trans.zh_CN = public/language/zh-CN/top.json trans.zh_TW = public/language/zh-TW/top.json @@ -3830,6 +3954,7 @@ trans.sv = public/language/sv/topic.json trans.th = public/language/th/topic.json trans.tr = public/language/tr/topic.json trans.uk = public/language/uk/topic.json +trans.ur = public/language/ur/topic.json trans.vi = public/language/vi/topic.json trans.zh_CN = public/language/zh-CN/topic.json trans.zh_TW = public/language/zh-TW/topic.json @@ -3884,6 +4009,7 @@ trans.sv = public/language/sv/unread.json trans.th = public/language/th/unread.json trans.tr = public/language/tr/unread.json trans.uk = public/language/uk/unread.json +trans.ur = public/language/ur/unread.json trans.vi = public/language/vi/unread.json trans.zh_CN = public/language/zh-CN/unread.json trans.zh_TW = public/language/zh-TW/unread.json @@ -3938,6 +4064,7 @@ trans.sv = public/language/sv/uploads.json trans.th = public/language/th/uploads.json trans.tr = public/language/tr/uploads.json trans.uk = public/language/uk/uploads.json +trans.ur = public/language/ur/uploads.json trans.vi = public/language/vi/uploads.json trans.zh_CN = public/language/zh-CN/uploads.json trans.zh_TW = public/language/zh-TW/uploads.json @@ -3992,6 +4119,7 @@ trans.sv = public/language/sv/user.json trans.th = public/language/th/user.json trans.tr = public/language/tr/user.json trans.uk = public/language/uk/user.json +trans.ur = public/language/ur/user.json trans.vi = public/language/vi/user.json trans.zh_CN = public/language/zh-CN/user.json trans.zh_TW = public/language/zh-TW/user.json @@ -4046,6 +4174,7 @@ trans.sv = public/language/sv/users.json trans.th = public/language/th/users.json trans.tr = public/language/tr/users.json trans.uk = public/language/uk/users.json +trans.ur = public/language/ur/users.json trans.vi = public/language/vi/users.json trans.zh_CN = public/language/zh-CN/users.json trans.zh_TW = public/language/zh-TW/users.json @@ -4100,6 +4229,7 @@ trans.sv = public/language/sv/world.json trans.th = public/language/th/world.json trans.tr = public/language/tr/world.json trans.uk = public/language/uk/world.json +trans.ur = public/language/ur/world.json trans.vi = public/language/vi/world.json trans.zh_CN = public/language/zh-CN/world.json trans.zh_TW = public/language/zh-TW/world.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 33da12d0e3..13fce6fb89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2064 @@ +#### v4.8.1 (2026-01-28) + +##### Chores + +* fix progress (a82f18cc) +* fix typo in upgrade script name (619819de) +* up composer (871089da) +* up composer (a061672d) +* up themes (d2e1629f) +* up link-preview (f90c8649) +* up dbsearch (469a8ef9) +* up harmony (c1a92c47) +* up harmony (317be96f) +* incrementing version number - v4.8.0 (3fac737a) +* update changelog for v4.8.0 (a9fbcf2a) +* incrementing version number - v4.7.2 (cd419d8a) +* incrementing version number - v4.7.1 (afb88805) +* incrementing version number - v4.7.0 (e82d40f8) +* incrementing version number - v4.6.3 (9fc5b0f3) +* incrementing version number - v4.6.2 (f98747db) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* upgrade script to handle topics that were already pruned (03b7374c) +* closes #13899 (f98de3e9) +* #10682, fix all the other rss routes as well (385a4d03) +* protocol (da5605e0) +* closes #12986 (310e90c7) +* #13919 (b2c6fbed) +* use min (090b9f55) +* #13918, make arrayLimit configurable increase default to 50 (d25e7726) +* closes #13258, dont mark digest as delivered if it fails (f29c9f06) +* wrap fields in quotes in user csv export (1b08aef2) +* closes #13199 normalize accept header (ec4e7ef1) +* closes #10682, strip unicode control chars (d867d8ad) +* require (d3f653e6) +* remove bidi chars from displayname (07d2c946) +* closes #11499 (50c26dd5) +* remove lowercase bidi controls as well (512b1e72) +* #13909, show 413 error properly (39af8383) +* closes #11606, detect musl and use sass instead (442f9f1d) +* make translator.unescape stricter like escape (e505e369) +* closes #13887, make translator.escape stricter (b2fa7304) +* closes #13897, display group create errors properly (7d36c757) +* consider crossposts when building teasers, fixes #13891 (c494d002) +* #13892, logical flaw (98c0a3fe) + +##### Refactors + +* get rid of map, move parallel calls into promise.all (e231c010) +* dont include scheduled topics in unread (7bc9fe3b) +* remove chats.initialized, all events handlers are removed before being added (fffe039f) +* move chat page events to a new file (ab39e7f8) +* add guards against bad data & infi loops (6b3ec636) +* tags were moved into topic hash a while ago (2ba8907a) +* already checked inside user.isPasswordValid (635715ef) +* get rid of intersect and use a faster method to load recently created tids (1cbc128a) +* shorter params (bb6ed76e) +* use async/await for group search (fe4a4476) +* put alltime in query string for term (639ea42d) +* crossposts.get to support multiple tids (57a73c48) + +##### Tests + +* add missing awaits, change error message (918bb044) +* dont return cross posts (be5b36bc) + +#### v4.8.0 (2026-01-14) + +##### Chores + +* **deps:** + * update dependency @stylistic/eslint-plugin to v5.7.0 (#13879) (be0d43cf) + * update commitlint monorepo to v20.3.1 (#13876) (c88ce519) + * update dependency sass-embedded to v1.97.2 (#13870) (27d511ff) + * update commitlint monorepo to v20.3.0 (#13865) (447cfd03) + * update dependency smtp-server to v3.18.0 (#13858) (f35c77dd) + * update dependency jsdom to v27.4.0 (#13860) (37c052f4) + * update dependency sass-embedded to v1.97.1 (#13850) (d28866ab) + * update dependency sass-embedded to v1.97.0 (#13837) (168b6e63) + * update dependency smtp-server to v3.17.1 (#13829) (ad895efb) + * update dependency @eslint/js to v9.39.2 (#13830) (22fe83f0) + * update github artifact actions (#13831) (b1696218) + * update actions/cache action to v5 (#13828) (0fcc8543) + * update dependency smtp-server to v3.17.0 (#13824) (3adcbe0f) + * update dependency sass-embedded to v1.96.0 (#13821) (b992511b) + * update dependency sass-embedded to v1.95.1 (#13817) (a2f2c8c7) + * update dependency jsdom to v27.3.0 (#13814) (a35c326a) + * update commitlint monorepo to v20.2.0 (#13810) (e50edd52) + * update dependency lint-staged to v16.2.7 (#13785) (76b6b3b2) + * update actions/checkout action to v6 (#13802) (7f21a171) +* bump profile max upload size default (bed6ed3c) +* up themes (b323b5d8) +* up markdown (eb77c9bf) +* up mentions (648d9c78) +* incrementing version number - v4.7.2 (cd419d8a) +* update changelog for v4.7.2 (2f0526b8) +* incrementing version number - v4.7.1 (afb88805) +* allow direct testing in test/categories.js (29687722) +* incrementing version number - v4.7.0 (e82d40f8) +* incrementing version number - v4.6.3 (9fc5b0f3) +* incrementing version number - v4.6.2 (f98747db) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Documentation Changes + +* update openapi schema for missing routes related to crossposting (d81b644d) + +##### New Features + +* user crossposts federate as:Announce (273bc68c) +* add missing files, minor changes to crossposts list modal (38fd1798) +* introduce new front-end UI button for cross-posting, hide move on topics in remote cids (0041cfe2) +* disallow moving topics to and from remote categories, + basic tests for topic moving (ea1e4c7d) +* API v3 calls to crosspost and uncrosspost a topic to and from a category (74172ecc) +* refactor out.announce.topic to allow user announces, refactor tests to accommodate (874ffd7b) +* stop extraneous vote and tids_read data from being saved for remote users (097d0802) +* support remote Dislike activity, federate out a Dislike on downvote, bwahahah (528cd258) +* expand postingRestrictedToMods mask testing, handle actor update for that prop (6a561050) +* setAddBulk (#13805) (7d5402fe) +* save privilege masking set when asserting group (f0a7a442) +* patch low-level privilege query calls to accept privilege masks at the cid level (4020e1be) +* federate out topic removal activities when topic is deleted and purged from a local category (3ab61615) + +##### Bug Fixes + +* i18n fallbacks (a73ab8ee) +* #13889, custom emoji from Piefed (0c75934a) +* #13888, decode html entities for AP category name and description (6eea4df5) +* derp (bcc204fa) +* bump themes (a4c470ff) +* guard against negative uids crossposting (2f96eed4) +* bump themes (943b53b0) +* calling sortedSetRemove to remove multiple values, instead of baking it into sortedSetRemoveBulk (82507c0f) +* unused values (b9b33f9f) +* typo, client-side handling of crossposts as pertains to uncategorized topics (7465762d) +* client-side handling of category selector when cross-posting so only local cids are sent to backend (ea417b06) +* update category sync logic to utilise crossposts instead (e5ee52e5) +* remove old remote user to remote category migration logic + tests (28249efb) +* update auto-categorization rules to also handle already-categorized topics via crosspost (148663c5) +* topic crosspost delete and purge handling (f6cc556d) +* bug where privileges users could not uncrosspost others' crossposts. Tests (0a0a7da9) +* allow non-mods to crosspost, move crosspost button out of topic tools, in-modal state updates (6daaad81) +* removed ajaxify refresh on crosspost commit, dynamically update post stats in template, logic fix (b981082d) +* nodeinfo route to publish federation.enabled in metadata section (14aa2bee) +* bump link-preview again (74e47820) +* bump link-preview (486e77c7) +* remove commented out require (ffc3d279) +* bump link-preview (cc1649e0) +* auto-enable post queue as default, adjust tests to compensate (9390ccb6) +* remove bidiControls from notification.bodyShort (b0679cad) +* author of boosted content was not targeted in the activity (b05199d8) +* closes #13872, use translator.compile for notification text (5a031d01) +* #13715, dont reduce hardcap if usersPerPage is < 50 (cb31e70e) +* dont use sass-embedded on freebsd, #13867 (b7de0cc7) +* wrong increment value (20918b52) +* increment progress on upgrade script (8abe0dfa) +* add join-lemmy context for outgoing category group actors context prop (f1d50c35) +* use setsAdd (d8e55d58) +* missing await (4a6dcf1a) +* admin privilege overrides only apply to local categories (7b194c69) +* have notes.assert call out.announce.topic only if uid is set (so, if note assertion is called via search; manual pull) (3b7bcba6) +* deep clone activity prop before execution; feps.announce (977a67f4) +* minor comment fix (411baa21) +* publish `postingRestrictedToMods` property in group actor (c365c1dc) +* **deps:** + * update dependency spdx-license-list to v6.11.0 (#13890) (9b1c32b1) + * update dependency diff to v8.0.3 (#13882) (974ab1f8) + * update dependency nodebb-theme-persona to v14.1.23 (#13878) (47074b3c) + * update dependency nodebb-theme-harmony to v2.1.31 (#13877) (125c8e58) + * update dependency body-parser to v2.2.2 (#13873) (e717f00e) + * update dependency sass to v1.97.2 (#13871) (5100cc4f) + * update dependency nodebb-plugin-markdown to v13.2.3 (#13869) (a8c18f8a) + * update dependency nodebb-theme-harmony to v2.1.30 (#13863) (49379e2e) + * update dependency nodebb-theme-persona to v14.1.22 (#13864) (e4435e52) + * update dependency @isaacs/ttlcache to v2.1.4 (#13861) (89abdca1) + * update socket.io packages to v4.8.3 (#13857) (6807f860) + * update dependency sass to v1.97.1 (#13856) (7325b995) + * update dependency nodebb-theme-persona to v14.1.20 (#13855) (b8f68fb4) + * update dependency nodebb-theme-harmony to v2.1.28 (#13854) (f98fd6dc) + * update dependency fs-extra to v11.3.3 (#13851) (160ce17f) + * update dependency nodemailer to v7.0.12 (#13853) (f6ef041c) + * update dependency nodebb-plugin-2factor to v7.6.1 (#13852) (abcb2382) + * update dependency validator to v13.15.26 (#13846) (2a10f904) + * update dependency nodebb-theme-persona to v14.1.19 (#13849) (b933d1a2) + * update dependency nodebb-theme-harmony to v2.1.27 (#13848) (61d8cba9) + * update dependency webpack to v5.104.1 (#13847) (bb5a90a3) + * update dependency esbuild to v0.27.2 (#13842) (5844e393) + * update dependency nodebb-plugin-mentions to v4.8.4 (#13845) (2ffa4383) + * update dependency webpack to v5.104.0 (#13839) (f16eec30) + * update dependency sass to v1.97.0 (#13838) (ab8dbb41) + * update dependency fetch-cookie to v3.2.0 (#13836) (0ef5cbbb) + * update dependency autoprefixer to v10.4.23 (#13835) (7c2e8330) + * update dependency terser-webpack-plugin to v5.3.16 (#13827) (da7c9b32) + * update dependency sass to v1.96.0 (#13822) (d4f53a62) + * update dependency winston to v3.19.0 (#13812) (81c232f1) + * update dependency cron to v4.4.0 (#13818) (f077c4ca) + * update dependency sass to v1.95.1 (#13816) (adedb7b6) + * update dependency sass to v1.95.0 (#13815) (eaa6e71a) + * update dependency terser-webpack-plugin to v5.3.15 (#13811) (10d2e929) + * update dependency esbuild to v0.27.1 (#13806) (6b1dcb4b) + * update dependency jsonwebtoken to v9.0.3 (#13807) (7b734cfd) + * update dependency ace-builds to v1.43.5 (#13797) (93057306) + * update dependency lru-cache to v11.2.4 (#13798) (731933a6) + * update dependency express to v4.22.1 (#13800) (38321220) + * update dependency ipaddr.js to v2.3.0 (#13801) (ad5cd27b) + * update dependency nodemailer to v7.0.11 (#13799) (ecec1f45) + * update dependency cron to v4.3.5 (#13796) (5ba6bea0) + * update dependency body-parser to v2.2.1 (#13795) (624ef616) + * update dependency @isaacs/ttlcache to v2.1.3 (#13791) (5f55ca85) + * update dependency sass to v1.94.2 (#13786) (1cb8b381) + * update dependency redis to v5.10.0 (#13787) (1bcfe3f0) + +##### Other Changes + +* fix... tests (d20906b5) +* still broken... more debug logs (a82e1f44) +* log mock results (8236b594) + +##### Refactors + +* check if tid is truthy (0e1ccfc9) +* crossposts.get to return limited category data (name, icon, etc.), fixed up crosspost modal to hide uncategorized and all categories options (349b0875) +* move crosspost methods into their own file in src/topics (1be88ca0) +* silence if-function deprecation on prod (403230cc) +* clear quick reply as soon as submitting (a331f8da) + +##### Tests + +* intify uid/cid if they are numbers (when getting crossposts) (47e37ed5) +* stop using partialDeepStrictEqual for now (0677689a) +* ensure auto-cat and cat sync logic properly integrates with crossposts (add163a4) +* crossposting behaviour and logic tests (947676ef) +* new test file for crossposts (3560b6a3) +* additional logic to allow multi-typing in schema type (4f1fa2d1) +* lowercase tags (81cac015) +* fix test to check for Secure in cookie string if test runner domain is https (5954015e) +* more out.announce tests (cfdbbb04) +* basic tests for activitypub.out (67912dc9) +* update activitypub._sent to save targets as well, updated tests to accommodate format change (41368ef8) +* test runs should not actually federate activities out (483ab083) +* check if tests pass without await (5414cf47) +* add back logs for failing test (301b5386) +* add a test for set db.exists (#13809) (69562704) +* fix failing test by adjusting the tests (c5292442) +* privilege masking tests (934e6be9) +* log label (22d3c523) +* log activities (e39c9149) +* on test fail show activities (841bd825) +* new mongodb deps (#13793) (287b2569) + +#### v4.7.2 (2025-12-24) + +##### Chores + +* up body-parser (59dd1ca6) +* up mentions (d505301f) +* incrementing version number - v4.7.1 (afb88805) +* update changelog for v4.7.1 (8668cfb3) +* incrementing version number - v4.7.0 (e82d40f8) +* incrementing version number - v4.6.3 (9fc5b0f3) +* incrementing version number - v4.6.2 (f98747db) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* update data-isowner when changing is ownership (1f9f2dff) +* bump 2factor (d0313712) + +##### Tests + +* change redis connection (#13844) (550411fb) +* add await to check tests (1305faa8) +* add back logs for failing test (9f8d5070) + +#### v4.7.1 (2025-12-17) + +##### Chores + +* up widget-essentials (9d666550) +* remove log (2142b680) +* up harmony (59f649b8) +* incrementing version number - v4.7.0 (e82d40f8) +* update changelog for v4.7.0 (1c0a43dc) +* incrementing version number - v4.6.3 (9fc5b0f3) +* incrementing version number - v4.6.2 (f98747db) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Continuous Integration + +* drop ARM v7 from docker builds (#13808) (254370c5) + +##### New Features + +* stop extraneous vote and tids_read data from being saved for remote users (9f729964) +* add hreflang to buildLinkTag (ba85474d) +* #13790, allow ssl setup in psql (5bd1f7b7) + +##### Bug Fixes + +* wrong increment value (b1fc5bfd) +* increment progress on upgrade script (9f94a721) +* disallow inline viewing of unsafe files (#13833) (5ae8d553) +* moving topic to cid=-1 will remove it from list (90a15134) +* show errors when saving settings (f49f540b) +* closes #13666, update category label (193aaf55) +* respect user pagination settings in infinite scroll (#13765) (#13788) (ebf2a2c5) +* remove hardcoded name for sentinel, #13794 (53e22acf) + +##### Other Changes + +* fix missing comma (9fb41c69) + +##### Reverts + +* spec change (b19281b0) + +##### Tests + +* fix tests (11b01dfc) + +#### v4.7.0 (2025-11-26) + +##### Chores + +* incrementing version number - v4.6.3 (9fc5b0f3) +* update changelog for v4.6.3 (3fd193e3) +* incrementing version number - v4.6.2 (f98747db) +* up dbsearch (dfe53d29) +* up harmony, closes #13753 (4e33c1df) +* up express-useragent (b5ea2089) +* up ttlcache to 2.x (a0a10c8b) +* up themes (52c56bc5) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) +* **deps:** + * update dependency @stylistic/eslint-plugin to v5.6.1 (#13778) (894f1988) + * update redis docker tag to v8.4.0 (#13782) (e24d8c17) + * update postgres docker tag to v18.1 (#13771) (3ea029bd) + * update dependency jsdom to v27.2.0 (#13770) (899414f4) + * update dependency smtp-server to v3.16.1 (#13755) (bc64d27f) + * update dependency mocha to v11.7.5 (#13754) (e1bf80dc) + * update redis docker tag to v8.2.3 (#13750) (4c5f7f60) + * update github artifact actions (#13730) (13c23fdd) + * update dependency @eslint/js to v9.39.1 (#13747) (4e7867a9) + * update dependency sass-embedded to v1.93.3 (#13745) (cb96701b) + * update dependency jsdom to v27.1.0 (#13743) (4ce4e773) + * update mongo docker tag to v8.2 (#13738) (97e5aa1d) + * update dependency smtp-server to v3.16.0 (#13737) (07d169d2) + * update dependency lint-staged to v16.2.6 (#13725) (e3c55f76) + * update dependency lint-staged to v16.2.5 (#13721) (83a172c9) + * update dependency @stylistic/eslint-plugin to v5.5.0 (#13717) (93d46c84) + * update dependency jsdom to v27.0.1 (#13718) (9d2b83f5) + * update dependency @eslint/js to v9.38.0 (#13716) (7fd9e894) + * update actions/setup-node action to v6 (#13708) (febe0ae0) + * update dependency smtp-server to v3.15.0 (#13702) (238600a0) + * update dependency lint-staged to v16.2.4 (#13699) (f608c7c7) + * update postgres docker tag to v18 (#13679) (923ddbc1) + * update dependency @eslint/js to v9.37.0 (#13693) (d73892ae) + * update redis docker tag to v8.2.2 (#13692) (4640a63e) + * update dependency mocha to v11.7.4 (#13685) (c7696667) + * update dependency @commitlint/cli to v20.1.0 (#13686) (eb06bda8) +* **i18n:** + * fallback strings for new resources: nodebb.admin-manage-categories (49567c72) + * fallback strings for new resources: nodebb.admin-settings-uploads (e7498e8f) + +##### New Features + +* federate out undo(announce) when moving topics (832477f8) +* native image appending for remote private notes (822f4edc) +* add isNumber to client-side helpers (172aabcb) +* handle Move(Context) activity (8ca52c7e) +* update Remove(Context) to use target instead of origin, federate out Move(Context) on topic move between local cids (d02e188a) +* context removal logic (aka moving topics to uncategorized, and federating this to other NodeBBs) (34e95e6d) +* add new setting to control posts uploads being shown as thumbs (97e59fbe) +* handle Delete(Context) as a move to cid -1 if the remote context still exists (f98a7216) +* handle incoming Announce(Delete), closes #13712 (4d5005b9) +* execute 1b12 rebroadcast logic on all tids even if not posted to a local cid (9583f0d4) +* auto-enable link-preview plugin on new installations (b153941c) +* bundle link-preview plugin (e7bdf6bc) +* federate topic deletion on topic deletion as well as purge (4d24309a) +* federate Delete on post delete as well as purge, topic deletion federates Announce(Delete(Object)) (93b6cb59) + +##### Bug Fixes + +* **deps:** + * bump mentions to fix #13637 (e3ac9ccf) + * update dependency rimraf to v6.1.2 (#13784) (5ab8f877) + * update dependency @isaacs/ttlcache to v2.1.2 (#13780) (cecc0fee) + * update dependency workerpool to v10.0.1 (#13781) (bfffb4b9) + * update dependency webpack to v5.103.0 (#13783) (5acfd184) + * update dependency sass to v1.94.1 (#13777) (b0c9bb1e) + * update dependency mongodb to v6.21.0 (#13772) (111ae163) + * update dependency sass to v1.94.0 (#13773) (c95bfcbf) + * update dependency validator to v13.15.23 (#13769) (93c69f9d) + * update dependency express-useragent to v2.0.2 (#13767) (e14d3ac1) + * update dependency autoprefixer to v10.4.22 (#13768) (9271e267) + * update dependency @isaacs/ttlcache to v2.1.1 (#13763) (f24bb090) + * update dependency esbuild to v0.27.0 (#13766) (63789ebb) + * update dependency cron to v4.3.4 (#13762) (6ad93cd3) + * update dependency sharp to v0.34.5 (#13758) (5be0a630) + * update dependency bcryptjs to v3.0.3 (#13751) (a34284df) + * update dependency sitemap to v9 (#13752) (1921ccaa) + * update dependency esbuild to v0.25.12 (#13748) (090eb088) + * update dependency rimraf to v6.1.0 (#13744) (a36d89fc) + * update dependency sass to v1.93.3 (#13746) (ba123073) + * update dependency sitemap to v8.0.2 (#13736) (b5c1e8e7) + * update mentions (5c3b1261) + * update dependency validator to v13.15.20 (#13733) (6f448ce2) + * bump mentions to 4.8.0 (964a5388) + * update dependency commander to v14.0.2 (#13731) (a49efe49) + * update dependency redis to v5.9.0 (#13727) (418717fd) + * update dependency nodemailer to v7.0.10 (#13726) (c1f6e52b) + * update dependency workerpool to v10 (#13723) (5a6c2097) + * update dependency sitemap to v8.0.1 (#13720) (1d9d7fc5) + * update dependency ace-builds to v1.43.4 (#13714) (27a0dc73) + * bump dbsearch (c25c6290) + * update dependency esbuild to v0.25.11 (#13710) (41b7a91d) + * update dependency chart.js to v4.5.1 (#13704) (bf37c7bd) + * update dependency nodebb-theme-persona to v14.1.15 (#13701) (fa18287d) + * update dependency nodebb-theme-harmony to v2.1.21 (#13700) (49a29325) + * update dependency nodemailer to v7.0.9 (#13695) (5d3709f0) + * update dependency semver to v7.7.3 (#13697) (a2892f60) + * update dependency webpack to v5.102.1 (#13698) (bb7b65ea) + * update dependency nodemailer to v7.0.7 (#13694) (5dc9f2c5) + * update dependency redis to v5.8.3 (#13691) (9b6e9b2a) + * update dependency winston to v3.18.3 (#13687) (19dc1025) +* null check on attachments property in assertPrivate (9d83a3d0) +* update announce and undo(announce) so that their IDs don't use timestamps (24e17683) +* incorrect topic event added when topic moved out of cid -1 (used to be a share by the user; since removed.) (2b733e4a) +* #13654, improper OrderedCollectionPage ID (aa7e078f) +* IS logic when body.height < window.height (bdb45248) +* update markdown and web-push to latest versions (c51b7b65) +* bump mentions to 4.8.2 (2ce691cb) +* rename activitypub.out.announce.category, federate out Delete on topic move to cid -1 (9bb8a955) +* bump harmony and persona for #13756 (c616e657) +* renderOverride to not clobber url if already set in template data (2066727f) +* bump themes for cross-post support, #13396 (9d3e8179) +* add replies in parallel during note assertion (4858abe1) +* logic error in context generation (748cc5ee) +* relax toPid assertion checks so that it only checks that it is a number or uri (30b1212a) +* update logic so that purging a post does not remove toPid fields from children, updated addParentPosts so that post existence is checked (f6219d00) +* update category mock to save full handle (524df6e5) +* logic error in out.remove.context (ab9154aa) +* cross-check remove(context) target prop against cid (194cedb4) +* update logic re: federating out topic moves (4f2f872b) +* bad var (22868d3f) +* call api.topics method on topic move during note assertion, have category announce new topic on note assertion (3df4970c) +* do not include image or icon props if they are falsy values (603068ae) +* rebroadcasting logic should only execute for local tids if the remote cid is not addressed already (1d529473) +* move Announce(Delete) out of topics.move and into topics API method (fadac616) +* do not include actor from reflected activity when rebroadcasting remote cid (3fa74d4c) +* broken category urls in to, cc (d4695f10) +* update getPrivateKey to send application actor key when cid 0 (a45f6f9c) +* update targets in 1b12 rebroadcast when cid is remote (58a9e1c4) +* update 1b12 rebroadcast logic to send as application actor if post is in remote cid (79d08853) +* regression caused by d3b3720915f5846e8f5a8e0bee9c17b3ff233902 (af5efbd7) +* crash in tests (6c210068) +* add attachments to retrieved post data onNewPost (07bed55e) + +##### Other Changes + +* //github.com/NodeBB/NodeBB/issues/13713 (2425f3b6) + +##### Refactors + +* deleteOrRestore internal method to federate out a Delete on delete, not just purge; better adheres to FEP 4f05 (e6911be3) +* get rid of post.exists check, if post doesnt exist content is falsy (17944037) +* move all methods in src/api/activitypub.js to src/activitypub.out.js (3ede64d8) +* user announces no longer occur on topic move. Instead, the new category announces. Only occurs when topic moved to local categories. (e09bb8b6) +* inbox announce(delete) handling to also handle context deletion, #13712 (2b2028e4) +* move post attachment handling directly into posts.create (d3b37209) + +##### Reverts + +* remove `federatedDescription` category field, closes #13757 (ed83bc5b) + +##### Tests + +* update test for toPid logic to reflect that toPid stays even if parent is purged (98a1101d) + +#### v4.6.3 (2025-11-20) + +##### Chores + +* incrementing version number - v4.6.2 (f98747db) +* update changelog for v4.6.2 (8da3819c) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* update validator dep. to get fix for CVE-2025-56200 (af477d0c) +* missing logic in mocks.notes.private that precluded the use of emoji (76a07d59) +* tiny fix for IS when page is empty (12dab849) + +#### v4.6.2 (2025-11-19) + +##### Chores + +* up emoji (5bc5bb3d) +* up peace, closes #13774 (f764b791) +* incrementing version number - v4.6.1 (f47aa678) +* update changelog for v4.6.1 (655c858b) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* #13779, svg uploads (e3002411) +* #13776, if plugin is in install/package.json use latest version from there (abfb6d13) +* category labels showing up on infinite scroll on category page (dece0628) +* crash in resolveInboxes (9900171f) +* log out user if session cookie resolves to non-existent uid (5d9da603) +* make i18n test failure message easier to read (3a81f903) +* wrong auto-categorization if group actor is explicitly included in `audience` (be4d0e81) +* order of operations when updating category handle (5cfec5b1) +* closes #13729, fix filename encoding (9410f466) + +##### Other Changes + +* fix lint (008e1ae4) + +##### Refactors + +* remove unused share (aacd27ee) + +##### Tests + +* add test for #13729 (430a3e81) + +#### v4.6.1 (2025-10-17) + +##### Chores + +* up persona (b309a672) +* up harmony (79327e6c) +* incrementing version number - v4.6.0 (ee395bc5) +* update changelog for v4.6.0 (c0d9bb07) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* do not include image or icon props if they are falsy values (ecf95d18) +* #13705, don't cover link if preview is opening up (499c50a4) +* logic error in image mime type checking (623cec9d) +* omg what. (ec399897) + +#### v4.6.0 (2025-10-01) + +##### Chores + +* remove unneeded secureRandom require (3fcaa678) +* incrementing version number - v4.5.2 (ad2da639) +* update changelog for v4.5.2 (9a596d67) +* fix grammatical error in language string (cf3964be) +* remove formatApiResponse logging (feda629f) +* up eslint (a5ea4b40) +* incrementing version number - v4.5.1 (69f4b61f) +* update default settings (5d653571) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) +* **deps:** + * update dependency lint-staged to v16.2.3 (#13681) (d7e93a5d) + * update actions/download-artifact action to v5 (#13646) (30ca0000) + * update dependency @eslint/js to v9.36.0 (#13670) (a4d8619b) + * update commitlint monorepo to v20 (#13678) (6dab3f2e) + * update dependency @stylistic/eslint-plugin to v5.4.0 (#13671) (3370c064) + * update dependency lint-staged to v16.2.1 (#13672) (13ce106b) + * update dependency sass-embedded to v1.93.2 (#13673) (df9d637c) + * update dependency jsdom to v27 (#13653) (3238248e) + * update dependency sass-embedded to v1.92.1 (#13638) (15b0b540) + * update dependency lint-staged to v16.1.6 (#13635) (7147a2e3) + * update actions/setup-node action to v5 (#13647) (4f5e770c) + * update dependency mocha to v11.7.2 (#13636) (ac90ef8c) +* **i18n:** + * fallback strings for new resources: nodebb.admin-manage-categories (6055b345) + * fallback strings for new resources: nodebb.admin-manage-categories (8730073a) + * fallback strings for new resources: nodebb.admin-manage-categories (8d4e4652) + * fallback strings for new resources: nodebb.admin-settings-activitypub (89390101) + +##### Documentation Changes + +* update openapi schema to refer to try.nodebb.org instead of example.org (56a93366) + +##### New Features + +* ability to nickname remote categories, closes #13677 (bd80b77a) +* allow activities to be addressed to as:Public or Public to be treated as public content (5f4790a4) +* allow user auto-categorization rule (1d6a9fe7) +* add minor pre-processing step to better handle header elements in incoming html (15f9fbaa) + +##### Bug Fixes + +* login handler to handle if non-confirmed email is entered (5ed19ef8) +* allow quote-inline class in mocks sanitizer so quote-post fallback elements can be detected and removed during title generation, fixes #13688 (675178ac) +* force outgoing page on direct access to `/ap` handler (9cee7999) +* update outgoing page to match 404 design (954e7bc8) +* don\'t begin processing local login if the passed-in username isn't even valid (c3df68f2) +* #13667, record to instances:lastSeen instead of domains:lastSeen (7184507b) +* #13676, bug where nested remote categories could not be removed (175dc209) +* regression 218f5ea from via, stricter check on whether the calling user is a remote uid (8c553b18) +* #13668, privilege checking on topic create for remote users; was not properly checking against fediverse pseudo-user (218f5eab) +* update logic as to whether a post is served as an article or not (d122bf4a) +* update activitypubFilterList logic so that it is also checked on resolveInbox and ActivityPub.get methods, updated instances.isAllowed to no longer return a promise (be9212b5) +* add missing unlock in nested try/catch (9184a7a4) +* wrap majority of note assertion logic in try..catch to handle exceptions so that the lock is always released (95fb084c) +* use newline_boundaries param for tokenizer during title and summary generation, attempt to serve HTML in summary generation (2ea624fc) +* deprecated call to api.topics.move (0f9015f0) +* **deps:** + * update dependency webpack to v5.102.0 (#13683) (17dba0b0) + * update dependency mongodb to v6.20.0 (#13665) (9b00ff1e) + * update dependency lru-cache to v11.2.2 (#13669) (00d80616) + * update dependency sass to v1.93.2 (#13674) (1b5804e1) + * update fontsource monorepo (#13663) (6e84e35f) + * update dependency esbuild to v0.25.10 (#13664) (9b48bbd5) + * update dependency sharp to v0.34.4 (#13662) (c8680f30) + * update dependency satori to v0.18.3 (#13660) (b2d91dc3) + * update dependency nodebb-theme-harmony to v2.1.20 (#13659) (b845aa48) + * update dependency fs-extra to v11.3.2 (#13658) (8324be2d) + * update dependency @fontsource/inter to v5.2.7 (#13655) (db892509) + * update dependency commander to v14.0.1 (#13652) (19f39198) + * update dependency bootswatch to v5.3.8 (#13651) (1e82af66) + * update dependency sass to v1.92.1 (#13645) (10344c98) + * update dependency workerpool to v9.3.4 (#13650) (6a1e9e8a) + * update dependency lru-cache to v11.2.1 (#13644) (6adfbb24) + +##### Other Changes + +* disallow checkHeader from returning a URL from a different origin than the passed-in URL (4776d012) +* 'nickname' and 'descriptionParsed' use in categories controller (051043b6) + +##### Performance Improvements + +* update old upgrade scripts to use bulkSet/Add (2b987d09) + +##### Refactors + +* notes.assert to add finally block, update assertPayload to update instances:lastSeen via method instead of direct db call (559155da) + +##### Reverts + +* post queue changes to fix tests (10350ea6) + +##### Tests + +* fix message (d6e7e168) +* show tids on test fail (8614d825) +* more fixes for note vs. article (3bba9029) +* short OPs create Notes again (15878087) +* ap timeouts (8d6a0f02) +* disable post queue when testing posting logic (9bfce68b) + +#### v4.5.2 (2025-09-29) + +##### Chores + +* remove obsolete deprecation (52fec493) +* up persona (405d2172) +* incrementing version number - v4.5.1 (69f4b61f) +* update changelog for v4.5.1 (a9fffd7c) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* add a term param to recent controller so it can be controller without req.query.term (9c18c6fe) +* add a new hook to override generateUrl in navigator.js (68a8db85) +* add topic templates per category, closes #13649 (0311b98e) + +##### Bug Fixes + +* skip header checking during note assertion if test runner is active (7abdfd86) +* update note assertion topic members check to simpler posts.exists check (d0c05826) +* re-jig handling of ap tag values so that only hashtags are considered (not Piefed community tags, etc.) (4d68e3fe) +* missing actor assertion on 1b12 announced upboat (f9edb13f) +* use parameterized query for key lookup (6cca55e3) +* add pre-processing step to title generation logic so sbd doesn't fall over so badly (f7c47429) +* switch to action (f7bbec7c) +* handle cases where incoming ap object tag can be a non-array (b66c30a2) +* local pids not always converted to absolute URLs on topic actor controller (f67942ca) +* #13657, fix remote category data inconsistency in `sendNotificationToPostOwner` (225bf85e) +* don't show votes on unread if rep system disabled (dfe19a98) +* if reputation is disabled hide votes on /recent (8a786c71) +* favicon path (e2dc592c) +* check brand:touchIcon for correct path (56fad0be) +* remove .auth call (f9ddbeba) +* port the try/catch for notes.assert from develop (f9688b36) +* perform Link header check on note assertion only when skipChecks is falsy (953c051c) +* make auto-categorization logic case-insensitive (527f27af) +* closes #13641, log test email sending errors server side (b3ffa007) +* pass object to.auth (290a9395) +* **deps:** bump 2factor to 7.6.0 (d1f5060f) + +##### Other Changes + +* remove unused (a6674f67) +* fix (a37521b0) + +##### Performance Improvements + +* update upgrade script to use bulk methods (0a2fa45d) +* update old upgrade scripts to use bulkSet/Add (32d0ee48) + +#### v4.5.1 (2025-09-04) + +##### Chores + +* up dbsearch (c07e81d2) +* incrementing version number - v4.5.0 (f05c5d06) +* update changelog for v4.5.0 (86d03b1e) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* use _variables.scss overrides from acp in custom skins and bootswatch skins as well (0c48e0e9) + +##### Bug Fixes + +* remove unused dependency (8d7e3537) +* remove test for 1b12 announce on topic move (as this no longer occurs) (9221d34f) +* use existing id if checkHeader returns false (e6996846) +* regression that caused Piefed (or potentially others) content to be dropped on receipt (86d9016f) +* remove faulty code that tried to announce a remote object but couldn't as the ID was not a number (7adfe39e) + +#### v4.5.0 (2025-09-03) + +##### Chores + +* **deps:** + * pin dependency @stylistic/eslint-plugin to 5.3.1 (#13634) (4ade6007) + * update dependency sass-embedded to v1.91.0 (#13614) (e504ee34) + * update dependency @eslint/js to v9.34.0 (#13612) (dfc558cd) + * update redis docker tag to v8.2.1 (#13603) (02228c04) + * update dependency lint-staged to v16.1.5 (#13585) (f4f7953a) + * update postgres docker tag to v17.6 (#13599) (62d15a0e) + * update dependency @eslint/js to v9.33.0 (#13589) (bfdf47b6) + * update actions/checkout action to v5 (#13590) (311bbefa) + * update dependency sass-embedded to v1.90.0 (#13581) (c8694333) + * update dependency lint-staged to v16.1.4 (#13575) (34ecdf20) + * update redis docker tag to v8.2.0 (#13577) (25bc9ba0) + * update dependency @eslint/js to v9.31.0 (#13545) (97a5d543) + * update redis docker tag to v8.0.3 (#13539) (1b80910e) + * update dependency @eslint/js to v9.30.1 (#13524) (6d7df13f) + * update dependency @eslint/js to v9.30.0 (#13519) (15ea1233) + * update dependency smtp-server to v3.14.0 (#13515) (a41d2c0b) + * update dependency mocha to v11.7.1 (#13509) (bbacd8f6) + * update dependency mocha to v11.7.0 (#13502) (0a0dd1c1) + * update dependency @eslint/js to v9.29.0 (#13491) (2046ca72) + * update dependency lint-staged to v16.1.2 (#13492) (d6ba7930) + * update dependency sass-embedded to v1.89.2 (#13482) (f5651787) + * update dependency mocha to v11.6.0 (#13479) (9b4082dc) + * update dependency smtp-server to v3.13.8 (#13464) (d239125f) + * update redis docker tag to v8.0.2 (#13465) (166aaa7a) + * update dependency @eslint/js to v9.28.0 (#13469) (b3170c9c) + * update dependency sass-embedded to v1.89.1 (#13463) (32f13162) + * update dependency lint-staged to v16.1.0 (#13449) (6efe3fdd) + * update dependency mocha to v11.5.0 (#13442) (c1846475) + * update dependency smtp-server to v3.13.7 (#13437) (136e8814) + * update dependency sass-embedded to v1.89.0 (#13425) (aa977282) + * update dependency mocha to v11.4.0 (#13435) (5d017710) + * update dependency mocha to v11.3.0 (#13426) (650eeac9) + * update dependency @eslint/js to v9.27.0 (#13429) (475b0704) +* **i18n:** + * fallback strings for new resources: nodebb.admin-settings-activitypub (cb00fb3b) + * fallback strings for new resources: nodebb.admin-manage-categories, nodebb.admin-settings-activitypub (40bda8fc) + * fallback strings for new resources: nodebb.social (eeabc990) + * fallback strings for new resources: nodebb.admin-dashboard (5d16fdc9) + * fallback strings for new resources: nodebb.admin-development-info (59c1ce85) + * fallback strings for new resources: nodebb.admin-development-info (5b54e926) + * fallback strings for new resources: nodebb.modules (f5aca114) + * fallback strings for new resources: nodebb.error (efb14ead) + * fallback strings for new resources: nodebb.error (e1eb76fe) +* enable dbsearch on new installs (567f453b) +* up peace (fdd0152e) +* up harmony (6d60f945) +* use fontsource-utils/scss to get rid of deprecation warning (44c0413c) +* up eslibt (e68deaac) +* up widget essentials (e7b47995) +* incrementing version number - v4.4.6 (074043ad) +* update changelog for v4.4.6 (3895a059) +* incrementing version number - v4.4.5 (6f106923) +* up eslint (637373e3) +* up dbsearch (dae81b76) +* up eslint-plugin (18d6e5e1) +* up eslint (c056bf56) +* remove logs (0315e369) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* up eslint (536ae9d6) +* incrementing version number - v4.4.2 (55c510ae) +* eslint config (0d595008) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Continuous Integration + +* use native arm runners for building docker images (#13627) (931b7345) + +##### Documentation Changes + +* add missing routes to openapi schema (0f44034e) +* openapi typo (560cc2eb) +* update openapi schema for relays and rules (a9a12a9f) +* openapi schema fixes for auto-categorization commits (c0248ca5) + +##### New Features + +* use sbd to more intelligently put together a sub-500 character summary based on existing sentences in post content (35641f37) +* add sbd dependency to improve title generation (and for summary generation, later) (82686322) +* send local posts out to established relays (aa26dfb3) +* relay handshake logic, handle Follow/Accept, send back Accept. (f4d1df7c) +* adding and removing relays from AP settings page in ACP (1e0fb20d) +* apply auto-categorization logic (165af50d) +* ability to add/remove auto-categorization rules for incoming federated content (bdcf28a3) +* re-jigger 'add category' button to allow addition of remote category to main index (75639c86) +* add Urdu localisation, thank you! (8c6992f5) +* add wordpress (82037dee) +* add wordpress (c10656ec) +* only mark notifications read that match current filter (9d39ed51) +* closes #13578, increase uniquevisitors (e1423636) +* add new brite skin from bootswatch (e851a523) +* add filter:post.getDiffs (97d4994a) +* add filter:post.getDiffs (90a65129) +* add expose-gc flag to loader (bba18e31) +* add ap pageviews analytics (559a2d23) +* add heap snapshot (f88329db) +* add option to toggle chat join/leave message (92a3859f) +* add protection mechanism to request lib so that network requests to reserved IP ranges throw an error (9d3b8c3a) + +##### Bug Fixes + +* **deps:** + * update dependency satori to v0.18.2 (#13628) (2dc39f1e) + * update dependency ace-builds to v1.43.3 (#13633) (7adabd60) + * update dependency nodemailer to v7.0.6 (#13630) (07b9cd16) + * update dependency mongodb to v6.19.0 (#13619) (6d856545) + * update dependency sass to v1.91.0 (#13615) (08ea56bd) + * update dependency bootstrap to v5.3.8 (#13618) (29a7402f) + * update dependency nodebb-theme-harmony to v2.1.17 (#13607) (2f4cf26c) + * update dependency nodebb-theme-peace to v2.2.47 (#13608) (8af76f3c) + * update dependency redis to v5.8.2 (#13606) (138c6753) + * update dependency webpack to v5.101.3 (#13602) (996740bd) + * update dependency webpack to v5.101.2 (#13598) (90bddccb) + * update dependency nodebb-widget-essentials to v7.0.40 (#13597) (f5b0444b) + * update dependency tough-cookie to v6 (#13600) (ceb65d13) + * update dependency esbuild to v0.25.9 (#13593) (9ef4cfa2) + * update dependency redis to v5.8.1 (#13594) (0f72b8cd) + * update dependency webpack to v5.101.1 (#13588) (c67aa43f) + * update dependency sass to v1.90.0 (#13582) (abf7dd74) + * update dependency fs-extra to v11.3.1 (#13579) (5ce556d4) + * update dependency redis to v5.8.0 (#13580) (3c3e4486) + * update dependency redis to v5.7.0 (#13570) (27d60a19) + * update dependency cron to v4.3.3 (#13573) (0b4efa14) + * update dependency satori to v0.16.2 (#13569) (70d3a29c) + * update dependency webpack to v5.101.0 (#13567) (6fc8dfa9) + * update dependency satori to v0.16.1 (#13560) (2d1a5fea) + * update dependency redis to v5.6.1 (#13564) (1262aee8) + * update dependency mongodb to v6.18.0 (#13563) (8e9d3843) + * update dependency esbuild to v0.25.8 (#13559) (6a732e36) + * update dependency esbuild to v0.25.7 (#13557) (1697e36f) + * update dependency express-session to v1.18.2 (#13554) (0eb0a67a) + * update dependency morgan to v1.10.1 (#13555) (0e457f15) + * update dependency multer to v2.0.2 (#13556) (35ca0e3b) + * update dependency compression to v1.8.1 (#13553) (12b9f4c7) + * update dependency ace-builds to v1.43.2 (#13548) (57564190) + * update dependency webpack to v5.100.2 (#13549) (0b398bba) + * update dependency webpack to v5.100.1 (#13544) (d8c26bec) + * update dependency cron to v4.3.2 (#13546) (e838bb26) + * update dependency nodebb-theme-peace to v2.2.46 (#13542) (e4f56e83) + * update dependency webpack to v5.100.0 (#13541) (4a5a4fe6) + * update dependency redis to v5.6.0 (#13540) (a6cb933b) + * update dependency esbuild to v0.25.6 (#13538) (8960fdb3) + * update dependency nodemailer to v7.0.5 (#13537) (c6f4148b) + * update dependency nodebb-theme-peace to v2.2.45 (#13529) (991f518e) + * update dependency nodebb-plugin-web-push to v0.7.5 (#13523) (ceae2aa1) + * update dependency ace-builds to v1.43.1 (#13525) (aba2ddad) + * update dependency nodemailer to v7.0.4 (#13522) (f1fbea7b) + * update dependency pg to v8.16.3 (#13517) (fd82919e) + * update dependency workerpool to v9.3.3 (#13518) (655a3bd3) + * update dependency pg-cursor to v2.15.3 (#13516) (6e5083c2) + * update dependency pg to v8.16.2 (#13505) (d2f0944e) + * update dependency nodebb-theme-peace to v2.2.44 (#13514) (59090931) + * update dependency nodebb-theme-harmony to v2.1.16 (#13513) (4be2e82b) + * update dependency bootswatch to v5.3.7 (#13510) (1eefaf5c) + * update dependency pg-cursor to v2.15.2 (#13506) (10f7b49b) + * update dependency ace-builds to v1.43.0 (#13507) (e360f649) + * update dependency pg-cursor to v2.15.1 (#13504) (3b364ba1) + * update dependency pg to v8.16.1 (#13503) (819e2805) + * update dependency bootstrap to v5.3.7 (#13499) (e84fc739) + * update dependency connect-redis to v9 (#13497) (d3faff36) + * update dependency chart.js to v4.5.0 (#13495) (f36a5ac8) + * update dependency postcss to v8.5.6 (#13494) (703fcbbf) + * update dependency postcss to v8.5.5 (#13490) (c101d0d5) + * update dependency sass to v1.89.2 (#13487) (442c6e71) + * update dependency nodebb-plugin-emoji to v6.0.3 (#13486) (efcbbf29) + * update dependency serve-favicon to v2.5.1 (#13488) (d2a7eecb) + * update dependency @fontsource/inter to v5.2.6 (#13477) (c04bd7cc) + * update dependency satori to v0.15.2 (#13481) (78ebe298) + * update dependency satori to v0.14.0 (#13476) (29afcd36) + * update dependency workerpool to v9.3.2 (#13452) (6b33b1f4) + * update dependency satori to v0.13.2 (#13468) (44d1a17b) + * update dependency postcss to v8.5.4 (#13453) (1c432925) + * update dependency multer to v2.0.1 (#13466) (d0060e5d) + * update dependency sass to v1.89.1 (#13467) (602417d0) + * update dependency ace-builds to v1.42.0 (#13470) (c363b84e) + * update dependency mongodb to v6.17.0 (#13471) (a3cc99a2) + * update dependency cron to v4.3.1 (#13457) (3694f655) + * update dependency validator to v13.15.15 (#13451) (36f0cf25) + * update dependency esbuild to v0.25.5 (#13447) (6a5bbe92) + * update dependency nodebb-plugin-dbsearch to v6.2.18 (#13445) (3ca6a9bc) + * update dependency bootbox to v6.0.4 (#13443) (e3a7fb5c) + * update dependency diff to v8.0.2 (#13440) (76a624b9) + * update dependency commander to v14 (#13434) (1d624aad) + * update dependency webpack to v5.99.9 (#13438) (314a4ff0) + * update dependency connect-redis to v8.1.0 (#13433) (ee8e223f) + * update dependency nodebb-plugin-dbsearch to v6.2.17 (#13432) (42f16da5) + * update dependency sass to v1.89.0 (#13427) (2417a79b) +* display proper id if lock fails (19aa8a71) +* closes #13624, update post fields before schedule code (9d4a9b83) +* #13622, WordPress blog URLs not asserting properly (4ef605b1) +* closes #13625, fix utils.params so it works with relative_paths (a0e78ff8) +* remove webfinger error log (a0be4a28) +* urlencoded param in openapi spec example (5f7085f3) +* re-ordering dependencies because raisins (cbdc90a4) +* missed a tab character (788301a5) +* random hotkeys adding dependencies to my project smh (771b8dcb) +* parseAndTranslate bug (40973ca7) +* internationalize relay states (6576468e) +* minor fixes for yukimochi/Activity-Relay compatibility (28b63891) +* inbox.announce to not reject activities from relays (b1dbb19c) +* handle webfinger responses with subject missing scheme (4967492f) +* closes #13501 (bf279d71) +* closes #13620 (027d6f30) +* rare crash if queued item is no longer in db but id is in post:queue (e79dfeb7) +* jquery selector on post edit (f5ad7862) +* relative paths in openapi schema (a771b17f) +* add missing routes to write.yaml (e8401472) +* only process unique slugs (312df523) +* remove special-case logic that added a requested object to a topic if its defined context didn't actually contain it (70d7e329) +* return null if field is falsy (09898b94) +* mark-all read notifications button (c16f9d64) +* catch exceptions in assertPayload, closes #13611 (9bdf24f0) +* add missing files (057e3b79) +* add missing file to ur language folder (ecab347b) +* regression caused by cc6fd49c4d2ddc6970ea23011dece5ba91517ec0 (06c38247) +* protocol-relative URLs being accidentally munged, #13592 (cc6fd49c) +* cache lookup error when doing loopback calls (67389639) +* image handling when image url received is not a path with an extension (b4ff7906) +* readd retry items (c6889f08) +* set noindex tag on remote profiles as well (fe160160) +* duplicate canonical link header (c8ad0867) +* add rel canonical to remote user profiles (8ce5498f) +* ap queue id to use payload.type payload.id (a8bf4ea0) +* clearTimeout if item is evicted from cache (0997fbfa) +* sometimes summary is null/undefined (65364bfa) +* don't translate text on admin logs page (f6ed7ec2) +* change the client side reloginTimer to match setting (c43c3533) +* redis connect host/port (eac3d0a0) +* closes #13558, override/extend json opts from config.json (25c24298) +* add missing cache name (3f520c33) +* add missing ap pageview middleware (01f2effc) +* set to empty string if undefined (0ef98ec4) +* make clickable element anchor (dbed2db9) +* for attribute, remove upload trigger when click inputs (329f98d5) +* check topic and thumbs (72fec565) +* closes #13526, dont send multiple emails when user is invited (5a5ca8a5) +* pubsub on node-redis (f7f70468) +* typo (2280ea88) +* ensure check returns false if no addresses are looked up, fix bug where cached value got changed accidentally (6478532b) +* wrap cached returns for dns lookups in nextTick (010113a9) +* #13459, unread indicators for remote categories (6411c197) +* further guard against DNS rebinding attack (a8e613e1) +* undefined check, allow plugins to append to allow list (70c04f0c) +* simplify dns to use .lookup instead of .resolve4 and .resolve6, automatically allow requests to own hostname (df360216) +* return 200 for non-implemented activities instead of 501 (fcb3bfbc) +* remove null categories (28c021a0) +* patch ap .probe() so that it does not execute on requests for its own resources (a80edfa1) +* bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in `attributedTo` (8f933459) + +##### Other Changes + +* fix comma dangle (d4bf5f0c) +* fix lint issue (5dfd2413) +* remove unused url (076cc9e8) + +##### Refactors + +* revert, don't need to pass relative_path (f67265da) +* leaner utils.params for relative path (648c4543) +* remove invalid queued items (b73ee309) +* braces (f83d2536) +* add missing awaits (5ee1fd02) +* category listing logic to allow remote categories to be added, disabled, and re-arranged in main forum index (cb0b6092) +* show code/stack when dep check fails (f8733e06) +* dont del if cache disabled (bc40d79c) +* remove old arg (8305a742) +* if user.delete fails in actor prune (d5f6d158) +* use promise.all (472df3aa) +* use promise.all (6eab44a0) +* move ap retry queue from lru cache to db (#13568) (b3a4a128) +* log uid that failed (de71cc63) +* change default teaser to last-post (8ba230a2) +* copy session/headers when building req (e4a0160e) +* show both days and hours (1d7c32a5) +* add missing cache name (272008bb) +* another missing cache name (0fdde132) +* add names to caches, add max to request cache (a08551a5) +* closes #13547, process user uploads via batch (1ad97ac1) +* move post uploads to post hash (#13533) (24e7cf4a) +* parallel socket.io adapter (0b9bfc1c) +* use strings for cids (57a5de26) + +##### Reverts + +* remove heapdump (e74996fb) + +##### Tests + +* delete commented-out test (70bbed93) +* add timeout to ap.helpers.query (8f7411c3) +* more logs (8e160fe0) +* add more logs (f703a94b) +* add more logs (681ce8bf) +* debug timeout (029da6c5) +* more logs for failing test (79c6e72c) +* catch error in failing test (69a6c150) +* sharp invalid png (1ea10eff) +* latest sharp (3cdf28bd) +* add logs for test that's timing out (15155809) +* use protocol of test runner (04815497) +* fix notification tests (f8a0a7e1) +* one more fix (95f6688c) +* fix spec (7393bdd4) +* fix openapi (1071ac0c) +* fix meta test (1776bd1d) +* test fixes for default teaser change (8eedb38a) +* add openapi spec (020e0ad1) +* try timeout again (27aab921) +* disable timeout (930ff21f) +* psql fix (85e2d7d3) +* one more test fix (22d1972f) +* fix test, add joinLeaveMessages to newRoom (7acd63c2) +* increase timeout (fa31ba05) +* on more (1a85fafb) +* testing timeout on failing test (82c8034c) +* remove ci env (39d243b0) +* add a null field test (1fc91d5e) + +#### v4.4.6 (2025-08-06) + +##### Chores + +* incrementing version number - v4.4.5 (6f106923) +* update changelog for v4.4.5 (de05dad2) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* add new brite skin from bootswatch (567ed875) + +##### Bug Fixes + +* pass max-memory expose-gc as process args (d5f57af3) + +#### v4.4.5 (2025-07-31) + +##### Chores + +* **config:** migrate config renovate.json (#13565) (5a864150) +* incrementing version number - v4.4.4 (d323af44) +* update changelog for v4.4.4 (7b14e267) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* add filter:post.getDiffs (bbb9a460) + +##### Bug Fixes + +* clearTimeout if item is evicted from cache (5f696176) +* use sharp to convert svg to png, closes #13534 (b74c7898) +* use filename to check for svg, tempPath doesn't always have extension (5bcf078a) +* apply sanitizeSvg to regular uploads and uploads from manage uploads acp page (a8f4c5e6) + +##### Refactors + +* use promise.all (7c00e814) + +##### Tests + +* one more fix (5f5a6972) +* fix spec (3b609316) +* fix openapi (c7c83e0e) +* increase timeout of failing test (fe9b49e3) + +#### v4.4.4 (2025-06-18) + +##### Chores + +* incrementing version number - v4.4.3 (d354c2eb) +* update changelog for v4.4.3 (0c9297f8) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* link to post in preview timestamp (8c69c6a0) +* Add live reload functionality with Grunt watch and Socket.IO (#13489) (84d99a0f) +* closes #13484, post preview changes (14e30c4b) + +##### Bug Fixes + +* sanitize svg when uploading site-logo, default avatar and og:image (da2597f8) +* Revise package hash check in Docker entrypoint.sh (#13483) (6c5b2268) +* more edge cases (32faaba0) +* #13484, clear tooltip if cursor leaves link (0ebb31fe) + +##### Other Changes + +* fix lint (8ab034d8) + +##### Refactors + +* send single message (dc37789b) + +#### v4.4.3 (2025-06-09) + +##### Chores + +* up composer (5f51dfc4) +* incrementing version number - v4.4.2 (55c510ae) +* update changelog for v4.4.2 (6d40a211) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* escape, query params (b02eb57d) +* closes #13475, don't store escaped username (806e54bf) + +#### v4.4.2 (2025-06-02) + +##### Chores + +* up eslint stylistic (fd2ae726) +* up dbsearch (e2de0ec2) +* up dbsearch (30aa0fe6) +* up harmony (99234b3f) +* up harmony (a16bc738) +* incrementing version number - v4.4.1 (5ae79b4e) +* update changelog for v4.4.1 (a686cf20) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* add action:post-queue.save (ebb88c12) +* restrict access to ap.probe method to registered users, add rate limiting protection (e70e990a) + +##### Bug Fixes + +* return 200 for non-implemented activities instead of 501 (524a1e8b) +* closes #13458, check if plugin is system (b1022566) +* add try..catch around topics.post in note assertion logic (cc927026) +* don't throw on unknown post on Undo(Like) (83a55f6a) +* add try..catch wrapper around Announce(Like) call to internal method so as to not return a 500 — just drop the Like activity (629eec7b) +* browser title translation (390f6428) +* allow guests to load topic tools if they have privilege to view them (78de8c6d) +* closes #13454, align dropdowns to opposite side on rtl (72417d82) +* send actor in undo(follow) (49b5268e) +* missed handling zset on ap unfollow (b20a6ed0) +* additional tests for remote privileges, enforcing privileges for remote edits and deletes (a888b868) + +##### Tests + +* fix groups:find webfinger test (0c1a6183) + +#### v4.4.1 (2025-05-16) + +##### Chores + +* up themes (61a63851) +* incrementing version number - v4.4.0 (0a75eee3) +* update changelog for v4.4.0 (09cc91d5) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* save width and height values into post attachment (3674fa57) +* use local date string for digest subject (3d96afb2) + +##### Bug Fixes + +* openapi schema to handle additional `attachments` field in postsobject (ce5ef1ab) +* group edit url (0a574d72) +* add attachments to getpostsummaries call in search, #13324 (8f9f3771) +* bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in `attributedTo` (a460a550) +* #13419, handle remote content with mediaType text/markdown (45a11d45) + +##### Refactors + +* create date once per digest.send (6c3e2a8e) + +##### Tests + +* fix tests to account for a460a55064e1280f36a0021e0510c7c557251030 (948bfe46) + +#### v4.4.0 (2025-05-14) + +##### Breaking Changes + +* removal of deprecated privilege hooks (8ea377a4) +* removal of `filter:flags.getFilters` (547fb482) +* removal of `filter:user.verify.code` (7e25946c) +* removal of `filter:post.purge` (df5c1a93) +* removal of `filter:post.purge` (c84b72fb) +* removal of `filter:router.page` (9d8061ea) +* removal of `filter:email.send` (b73a8d3e) + +##### Chores + +* **deps:** + * update redis docker tag to v8.0.1 (#13415) (fbe97b4e) + * update redis docker tag to v8 (#13387) (1df7313c) + * update postgres docker tag to v17.5 (#13398) (d319b0aa) + * update dependency sass-embedded to v1.88.0 (#13402) (694c79bc) + * update dependency lint-staged to v16 (#13404) (9d877481) + * update commitlint monorepo to v19.8.1 (#13394) (7a7a4f0a) + * update dependency lint-staged to v15.5.2 (#13383) (96dc5c89) + * update dependency @eslint/js to v9.26.0 (#13371) (450ce3b8) + * update dependency mocha to v11.2.2 (#13366) (e958010f) +* incrementing version number - v4.3.2 (b92b5d80) +* update changelog for v4.3.2 (0aa9c187) +* incrementing version number - v4.3.1 (308e6b9f) +* remove unused require (15b6a2c1) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Documentation Changes + +* remove since-removed `labels` property from api (860ac895) + +##### Bug Fixes + +* adjust Peertube-specific handling to shove mp4 into post attachments, #13324 (799b08db) +* #13081, don't add mention when you are replying to yourself (d5865613) +* add `announces` to postdataobject schema (0f576a42) +* #13375, plus additional tests (fe13c755) +* missing awaits, more comprehensive 1b12 tests (5802c7dd) +* another case (6bfe4e62) +* handle missing orderedItems property in followers route (e042201f) +* missing await (651ebaaf) +* handle missing orderedItems (53bb0bbc) +* extra `orderedItems` property in generated paginated OrderedCollection, #13153 (f83b1fbf) +* #13153, follower and following collections to use generateCollection helper (a2de7aae) +* #13374, updates to posts.edit to handle remote content updates better (b4338489) +* leftover `handle` var (625ce96f) +* AP inbox update handling for non-note objects (f8d012c8) +* 1b12 creates being dropped (9f80d10d) +* update AP api (un)follow ids to be url encoded id instead of handle (7cf61ab0) +* **deps:** + * update dependency diff to v8 (#13409) (919d62ab) + * update dependency sanitize-html to v2.17.0 (#13418) (3e18af1e) + * update dependency satori to v0.13.1 (#13408) (f176d6b2) + * update dependency pg-cursor to v2.15.0 (#13414) (7320a858) + * update dependency nodebb-plugin-markdown to v13.2.1 (#13416) (84b8ecc7) + * update dependency semver to v7.7.2 (#13410) (366651d6) + * update dependency pg to v8.16.0 (#13411) (0825c569) + * update dependency nodebb-plugin-mentions to v4.7.6 (#13417) (383a7ce5) + * update dependency lru-cache to v11 (#12685) (23374fd7) + * update dependency rimraf to v6 (#12686) (6a4ffe02) + * update dependency bootswatch to v5.3.6 (#13400) (7a7cf830) + * update dependency csrf-sync to v4.2.1 (#13401) (ecce9998) + * update dependency sass to v1.88.0 (#13403) (7ffba218) + * update dependency nodemailer to v7.0.3 (#13395) (af3afba0) + * update dependency nodemailer to v7 (#13381) (0b4d403c) + * update dependency csrf-sync to v4.2.0 (#13364) (4f0f67a4) + * update dependency webpack to v5.99.8 (#13390) (c7a164ae) + * update dependency bootstrap to v5.3.6 (#13384) (e6a19612) + * update dependency esbuild to v0.25.4 (#13385) (b6f4de5b) + * update dependency @fontsource/poppins to v5.2.6 (#13376) (e2a8cf98) + * update dependency nodebb-plugin-mentions to v4.7.5 (#13386) (2c0aba02) + * update dependency nodebb-widget-essentials to v7.0.38 (#13380) (7f757615) + * update dependency nodebb-theme-persona to v14.1.11 (#13379) (954aa541) + * update dependency nodebb-theme-peace to v2.2.42 (#13378) (2aa0bfc5) + * update dependency nodebb-theme-harmony to v2.1.12 (#13377) (72b3a215) + * update dependency ace-builds to v1.41.0 (#13372) (4b78710b) + * bump markdown (f3bd8590) + +##### Other Changes + +* //github.com/NodeBB/NodeBB/issues/13367 (39953ee1) + +##### Refactors + +* use a single until (1b0b1da6) +* Helpers.generateCollection so that total count and a bound function can be passed in, #13153 (7f59238d) + +##### Tests + +* a few additional tests for announce handling (61f6806b) +* fix regression from 5802c7ddd9506a4e296f6dbdf2d9a32621c7f4ef (5b118904) +* fix broken test due to adjusted note assertion relation logic (9dc91f11) +* update filter:router.page tests to response:router.page (a819d39c) +* adjustment for now-removed labels property (52df41b9) + +#### v4.3.2 (2025-05-12) + +##### Chores + +* up mentions (fcf9e8b7) +* incrementing version number - v4.3.1 (308e6b9f) +* update changelog for v4.3.1 (2310a7b8) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* sql injection in sortedSetScan (16504bad) +* escape flag filters (285d438c) +* #13407, don't restart user jobs (31be083e) +* closes #13405, catch errors in ap.verify (8174578c) +* send proper accept header for outgoing webfinger requests (20ab9069) +* wrap generateCollection calls in try..catch to send 404 if thrown (64fdf91b) +* #13397, null values in category sync list (26e6a222) +* #13392, regression from c6f2c87, unable to unfollow from pending follows (401ff797) +* #13397, update getCidByHandle to work with remote categories, fix sync with handles causing issues with null entries (a9a5ab5e) +* correct stage name in dev dockerfile (#13393) (10077d0f) + +##### Refactors + +* wrap ap routes in try/catch (00668bdc) +* call verify if request is POST (dfa21329) + +#### v4.3.1 (2025-05-07) + +##### Chores + +* node 18 eol (800426d6) +* up widgets (ee2f91ad) +* up themes (18867fb1) +* update bundled plugins to use eslint9 (343f13e1) +* incrementing version number - v4.3.0 (bff291db) +* update changelog for v4.3.0 (76c03019) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Other Changes + +* //github.com/NodeBB/NodeBB/issues/13367 (d35aad31) + +##### Tests + +* fix android test (31af05c7) +* fix android test (25979294) +* fix a test (7ef79981) + +#### v4.3.0 (2025-05-01) + +##### Chores + +* **i18n:** + * fallback strings for new resources: nodebb.category, nodebb.world (2827498d) + * fallback strings for new resources: nodebb.error (c889d60c) + * fallback strings for new resources: nodebb.admin-settings-user, nodebb.user (d3409b40) + * fallback strings for new resources: nodebb.global (6c163f7c) + * fallback strings for new resources: nodebb.notifications (dcf34e3d) + * fallback strings for new resources: nodebb.error (b1e95bc6) +* v4.3.0-beta.2 (e3e78445) +* v4.3.0-beta.1 (0689da81) +* cut 4.3.0-alpha.3 (2e3e675b) +* cut v4.3.0-alpha.2 (c4690392) +* cut v4.3.0-alpha (4379df68) +* up pg, pg-cursor (#13351) (b57ce29d) +* incrementing version number - v4.2.2 (17fecc24) +* update changelog for v4.2.2 (ce196589) +* up eslint-nodebb (853244a1) +* up themes (4301bf97) +* incrementing version number - v4.2.1 (852a270c) +* up dbsearch (bc8126c7) +* up dbsearch (4b9331d9) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) +* **deps:** + * update redis docker tag to v7.4.3 (#13358) (7800016f) + * update dependency sass-embedded to v1.87.0 (#13347) (bee79784) + * update dependency @eslint/js to v9.25.1 (#13344) (25e4e844) + * update dependency eslint-config-nodebb to v1.1.3 (#13343) (d687fe60) + * update dependency @eslint/js to v9.25.0 (#13342) (0cc492c6) + * update dependency lint-staged to v15.5.1 (#13319) (9046acea) + * update dependency jsdom to v26.1.0 (#13330) (3f000ed6) + * update dependency @eslint/js to v9.24.0 (#13310) (41be539f) + * update dependency sass-embedded to v1.86.3 (#13301) (0bd43940) + * update dependency sass-embedded to v1.86.2 (#13291) (fdcd2a84) + * update dependency @apidevtools/swagger-parser to v10.1.1 (#13037) (3305c7b0) + +##### Documentation Changes + +* update openapi spec with new (missing) properties (b32b7fcc) + +##### New Features + +* upgrade script to remote duplicate remote users and categories as per #13352 (5aee2f26) +* add new mixin clamp-fade, and handler for expanding it in category description (decc9cf1) +* handle Announce(Update(Note)) as well, #13320 (04473669) +* #13255, proper handling of upvotes shared by group actors (74e32a17) +* send the whole post content in `summary` as well (5c5fd3d4) +* show/hide categories on world page, #13255 (804208b7) +* notice on remote categories that have no local followers, #13255 (93a5b35f) +* add new option to categorySearch module, `defaultCategories`, use to populate the category list when you don't want to poll backend for the main category list (a487d5f6) +* category quick search on world page, theme version updates, #13255 (17909516) +* show tracked/watched remote categories in world page, #13255 (34ab6771) +* remote user to category migration should also migrate local user follows into category watches (ac7b7f81) +* allowing manual group assertion via category search input (23b3148c) +* remote group actors migrated to categories if they were previous asserted as remote users (d19f692b) +* #13255 new topics in remote category addresses remote category, tests, fixes to tests (b8c531d5) +* #13255, deliver asserted topics to remote category followers (9c1d5cd3) +* #13255, add category name and handle to category search zset (876d1b04) +* integrate remote category pruning into actor pruning logic (9b5855f7) +* migration of group-as-user to group-as-category, remote category purging, more tests (4be0f73a) +* asserted topics and posts to remote categories will notify and add to unread based on remote category watch state (f483e883) +* also include category in `to` field when mocking post for federation (f73f727d) +* chat allow/deny list, closes #13359 (a5afad27) +* show topic follower counts (#13326) (bf2d4c46) +* federate out as:Article with `preview` for root-level posts in a topic, instead of `as:Note` (3c4be773) +* body-parser-2.x test (#13278) (389bc062) +* upgrade commander, get rid of custom color & wrapping code (485562d5) +* testing eslint9 (#13266) (68136641) + +##### Bug Fixes + +* bump harmony (2bf2e556) +* #13352, also do the webfinger backreference check when calling assertGroup (2572cbf5) +* regression that caused non-public content with source.content to fail parsing (e9b3306e) +* closes #13360, catch error in buildAccountData middleware (537a7428) +* bug where disparate ids all claiming to be the same handle were causing duplicate remote users due to collisions, #13352 (c2a3ef81) +* posts incorrectly excluded from results if result pid is in a remote category (a0a8c462) +* ap helpers.makeSet to handle undefined property values (d020e334) +* add back localCategories to categorySearch when defaultCategories is supplied (d58d5861) +* remote bare hash for remote users on prune as well (f02d9661) +* missing teasers for remote categories on /world (695312f1) +* remove superfluous privilege filter in markAllRead (0fab4255) +* reversed image and icon for remote categories, omit fa icon if remote category has icon property set, #13255 (dabcefaf) +* closes #13289, id can be null (f1d1d082) +* marking remote category topics as read (4a7111d0) +* markAllRead to get tids based on same logic as unread page, instead of marking all recent posts read (b0236735) +* key ownership cross-check to also work with remote categories, #13255 (6dee3e56) +* #13255, assert all recipients of the main post when asserting a note, so that remote categories can be discovered (1f046782) +* remote categories should not show up in a user's follow lists (4d1d7c3d) +* #13255, remote user-to-category migration should not move shares that are already in an existing cid (3213da1c) +* proper handling of actors.qualify response (f2e0ba21) +* missing dep (2cb6d10d) +* topics in remote categories showing up in /recent (c4274a3d) +* regression that caused resolveInboxes to always return empty, added tests for resolveInboxes (0246c146) +* spread fail, @julianlam (c1b71964) +* filter out non-asserted targets when sending ap messages, diff. getter method when passed-in ID is a remote category (309deb0d) +* tag whitelist check socket call for remote categories (c5901e0d) +* migrate topics as system user instead of uid 0 (ee34396c) +* do not send out ap (undo:)follow if local user or category is (not)already following (c6f2c874) +* allow category controller to respond also by remote category id (0b333fb7) +* #13255, update category search logic to allow for remote categories (6e23de46) +* delete shares zset on account deletion (7ccd6b73) +* persona tooltip so it doesn't appear when dropdowns are open (ea9f7903) +* lang keys (4277765b) +* closes #13353, don't use index for finding plugin data (ed92ffaf) +* regression on search query that is a url, via 3526c937ccec843d4637efa894f49efc9bac5493 (41252197) +* closes #13313, add error:post-deleted (b49a4586) +* upgrade script (52ca086b) +* tag urls getting double escaped (#13306) (3526c937) +* notifications.markAllRead (bf243e07) +* posts.uploads.usage since paths changed (c41c7e8a) +* req.body can be undefined (cd70e6c6) +* closes #13298, catch exceptions in webfinger and nodeinfo (bbfd6445) +* closes #13205, make parent post font-size small until expanded (ef98f8f9) +* closes #13275, set 'announces' after adding to zset to prevent race condition (2c59007b) +* use slug instead of groupname in acp for groups (d9f33204) +* do not await the batch call to sendMessage (aa4f23bf) +* commenting out outward federation of Add activity, pending forumwg discussion (4b22f297) +* on user deletion during assertion (due to 410), if delete fails, just run ap post-deletion to clean up (4bbe27d4) +* race condition in test (807a8c66) +* **deps:** + * update dependency pg to v8.15.6 (#13362) (7f533167) + * update dependency pg-cursor to v2.14.6 (#13363) (03e06784) + * update dependency webpack to v5.99.7 (#13361) (0a3e4d61) + * update dependency pg to v8.15.5 (#13356) (74558b0f) + * update dependency ace-builds to v1.40.1 (#13354) (4eec053a) + * update dependency esbuild to v0.25.3 (#13355) (43d7d47f) + * update dependency pg-cursor to v2.14.5 (#13350) (00be573d) + * update dependency pg to v8.15.2 (#13349) (b49436de) + * update dependency sass to v1.87.0 (#13348) (50a58bbc) + * update dependency connect-redis to v8.0.3 (#13345) (8d84206f) + * update dependency mongodb to v6.16.0 (#13346) (3c24810d) + * update dependency chart.js to v4.4.9 (#13328) (0c5ef0e8) + * update dependency nconf to v0.13.0 (#13333) (6f8c7aba) + * update dependency ace-builds to v1.40.0 (#13331) (5d461f04) + * update dependency nodemailer to v6.10.1 (#13329) (6fbb2b4b) + * update dependency ioredis to v5.6.1 (#13318) (74d9806d) + * update dependency cron to v4.3.0 (#13332) (f96ce25a) + * update dependency sanitize-html to v2.16.0 (#13339) (24a5f407) + * update dependency webpack to v5.99.6 (#13341) (cac1c37b) + * update dependency bootbox to v6.0.3 (#13327) (8f784bb3) + * update dependency nodebb-theme-harmony to v2.1.6 (#13314) (1bd12622) + * update dependency nodebb-theme-persona to v14.1.5 (#13316) (6de89952) + * update dependency webpack to v5.99.5 (#13312) (140440cc) + * update dependency cron to v4.1.4 (#13307) (5fa09a67) + * update dependency nodebb-theme-persona to v14.1.2 (#13311) (6f3e9853) + * update dependency nodebb-theme-harmony to v2.1.3 (#13309) (9905e6aa) + * update dependency bootswatch to v5.3.5 (#13308) (4b511546) + * update dependency bootstrap to v5.3.5 (#13304) (22b3dc65) + * update dependency sass to v1.86.3 (#13302) (70e788ba) + * update dependency spdx-license-list to v6.10.0 (#13303) (cd2ed209) + * update dependency bootstrap to v5.3.4 (#13299) (d9074dbc) + * update dependency nodebb-plugin-mentions to v4.7.3 (#13294) (dd3d1917) + * update dependency bootbox to v6.0.2 (#13293) (dfd2621e) + * update dependency sass to v1.86.2 (#13295) (1a763cbb) + * update dependency benchpressjs to v2.5.5 (#13292) (311cbec2) + * update dependency nodebb-theme-harmony to v2.1.0 (#13296) (5caadd2d) + * update dependency esbuild to v0.25.2 (#13284) (e6a02176) + * update dependency cron to v4.1.3 (#13282) (c46c2623) + * update dependency cron to v4.1.2 (#13281) (11f7b42c) + * update dependency satori to v0.12.2 (#13280) (28ec8a79) + * update dependency cron to v4.1.1 (#13276) (57819810) + * update dependency nodebb-plugin-composer-default to v10.2.49 (#13272) (40ecffa0) + * update dependency nodebb-theme-harmony to v2.0.42 (#13274) (5b40c149) + * bump mentions (7d32cdac) + * update dependency validator to v13.15.0 (#13273) (d275af60) + * update dependency csrf-sync to v4.1.0 (#13268) (92caab97) + * update dependency nodebb-theme-persona to v14.0.17 (#13263) (ca479efc) + * update dependency nodebb-theme-harmony to v2.0.41 (#13262) (33c25ce6) + * update dependency nodebb-plugin-composer-default to v10.2.48 (#13261) (5b98af9f) + * update dependency sanitize-html to v2.15.0 (#13264) (e17163ad) + * update dependency ace-builds to v1.39.1 (#13260) (532fea99) + +##### Other Changes + +* yup. (28b7a203) +* fix tabs (4cdfcf95) +* fix typo (352c42d8) + +##### Refactors + +* use promise.all (74661381) +* categories.sortTidsBySet to not take cid, retrieve from tids themselves (bfc7daf2) +* allow topics to be asserted directly into a remote category, or -1 otherwise (0fa98237) +* ability to browse to remote categories, group actor assertion logic, etc. -- no logic to assign topics to remote categories yet (1f40995f) +* remove datepicker (be7959e5) +* add sping/ping into openapi (8ffbc359) +* moved these rules to nodebb-config (e184c910) +* switch eslint configs to esm (92d6e022) +* move topic/post menu lists to core (9f93cc9b) +* show topic tools if plugins add them (87aacc89) +* remove reply icons (b73fb67b) +* remove debug log (021b3af0) +* break long line (c93dc589) +* only write to db on runJobs processes (a6839b61) +* use bulk increment (667367a6) +* remove spammy error log (9637abca) + +##### Reverts + +* use of vanity domains, needs rethinking. Originally added in 709a02d97ae7acbab08c7fa1fecfd01e0dcadcc7 (55c89969) + +##### Tests + +* article for new topic, note for replies (3e508d6c) +* missing clear ap send cache (512f889e) +* additional test for ensuring handle:uid is continually set even after re-assertion (39fc9bae) +* additional test for remote category topic assertion when ignoring category (c2f77cee) +* #13255, reply to topic in remote category addresses remote category (85e7c1a2) +* remote user pruning tests (53dc79a1) +* introduce overrides into person and group mocks (4f748158) +* have ap helper mocks for person and group auto-save to ap cache (80069a19) +* add failing tests for actor/group assertion via wrong method, remote user to category migration (afc47643) +* add tests for topics slotting into remote categories if addressed (804052f2) +* group actor assertion tests (ca9a5b6d) +* shorter test (e0235a1e) +* eslint for tests (f864a5a4) +* fix tests (321defb9) +* fix test expecting Note when it is now Article (5d94f2ca) + +#### v4.2.2 (2025-04-22) + +##### Chores + +* incrementing version number - v4.2.1 (852a270c) +* update changelog for v4.2.1 (4cee37b9) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* escape displayname in topic events (42a5a127) +* closes #13336, allow main post deletion from "delete posts" tool (4f13eb03) +* bug where generateHandle would throw when passed in an invalid slug (2a98a9b3) + +##### Other Changes + +* fix semi (6832541c) +* fix missing comma (d59a5728) + +##### Refactors + +* use sortedSetsCard (46ed56cf) + +#### v4.2.1 (2025-04-10) + +##### Chores + +* up harmony (d161eb6f) +* up persona (2237e17a) +* up persona (75f1f6fb) +* incrementing version number - v4.2.0 (87581958) +* update changelog for v4.2.0 (c9e0198d) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* closes #13317, fix email confirm for changing email (33d50637) +* check if latestversion is valid before using semver.gt (6fe066ce) +* closes #13256, allow keyboard access to icon colors (c6620170) + +##### Refactors + +* get rid of async.parallel (e722e869) +* remove pointless true (747457d7) +* make register intro heading (c258f597) + #### v4.2.0 (2025-03-19) ##### Chores diff --git a/Gruntfile.js b/Gruntfile.js index dcfa831cd6..60d8f8b23e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -173,7 +173,10 @@ module.exports = function (grunt) { winston.error(err.stack); } if (worker) { - worker.send({ compiling: compiling }); + worker.send({ + compiling: compiling, + livereload: true, // Send livereload event via Socket.IO for instant browser refresh + }); } }); }); diff --git a/README.md b/README.md index 67cde8daac..fb8b8702fe 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ Our minimalist "Harmony" theme gets you going right away, no coding experience r NodeBB requires the following software to be installed: -* A version of Node.js at least 18 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions)) -* MongoDB, version 3.6 or greater **or** Redis, version 2.8.9 or greater +* A version of Node.js at least 20 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions)) +* MongoDB, version 5 or greater **or** Redis, version 7.2 or greater * If you are using [clustering](https://docs.nodebb.org/configuring/scaling/) you need Redis installed and configured. * nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB) diff --git a/app.js b/app.js index b6067d726b..c493817491 100644 --- a/app.js +++ b/app.js @@ -33,7 +33,6 @@ const path = require('path'); const file = require('./src/file'); process.env.NODE_ENV = process.env.NODE_ENV || 'production'; -global.env = process.env.NODE_ENV || 'production'; // Alternate configuration file support const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json'); diff --git a/dev.Dockerfile b/dev.Dockerfile index 4946d18725..b02384558a 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -58,8 +58,8 @@ RUN corepack enable \ && mkdir -p /usr/src/app/logs/ /opt/config/ \ && chown -R ${USER}:${USER} /usr/src/app/ /opt/config/ -COPY --from=build --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/install/docker/setup.json /usr/src/app/ -COPY --from=build --chown=${USER}:${USER} /usr/bin/tini /usr/src/app/install/docker/entrypoint.sh /usr/local/bin/ +COPY --from=git --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/install/docker/setup.json /usr/src/app/ +COPY --from=git --chown=${USER}:${USER} /usr/bin/tini /usr/src/app/install/docker/entrypoint.sh /usr/local/bin/ COPY --from=node_modules_touch --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/ COPY --from=git --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/ diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index e7af970bf5..9f4db32813 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -14,7 +14,7 @@ services: - ./install/docker/setup.json:/usr/src/app/setup.json postgres: - image: postgres:17.4-alpine + image: postgres:18.2-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:7.4.2-alpine + image: redis:8.6.0-alpine restart: unless-stopped command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning'] # command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF diff --git a/docker-compose-redis.yml b/docker-compose-redis.yml index aadc72e198..270a6588dd 100644 --- a/docker-compose-redis.yml +++ b/docker-compose-redis.yml @@ -14,7 +14,7 @@ services: - ./install/docker/setup.json:/usr/src/app/setup.json redis: - image: redis:7.4.2-alpine + image: redis:8.6.0-alpine restart: unless-stopped command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning'] # command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF diff --git a/docker-compose.yml b/docker-compose.yml index 7877582680..0faafb8f0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: - mongo-data:/data/db - ./install/docker/mongodb-user-init.js:/docker-entrypoint-initdb.d/user-init.js redis: - image: redis:7.4.2-alpine + image: redis:8.6.0-alpine restart: unless-stopped command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning'] # command: ['redis-server', '--save', '60', '1', '--loglevel', 'warning'] # uncomment if you want to use snapshotting instead of AOF @@ -34,7 +34,7 @@ services: - redis postgres: - image: postgres:17.4-alpine + image: postgres:18.2-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb diff --git a/eslint.config.cjs b/eslint.config.mjs similarity index 56% rename from eslint.config.cjs rename to eslint.config.mjs index fb29b4001f..5b3c417f7d 100644 --- a/eslint.config.cjs +++ b/eslint.config.mjs @@ -1,11 +1,15 @@ 'use strict'; -const serverConfig = require('eslint-config-nodebb'); -const publicConfig = require('eslint-config-nodebb/public'); -const { configs } = require('@eslint/js'); -const globals = require('globals'); +import serverConfig from 'eslint-config-nodebb'; +import publicConfig from 'eslint-config-nodebb/public'; +import commonRules from 'eslint-config-nodebb/common'; -module.exports = [ +import { defineConfig } from 'eslint/config'; +import stylisticJs from '@stylistic/eslint-plugin' +import js from '@eslint/js'; +import globals from 'globals'; + +export default defineConfig([ { ignores: [ 'node_modules/', @@ -27,15 +31,13 @@ module.exports = [ 'install/docker/', ], }, - configs.recommended, - { - rules: { - 'no-bitwise': 'warn', - 'no-await-in-loop': 'warn', - } - }, // tests { + plugins: { + js, + '@stylistic/js': stylisticJs, + }, + extends: ['js/recommended'], files: ['test/**/*.js'], languageOptions: { ecmaVersion: 2020, @@ -50,13 +52,19 @@ module.exports = [ after: 'readonly', afterEach: 'readonly', }, - }, + }, rules: { - 'no-unused-vars': 'off', + ...commonRules, + 'no-unused-vars': 'off', 'no-prototype-builtins': 'off', } - }, + }, ...publicConfig, - ...serverConfig -]; + ...serverConfig, + { + rules: { + 'preserve-caught-error': 'off' + } + } +]); diff --git a/install/data/defaults.json b/install/data/defaults.json index b17bbbb135..449234192c 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -24,8 +24,8 @@ "newbieChatMessageDelay": 120000, "notificationSendDelay": 60, "newbieReputationThreshold": 3, - "postQueue": 0, - "postQueueReputationThreshold": 0, + "postQueue": 1, + "postQueueReputationThreshold": 1, "groupsExemptFromPostQueue": ["administrators", "Global Moderators"], "groupsExemptFromNewUserRestrictions": ["administrators", "Global Moderators"], "groupsExemptFromMaintenanceMode": ["administrators", "Global Moderators"], @@ -36,8 +36,9 @@ "maximumTagsPerTopic": 5, "minimumTagLength": 3, "maximumTagLength": 15, - "undoTimeout": 10000, + "undoTimeout": 0, "allowTopicsThumbnail": 1, + "showPostUploadsAsThumbnails": 1, "registrationType": "normal", "registrationApprovalType": "normal", "allowAccountDelete": 1, @@ -56,6 +57,7 @@ "rejectImageWidth": 5000, "rejectImageHeight": 5000, "resizeImageQuality": 80, + "convertPastedImageTo": "image/jpeg", "topicThumbSize": 512, "minimumTitleLength": 3, "maximumTitleLength": 255, @@ -69,14 +71,14 @@ "maximumChatMessageLength": 1000, "maximumRemoteChatMessageLength": 5000, "maximumChatRoomNameLength": 50, - "maximumProfileImageSize": 256, + "maximumProfileImageSize": 2048, "maximumCoverImageSize": 2048, "profileImageDimension": 200, "profile:convertProfileImageToPNG": 0, "profile:keepAllUserImages": 0, "gdpr_enabled": 1, "allowProfileImageUploads": 1, - "teaserPost": "last-reply", + "teaserPost": "last-post", "showPostPreviewsOnHover": 1, "allowPrivateGroups": 1, "unreadCutoff": 2, @@ -140,6 +142,7 @@ "feeds:disableSitemap": 0, "feeds:disableRSS": 0, "sitemapTopics": 500, + "sitemapCacheDurationHours": 24, "maintenanceMode": 0, "maintenanceModeStatus": 503, "upvoteVisibility": "all", diff --git a/install/docker/entrypoint.sh b/install/docker/entrypoint.sh index dd17d707e7..a38aa4196f 100755 --- a/install/docker/entrypoint.sh +++ b/install/docker/entrypoint.sh @@ -12,6 +12,7 @@ set_defaults() { export SETUP="${SETUP:-}" export PACKAGE_MANAGER="${PACKAGE_MANAGER:-npm}" export OVERRIDE_UPDATE_LOCK="${OVERRIDE_UPDATE_LOCK:-false}" + export NODEBB_ADDITIONAL_PLUGINS="${NODEBB_ADDITIONAL_PLUGINS:-}" } # Function to check if a directory exists and is writable @@ -103,7 +104,7 @@ build_forum() { local config="$1" local start_build="$2" local package_hash=$(md5sum install/package.json | head -c 32) - if [ "$package_hash" = "$(cat $CONFIG_DIR/install_hash.md5 || true)" ]; then + if [ "$package_hash" != "$(cat $CONFIG_DIR/install_hash.md5 || true)" ]; then echo "package.json was updated. Upgrading..." /usr/src/app/nodebb upgrade --config="$config" || { echo "Failed to build NodeBB. Exiting..." @@ -172,6 +173,33 @@ debug_log() { echo "DEBUG: $message" } +install_additional_plugins() { + if [[ ! -z ${NODEBB_ADDITIONAL_PLUGINS} ]]; then + export START_BUILD="true" + for plugin in "${NODEBB_ADDITIONAL_PLUGINS[@]}"; do + echo "Installing additional plugin ${plugin}..." + case "$PACKAGE_MANAGER" in + yarn) yarn install || { + echo "Failed to install plugin ${plugin} with yarn" + exit 1 + } ;; + npm) npm install || { + echo "Failed to install plugin ${plugin} with npm" + exit 1 + } ;; + pnpm) pnpm install || { + echo "Failed to install plugin ${plugin} with pnpm" + exit 1 + } ;; + *) + echo "Unknown package manager: $PACKAGE_MANAGER" + exit 1 + ;; + esac + done + fi +} + # Main function main() { set_defaults @@ -182,12 +210,14 @@ main() { debug_log "PACKAGE_MANAGER: $PACKAGE_MANAGER" debug_log "CONFIG location: $CONFIG" debug_log "START_BUILD: $START_BUILD" + debug_log "NODEBB_ADDITIONAL_PLUGINS: ${NODEBB_ADDITIONAL_PLUGINS}" if [ -n "$SETUP" ]; then start_setup_session "$CONFIG" fi if [ -f "$CONFIG" ]; then + install_additional_plugins start_forum "$CONFIG" "$START_BUILD" else start_installation_session "$NODEBB_INIT_VERB" "$CONFIG" diff --git a/install/package.json b/install/package.json index 6169ec2b4b..abf3675dba 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.2.0", + "version": "4.8.1", "homepage": "https://www.nodebb.org", "repository": { "type": "git", @@ -29,56 +29,55 @@ }, "dependencies": { "@adactive/bootstrap-tagsinput": "0.8.2", - "@fontsource/inter": "5.2.5", - "@fontsource/poppins": "5.2.5", + "@fontsource-utils/scss": "0.2.2", + "@fontsource/inter": "5.2.8", + "@fontsource/poppins": "5.2.7", "@fortawesome/fontawesome-free": "6.7.2", - "@isaacs/ttlcache": "1.4.1", + "@isaacs/ttlcache": "2.1.4", "@nodebb/spider-detector": "2.0.3", "@popperjs/core": "2.11.8", - "@resvg/resvg-js": "2.6.2", "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.39.1", + "ace-builds": "1.43.6", "archiver": "7.0.1", "async": "3.2.6", - "autoprefixer": "10.4.21", - "bcryptjs": "3.0.2", + "autoprefixer": "10.4.24", + "bcryptjs": "3.0.3", "benchpressjs": "2.5.5", - "body-parser": "2.2.0", - "bootbox": "6.0.2", - "bootstrap": "5.3.5", - "bootswatch": "5.3.5", + "body-parser": "2.2.2", + "bootbox": "6.0.4", + "bootstrap": "5.3.8", + "bootswatch": "5.3.8", "chalk": "4.1.2", - "chart.js": "4.4.8", + "chart.js": "4.5.1", "cli-graph": "3.2.2", "clipboard": "2.0.11", - "commander": "13.1.0", + "commander": "14.0.3", "compare-versions": "6.1.1", - "compression": "1.8.0", + "compression": "1.8.1", "connect-flash": "0.1.1", - "connect-mongo": "5.1.0", - "connect-multiparty": "2.2.0", + "connect-mongo": "6.0.0", "connect-pg-simple": "10.0.0", - "connect-redis": "8.0.2", + "connect-redis": "9.0.0", "cookie-parser": "1.4.7", - "cron": "4.1.4", + "cron": "4.4.0", "cropperjs": "1.6.2", - "csrf-sync": "4.1.0", + "csrf-sync": "4.2.1", "daemon": "1.1.0", - "diff": "7.0.0", - "esbuild": "0.25.2", - "express": "5.1.0", - "express-session": "1.18.1", - "express-useragent": "1.0.15", - "fetch-cookie": "3.1.0", + "diff": "8.0.3", + "esbuild": "0.27.3", + "express": "5.2.1", + "express-session": "1.19.0", + "express-useragent": "2.1.0", + "fetch-cookie": "3.2.0", "file-loader": "6.2.0", - "fs-extra": "11.3.0", + "fs-extra": "11.3.3", "graceful-fs": "4.2.11", "helmet": "7.2.0", "html-to-text": "9.0.5", "imagesloaded": "5.0.0", - "ipaddr.js": "2.2.0", + "ipaddr.js": "2.3.0", "jquery": "3.7.1", "jquery-deserialize": "2.0.0", "jquery-form": "4.3.0", @@ -86,97 +85,102 @@ "jquery-ui": "1.14.1", "jsesc": "3.1.0", "json2csv": "5.0.7", - "jsonwebtoken": "9.0.2", - "lodash": "4.17.21", + "jsonwebtoken": "9.0.3", + "lodash": "4.17.23", "logrotate-stream": "0.2.9", - "lru-cache": "10.4.3", + "lru-cache": "11.2.6", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.15.0", - "morgan": "1.10.0", + "mongodb": "7.1.0", + "morgan": "1.10.1", "mousetrap": "1.6.5", - "multiparty": "4.2.3", - "nconf": "0.12.1", - "nodebb-plugin-2factor": "7.5.9", - "nodebb-plugin-composer-default": "10.2.49", - "nodebb-plugin-dbsearch": "6.2.15", - "nodebb-plugin-emoji": "6.0.2", + "multer": "2.0.2", + "nconf": "0.13.0", + "nodebb-plugin-2factor": "7.6.1", + "nodebb-plugin-composer-default": "10.3.16", + "nodebb-plugin-dbsearch": "6.3.5", + "nodebb-plugin-emoji": "6.0.5", "nodebb-plugin-emoji-android": "4.1.1", - "nodebb-plugin-markdown": "13.1.1", - "nodebb-plugin-mentions": "4.7.3", - "nodebb-plugin-spam-be-gone": "2.3.1", - "nodebb-plugin-web-push": "0.7.3", - "nodebb-rewards-essentials": "1.0.1", - "nodebb-theme-harmony": "2.1.3", - "nodebb-theme-lavender": "7.1.18", - "nodebb-theme-peace": "2.2.39", - "nodebb-theme-persona": "14.1.2", - "nodebb-widget-essentials": "7.0.36", - "nodemailer": "6.10.0", + "nodebb-plugin-link-preview": "2.2.3", + "nodebb-plugin-markdown": "13.2.4", + "nodebb-plugin-mentions": "4.8.16", + "nodebb-plugin-spam-be-gone": "2.3.2", + "nodebb-plugin-web-push": "0.7.6", + "nodebb-rewards-essentials": "1.0.2", + "nodebb-theme-harmony": "2.2.11", + "nodebb-theme-lavender": "7.1.21", + "nodebb-theme-peace": "2.2.51", + "nodebb-theme-persona": "14.2.5", + "nodebb-widget-essentials": "7.0.42", + "nodemailer": "8.0.1", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", - "pg": "8.14.1", - "pg-cursor": "2.13.1", - "postcss": "8.5.3", + "pg": "8.18.0", + "pg-cursor": "2.17.0", + "postcss": "8.5.6", "postcss-clean": "1.2.0", + "pretty": "^2.0.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "ioredis": "5.6.0", - "rimraf": "5.0.10", + "qs": "6.14.2", + "redis": "5.10.0", + "rimraf": "6.1.2", "rss": "1.2.2", "rtlcss": "4.3.0", - "sanitize-html": "2.15.0", - "sass": "1.86.3", - "satori": "0.12.2", - "semver": "7.7.1", - "serve-favicon": "2.5.0", - "sharp": "0.32.6", - "sitemap": "8.0.0", - "socket.io": "4.8.1", - "socket.io-client": "4.8.1", + "sanitize-html": "2.17.0", + "sass": "1.97.3", + "satori": "0.19.2", + "sbd": "^1.0.19", + "semver": "7.7.4", + "serve-favicon": "2.5.1", + "sharp": "0.34.5", + "sitemap": "9.0.0", + "socket.io": "4.8.3", + "socket.io-client": "4.8.3", "@socket.io/redis-adapter": "8.3.0", - "sortablejs": "1.15.6", - "spdx-license-list": "6.10.0", - "terser-webpack-plugin": "5.3.14", + "sortablejs": "1.15.7", + "spdx-license-list": "6.11.0", + "terser-webpack-plugin": "5.3.16", "textcomplete": "0.18.2", "textcomplete.contenteditable": "0.1.1", "timeago": "1.6.7", "tinycon": "0.6.8", "toobusy-js": "0.5.1", - "tough-cookie": "5.1.2", - "validator": "13.15.0", - "webpack": "5.99.5", + "tough-cookie": "6.0.0", + "undici": "^7.10.0", + "validator": "13.15.26", + "webpack": "5.105.2", "webpack-merge": "6.0.1", - "winston": "3.17.0", - "workerpool": "9.2.0", + "winston": "3.19.0", + "workerpool": "10.0.1", "xml": "1.0.1", - "xregexp": "5.1.2", "yargs": "17.7.2", "zxcvbn": "4.4.2" }, "devDependencies": { "@apidevtools/swagger-parser": "10.1.0", - "@commitlint/cli": "19.8.0", - "@commitlint/config-angular": "19.8.0", + "@commitlint/cli": "20.4.1", + "@commitlint/config-angular": "20.4.1", "coveralls": "3.1.1", - "@eslint/js": "9.24.0", - "eslint-config-nodebb": "1.0.7", - "eslint-plugin-import": "2.31.0", + "@eslint/js": "10.0.1", + "@stylistic/eslint-plugin": "5.8.0", + "eslint-config-nodebb": "2.0.1", + "globals": "17.3.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", - "jsdom": "26.0.0", - "lint-staged": "15.5.0", - "mocha": "11.1.0", + "jsdom": "28.0.0", + "lint-staged": "16.2.7", + "mocha": "11.7.5", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.13.6" + "smtp-server": "3.18.1" }, "optionalDependencies": { - "sass-embedded": "1.86.3" + "sass-embedded": "1.97.3" }, "resolutions": { "*/jquery": "3.7.1" @@ -185,7 +189,7 @@ "url": "https://github.com/NodeBB/NodeBB/issues" }, "engines": { - "node": ">=18" + "node": ">=20" }, "maintainers": [ { diff --git a/loader.js b/loader.js index 8b25906452..334e156192 100644 --- a/loader.js +++ b/loader.js @@ -2,7 +2,6 @@ const nconf = require('nconf'); const fs = require('fs'); -const url = require('url'); const path = require('path'); const { fork } = require('child_process'); const logrotate = require('logrotate-stream'); @@ -99,9 +98,14 @@ Loader.start = function () { function forkWorker(index, isPrimary) { const ports = getPorts(); const args = []; + const execArgv = []; if (nconf.get('max-memory')) { - args.push(`--max-old-space-size=${nconf.get('max-memory')}`); + execArgv.push(`--max-old-space-size=${nconf.get('max-memory')}`); } + if (nconf.get('expose-gc')) { + execArgv.push('--expose-gc'); + } + if (!ports[index]) { return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`); } @@ -109,10 +113,10 @@ function forkWorker(index, isPrimary) { process.env.isPrimary = isPrimary; process.env.isCluster = nconf.get('isCluster') || ports.length > 1; process.env.port = ports[index]; - const worker = fork(appPath, args, { silent: silent, env: process.env, + execArgv: execArgv, }); worker.index = index; @@ -135,7 +139,7 @@ function getPorts() { console.log('[cluster] url is undefined, please check your config.json'); process.exit(); } - const urlObject = url.parse(_url); + const urlObject = new URL(_url); let port = nconf.get('PORT') || nconf.get('port') || urlObject.port || 4567; if (!Array.isArray(port)) { port = [port]; diff --git a/public/language/ar/admin/advanced/cache.json b/public/language/ar/admin/advanced/cache.json index 6c71760658..252ec0e37a 100644 --- a/public/language/ar/admin/advanced/cache.json +++ b/public/language/ar/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "التخزين المؤقت للمشاركات", - "group-cache": "التخزين المؤقت للمجموعات", - "local-cache": "تخزين مؤقت محلي", - "object-cache": "تخزين مؤقت للأشياء", "percent-full": "1% كاملة", "post-cache-size": "حجم التخزين المؤقت للمشاركات", "items-in-cache": "العناصر في التخزين المؤقت" diff --git a/public/language/ar/admin/dashboard.json b/public/language/ar/admin/dashboard.json index b44c50d859..fe7d88a0c9 100644 --- a/public/language/ar/admin/dashboard.json +++ b/public/language/ar/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "زيارات الصفحات المسجلة", "graphs.page-views-guest": "زيارات الصفحات للزوار", "graphs.page-views-bot": "زيارات الصفحات الآلية", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "زوار فريدين", "graphs.registered-users": "مستخدمين مسجلين", "graphs.guest-users": "المستخدمين الزوار", diff --git a/public/language/ar/admin/development/info.json b/public/language/ar/admin/development/info.json index 4c97beee13..f02386f6c5 100644 --- a/public/language/ar/admin/development/info.json +++ b/public/language/ar/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json index 626e15e5bc..64beb3ce28 100644 --- a/public/language/ar/admin/manage/categories.json +++ b/public/language/ar/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "اعدادات القسم", "edit-category": "Edit Category", "privileges": "الصلاحيات", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/ar/admin/manage/custom-reasons.json b/public/language/ar/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ar/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ar/admin/manage/privileges.json b/public/language/ar/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/ar/admin/manage/privileges.json +++ b/public/language/ar/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/ar/admin/manage/users.json b/public/language/ar/admin/manage/users.json index 2cdb2590dc..7fd1f6dc65 100644 --- a/public/language/ar/admin/manage/users.json +++ b/public/language/ar/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/ar/admin/settings/activitypub.json b/public/language/ar/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/ar/admin/settings/activitypub.json +++ b/public/language/ar/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/ar/admin/settings/chat.json b/public/language/ar/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/ar/admin/settings/chat.json +++ b/public/language/ar/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/ar/admin/settings/email.json b/public/language/ar/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/ar/admin/settings/email.json +++ b/public/language/ar/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/ar/admin/settings/notifications.json b/public/language/ar/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/ar/admin/settings/notifications.json +++ b/public/language/ar/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/ar/admin/settings/uploads.json b/public/language/ar/admin/settings/uploads.json index b8d85be443..84ab06180c 100644 --- a/public/language/ar/admin/settings/uploads.json +++ b/public/language/ar/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "السماح للاعضاء برفع الصور المصغرة للموضوع", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "حجم الصورة المصغرة للموضوع", "allowed-file-extensions": "إمتدادات الملفات المسموح بها", "allowed-file-extensions-help": "أدخل قائمة بامتدادات الملفات مفصولة بفواصل (مثال: pdf,xls,doc). القائمة الفارغة تعني أن كل الامتدادات مسموح بها.", diff --git a/public/language/ar/admin/settings/user.json b/public/language/ar/admin/settings/user.json index 47828283c1..17d1544078 100644 --- a/public/language/ar/admin/settings/user.json +++ b/public/language/ar/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "عرض البريد الإلكتروني", "show-fullname": "عرض الاسم الكامل", "restrict-chat": "السماح فقط برسائل الدردشة من المستخدمين الذين أتبعهم", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/ar/admin/settings/web-crawler.json b/public/language/ar/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/ar/admin/settings/web-crawler.json +++ b/public/language/ar/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/ar/aria.json b/public/language/ar/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/ar/aria.json +++ b/public/language/ar/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/ar/category.json b/public/language/ar/category.json index 7aca285f7c..7546578774 100644 --- a/public/language/ar/category.json +++ b/public/language/ar/category.json @@ -1,12 +1,13 @@ { "category": "قسم", "subcategories": "قسم فرعي", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "موضوع جديد", "guest-login-post": "سجل الدخول للمشاركة", "no-topics": "لا توجد مواضيع في هذه القسملم لا تحاول إنشاء موضوع؟
", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "تصفح", "no-replies": "لم يرد أحد", "no-new-posts": "لا توجد مشاركات جديدة.", diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 3e5a26faa5..231b19292e 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "لم تقم بتسجيل الدخول", "account-locked": "تم حظر حسابك مؤقتًا.", "search-requires-login": "البحث في المنتدى يتطلب حساب - الرجاء تسجيل الدخول أو التسجيل", @@ -146,6 +147,7 @@ "post-already-restored": "سبق وتم إلغاء حذف هذا الرد", "topic-already-deleted": "سبق وتم حذف هذا الموضوع", "topic-already-restored": "سبق وتم إلغاء حذف هذا الرد", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "لا يمكنك محو المشاركة الأساسية، يرجى حذف الموضوع بدلاً عن ذلك", "topic-thumbnails-are-disabled": "الصور المصغرة غير مفعلة.", "invalid-file": "ملف غير مقبول", @@ -154,6 +156,8 @@ "about-me-too-long": "نأسف، ( عني ) لا يمكن أن يكون أكثر من %1 حرف.", "cant-chat-with-yourself": "لايمكنك فتح محادثة مع نفسك", "chat-restricted": "هذا المستخدم عطل المحادثات الواردة عليه. يجب أن يتبعك حتى تتمكن من فتح محادثة معه.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "نظام المحادثة معطل.", "too-many-messages": "لقد أرسلت الكثير من الرسائل، الرجاء اﻹنتظار قليلاً", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/ar/global.json b/public/language/ar/global.json index da3d4006ca..9353a7823f 100644 --- a/public/language/ar/global.json +++ b/public/language/ar/global.json @@ -68,6 +68,7 @@ "users": "الأعضاء", "topics": "المواضيع", "posts": "المشاركات", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "مصوت بالسالب", "views": "المشاهدات", "posters": "Posters", + "watching": "Watching", "reputation": "السمعة", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/ar/groups.json b/public/language/ar/groups.json index 1e7554fc79..fda3115df2 100644 --- a/public/language/ar/groups.json +++ b/public/language/ar/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "المجموعات", "members": "Members", + "x-members": "%1 member(s)", "view-group": "معاينة المجموعة", "owner": "مالك المجموعة", "new-group": "أنشئ مجموعة جديدة", diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json index 1bf14cdc27..ef31ecd03b 100644 --- a/public/language/ar/modules.json +++ b/public/language/ar/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "إخفاء المعاينة", "composer.help": "Help", "composer.user-said-in": "%1 كتب في %2", - "composer.user-said": "%1 كتب:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "هل أنت متأكد أنك تريد التخلي عن التغييرات؟", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/ar/notifications.json b/public/language/ar/notifications.json index f0c20e1188..7fefcfe7e5 100644 --- a/public/language/ar/notifications.json +++ b/public/language/ar/notifications.json @@ -22,7 +22,7 @@ "upvote": "الموافقين", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "الحظر", "new-message-from": "رسالة جديدة من %1", "new-messages-from": "%1 new messages from %2", @@ -32,13 +32,13 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 أضاف صوتًا إيجابيا إلى مشاركتك في %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", - "user-flagged-post-in": "%1 أشعَرَ بمشاركة مخلة في %2", + "user-flagged-post-in": "%1 flagged a post in %2", "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 أضاف ردا إلى: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 أنشأ موضوعًا جديدًا: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 صار يتابعك.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "تم التحقق من عنوان البريد الإلكتروني", "email-confirmed-message": "شكرًا على إثبات صحة عنوان بريدك الإلكتروني. صار حسابك مفعلًا بالكامل.", "email-confirm-error-message": "حدث خطأ أثناء التحقق من عنوان بريدك الإلكتروني. ربما رمز التفعيل خاطئ أو انتهت صلاحيته.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "تم إرسال بريد التفعيل.", "none": "None", "notification-only": "التنبيهات فقط", diff --git a/public/language/ar/social.json b/public/language/ar/social.json index b36256840e..4e95bbb8ba 100644 --- a/public/language/ar/social.json +++ b/public/language/ar/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "تسجيل الدخول باستخدام فيسبوك", "continue-with-facebook": "التسجيل باستخدام فيسبوك", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/ar/themes/harmony.json b/public/language/ar/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/ar/themes/harmony.json +++ b/public/language/ar/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json index fffdfd022c..dff6581696 100644 --- a/public/language/ar/topic.json +++ b/public/language/ar/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "اضغط هنا للعودة لأخر مشاركة مقروءة في الموضوع", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "أقفل الموضوع", "thread-tools.unlock": "إلغاء إقفال الموضوع", "thread-tools.move": "نقل الموضوع", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "نقل الكل", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "تحميل الفئات", "confirm-move": "انقل", + "confirm-crosspost": "Cross-post", "confirm-fork": "فرع", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "تحميل المزيد من المشاركات", "move-topic": "نقل الموضوع", "move-topics": "نقل المواضيع", + "crosspost-topic": "Cross-post Topic", "move-post": "نقل المشاركة", "post-moved": "تم نقل المشاركة", "fork-topic": "فرع الموضوع", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "أدخل عنوان موضوعك هنا...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "الرد على %1", "composer.new-topic": "موضوع جديد", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "جاري الرفع", "composer.thumb-url-label": "ألصق رابط الصورة المصغرة للموضوع", "composer.thumb-title": "إضافة صورة مصغرة للموضوع", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/ar/user.json b/public/language/ar/user.json index 590aad2ece..cae86be96e 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -105,6 +105,10 @@ "show-email": "أظهر بريدي الإلكتروني", "show-fullname": "أظهر اسمي الكامل", "restrict-chats": "لاتسمح بورود محادثات إلا من طرف المستخدمين الذين أتابعهم.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "اشترك في النشرة الدورية", "digest-description": "استلام اشعارات بآخر مستجدات هذا القسم (التنبيهات والمواضيع الجديدة) عبر البريد الإلكتروني وفقا لجدول زمني محدد.", "digest-off": "غير مفعل", diff --git a/public/language/ar/world.json b/public/language/ar/world.json index 3753335278..e6694bf507 100644 --- a/public/language/ar/world.json +++ b/public/language/ar/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/az/admin/advanced/cache.json b/public/language/az/admin/advanced/cache.json index b95975e307..6306a71017 100644 --- a/public/language/az/admin/advanced/cache.json +++ b/public/language/az/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Keş", - "post-cache": "Yazıların keşi", - "group-cache": "Qrup keşi", - "local-cache": "Lokal keş", - "object-cache": "Obyekt keşi", "percent-full": "%1% dolu", "post-cache-size": "Yazıların keş ölçüsü", "items-in-cache": "Keşdəki elementlər" diff --git a/public/language/az/admin/dashboard.json b/public/language/az/admin/dashboard.json index 8e7b0253d4..ec74e422a9 100644 --- a/public/language/az/admin/dashboard.json +++ b/public/language/az/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Səhifə Baxışları qeydə alınıb", "graphs.page-views-guest": "Səhifə baxışı qonaq", "graphs.page-views-bot": "Səhifə baxış botu", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unikal ziyarətçilər", "graphs.registered-users": "Qeydiyyatdan keçmiş istifadəçilər", "graphs.guest-users": "Qonaqlar", diff --git a/public/language/az/admin/development/info.json b/public/language/az/admin/development/info.json index a61eab10d3..23f093410e 100644 --- a/public/language/az/admin/development/info.json +++ b/public/language/az/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "onlayn", "git": "git", - "process-memory": "proses yaddaşı", + "process-memory": "rss/heap used", "system-memory": "sistem yaddaşı", "used-memory-process": "Proseslər yaddaşdan istifadə edir", "used-memory-os": "İstifadə olunmuş sistem yaddaşı", diff --git a/public/language/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json index 460c4cd852..ed6b0c63d5 100644 --- a/public/language/az/admin/manage/categories.json +++ b/public/language/az/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Kateqoriyaları idarə et", "add-category": "Kateqoriya əlavə et", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Keç...", "settings": "Kateqoriya parametrləri", "edit-category": "Kateqoriyanı redaktə et", "privileges": "İmtiyazlar", "back-to-categories": "Kateqoriyalara qayıt", + "id": "Category ID", "name": "Kateqoriya adı", "handle": "Kateqoriya dəstəyi", "handle.help": "Kateqoriya dəstəyiniz istifadəçi adına bənzər digər şəbəkələrdə bu kateqoriyanın təmsili kimi istifadə olunur. Kateqoriya sapı mövcud istifadəçi adı və ya istifadəçi qrupuna uyğun olmamalıdır.", "description": "Kateqoriya təsviri", - "federatedDescription": "Federasiya təsviri", - "federatedDescription.help": "Bu mətn digər vebsaytlar/tətbiqlər tərəfindən sorğulandıqda kateqoriya təsvirinə əlavə olunacaq.", - "federatedDescription.default": "Bu, aktual müzakirələrdən ibarət forum kateqoriyasıdır. Bu kateqoriyanı qeyd etməklə yeni müzakirələrə başlaya bilərsiniz.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Arxa fon rəngi", "text-color": "Mətnin rəngi", "bg-image-size": "Fon şəklinin ölçüsü", @@ -103,6 +107,11 @@ "alert.create-success": "Kateqoriya uğurla yaradıldı!", "alert.none-active": "Aktiv kateqoriyalarınız yoxdur.", "alert.create": "Kateqoriya yarat", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Bu \"%1\" kateqoriyasını həqiqətən təmizləmək istəyirsiniz?

Xəbərdarlıq! Bu kateqoriyadakı bütün mövzular və yazılar silinəcək!

Kateqoriyanın təmizlənməsi bütün mövzuları və yazıları siləcək və kateqoriyanı verilənlər bazasından siləcək. Kateqoriyanı müvəqqəti olaraq silmək istəyirsinizsə, bunun əvəzinə kateqoriyanı \"deaktiv etmək\" istəyəcəksiniz.

", "alert.purge-success": "Kateqoriya təmizləndi!", "alert.copy-success": "Parametrlər kopyalandı!", diff --git a/public/language/az/admin/manage/custom-reasons.json b/public/language/az/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/az/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/az/admin/manage/privileges.json b/public/language/az/admin/manage/privileges.json index 605763630d..3d584c4cee 100644 --- a/public/language/az/admin/manage/privileges.json +++ b/public/language/az/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Mövzulara daxil olun", "create-topics": "Mövzular yarat", "reply-to-topics": "Mövzulara cavab ver", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Mövzuları təqvim et", "tag-topics": "Mövzuları teqlə", "edit-posts": "Yazıları redaktə et", diff --git a/public/language/az/admin/manage/users.json b/public/language/az/admin/manage/users.json index 080795df26..2c2572f435 100644 --- a/public/language/az/admin/manage/users.json +++ b/public/language/az/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "İstifadəçi(lər)iməzmunu silin", "download-csv": "CSV-ni endir", "custom-user-fields": "Fərdi istifadəçi sahələri", + "custom-reasons": "Custom Reasons", "manage-groups": "Qrupları idarə et", "set-reputation": "Reputasiya təyin et", "add-group": "Qrup əlavə et", @@ -77,9 +78,11 @@ "temp-ban.length": "Uzunluq", "temp-ban.reason": "Səbəb (İstəyə görə)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Saat", "temp-ban.days": "Günlər", "temp-ban.explanation": "Qadağanın müddətini daxil edin. Nəzərə alın ki, 0 vaxtı daimi qadağa hesab olunacaq.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Həqiqətən bu istifadəçini həmişəlik qadağan etmək istəyirsiniz?", "alerts.confirm-ban-multi": "Bu istifadəçiləri həmişəlik qadağan etmək istəyirsiniz?", diff --git a/public/language/az/admin/settings/activitypub.json b/public/language/az/admin/settings/activitypub.json index f7bb02e10b..2399a20908 100644 --- a/public/language/az/admin/settings/activitypub.json +++ b/public/language/az/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Axtarış vaxtı (millisaniyə)", "probe-timeout-help": "(Defolt: 2000) Əgər axtarış sorğusu müəyyən edilmiş vaxt çərçivəsində cavab almazsa, onun əvəzinə istifadəçi birbaşa linkə göndəriləcək. Saytlar ləng cavab verirsə və əlavə vaxt vermək istəyirsinizsə, bu rəqəmi daha yüksək tənzimləyin.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtrlə", "count": "Bu NodeBB hazırda %1 server(lər)dən xəbərdardır", "server.filter-help": "NodeBB ilə federasiyaya mane olmaq istədiyiniz serverləri göstərin. Alternativ olaraq, bunun əvəzinə xüsusi serverlərlə federasiyaya seçimlə icazə verə bilərsiniz. Hər iki variant bir-birini istisna etsə də, dəstəklənir.", diff --git a/public/language/az/admin/settings/chat.json b/public/language/az/admin/settings/chat.json index d42cdb5f36..acc57262dc 100644 --- a/public/language/az/admin/settings/chat.json +++ b/public/language/az/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Söhbət otağı adlarının maksimum uzunluğu", "max-room-size": "Söhbət otaqlarında maksimum istifadəçi sayı", "delay": "Söhbət mesajları arasındakı vaxt (ms)", - "notification-delay": "Söhbət mesajları üçün bildiriş gecikməsi", - "notification-delay-help": "Bu vaxt arasında göndərilən əlavə mesajlar toplanır və istifadəçiyə hər gecikmə müddətində bir dəfə bildiriş göndərilir. Gecikməni söndürmək üçün bunu 0-a qoyun.", "restrictions.seconds-edit-after": "Söhbət mesajının redaktə oluna biləcəyi saniyələrin sayı.", "restrictions.seconds-delete-after": "Söhbət mesajının silinə biləcəyi saniyələrin sayı." } \ No newline at end of file diff --git a/public/language/az/admin/settings/email.json b/public/language/az/admin/settings/email.json index 93af324c95..5ba8a8e270 100644 --- a/public/language/az/admin/settings/email.json +++ b/public/language/az/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Əlaqələrin birləşdirilməsi NodeBB-nin hər e-poçt üçün yeni əlaqə yaratmasının qarşısını alır. Bu seçim yalnız SMTP Nəqliyyatı aktiv olduqda tətbiq edilir.", "smtp-transport.allow-self-signed": "Özünü imzalayan sertifikatlara icazə verin", "smtp-transport.allow-self-signed-help": "Bu parametri aktivləşdirərək siz öz imzalı və ya etibarsız TLS sertifikatlarından istifadə etmək imkanı əldə edəcəksiniz.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "E-poçt şablonunu redaktə et", "template.select": "E-poçt şablonunu seç", "template.revert": "Orijinala qayıt", + "test-smtp-settings": "Test SMTP Settings", "testing": "Elektron poçt testi", + "testing.success": "Test Email Sent.", "testing.select": "E-poçt şablonunu seç", "testing.send": "Test məktubu göndər", - "testing.send-help": "Test məktubu hazırda daxil olmuş istifadəçinin e-poçt ünvanına göndəriləcək.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "E-poçt həzmləri", "subscriptions.disable": "E-poçt həzmlərini deaktiv edin", "subscriptions.hour": "Digest saatı", diff --git a/public/language/az/admin/settings/notifications.json b/public/language/az/admin/settings/notifications.json index 224f455018..52ea8440e1 100644 --- a/public/language/az/admin/settings/notifications.json +++ b/public/language/az/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Xoş gəlmisiniz bildirişi", "welcome-notification-link": "Xoş gəlmisiniz bildiriş linki", "welcome-notification-uid": "Xoş gəlmisiniz bildiriş istifadəçisi (UID)", - "post-queue-notification-uid": "Yazı növbəsi istifadəçisi (UID)" + "post-queue-notification-uid": "Yazı növbəsi istifadəçisi (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/az/admin/settings/uploads.json b/public/language/az/admin/settings/uploads.json index ceed4ae396..154478deba 100644 --- a/public/language/az/admin/settings/uploads.json +++ b/public/language/az/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Bu dəyərdən daha geniş şəkillər rədd ediləcək.", "reject-image-height": "Maksimum şəklin hündürlüyü (piksellə)", "reject-image-height-help": "Bu dəyərdən yüksək olan şəkillər rədd ediləcək.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "İstifadəçilərə mövzu miniatürlərini yükləməyə icazə ver", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Mövzu thumb ölçüsü", "allowed-file-extensions": "İcazə verilən fayl uzantıları", "allowed-file-extensions-help": "Fayl uzantılarının vergüllə ayrılmış siyahısını buraya daxil edin (məsələn, pdf, xls, doc). Boş siyahı bütün genişləndirmələrə icazə verildiyini bildirir.", diff --git a/public/language/az/admin/settings/user.json b/public/language/az/admin/settings/user.json index db190ba64c..6922744259 100644 --- a/public/language/az/admin/settings/user.json +++ b/public/language/az/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "E-poçtu göstər", "show-fullname": "Tam adı göstər", "restrict-chat": "Yalnız izlədiyim istifadəçilərdən gələn söhbət mesajlarına icazə ver", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Gedən bağlantıları yeni tabda açın", "topic-search": "Mövzudaxili axtarışı aktivləşdir", "update-url-with-post-index": "Mövzulara baxarkən url-i post indeksi ilə yenilə", diff --git a/public/language/az/admin/settings/web-crawler.json b/public/language/az/admin/settings/web-crawler.json index 1133674b7a..8bbf1cd002 100644 --- a/public/language/az/admin/settings/web-crawler.json +++ b/public/language/az/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "RSS Lentlərini söndür", "disable-sitemap-xml": "Sitemap.xml-ni deaktiv et", "sitemap-topics": "Sayt xəritəsində göstəriləcək mövzuların sayı", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Sayt xəritəsi keşini təmizlə", "view-sitemap": "Saytın Xəritəsinə bax" } \ No newline at end of file diff --git a/public/language/az/aria.json b/public/language/az/aria.json index d4813bf642..5552b75196 100644 --- a/public/language/az/aria.json +++ b/public/language/az/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "%1 istifadəçisi üçün profil səhifəsi", "user-watched-tags": "İstifadəçinin izlədiyi təqlər", "delete-upload-button": "Yükləmə düyməsini silmək", - "group-page-link-for": "%1 üçün qrup səhifəsi linki" + "group-page-link-for": "%1 üçün qrup səhifəsi linki", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/az/category.json b/public/language/az/category.json index 0b87448241..2da93cc400 100644 --- a/public/language/az/category.json +++ b/public/language/az/category.json @@ -1,12 +1,13 @@ { "category": "Kateqoriya", "subcategories": "Alt kateqoriyalar", - "uncategorized": "Kateqoriyasız", - "uncategorized.description": "Mövcud kateqoriyalara tam uyğun gəlməyən mövzular", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "Bu kateqoriya açıq sosial şəbəkədən %1 idarəsi vasitəsilə izlənilə bilər", "new-topic-button": "Yeni mövzu", "guest-login-post": "Yazmaq üçün daxil ol", "no-topics": "Bu kateqoriyada heç bir mövzu yoxdur. Niyə birini dərc etməyə cəhd etmirsiz?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "gözdən keçirmə", "no-replies": "Heç kim cavab yazmayıb", "no-new-posts": "Yeni yazı yoxdur.", diff --git a/public/language/az/error.json b/public/language/az/error.json index 382ebe1a64..366743f84c 100644 --- a/public/language/az/error.json +++ b/public/language/az/error.json @@ -3,6 +3,7 @@ "invalid-json": "Yanlış JSON", "wrong-parameter-type": "`%1` mülkiyyəti üçün %3 növünün dəyəri gözlənilən idi, lakin bunun əvəzinə %2 alındı", "required-parameters-missing": "Bu API çağırışında tələb olunan parametrlər yoxdur: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Siz hesaba daxil olmamısınız.", "account-locked": "Hesabınız müvəqqəti olaraq bloklanıb", "search-requires-login": "Axtarış üçün hesab tələb olunur - zəhmət olmasa daxil olun və ya qeydiyyatdan keçin.", @@ -67,8 +68,8 @@ "no-chat-room": "Söhbət otağı mövcud deyil", "no-privileges": "Bu əməliyyat üçün kifayət qədər imtiyazınız yoxdur.", "category-disabled": "Kateqoriya deaktiv edilib", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Yazı silindi", + "topic-locked": "Mövzu kilidləndi", "post-edit-duration-expired": "Sizə yazıları dərc etdikdən sonra yalnız %1 saniyə ərzində redaktə etmək icazəsi verilir", "post-edit-duration-expired-minutes": "Paylaşdıqdan sonra yalnız %1 dəqiqə ərzində yazıları redaktə etmək icazəniz var", "post-edit-duration-expired-minutes-seconds": "Göndərdikdən sonra yalnız %1 dəqiqə %2 saniyə ərzində yazıları redaktə etməyə icazəniz var", @@ -146,6 +147,7 @@ "post-already-restored": "Bu yazı artıq bərpa olunub", "topic-already-deleted": "Bu mövzu artıq silinib", "topic-already-restored": "Bu mövzu artıq bərpa olunub", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Siz əsas yazını silə bilməzsiniz, lütfən, əvəzinə mövzunu silin", "topic-thumbnails-are-disabled": "Mövzu kiçik şəkilləri deaktiv edilib.", "invalid-file": "Etibarsız fayl", @@ -154,6 +156,8 @@ "about-me-too-long": "Üzr istəyirik, mənim haqqımda %1 simvoldan uzun ola bilməz.", "cant-chat-with-yourself": "Özünüzlə söhbət edə bilməzsiniz!", "chat-restricted": "Bu istifadəçi söhbət mesajlarını məhdudlaşdırıb. Siz onlarla söhbət etməzdən əvvəl onlar sizi izləməlidirlər", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "Siz bu istifadəçi tərəfindən bloklanmısınız.", "chat-disabled": "Söhbət sistemi deaktiv edilib", "too-many-messages": "Həddən artıq çox mesaj göndərmisiniz, bir az gözləyin,", @@ -225,6 +229,7 @@ "no-topics-selected": "Mövzu seçilməyib!", "cant-move-to-same-topic": "Yazı eyni mövzuya köçürülə bilməz!", "cant-move-topic-to-same-category": "Mövzunu eyni kateqoriyaya köçürmək mümkün deyil!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Özünüzü bloklaya bilməzsiniz!", "cannot-block-privileged": "Siz administratorları və ya qlobal moderatorları bloklaya bilməzsiniz", "cannot-block-guest": "Qonaq digər istifadəçiləri bloklaya bilməz", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Hazırda serverə daxil olmaq mümkün deyil. Yenidən cəhd etmək üçün bura klikləyin və ya daha sonra yenidən cəhd edin", "invalid-plugin-id": "Yanlış plagin identifikatoru", "plugin-not-whitelisted": "Plugini quraşdırmaq mümkün deyil – yalnız NodeBB Paket Meneceri tərəfindən ağ siyahıya alınmış plaginlər ACP vasitəsilə quraşdırıla bilər", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "ACP vasitəsilə plagin quraşdırılması deaktiv edilib", "plugins-set-in-configuration": "Sizə plagin vəziyyətini dəyişdirmək icazəsi verilmir, çünki onlar icra zamanı təyin olunur (config.json, ətraf mühit dəyişənləri və ya terminal arqumentləri), lütfən, bunun əvəzinə konfiqurasiyanı dəyişdirin.", "theme-not-set-in-configuration": "Konfiqurasiyada aktiv plaginləri təyin edərkən, mövzuların dəyişdirilməsi ACP-də yeniləmədən əvvəl yeni mövzunun aktiv plaginlərin siyahısına əlavə edilməsini tələb edir.", @@ -246,6 +252,7 @@ "api.401": "Düzgün giriş sessiyası tapılmadı. Daxil olun və yenidən cəhd edin.", "api.403": "Bu zəng etmək səlahiyyətiniz yoxdur", "api.404": "Yanlış API çağırışı", + "api.413": "The request payload is too large", "api.426": "Api-yə sorğular üçün HTTPS tələb olunur, xahiş edirik sorğunuzu HTTPS vasitəsilə yenidən göndərin", "api.429": "Həddindən artıq sorğu göndərmisiniz, lütfən, biraz sonra yenidən cəhd edin", "api.500": "Sorğunuza xidmət göstərməyə cəhd edərkən gözlənilməz xəta ilə qarşılaşdı.", diff --git a/public/language/az/global.json b/public/language/az/global.json index 5db93d8d61..2fe835ca89 100644 --- a/public/language/az/global.json +++ b/public/language/az/global.json @@ -68,6 +68,7 @@ "users": "İstifadəçilər", "topics": "Mövzu", "posts": "Yazı", + "crossposts": "Cross-posts", "x-posts": "%1 yazı", "x-topics": "%1 mövzu", "x-reputation": "%1 reputasiya", @@ -82,6 +83,7 @@ "downvoted": "Mənfi səs verildi", "views": "Baxış", "posters": "Yazarlar", + "watching": "İzlənilir", "reputation": "Reputasiya", "lastpost": "Son yazı", "firstpost": "İlk yazı", diff --git a/public/language/az/groups.json b/public/language/az/groups.json index 72ebe6bbfd..451b09d59e 100644 --- a/public/language/az/groups.json +++ b/public/language/az/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Bütün qruplar", "groups": "Qruplar", "members": "Üzvlər", + "x-members": "%1 member(s)", "view-group": "Qrupa bax", "owner": "Qrup sahibi", "new-group": "Yeni qrup yarat", diff --git a/public/language/az/modules.json b/public/language/az/modules.json index dad624a47f..c4bf165921 100644 --- a/public/language/az/modules.json +++ b/public/language/az/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "İstifadəçi əlavə et", "chat.notification-settings": "Bildiriş parametrləri", "chat.default-notification-setting": "Defolt bildiriş parametri", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Defolt otaq", "chat.notification-setting-none": "Bildiriş yoxdur", "chat.notification-setting-at-mention-only": "yalnız @qeyd", @@ -81,7 +82,7 @@ "composer.hide-preview": "Önizləməni gizlət", "composer.help": "Yardım", "composer.user-said-in": "%1 %2-də dedi:", - "composer.user-said": "%1 dedi:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Bu yazını silmək istədiyinizə əminsiniz?", "composer.submit-and-lock": "Göndər və kilidlə", "composer.toggle-dropdown": "Açılan menyunu dəyiş", diff --git a/public/language/az/notifications.json b/public/language/az/notifications.json index 00a457de2f..dd0981a302 100644 --- a/public/language/az/notifications.json +++ b/public/language/az/notifications.json @@ -22,7 +22,7 @@ "upvote": "Müsbət səslər", "awards": "Mükafatlar", "new-flags": "Yeni bayraqlar", - "my-flags": "Mənə təyin olunmuş bayraqlar", + "my-flags": "My Flags", "bans": "Qadağalar", "new-message-from": "%1-dən yeni mesaj", "new-messages-from": "%2-dən %1 yeni mesaj", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1%2 %4-də yazdı", "user-posted-in-public-room-triple": "%1, %2%3 %5 ilə yazır", "user-posted-in-public-room-multiple": "%1, %2 və %3 digərləri %5-də yazıblar", - "upvoted-your-post-in": "%1, %2-də yazınıza müsbət səs verdi.", - "upvoted-your-post-in-dual": "%1%2 %3-də yazınıza müsbət səs verdi.", - "upvoted-your-post-in-triple": "%1, %2%3 %4-də yazınıza müsbət səs verdi.", - "upvoted-your-post-in-multiple": "%1, %2 və %3 digərləri %4-də yazınıza müsbət səs verdilər.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 yazınızı %2-ə köçürdü", "moved-your-topic": "%1 %2-ni köçürdü", - "user-flagged-post-in": "%1, %2-də yazını qeyd etdi", - "user-flagged-post-in-dual": "%1%2 yazını %3-də qeyd etdi", - "user-flagged-post-in-triple": "%1, %2%3 %4-də postu qeyd etdi", - "user-flagged-post-in-multiple": "%1, %2 və %3 başqaları %4-də yazını işarələdi", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "% 1 istifadəçi profilini işarələdi (%2)", "user-flagged-user-dual": "%1%2 istifadəçi profilini qeyd etdi (%3)", "user-flagged-user-triple": "%1, %2%3 istifadəçi profilini qeyd etdi (%4)", "user-flagged-user-multiple": "%1, %2 və digər %3 digər istifadəçi profilini qeyd etdi (%4)", - "user-posted-to": "%1 cavab yazdı: %2", - "user-posted-to-dual": "%1%2 cavablar göndərdi: %3", - "user-posted-to-triple": "%1, %2%3 cavablar göndərdi: %4", - "user-posted-to-multiple": "%1, %2 və %3 başqaları cavab yazmışdır: %4", - "user-posted-topic": "%1 yeni mövzu yerləşdirdi: %2", - "user-edited-post": "%1 %2-də yazını redaktə etdi", - "user-posted-topic-with-tag": "%1, %2-ni dərc etdi (%3 ilə taq edilib)", - "user-posted-topic-with-tag-dual": "%1 paylaşdı %2 (%3 və %4 təqli)", - "user-posted-topic-with-tag-triple": "%1 paylaşdı %2 (teqli %3, %4 və %5)", - "user-posted-topic-with-tag-multiple": "%1, %2-ni dərc etdi (%3 ilə işarələnmiş)", - "user-posted-topic-in-category": "%1 %2-də yeni mövzu yerləşdirdi", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 sizi izləməyə başladı.", "user-started-following-you-dual": "%1%2 sizi izləməyə başladı.", "user-started-following-you-triple": "%1, %2%3 sizi izləməyə başladı.", @@ -71,11 +71,11 @@ "users-csv-exported": "İstifadəçilər csv faylına ixrac edildi, yükləmək üçün klikləyin", "post-queue-accepted": "Növbəyə qoyduğunuz yazı qəbul edildi. Yazınıza baxmaq üçün bura klikləyin.", "post-queue-rejected": "Növbəyə qoyduğunuz yazı rədd edildi.", - "post-queue-notify": "Növbəyə qoyulmuş yazı bildiriş aldı:
\"% 1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-poçt təsdiqləndi", "email-confirmed-message": "E-poçtunuzu təsdiq etdiyiniz üçün təşəkkür edirik. Artıq hesabınız tam aktivləşdirilib.", "email-confirm-error-message": "E-poçt ünvanınızı təsdiqkləyərkən problem baş verdi. Ola bilsin ki, kod etibarsız olub və ya vaxtı keçib.", - "email-confirm-error-message-already-validated": "E-poçt ünvanınız artıq təsdiqlənib.", "email-confirm-sent": "Təsdiq e-poçtu göndərildi.", "none": "Heç biri", "notification-only": "Yalnız bildiriş", diff --git a/public/language/az/social.json b/public/language/az/social.json index 42afa9db1a..e9010887fe 100644 --- a/public/language/az/social.json +++ b/public/language/az/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Facebook ilə daxil olun", "continue-with-facebook": "Facebook ilə davam edin", "sign-in-with-linkedin": "LinkedIn ilə daxil olun", - "sign-up-with-linkedin": "LinkedIn ilə qeydiyyatdan keç" + "sign-up-with-linkedin": "LinkedIn ilə qeydiyyatdan keç", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/az/themes/harmony.json b/public/language/az/themes/harmony.json index 485d431e77..1a3fe209b9 100644 --- a/public/language/az/themes/harmony.json +++ b/public/language/az/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Örtüklər", + "light": "Light", + "dark": "Dark", "collapse": "Yığmaq", "expand": "Açmaq", "sidebar-toggle": "Yan panel aç/bağla", diff --git a/public/language/az/topic.json b/public/language/az/topic.json index d8993f4098..67414c4c79 100644 --- a/public/language/az/topic.json +++ b/public/language/az/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 %3-də bu mövzuya istinad etdi", "user-forked-topic-ago": "%1 bu mövzunu nüsxələdi %3", "user-forked-topic-on": "%1 bu mövzunu %3-də nüsxələdi", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Bu mövzuda son oxunmuş yazıya qayıtmaq üçün bura klikləyin.", "flag-post": "Bu postu işarələ", "flag-user": "Bu istifadəçini qeyd et", @@ -103,6 +105,7 @@ "thread-tools.lock": "Mövzunu kilidlə", "thread-tools.unlock": "Mövzunun kilidini aç", "thread-tools.move": "Mövzunu köçür", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Yazıları köçür", "thread-tools.move-all": "Hamısını köçür", "thread-tools.change-owner": "Sahibini dəyiş", @@ -132,6 +135,7 @@ "pin-modal-help": "Siz istəyə görə burada bərkidilmiş mövzu(lar) üçün bitmə tarixi təyin edə bilərsiniz. Alternativ olaraq, mövzu əl ilə çıxarılana qədər bərkidilmiş vəziyyətdə qalması üçün bu sahəni boş qoya bilərsiniz.", "load-categories": "Kateqoriyalar yüklənir", "confirm-move": "Köçür", + "confirm-crosspost": "Cross-post", "confirm-fork": "Kopyala", "bookmark": "Əlfəcin", "bookmarks": "Əlfəcinlər", @@ -141,6 +145,7 @@ "loading-more-posts": "Daha çox yazı yüklə", "move-topic": "Mövzunu köçür", "move-topics": "Mövzuları köçür", + "crosspost-topic": "Cross-post Topic", "move-post": "Yazını köçür", "post-moved": "Yazı köçürüldü!", "fork-topic": "Mövzu kopyala", @@ -163,6 +168,9 @@ "move-topic-instruction": "Hədəf kateqoriyasını seçin və sonra köçürmə düyməsini sıxın", "change-owner-instruction": "Başqa istifadəçiyə təyin etmək istədiyiniz yazıların üzərinə klikləyin", "manage-editors-instruction": "Aşağıda bu yazını redaktə edə biləcək istifadəçiləri idarə edin.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Mövzunuzu bura daxil edin...", "composer.handle-placeholder": "Buraya adınızı/dəstəklərinizi daxil edin", "composer.hide": "Gizlət", @@ -174,6 +182,7 @@ "composer.replying-to": "%1 cavab verilir", "composer.new-topic": "Yeni mövzu", "composer.editing-in": "%1-də yazı redaktə edilir", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "yüklənir...", "composer.thumb-url-label": "Mövzuya kiçik şəkli URL kimi yerləşdir", "composer.thumb-title": "Bu mövzuya kiçik şəkil əlavə et", @@ -224,5 +233,8 @@ "unread-posts-link": "Oxunmamış yazıların linki", "thumb-image": "Mövzunun kiçik şəkli", "announcers": "Paylaşımlar", - "announcers-x": "Paylaşımlar (%1)" + "announcers-x": "Paylaşımlar (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/az/user.json b/public/language/az/user.json index 321c99855d..4fe318a3db 100644 --- a/public/language/az/user.json +++ b/public/language/az/user.json @@ -105,6 +105,10 @@ "show-email": "E-poçtumu göstər", "show-fullname": "Tam adımı göstərin", "restrict-chats": "Yalnız izlədiyim istifadəçilərdən gələn söhbət mesajlarına icazə verin", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Digest-ə abunə ol", "digest-description": "Müəyyən edilmiş cədvələ uyğun olaraq bu forum üçün e-poçt yeniləmələrinə (yeni bildirişlər və mövzular) abunə olun", "digest-off": "Söndür", diff --git a/public/language/az/world.json b/public/language/az/world.json index 8a1807f3d8..a61b75e11b 100644 --- a/public/language/az/world.json +++ b/public/language/az/world.json @@ -1,7 +1,12 @@ { "name": "Dünya", - "popular": "Populyar mövzular", - "recent": "Bütün mövzular", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Yardım", "help.title": "Bu səhifə nədir?", @@ -14,5 +19,7 @@ "onboard.title": "Sizin fediverse pəncərəniz...", "onboard.what": "Bu, yalnız bu forumdan kənarda tapılan məzmundan ibarət sizin fərdiləşdirilmiş kateqoriyanızdır. Bu səhifədə nəyinsə görünüb-göstərilməməsi onları izlədiyinizdən və ya həmin postun izlədiyiniz biri tərəfindən paylaşılıb-paylaşılmamasından asılıdır.", "onboard.why": "Bu forumdan kənarda gedən çox şey var və bunların heç də hamısı maraqlarınıza uyğun deyil. Buna görə də insanları izləmək, kimdənsə daha çox görmək istədiyinizi bildirməyin ən yaxşı yoludur.", - "onboard.how": "Bu arada, bu forumun daha nələr haqqında bildiyini görmək üçün yuxarıdakı qısayol düymələrinə klikləyə və bəzi yeni məzmunlar kəşf etməyə başlaya bilərsiniz!" + "onboard.how": "Bu arada, bu forumun daha nələr haqqında bildiyini görmək üçün yuxarıdakı qısayol düymələrinə klikləyə və bəzi yeni məzmunlar kəşf etməyə başlaya bilərsiniz!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/bg/admin/advanced/cache.json b/public/language/bg/admin/advanced/cache.json index 1d8c090be7..2282238def 100644 --- a/public/language/bg/admin/advanced/cache.json +++ b/public/language/bg/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Кеш", - "post-cache": "Кеш за публикации", - "group-cache": "Кеш за групи", - "local-cache": "Локален кеш", - "object-cache": "Кеш за обекти", "percent-full": "Запълненост: %1%", "post-cache-size": "Размер на кеша за публикации", "items-in-cache": "Елементи в кеша" diff --git a/public/language/bg/admin/dashboard.json b/public/language/bg/admin/dashboard.json index d7839ed1ff..f749044a69 100644 --- a/public/language/bg/admin/dashboard.json +++ b/public/language/bg/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Преглеждания на страниците от регистрирани потребители", "graphs.page-views-guest": "Преглеждания на страниците от гости", "graphs.page-views-bot": "Преглеждания на страниците от ботове", + "graphs.page-views-ap": "Преглеждания на страницата от ActivityPub", "graphs.unique-visitors": "Уникални посетители", "graphs.registered-users": "Регистрирани потребители", "graphs.guest-users": "Гости", diff --git a/public/language/bg/admin/development/info.json b/public/language/bg/admin/development/info.json index 71f1e63c4b..5ee980d748 100644 --- a/public/language/bg/admin/development/info.json +++ b/public/language/bg/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "на линия", "git": "git", - "process-memory": "памет на процеса", + "process-memory": "rss/heap used", "system-memory": "системна памет", "used-memory-process": "Използвана памет от процеса", "used-memory-os": "Използвана системна памет", diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index 31531b4c16..755a7a8caa 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Управление на категориите", "add-category": "Добавяне на категория", + "add-local-category": "Добавяне на локална категория", + "add-remote-category": "Добавяне на отдалечена категория", + "remove": "Премахване", + "rename": "Преименуване", "jump-to": "Прехвърляне към…", "settings": "Настройки на категорията", "edit-category": "Редактиране на категорията", "privileges": "Правомощия", "back-to-categories": "Назад към категориите", + "id": "Идентификатор на категорията", "name": "Име на категорията", "handle": "Идентификатор на категорията", "handle.help": "Идентификаторът на категорията се ползва за представяне на тази категория в други мрежи, подобно на потребителското име. Този идентификатор не трябва да съвпада със съществуващо потребителско име или потребителска група.", "description": "Описание на категорията", - "federatedDescription": "Федерирано описание", - "federatedDescription.help": "Този текст ще бъде добавен към описанието на категорията, когато други уеб сайтове и приложения изискват информация за нея.", - "federatedDescription.default": "Това е категория във форума, съдържаща тематични дискусии. Може да започнете нова дискусия, като споменете този форум.", + "topic-template": "Шаблон за темите", + "topic-template.help": "Създайте шаблон за новите теми в тази категория.", "bg-color": "Цвят на фона", "text-color": "Цвят на текста", "bg-image-size": "Размер на фоновото изображение", @@ -103,6 +107,11 @@ "alert.create-success": "Категорията е създадена успешно!", "alert.none-active": "Нямате активни категории.", "alert.create": "Създаване на категория", + "alert.add": "Добавяне на категория", + "alert.add-help": "Отдалечена категория може да бъде добавена в списъка с категории, като посочите нейния идентификатор.

Забележка – отдалечената категория може да не отразява всички публикувани теми, освен ако поне един локален потребител не я следи/наблюдава.", + "alert.rename": "Преименуване на отдалечена категория", + "alert.rename-help": "Въведете новото име за тази категория. Оставете празно, за да върнете оригиналното име.", + "alert.confirm-remove": "Наистина ли искате да премахнете тази категория? Можете да я добавите отново по всяко време.", "alert.confirm-purge": "

Наистина ли искате да изтриете категорията „%1“?

Внимание! Всички теми и публикации в тази категория ще бъдат изтрити!

Изтриването на категорията ще премахне всички теми и публикации, и ще изтрие категорията от базата данни. Ако искате да премахнете категорията временно, можете просто да я „изключите“.

", "alert.purge-success": "Категорията е изтрита!", "alert.copy-success": "Настройките са копирани!", diff --git a/public/language/bg/admin/manage/custom-reasons.json b/public/language/bg/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/bg/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/bg/admin/manage/privileges.json b/public/language/bg/admin/manage/privileges.json index 008017eea5..6e8295b33f 100644 --- a/public/language/bg/admin/manage/privileges.json +++ b/public/language/bg/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Достъп до теми", "create-topics": "Създаване на теми", "reply-to-topics": "Отговаряне в теми", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Насрочване на теми", "tag-topics": "Поставяне на етикети на теми", "edit-posts": "Редактиране на публикации", diff --git a/public/language/bg/admin/manage/users.json b/public/language/bg/admin/manage/users.json index 550798d958..8236b2206c 100644 --- a/public/language/bg/admin/manage/users.json +++ b/public/language/bg/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Изтриване на потребителя/ите и съдържанието", "download-csv": "Сваляне във формат „CSV“", "custom-user-fields": "Персонализирани потребителски полета", + "custom-reasons": "Custom Reasons", "manage-groups": "Управление на групите", "set-reputation": "Задаване на репутация", "add-group": "Добавяне на група", @@ -77,9 +78,11 @@ "temp-ban.length": "Продължителност", "temp-ban.reason": "Причина (незадължително)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Часове", "temp-ban.days": "Дни", "temp-ban.explanation": "Въведете продължителността на блокирането. Стойност от 0 ще направи блокирането за постоянно.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Наистина ли искате да блокирате този потребител за постоянно?", "alerts.confirm-ban-multi": "Наистина ли искате да блокирате тези потребители за постоянно?", diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 59c76176bd..dea26e2dfa 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Време за изчакване на проверката (милисекунди)", "probe-timeout-help": "(По подразбиране: 2000) Ако проверката не получи отговор в рамките на зададеното време, потребителят ще бъде изпратен директно на адреса на връзката. Задайте по-голямо число, ако уеб сайтовете отговарят по-бавно и искате да им дадете повече време.", + "rules": "Категоризиране", + "rules-intro": "Съдържанието открито чрез ActivityPub може да бъде категоризирано автоматично следвайки определени правила (например дума отбелязана с диез)", + "rules.modal.title": "Как работи това", + "rules.modal.instructions": "Цялото входящо съдържание се проверява спрямо правилата и ако има съвпадения – те се преместват в избраната категория.

Забележка Съдържанието, което вече е категоризирано (например в отдалечена категория) няма да преминава тези проверки.", + "rules.add": "Добавяне на ново правило", + "rules.help-hashtag": "Ще се търсят съвпадения с теми съдържащи тази дума с диез (не се прави разлика между главни и малки букви). Не въвеждайте знака #", + "rules.help-user": "Ще се търсят теми създадени от този потребител. Въведете псевдоним или пълен идентификатор (например bob@example.org или https://example.org/users/bob.", + "rules.type": "Тип", + "rules.value": "Стойност", + "rules.cid": "Категория", + + "relays": "Препредавател", + "relays.intro": "Препредавателят подобрява отриването на съдържание за и от Вашият NodeBB. Абонирането за препредавател означава, че съдържанието получено от него ще бъде препредавано тук, а съдържанието публикувано тук, ще бъде излъчвано от него за останалите.", + "relays.warning": "Забележка: препредавателите могат да доставят огромно количество трафик, което може да увеличи разходите Ви за съхранение и обработка.", + "relays.litepub": "NodeBB използва стандарт за препредаване в стила на LitePub. Адресът, който въведете тук, трябва да завършва с /actor.", + "relays.add": "Добавяне на нов препредавател", + "relays.relay": "Препредавател", + "relays.state": "Състояние", + "relays.state-0": "В изчакване", + "relays.state-1": "Само приемане", + "relays.state-2": "Активен", + "server-filtering": "Филтриране", "count": "Този NodeBB в момента знае за наличието на %1 сървър(а)", "server.filter-help": "Посочете сървърите, с които не искате Вашият NodeBB да осъществява връзка. Или можете вместо това да посочите конкретни сървъри, с които разрешавате връзката. И двете възможности са налични, но може да изберете само една от тях.", diff --git a/public/language/bg/admin/settings/chat.json b/public/language/bg/admin/settings/chat.json index 040b075a35..b1a62e3e68 100644 --- a/public/language/bg/admin/settings/chat.json +++ b/public/language/bg/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Максимална дължина на имената на стаи за разговори", "max-room-size": "Максимален брой потребители в стая за разговор", "delay": "Време между съобщенията в разговорите (мсек)", - "notification-delay": "Забавяне преди известяване за съобщения в разговорите", - "notification-delay-help": "Допълнителните съобщения, изпратени в рамките на това време, се комбинират, и потребителят получава по едно известие за всеки такъв период на забавяне. Задайте стойност 0, за да изключите забавянето.", "restrictions.seconds-edit-after": "Брой секунди, през които съобщенията в разговор могат да бъдат редактирани.", "restrictions.seconds-delete-after": "Брой секунди, през които съобщенията в разговор могат да бъдат изтрити." } \ No newline at end of file diff --git a/public/language/bg/admin/settings/email.json b/public/language/bg/admin/settings/email.json index 1539bb0f03..4f3ff7b24b 100644 --- a/public/language/bg/admin/settings/email.json +++ b/public/language/bg/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Групирането на връзките предотвратява създаването на нова връзка за всяко е-писмо. Тази настройка има ефект, само ако е включено „Транспорт чрез SMTP“.", "smtp-transport.allow-self-signed": "Разрешаване на самоподписаните сертификатите", "smtp-transport.allow-self-signed-help": "Включването на тази настройка ще позволи ползването на самоподписани и невалидни сертификати TLS.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Редактирана не шаблона за е-писма", "template.select": "Изберете шаблон за е-писма", "template.revert": "Връщане на оригинала", + "test-smtp-settings": "Test SMTP Settings", "testing": "Проба на е-писмата", + "testing.success": "Test Email Sent.", "testing.select": "Изберете шаблон за е-писма", "testing.send": "Изпращане на пробно е-писмо", - "testing.send-help": "Пробното е-писмо ще бъде изпратено до е-пощата на текущо вписания потребител.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Резюмета по е-поща", "subscriptions.disable": "Изключване на резюметата по е-пощата", "subscriptions.hour": "Време за разпращане", diff --git a/public/language/bg/admin/settings/notifications.json b/public/language/bg/admin/settings/notifications.json index c3831f2bc2..3dd327fd85 100644 --- a/public/language/bg/admin/settings/notifications.json +++ b/public/language/bg/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Приветствено известие", "welcome-notification-link": "Връзка за приветственото известие", "welcome-notification-uid": "Потр. ид. за приветственото известие", - "post-queue-notification-uid": "Потр. ид. за опашката с публикации" + "post-queue-notification-uid": "Потр. ид. за опашката с публикации", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/bg/admin/settings/uploads.json b/public/language/bg/admin/settings/uploads.json index 4820730824..a7bbaf5d9b 100644 --- a/public/language/bg/admin/settings/uploads.json +++ b/public/language/bg/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Изображенията, чиято ширина е по-голяма от тази стойност, ще бъдат отхвърляни.", "reject-image-height": "Максимална височина на изображенията (в пиксели)", "reject-image-height-help": "Изображенията, чиято височина е по-голяма от тази стойност, ще бъдат отхвърляни.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Позволяване на потребителите да качват миниатюрни изображения за темите", + "show-post-uploads-as-thumbnails": "Показване на качените файлове в публикациите като миниатюрни изображения", "topic-thumb-size": "Размер на миниатюрите за темите", "allowed-file-extensions": "Разрешени файлови разширения", "allowed-file-extensions-help": "Въведете файловите разширения, разделени със запетаи (пример: pdf,xls,doc). Ако списъкът е празен, всички файлови разширения ще бъдат разрешени.", diff --git a/public/language/bg/admin/settings/user.json b/public/language/bg/admin/settings/user.json index c4725d85f9..fb40289a99 100644 --- a/public/language/bg/admin/settings/user.json +++ b/public/language/bg/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Показване на е-пощата", "show-fullname": "Показване на пълното име", "restrict-chat": "Разрешаване на съобщенията само от потребители, които следвам", + "disable-incoming-chats": "Забраняване на входящите съобщения", "outgoing-new-tab": "Отваряне на външните връзки в нов подпрозорец", "topic-search": "Включване на търсенето в темите", "update-url-with-post-index": "Обновяване на адресната лента с номера на публикацията по време на разглеждане на темите", diff --git a/public/language/bg/admin/settings/web-crawler.json b/public/language/bg/admin/settings/web-crawler.json index 88ca3da0f9..8286054a1f 100644 --- a/public/language/bg/admin/settings/web-crawler.json +++ b/public/language/bg/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Изключване на емисиите чрез RSS", "disable-sitemap-xml": "Изключване на картата на уеб сайта („Sitemap.xml“)", "sitemap-topics": "Брой теми за показване в картата на уеб сайта", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Изчистване на кеша на картата на уеб сайта", "view-sitemap": "Преглед на картата на уеб сайта" } \ No newline at end of file diff --git a/public/language/bg/aria.json b/public/language/bg/aria.json index ac14065b54..ef88594736 100644 --- a/public/language/bg/aria.json +++ b/public/language/bg/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Профилна страница за потребителя %1", "user-watched-tags": "Наблюдавани от потребителя етикети", "delete-upload-button": "Бутон за изтриване на каченото", - "group-page-link-for": "Връзка към груповата страница за %1" + "group-page-link-for": "Връзка към груповата страница за %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/bg/category.json b/public/language/bg/category.json index dbd313a08a..72b561fb58 100644 --- a/public/language/bg/category.json +++ b/public/language/bg/category.json @@ -1,12 +1,13 @@ { "category": "Категория", "subcategories": "Подкатегории", - "uncategorized": "Без категория", - "uncategorized.description": "Теми, които не пасват на никоя конкретна категория", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "Тази категория може да бъде последвана от отворената социална мрежа от чрез идентификатора %1", "new-topic-button": "Нова тема", "guest-login-post": "Впишете се, за да можете да публикувате", "no-topics": "Все още няма теми в тази категория.
Защо не създадете някоя?", + "no-followers": "Никой на този уеб сайт не следи или наблюдава тази категория. Започнете да следвате или наблюдавате тази категория, за да получавате известия за нея.", "browsing": "разглежда", "no-replies": "Няма отговори", "no-new-posts": "Няма нови публикации.", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 139bc6c444..81cd3f18e0 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -3,6 +3,7 @@ "invalid-json": "Неправилен JSON", "wrong-parameter-type": "За свойството `%1` се очакваше стойност от тип %3, но вместо това беше получено %2", "required-parameters-missing": "Липсват задължителни параметри от това извикване към ППИ: %1", + "reserved-ip-address": "Мрежовите заявки до IP адреси от резервирани области не са позволени.", "not-logged-in": "Изглежда не сте се вписали в системата.", "account-locked": "Вашият акаунт беше заключен временно", "search-requires-login": "Търсенето изисква регистриран акаунт! Моля, впишете се или се регистрирайте!", @@ -146,6 +147,7 @@ "post-already-restored": "Тази публикация вече е възстановена", "topic-already-deleted": "Тази тема вече е изтрита", "topic-already-restored": "Тази тема вече е възстановена", + "topic-already-crossposted": "Тази тема вече е била допълнително публикуване там.", "cant-purge-main-post": "Не можете да изчистите първоначалната публикация. Моля, вместо това изтрийте темата.", "topic-thumbnails-are-disabled": "Иконките на темите са изключени.", "invalid-file": "Грешен файл", @@ -154,6 +156,8 @@ "about-me-too-long": "Съжаляваме, но информацията за Вас трябва да съдържа не повече от %1 символ(а).", "cant-chat-with-yourself": "Не можете да пишете съобщение на себе си!", "chat-restricted": "Този потребител е ограничил съобщенията до себе си. Той трябва първо да Ви последва, преди да можете да си пишете с него.", + "chat-allow-list-user-already-added": "Този потребител вече е в списъка с разрешени", + "chat-deny-list-user-already-added": "Този потребител вече е в списъка със забранени", "chat-user-blocked": "Бяхте блокиран(а) от този потребител.", "chat-disabled": "Системата за разговори е изключена", "too-many-messages": "Изпратили сте твърде много съобщения. Моля, изчакайте малко.", @@ -225,6 +229,7 @@ "no-topics-selected": "Няма избрани теми!", "cant-move-to-same-topic": "Публикацията не може да бъде преместена в същата тема!", "cant-move-topic-to-same-category": "Темата не може да бъде преместена в същата категория!", + "cant-move-topic-to-from-remote-categories": "Не можете да премествате теми в или извън отдалечени категории. Вместо това може да направите допълнителни публикуване", "cannot-block-self": "Не можете да блокирате себе си!", "cannot-block-privileged": "Не можете да блокирате администратори и глобални модератори", "cannot-block-guest": "Гостите не могат да блокират други потребители", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "В момента сървърът е недостъпен. Натиснете тук, за да опитате отново, или опитайте пак по-късно.", "invalid-plugin-id": "Грешен идентификатор на добавка", "plugin-not-whitelisted": "Добавката не може да бъде инсталирана – само добавки, одобрени от пакетния мениджър на NodeBB могат да бъдат инсталирани чрез ACP", + "cannot-toggle-system-plugin": "Не можете да превключите състоянието на системна добавка", "plugin-installation-via-acp-disabled": "Инсталирането на добавки чрез ACP е изключено", "plugins-set-in-configuration": "Не можете да променяте състоянието на добавката, тъй като то се определя по време на работата ѝ (чрез config.json, променливи на средата или аргументи при изпълнение). Вместо това може да промените конфигурацията.", "theme-not-set-in-configuration": "Когато определяте активните добавки в конфигурацията, промяната на темите изисква да се добави новата тема към активните добавки, преди актуализирането ѝ в ACP", @@ -246,6 +252,7 @@ "api.401": "Няма намерена сесия. Моля, впишете се и опитайте отново.", "api.403": "Нямате право да изпълните тази команда", "api.404": "Неправилна команда към ППИ", + "api.413": "The request payload is too large", "api.426": "Заявките към ППИ за писане изискват HTTPS. Изпратете отново заявката си чрез HTTPS", "api.429": "Направили сте твърде много заявки. Моля, опитайте отново по-късно.", "api.500": "При обработката на заявката Ви възникна неочаквана грешка.", diff --git a/public/language/bg/global.json b/public/language/bg/global.json index 784d3baaf6..a6466bce82 100644 --- a/public/language/bg/global.json +++ b/public/language/bg/global.json @@ -68,6 +68,7 @@ "users": "Потребители", "topics": "Теми", "posts": "Публ.", + "crossposts": "Cross-posts", "x-posts": "%1 публикации", "x-topics": "%1 теми", "x-reputation": "%1 репутация", @@ -82,6 +83,7 @@ "downvoted": "С отрицателни гласове", "views": "Прегл.", "posters": "Участници", + "watching": "Наблюдаващи", "reputation": "Репутация", "lastpost": "Последна публикация", "firstpost": "Първа публикация", diff --git a/public/language/bg/groups.json b/public/language/bg/groups.json index 8a9b3e675f..e2eda68c47 100644 --- a/public/language/bg/groups.json +++ b/public/language/bg/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Всички групи", "groups": "Групи", "members": "Членове", + "x-members": "%1 member(s)", "view-group": "Преглед на групата", "owner": "Собственик на групата", "new-group": "Създаване на нова група", diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index ba3da12344..59cada982b 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Добавяне на потребител", "chat.notification-settings": "Настройки за известията", "chat.default-notification-setting": "Стандартни настройки за известията", + "chat.join-leave-messages": "Съобщения за присъединяване/напускане", "chat.notification-setting-room-default": "По подразбиране за стаята", "chat.notification-setting-none": "Без известия", "chat.notification-setting-at-mention-only": "Само @споменавания", @@ -81,7 +82,7 @@ "composer.hide-preview": "Скриване на прегледа", "composer.help": "Помощ", "composer.user-said-in": "%1 каза в %2:", - "composer.user-said": "%1 каза:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Наистина ли искате да отхвърлите тази публикация?", "composer.submit-and-lock": "Публикуване и заключване", "composer.toggle-dropdown": "Превключване на падащото меню", @@ -120,7 +121,7 @@ "bootbox.ok": "Добре", "bootbox.cancel": "Отказ", "bootbox.confirm": "Потвърждаване", - "bootbox.submit": "Публикуване", + "bootbox.submit": "Изпращане", "bootbox.send": "Изпращане", "cover.dragging-title": "Наместване на снимката", "cover.dragging-message": "Преместете снимката на желаното положение и натиснете „Запазване“", diff --git a/public/language/bg/notifications.json b/public/language/bg/notifications.json index 864165e0ed..cc3d4ce074 100644 --- a/public/language/bg/notifications.json +++ b/public/language/bg/notifications.json @@ -22,7 +22,7 @@ "upvote": "Положителни гласове", "awards": "Награди", "new-flags": "Нови докладвания", - "my-flags": "Докладвания, назначени на мен", + "my-flags": "Моите доклади", "bans": "Блокирания", "new-message-from": "Ново съобщение от %1", "new-messages-from": "%1 нови съобщения от %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 и %2 писаха в %4", "user-posted-in-public-room-triple": "%1, %2 и %3 писаха в %5", "user-posted-in-public-room-multiple": "%1, %2 и %3 други писаха в %5", - "upvoted-your-post-in": "%1 гласува положително за Ваша публикация в %2.", - "upvoted-your-post-in-dual": "%1 и %2 гласуваха положително за Ваша публикация в %3.", - "upvoted-your-post-in-triple": "%1, %2 и %3 гласуваха положително за Ваша публикация в %4.", - "upvoted-your-post-in-multiple": "%1, %2 и %3 други гласуваха положително за Ваша публикация в %4.", + "upvoted-your-post-in": "%1 гласува положително за Вашата публикация в %2", + "upvoted-your-post-in-dual": "%1 и %2 гласуваха положително за Вашата публикация в %3", + "upvoted-your-post-in-triple": "%1, %2 и %3 гласуваха положително за Вашата публикация в %4", + "upvoted-your-post-in-multiple": "%1, %2 и %3 други гласуваха положително за Вашата публикация в %4.", "moved-your-post": "%1 премести публикацията Ви в %2", "moved-your-topic": "%1 премести %2", "user-flagged-post-in": "%1 докладва публикация в %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 и %2 докладваха потребителски профил (%3)", "user-flagged-user-triple": "%1, %2 и %3 докладваха потребителски профил (%4)", "user-flagged-user-multiple": "%1, %2 и %3 други докладваха потребителски профил (%4)", - "user-posted-to": "%1 публикува отговор на: %2", - "user-posted-to-dual": "%1 и %2 публикуваха отговори на: %3", - "user-posted-to-triple": "%1, %2 и %3 публикуваха отговори на: %4", - "user-posted-to-multiple": "%1, %2 и %3 други публикуваха отговори на: %4", - "user-posted-topic": "%1 публикува нова тема: %2", + "user-posted-to": "%1 публикува отговор в %2", + "user-posted-to-dual": "%1 и %2 отговориха в %3", + "user-posted-to-triple": "%1, %2 и %3 отговориха в %4", + "user-posted-to-multiple": "%1, %2 и %3 други отговориха в %4", + "user-posted-topic": "%1 публикува %2", "user-edited-post": "%1 редактира публикация в %2", - "user-posted-topic-with-tag": "%1 публикува %2 (с етикет %3)", - "user-posted-topic-with-tag-dual": "%1 публикува %2 (с етикети %3 и %4)", - "user-posted-topic-with-tag-triple": "%1 публикува %2 (с етикети %3, %4 и %5)", - "user-posted-topic-with-tag-multiple": "%1 публикува %2 (с етикет %3)", - "user-posted-topic-in-category": "%1 публикува нова тема в %2", + "user-posted-topic-with-tag": "%1 публикува %2 (отбелязано с %3)", + "user-posted-topic-with-tag-dual": "%1 публикува %2 (отбелязано с %3 и %4)", + "user-posted-topic-with-tag-triple": "%1 публикува %2 (отбелязано с %3, %4 и %5)", + "user-posted-topic-with-tag-multiple": "%1 публикува %2 (отбелязано с %3)", + "user-posted-topic-in-category": "%1 публикува %2 в %3", "user-started-following-you": "%1 започна да Ви следва.", "user-started-following-you-dual": "%1 и %2 започнаха да Ви следват.", "user-started-following-you-triple": "%1, %2 и %3 започнаха да Ви следват.", @@ -71,11 +71,11 @@ "users-csv-exported": "Потребителите са изнесени във формат „csv“, щракнете за сваляне", "post-queue-accepted": "Вашата публикация, която чакаше в опашката, беше приета. Натиснете тук, за да я видите.", "post-queue-rejected": "Вашата публикация, която чакаше в опашката, беше отхвърлена.", - "post-queue-notify": "Публикация, чакаща в опашката, получи известие:
„%1“", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Публикацията в опашката получи известие: „%1“", "email-confirmed": "Е-пощата беше потвърдена", "email-confirmed-message": "Благодарим Ви, че потвърдихте е-пощата си. Акаунтът Ви е вече напълно активиран.", "email-confirm-error-message": "Възникна проблем при потвърждаването на е-пощата Ви. Може кодът да е грешен или давността му да е изтекла.", - "email-confirm-error-message-already-validated": "Адресът на е-пощата Ви вече е проверен.", "email-confirm-sent": "Изпратено е е-писмо за потвърждение.", "none": "Нищо", "notification-only": "Само известие", diff --git a/public/language/bg/social.json b/public/language/bg/social.json index 931e80c8c8..df4532b68e 100644 --- a/public/language/bg/social.json +++ b/public/language/bg/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Вписване с Facebook", "continue-with-facebook": "Продължаване с Facebook", "sign-in-with-linkedin": "Вписване с LinkedIn", - "sign-up-with-linkedin": "Регистриране с LinkedIn" + "sign-up-with-linkedin": "Регистриране с LinkedIn", + "sign-in-with-wordpress": "Вписване с WordPress", + "sign-up-with-wordpress": "Регистриране с WordPress" } \ No newline at end of file diff --git a/public/language/bg/themes/harmony.json b/public/language/bg/themes/harmony.json index ea6809c9b5..b66d55df79 100644 --- a/public/language/bg/themes/harmony.json +++ b/public/language/bg/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Тема на Harmony", "skins": "Облици", + "light": "Light", + "dark": "Dark", "collapse": "Свиване", "expand": "Разгъване", "sidebar-toggle": "Превключване на страничната лента", diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json index 35a1958077..f80093702c 100644 --- a/public/language/bg/topic.json +++ b/public/language/bg/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 направи препратка към тази тема на %3", "user-forked-topic-ago": "%1 раздели тази тема %3", "user-forked-topic-on": "%1 раздели тази тема на %3", + "user-crossposted-topic-ago": "%1 публикува тази тема допълнително и в %2 %3", + "user-crossposted-topic-on": "%1 публикува тази тема допълнително и в %2 на %3", "bookmark-instructions": "Щракнете тук, за да се върнете към последно прочетената публикация в тази тема.", "flag-post": "Докладване на тази публикация", "flag-user": "Докладване на този потребител", @@ -103,6 +105,7 @@ "thread-tools.lock": "Заключване на темата", "thread-tools.unlock": "Отключване на темата", "thread-tools.move": "Преместване на темата", + "thread-tools.crosspost": "Допълнително публикуване на темата", "thread-tools.move-posts": "Преместване на публикациите", "thread-tools.move-all": "Преместване на всички", "thread-tools.change-owner": "Промяна на собственика", @@ -132,6 +135,7 @@ "pin-modal-help": "Ако желаете, тук можете да посочите дата на давност за закачените теми. Можете и да оставите полето празно, при което темата ще остане закачена, докато не бъде откачена ръчно.", "load-categories": "Зареждане на категориите", "confirm-move": "Преместване", + "confirm-crosspost": "Допълнително публикуване", "confirm-fork": "Разделяне", "bookmark": "Отметка", "bookmarks": "Отметки", @@ -141,6 +145,7 @@ "loading-more-posts": "Зареждане на още публикации", "move-topic": "Преместване на темата", "move-topics": "Преместване на темите", + "crosspost-topic": "Допълнително публикуване на темата", "move-post": "Преместване на публикацията", "post-moved": "Публикацията беше преместена!", "fork-topic": "Разделяне на темата", @@ -163,6 +168,9 @@ "move-topic-instruction": "Изберете целевата категория и натиснете „Преместване“", "change-owner-instruction": "Натиснете публикациите, които искате да прехвърлите на друг потребител", "manage-editors-instruction": "Определете потребителите, които могат да редактират тази публикация по-долу.", + "crossposts.instructions": "Изберете една или повече категории, в които да публикувате. Темата или темите ще бъдат достъпни както от първоначалната категория, така и от всички допълнителни категории.", + "crossposts.listing": "Тази теме е била допълнително публикувана в следните локални категории:", + "crossposts.none": "Тази тема не е била допълнително публикувана в никакви други категории.", "composer.title-placeholder": "Въведете заглавието на темата си тук...", "composer.handle-placeholder": "Въведете името тук", "composer.hide": "Скриване", @@ -174,6 +182,7 @@ "composer.replying-to": "Отговор на %1", "composer.new-topic": "Нова тема", "composer.editing-in": "Редактиране на публикация в %1", + "composer.untitled-topic": "Тема без име", "composer.uploading": "качване...", "composer.thumb-url-label": "Поставете адреса на иконка за темата", "composer.thumb-title": "Добавете иконка към тази тема", @@ -224,5 +233,8 @@ "unread-posts-link": "Връзка към непрочетените публикации", "thumb-image": "Иконка на темата", "announcers": "Споделяния", - "announcers-x": "Споделяния (%1)" + "announcers-x": "Споделяния (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/bg/user.json b/public/language/bg/user.json index ef54ce2c7a..aed29f1423 100644 --- a/public/language/bg/user.json +++ b/public/language/bg/user.json @@ -105,6 +105,10 @@ "show-email": "Да се показва е-пощата ми", "show-fullname": "Да се показва цялото ми име", "restrict-chats": "Разрешаване на съобщенията само от потребители, които следвам", + "disable-incoming-chats": "Забраняване на входящите съобщения ", + "chat-allow-list": "Разрешаване на съобщенията от следните потребители", + "chat-deny-list": "Забраняване на съобщенията от следните потребители", + "chat-list-add-user": "Добавяне на потребител", "digest-label": "Абониране за резюмета", "digest-description": "Абониране за новини по е-пощата относно този форум (нови известия и теми) според избрания график", "digest-off": "Изключено", diff --git a/public/language/bg/world.json b/public/language/bg/world.json index b3e836a4d2..9da78356ee 100644 --- a/public/language/bg/world.json +++ b/public/language/bg/world.json @@ -1,7 +1,12 @@ { "name": "Свят", - "popular": "Популярни теми", - "recent": "Всички теми", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Помощ", "help.title": "Каква е тази страница?", @@ -14,5 +19,7 @@ "onboard.title": "Вашият прозорец към федивселената…", "onboard.what": "Това е Вашата персонализирана категория съставена само от съдържание извън този форум. Тук се появяват неща от хора, които следвате, както и такива споделени от тях.", "onboard.why": "Много неща се случват извън този форум, и не всичко отговаря на Вашите интереси. Затова следването на конкретни хора е най-добрият начин да покажете, че искате да виждате повече от тях.", - "onboard.how": "Междувременно можете да използвате бутоните в горната част, за да видите до какво има достъп този форум. Така може да започнете да откривате ново съдържание!" + "onboard.how": "Междувременно можете да използвате бутоните в горната част, за да видите до какво има достъп този форум. Така може да започнете да откривате ново съдържание!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/bn/admin/advanced/cache.json b/public/language/bn/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/bn/admin/advanced/cache.json +++ b/public/language/bn/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/bn/admin/dashboard.json b/public/language/bn/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/bn/admin/dashboard.json +++ b/public/language/bn/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/bn/admin/development/info.json b/public/language/bn/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/bn/admin/development/info.json +++ b/public/language/bn/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/bn/admin/manage/categories.json +++ b/public/language/bn/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/bn/admin/manage/custom-reasons.json b/public/language/bn/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/bn/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/bn/admin/manage/privileges.json b/public/language/bn/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/bn/admin/manage/privileges.json +++ b/public/language/bn/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/bn/admin/manage/users.json b/public/language/bn/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/bn/admin/manage/users.json +++ b/public/language/bn/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/bn/admin/settings/activitypub.json b/public/language/bn/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/bn/admin/settings/activitypub.json +++ b/public/language/bn/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/bn/admin/settings/chat.json b/public/language/bn/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/bn/admin/settings/chat.json +++ b/public/language/bn/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/bn/admin/settings/email.json b/public/language/bn/admin/settings/email.json index 9930054dc8..1e023cb050 100644 --- a/public/language/bn/admin/settings/email.json +++ b/public/language/bn/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "ইমেইল ডাইজেস্ট", "subscriptions.disable": "ইমেইল ডাইজেস্ট নিষ্ক্রিয়", "subscriptions.hour": "Digest Hour", diff --git a/public/language/bn/admin/settings/notifications.json b/public/language/bn/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/bn/admin/settings/notifications.json +++ b/public/language/bn/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/bn/admin/settings/uploads.json b/public/language/bn/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/bn/admin/settings/uploads.json +++ b/public/language/bn/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/bn/admin/settings/user.json b/public/language/bn/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/bn/admin/settings/user.json +++ b/public/language/bn/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/bn/admin/settings/web-crawler.json b/public/language/bn/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/bn/admin/settings/web-crawler.json +++ b/public/language/bn/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/bn/aria.json b/public/language/bn/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/bn/aria.json +++ b/public/language/bn/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/bn/category.json b/public/language/bn/category.json index bf67b89b1b..4a187f5e92 100644 --- a/public/language/bn/category.json +++ b/public/language/bn/category.json @@ -1,12 +1,13 @@ { "category": "বিভাগ", "subcategories": "উপবিভাগ", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "নতুন টপিক", "guest-login-post": "উত্তর দিতে লগিন করুন", "no-topics": "এই বিভাগে কোন আলোচনা নেই!
আপনি চাইলে নতুন আলোচনা শুরু করতে পারেন।", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "ব্রাউজিং", "no-replies": "কোন রিপ্লাই নেই", "no-new-posts": "নতুন কোন পোস্ট নাই", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 28bb5d5e66..db8e0db89c 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "আপনি লগিন করেননি", "account-locked": "আপনার অ্যাকাউন্ট সাময়িকভাবে লক করা হয়েছে", "search-requires-login": "Searching requires an account - please login or register.", @@ -146,6 +147,7 @@ "post-already-restored": "এই পোষ্টটি ইতিমধ্যে পুনরোদ্ধার করা হয়েছে", "topic-already-deleted": "এই টপিকটি ইতিমধ্যে ডিলিট করা হয়েছে", "topic-already-restored": "এই টপিকটি ইতিমধ্যে পুনরোদ্ধার করা হয়েছে", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "টপিক থাম্বনেল নিষ্ক্রিয় করা।", "invalid-file": "ভুল ফাইল", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "আপনি নিজের সাথে চ্যাট করতে পারবেন না!", "chat-restricted": "এই সদস্য তার বার্তালাপ সংরক্ষিত রেখেছেন। এই সদস্য আপনাকে ফলো করার পরই কেবলমাত্র আপনি তার সাথে চ্যাট করতে পারবেন", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/bn/global.json b/public/language/bn/global.json index 3b414f779c..09da03532a 100644 --- a/public/language/bn/global.json +++ b/public/language/bn/global.json @@ -68,6 +68,7 @@ "users": "ব্যবহারকারীগণ", "topics": "টপিক", "posts": "পোস্টগুলি", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "দেখেছেন", "posters": "Posters", + "watching": "Watching", "reputation": "সন্মাননা", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/bn/groups.json b/public/language/bn/groups.json index a9da47caac..5f41e521d0 100644 --- a/public/language/bn/groups.json +++ b/public/language/bn/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "গ্রুপসমূহ", "members": "Members", + "x-members": "%1 member(s)", "view-group": "গ্রুপ দেখুন", "owner": "Group Owner", "new-group": "Create New Group", diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json index d010f1ad37..b6a25b38bc 100644 --- a/public/language/bn/modules.json +++ b/public/language/bn/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 বলেছেন %2:", - "composer.user-said": "%1 বলেছেনঃ", + "composer.user-said": "%1 [said](%2):", "composer.discard": "আপনি কি নিশ্চিত যে আপনি এই পোস্ট বাতিল করতে ইচ্ছুক?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/bn/notifications.json b/public/language/bn/notifications.json index 6ee1d8b887..0386dc6670 100644 --- a/public/language/bn/notifications.json +++ b/public/language/bn/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "%1 থেকে নতুন বার্তা", "new-messages-from": "%1 new messages from %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 , %2 এ আপানার পোষ্টকে আপভোট করেছেন।", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", "user-flagged-post-in": "%1 flagged a post in %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 একটি উত্তর দিয়েছেন: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 আপনাকে অনুসরন করা শুরু করেছেন।", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "ইমেইল নিশ্চিত করা হয়েছে", "email-confirmed-message": "আপনার ইমেইল যাচাই করার জন্য আপনাকে ধন্যবাদ। আপনার অ্যাকাউন্টটি এখন সম্পূর্ণরূপে সক্রিয়।", "email-confirm-error-message": "আপনার ইমেল ঠিকানার বৈধতা যাচাইয়ে একটি সমস্যা হয়েছে। সম্ভবত কোডটি ভুল ছিল অথবা কোডের মেয়াদ শেষ হয়ে গিয়েছে।", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "নিশ্চিতকরণ ইমেইল পাঠানো হয়েছে।", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/bn/social.json b/public/language/bn/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/bn/social.json +++ b/public/language/bn/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/bn/themes/harmony.json b/public/language/bn/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/bn/themes/harmony.json +++ b/public/language/bn/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/bn/topic.json b/public/language/bn/topic.json index 6a89846c81..34f86c06f3 100644 --- a/public/language/bn/topic.json +++ b/public/language/bn/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "টপিক বন্ধ করুন", "thread-tools.unlock": "টপিক খুলে দিন", "thread-tools.move": "টপিক সরান", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "সমস্ত টপিক সরান", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "ক্যাটাগরী লোড করা হচ্ছে", "confirm-move": "সরান", + "confirm-crosspost": "Cross-post", "confirm-fork": "ফর্ক", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "আরো পোষ্ট লোড করা হচ্ছে", "move-topic": "টপিক সরান", "move-topics": "টপিক সমূহ সরান", + "crosspost-topic": "Cross-post Topic", "move-post": "পোষ্ট সরান", "post-moved": "পোষ্ট সরানো হয়েছে", "fork-topic": "টপিক ফর্ক করুন", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "আপনার টপিকের শিরোনাম দিন", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "%1 এর উত্তরে:", "composer.new-topic": "নতুন টপিক", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "আপলোডিং", "composer.thumb-url-label": "টপিকে থাম্বনেইল URL পেষ্ট করুন", "composer.thumb-title": "এই টপিকে থাম্বনেইল যোগ করুন", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/bn/user.json b/public/language/bn/user.json index bc0967f84c..ca2ca4bebb 100644 --- a/public/language/bn/user.json +++ b/public/language/bn/user.json @@ -105,6 +105,10 @@ "show-email": "আমার ইমেইল দেখাও", "show-fullname": "আমার সম্পূর্ণ নাম দেখাও", "restrict-chats": "আমি যাদের ফলো করি কেবলমাত্র তাদের থেকে বার্তা গ্রহন করা হোক", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "ডাইজেষ্টে সাবস্ক্রাইব করুন", "digest-description": "শিডিউল অনূযায়ী এই ফোরামের ইমেইল আপডেটের জন্য সাবস্ক্রাইব করুন (নতুন নোটিফিকেশন এবং টপিকসমূহ )", "digest-off": "বন্ধ", diff --git a/public/language/bn/world.json b/public/language/bn/world.json index 3753335278..e6694bf507 100644 --- a/public/language/bn/world.json +++ b/public/language/bn/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/cs/admin/advanced/cache.json b/public/language/cs/admin/advanced/cache.json index 67524bb9a6..b63b1f8548 100644 --- a/public/language/cs/admin/advanced/cache.json +++ b/public/language/cs/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Mezipaměť příspěvku", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% plný", "post-cache-size": "Velikost mezipaměti příspěvku", "items-in-cache": "Položek v mezipaměti" diff --git a/public/language/cs/admin/dashboard.json b/public/language/cs/admin/dashboard.json index ad5fd7cd94..7fccda35b3 100644 --- a/public/language/cs/admin/dashboard.json +++ b/public/language/cs/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Zobrazených stránek/registrovaní", "graphs.page-views-guest": "Zobrazených stránek/hosté", "graphs.page-views-bot": "Zobrazených stránek/bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Jedineční návštěvníci", "graphs.registered-users": "Registrovaní uživatelé", "graphs.guest-users": "Guest Users", diff --git a/public/language/cs/admin/development/info.json b/public/language/cs/admin/development/info.json index 19fb830e9d..80dc3e265d 100644 --- a/public/language/cs/admin/development/info.json +++ b/public/language/cs/admin/development/info.json @@ -3,12 +3,12 @@ "ip": "IP %1", "nodes-responded": "%1 vazeb odpovědělo během %2ms.", "host": "host", - "primary": "primary / jobs", + "primary": "primární / práce", "pid": "pid", "nodejs": "nodejs", "online": "připojen", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", @@ -19,7 +19,7 @@ "registered": "Registrován", "sockets": "Sockety", - "connection-count": "Connection Count", + "connection-count": "Počet připojení", "guests": "Hosté", "info": "Informace" diff --git a/public/language/cs/admin/manage/categories.json b/public/language/cs/admin/manage/categories.json index 6095eb1293..0a8a39620d 100644 --- a/public/language/cs/admin/manage/categories.json +++ b/public/language/cs/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Nastavení kategorie", "edit-category": "Edit Category", "privileges": "Oprávnění", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Název kategorie", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Popis kategorie", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Barva pozadí", "text-color": "Barva textu", "bg-image-size": "Velikost obrázku pozadí", @@ -103,6 +107,11 @@ "alert.create-success": "Kategorie byla úspěšně vytvořena.", "alert.none-active": "Nemáte žádné aktivní kategorie.", "alert.create": "Vytvořit kategorii", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Opravdu chcete vyčistit tuto kategorii \"%1\"?

UpozorněníVšechny témata a příspěvky v této kategorii budou smazána.

Smazání kategorie vyjme všechny témata a příspěvky a odstraní kategorii z databáze. Pokud chcete vyjmout kategorii dočasně, raději místo toho kategorii „zakažte”.

", "alert.purge-success": "Kategorie byla vyčištěna.", "alert.copy-success": "Nastavení bylo zkopírováno.", diff --git a/public/language/cs/admin/manage/custom-reasons.json b/public/language/cs/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/cs/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/cs/admin/manage/privileges.json b/public/language/cs/admin/manage/privileges.json index 46ac974a28..c0a8bf453b 100644 --- a/public/language/cs/admin/manage/privileges.json +++ b/public/language/cs/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Přístup k tématům", "create-topics": "Vytvořit téma", "reply-to-topics": "Odpovědět na téma", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Označit téma", "edit-posts": "Upravit příspěvek", diff --git a/public/language/cs/admin/manage/users.json b/public/language/cs/admin/manage/users.json index d335fd4a6f..e7fe1377ac 100644 --- a/public/language/cs/admin/manage/users.json +++ b/public/language/cs/admin/manage/users.json @@ -22,7 +22,8 @@ "delete-content": "Odstranit Obsah uživatele", "purge": "Odstranit uživatele a obsah", "download-csv": "Stáhnout jako CSV", - "custom-user-fields": "Custom User Fields", + "custom-user-fields": "Vlastní uživatelská pole", + "custom-reasons": "Custom Reasons", "manage-groups": "Spravovat skupiny", "set-reputation": "Set Reputation", "add-group": "Přidat skupinu", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Důvod (volitelné)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Hodiny", "temp-ban.days": "Dny", "temp-ban.explanation": "Zadejte délku trvání pro zákaz. Nezapomeňte, že 0 je považována jako trvalý zákaz.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Opravdu chcete trvale zakázat tohoto uživatele?", "alerts.confirm-ban-multi": "Opravdu chcete trvale zakázat tyto uživatele?", @@ -123,26 +126,26 @@ "alerts.x-users-found": "%1 user(s) found, (%2 seconds)", "alerts.select-a-single-user-to-change-email": "Select a single user to change email", "export": "Export", - "export-users-fields-title": "Select CSV Fields", + "export-users-fields-title": "Vybrat CSV pole", "export-field-email": "Email", - "export-field-username": "Username", + "export-field-username": "Uživatelské jméno", "export-field-uid": "UID", "export-field-ip": "IP", - "export-field-joindate": "Join date", - "export-field-lastonline": "Last Online", - "export-field-lastposttime": "Last Post Time", - "export-field-reputation": "Reputation", - "export-field-postcount": "Post Count", - "export-field-topiccount": "Topic Count", - "export-field-profileviews": "Profile Views", - "export-field-followercount": "Follower Count", - "export-field-followingcount": "Following Count", - "export-field-fullname": "Full Name", - "export-field-website": "Website", - "export-field-location": "Location", - "export-field-birthday": "Birthday", - "export-field-signature": "Signature", - "export-field-aboutme": "About Me", + "export-field-joindate": "Datum registrace", + "export-field-lastonline": "Naposledy online", + "export-field-lastposttime": "Čas posledního příspěvku", + "export-field-reputation": "Reputace", + "export-field-postcount": "Počet příspěvků", + "export-field-topiccount": "Počet témat", + "export-field-profileviews": "Počet zobrazení profilu", + "export-field-followercount": "Počet sledujících", + "export-field-followingcount": "Počet sledovaných", + "export-field-fullname": "Celé jméno", + "export-field-website": "Webová stránka", + "export-field-location": "Poloha", + "export-field-birthday": "Narozeniny", + "export-field-signature": "Podpis", + "export-field-aboutme": "O mně", "export-users-started": "Exporting users as csv, this might take a while. You will receive a notification when it is complete.", "export-users-completed": "Users exported as csv, click here to download.", diff --git a/public/language/cs/admin/menu.json b/public/language/cs/admin/menu.json index a7adf69e7f..9c04206f3f 100644 --- a/public/language/cs/admin/menu.json +++ b/public/language/cs/admin/menu.json @@ -10,7 +10,7 @@ "section-manage": "Spravovat", "manage/categories": "Kategorie", "manage/privileges": "Oprávnění", - "manage/tags": "Značky", + "manage/tags": "Štítky", "manage/users": "Uživatelé", "manage/admins-mods": "Správci a moderátoři", "manage/registration": "Registrační fronta", @@ -35,7 +35,7 @@ "settings/post": "Posts", "settings/chat": "Chats", "settings/pagination": "Stránkování", - "settings/tags": "Značky", + "settings/tags": "Štítky", "settings/notifications": "Oznámení", "settings/api": "API Access", "settings/activitypub": "Federation (ActivityPub)", diff --git a/public/language/cs/admin/settings/activitypub.json b/public/language/cs/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/cs/admin/settings/activitypub.json +++ b/public/language/cs/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/cs/admin/settings/advanced.json b/public/language/cs/admin/settings/advanced.json index 6f65a2bc45..608e14ba8f 100644 --- a/public/language/cs/admin/settings/advanced.json +++ b/public/language/cs/admin/settings/advanced.json @@ -38,7 +38,7 @@ "sockets.settings": "WebSocket Settings", "sockets.max-attempts": "Max Reconnection Attempts", - "sockets.default-placeholder": "Default: %1", + "sockets.default-placeholder": "Výchozí: %1", "sockets.delay": "Reconnection Delay", "compression.settings": "Compression Settings", diff --git a/public/language/cs/admin/settings/chat.json b/public/language/cs/admin/settings/chat.json index 206d2b753f..fa5dcdee57 100644 --- a/public/language/cs/admin/settings/chat.json +++ b/public/language/cs/admin/settings/chat.json @@ -5,13 +5,11 @@ "disable-editing": "Zakázat upravení/odstranění konverzační zprávy", "disable-editing-help": "Správci a globální moderátoři jsou vyjmuti z tohoto omezení", "max-length": "Maximální délka konverzační zprávy", - "max-length-remote": "Maximum length of remote chat messages", - "max-length-remote-help": "This value is usually set higher than the chat message maximum for local users as remote messages tend to be longer (with @ mentions, etc.)", + "max-length-remote": "Maximální délka zpráv ve vzdáleném chatu", + "max-length-remote-help": "Tato hodnota je obvykle nastavena vyšší než maximální délka zpráv pro místní uživatele, protože vzdálené zprávy bývají delší (např. s @ tagem apod.).", "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximální počet uživatelů v konverzační místnosti", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/cs/admin/settings/email.json b/public/language/cs/admin/settings/email.json index 96f3e8d545..0cf6ffc795 100644 --- a/public/language/cs/admin/settings/email.json +++ b/public/language/cs/admin/settings/email.json @@ -28,16 +28,22 @@ "smtp-transport.password": "Heslo", "smtp-transport.pool": "Enable pooled connections", "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", - "smtp-transport.allow-self-signed": "Allow self-signed certificates", - "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.allow-self-signed": "Povolit self-signed certifikáty", + "smtp-transport.allow-self-signed-help": "Povolením tohoto nastavení budete moci používat self-signed nebo neplatné TLS certifikáty.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Upravit šablonu e-mailu", "template.select": "Vybrat šablonu e-mailu", "template.revert": "Zpět k původnímu", + "test-smtp-settings": "Test SMTP Settings", "testing": "Test e-mailu", + "testing.success": "Test Email Sent.", "testing.select": "Vyberte šablonu e-mailu", "testing.send": "Odeslat testovací e-mail", - "testing.send-help": "Testovací e-mail bude odeslán aktuálně přihlášenému uživateli na jeho e-mailovou adresu z registrace.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "E-mailové odběry", "subscriptions.disable": "Zakázat e-mailové odběry", "subscriptions.hour": "Hodina přehledu", diff --git a/public/language/cs/admin/settings/notifications.json b/public/language/cs/admin/settings/notifications.json index 83e73d288e..b66ef3c6e2 100644 --- a/public/language/cs/admin/settings/notifications.json +++ b/public/language/cs/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Uvítání", "welcome-notification-link": "Odkaz na uvítání", "welcome-notification-uid": "Uvítání uživatele (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/cs/admin/settings/post.json b/public/language/cs/admin/settings/post.json index b0627af793..5b757e31de 100644 --- a/public/language/cs/admin/settings/post.json +++ b/public/language/cs/admin/settings/post.json @@ -4,11 +4,11 @@ "sorting.post-default": "Výchozí třídění příspěvků", "sorting.oldest-to-newest": "Od nejstarších po nejnovější", "sorting.newest-to-oldest": "Od nejnovějších po nejstarší", - "sorting.recently-replied": "Recently Replied", + "sorting.recently-replied": "Poslední příspěvky", "sorting.recently-created": "Recently Created", "sorting.most-votes": "Dle počtu hlasů", "sorting.most-posts": "Dle počtu příspěvků", - "sorting.most-views": "Most Views", + "sorting.most-views": "Nejvíce zobrazení", "sorting.topic-default": "Výchozí třídění tématu", "length": "Délka příspěvku", "post-queue": "Příspěvky ve frontě", diff --git a/public/language/cs/admin/settings/uploads.json b/public/language/cs/admin/settings/uploads.json index dea6aa44df..e95989dcd1 100644 --- a/public/language/cs/admin/settings/uploads.json +++ b/public/language/cs/admin/settings/uploads.json @@ -9,10 +9,10 @@ "private-extensions": "Přípona souborů je soukromá", "private-uploads-extensions-help": "Pro nastavení soukromí, zde zadejte seznam souborů oddělený čárkou (tj. pdf, xls,doc). prázdný seznam znamená, že všechny soubory jsou soukromé.", "resize-image-width-threshold": "Změnit velikost obrázků, jsou-li širší než určená šířka", - "resize-image-width-threshold-help": "(in pixels, default: 2000 pixels, set to 0 to disable)", + "resize-image-width-threshold-help": "(v pixelech, výchozí: 2000 pixelů, nastavte na 0 pro vypnutí)", "resize-image-width": "Změnit velikost obrázků na určenou šířku", "resize-image-width-help": "(v pixelech, výchozí: 760 pixelů, pro zakázání - nastavte 0)", - "resize-image-keep-original": "Keep original image after resize", + "resize-image-keep-original": "Ponechat původní obrázek po změně velikosti", "resize-image-quality": "Kvalita při změně velikosti obrázků", "resize-image-quality-help": "Pro snížení velikosti zmenšených obrázků použijte nižší nastavení kvality.", "max-file-size": "Maximální velikost souboru (v KiB)", @@ -21,7 +21,13 @@ "reject-image-width-help": "Širší obrázek než tato hodnota bude zamítnut.", "reject-image-height": "Maximální výška obrázku (v pixelech)", "reject-image-height-help": "Vyšší obrázek než tato hodnota bude zamítnut.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Povolit uživatelům nahrát miniatury témat", + "show-post-uploads-as-thumbnails": "Zobrazovat nahrané příspěvky jako miniatury", "topic-thumb-size": "Velikost miniatury tématu", "allowed-file-extensions": "Povolené přípony souborů", "allowed-file-extensions-help": "Zadejte seznam přípon souborů oddělených čárkou (např.: pdf, xls, doc). Prázdný seznam znamená, že všechny přípony jsou povoleny.", diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json index 645eaa4088..ec85df9d73 100644 --- a/public/language/cs/admin/settings/user.json +++ b/public/language/cs/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Zobrazit e-mail", "show-fullname": "Zobrazit celé jméno", "restrict-chat": "Povolit chatové zprávy jen od uživatelů, které sleduji", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Otevřít odchozí odkazy v nové záložce", "topic-search": "Povolit hledání v tématu", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/cs/admin/settings/web-crawler.json b/public/language/cs/admin/settings/web-crawler.json index a10912b938..ca84118faa 100644 --- a/public/language/cs/admin/settings/web-crawler.json +++ b/public/language/cs/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Zakázat zdroje RSS", "disable-sitemap-xml": "Zakázat Sitemap.xml", "sitemap-topics": "Počet témat zobrazených na mapě stránky", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Smazat mezipaměť mapy stránky", "view-sitemap": "Zobrazit mapu stránky" } \ No newline at end of file diff --git a/public/language/cs/aria.json b/public/language/cs/aria.json index 8e2c565c82..00d51a79f8 100644 --- a/public/language/cs/aria.json +++ b/public/language/cs/aria.json @@ -1,9 +1,10 @@ { - "post-sort-option": "Post sort option, %1", - "topic-sort-option": "Topic sort option, %1", - "user-avatar-for": "User avatar for %1", - "profile-page-for": "Profile page for user %1", - "user-watched-tags": "User watched tags", - "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "post-sort-option": "Možnost řazení příspěvků, %1", + "topic-sort-option": "Možnost řazení témat, %1", + "user-avatar-for": "Uživatelský avatar pro %1", + "profile-page-for": "Profilová stránka uživatele %1", + "user-watched-tags": "Štítky sledované uživatelem", + "delete-upload-button": "Tlačítko pro smazání nahraného souboru", + "group-page-link-for": "Odkaz na stránku skupiny pro %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/cs/category.json b/public/language/cs/category.json index 6787a5359d..ebe971aa3c 100644 --- a/public/language/cs/category.json +++ b/public/language/cs/category.json @@ -1,27 +1,28 @@ { "category": "Kategorie", "subcategories": "Podkategorie", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", - "handle.description": "This category can be followed from the open social web via the handle %1", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", + "handle.description": "Tuto kategorii lze sledovat z otevřeného sociálního webu pomocí identifikátoru %1.", "new-topic-button": "Nové téma", "guest-login-post": "Přihlásit se pro přispívání", "no-topics": "V této kategorii zatím nejsou žádné příspěvky.
Můžeš být první.", + "no-followers": "Nikdo na tomto webu tuto kategorii nesleduje. Začněte sledovat tuto kategorii, abyste začali dostávat aktualizace.", "browsing": "prohlíží", "no-replies": "Nikdo ještě neodpověděl", "no-new-posts": "Žádné nové příspěvky", "watch": "Sledovat", "ignore": "Ignorovat", "watching": "Sledováno", - "tracking": "Tracking", + "tracking": "Sledování", "not-watching": "Nesledováno", "ignoring": "Ignorováno", - "watching.description": "Notify me of new topics.
Show topics in unread & recent", - "tracking.description": "Shows topics in unread & recent", + "watching.description": "Upozorňovat mě na nová témata.
Zobrazovat témata v nepřečtených a nedávných", + "tracking.description": "Zobrazovat témata v nepřečtených a nedávných", "not-watching.description": "Nezobrazovat témata v nepřečtených, zobrazit poslední", - "ignoring.description": "Do not show topics in unread & recent", + "ignoring.description": "Nezobrazovat témata v nepřečtených a nedávných", "watching.message": "Nyní sledujete aktualizace pro tuto kategorii a všech podkategorii", - "tracking.message": "You are now tracking updates from this category and all subcategories", + "tracking.message": "Nyní sledujete aktualizace z této kategorie a všech jejích podkategorií.", "notwatching.message": "Nyní nesledujete aktualizace z této kategorie a všech podkategorií", "ignoring.message": "Nyní ignorujete aktualizace této kategorie a všech jejich kategorii", "watched-categories": "Sledované kategorie", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 7bb9b2a106..abd7dc9680 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -3,6 +3,7 @@ "invalid-json": "Neplatný JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Síťové požadavky na vyhrazené IP rozsahy nejsou povoleny.", "not-logged-in": "Zdá se, že nejste přihlášen/a", "account-locked": "Váš účet byl dočasně uzamknut", "search-requires-login": "Pro hledání je vyžadován účet – přihlaste se nebo zaregistrujte.", @@ -32,7 +33,7 @@ "folder-exists": "Folder exists", "invalid-pagination-value": "Neplatná hodnota stránkování, musí být alespoň %1 a nejvýše %2", "username-taken": "Uživatelské jméno je již použito", - "email-taken": "Email address is already taken.", + "email-taken": "E-mailová adresa je již použita.", "email-nochange": "The email entered is the same as the email already on file.", "email-invited": "Email was already invited", "email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.", @@ -67,8 +68,8 @@ "no-chat-room": "Chat room does not exist", "no-privileges": "Na tuto akci nemáte dostatečné oprávnění.", "category-disabled": "Kategorie zakázána", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Příspěvek smazán", + "topic-locked": "Téma zamčeno", "post-edit-duration-expired": "Je vám umožněno upravit příspěvky jen po %1 sekund/y od jeho vytvoření", "post-edit-duration-expired-minutes": "Je vám umožněno upravit příspěvky jen po %1 minut/y od jeho vytvoření", "post-edit-duration-expired-minutes-seconds": "Je vám umožněno upravit příspěvky jen po %1 minut/y a %2 sekund/y od jeho vytvoření", @@ -92,7 +93,7 @@ "category-not-selected": "Nebyla vybrána kategorie.", "too-many-posts": "Můžete přispívat jednou za %1 sekund - vyčkejte tedy, než vytvoříte další příspěvek", "too-many-posts-newbie": "Jako nový uživatel, můžete přispívat jednou za %1 sekund, dokud nezískáte pověst %2 - vyčkejte tedy, než vytvoříte další příspěvek", - "too-many-posts-newbie-minutes": "As a new user, you can only post once every %1 minute(s) until you have earned %2 reputation - please wait before posting again", + "too-many-posts-newbie-minutes": "Jako nový uživatel můžete psát pouze jednou každých %1 minut, dokud nezískáte %2 reputace – počkejte prosím před dalším příspěvkem.", "already-posting": "You are already posting", "tag-too-short": "Zadejte delší značku. Značky by měli mít alespoň %1 znaků", "tag-too-long": "Zadejte kratší značku. Značky nesmí být delší než %1 znaků", @@ -146,6 +147,7 @@ "post-already-restored": "Tento příspěvek byl již obnoven", "topic-already-deleted": "Toto téma bylo již odstraněno", "topic-already-restored": "Toto téma bylo již obnoveno", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Nemůžete vymazat hlavní příspěvek, místo toho odstraňte téma", "topic-thumbnails-are-disabled": "Miniatury témat jsou zakázány.", "invalid-file": "Neplatný soubor", @@ -154,7 +156,9 @@ "about-me-too-long": "Omlouváme se, ale \"O mně\" nesmí být delší než %1 znaků.", "cant-chat-with-yourself": "Nemůžete konverzovat sami se sebou.", "chat-restricted": "Tento uživatel má omezené konverzační zprávy. Nejdříve vás musí začít sledovat, než začnete spolu konverzovat", - "chat-user-blocked": "You have been blocked by this user.", + "chat-allow-list-user-already-added": "Tento uživatel je již na vašem seznamu povolených.", + "chat-deny-list-user-already-added": "Tento uživatel je již na vašem seznamu zakázaných.", + "chat-user-blocked": "Byli jste tímto uživatelem zablokováni.", "chat-disabled": "Konverzační systém zakázán", "too-many-messages": "Odeslal/a jste příliš mnoho zpráv, vyčkejte chvíli.", "invalid-chat-message": "Neplatná konverzační zpráva", @@ -169,7 +173,7 @@ "cant-add-users-to-chat-room": "Can't add users to chat room.", "cant-remove-users-from-chat-room": "Can't remove users from chat room.", "chat-room-name-too-long": "Chat room name too long. Names can't be longer than %1 characters.", - "remote-chat-received-too-long": "You received a chat message from %1, but it was too long and was rejected.", + "remote-chat-received-too-long": "Obdrželi jste zprávu z chatu od %1, ale byla příliš dlouhá a byla zamítnuta.", "already-voting-for-this-post": "Již jste v tomto příspěvku hlasoval.", "reputation-system-disabled": "Systém reputací je zakázán.", "downvoting-disabled": "Systém nesouhlasu je zakázán", @@ -183,22 +187,22 @@ "not-enough-reputation-min-rep-signature": "You need %1 reputation to add a signature", "not-enough-reputation-min-rep-profile-picture": "You need %1 reputation to add a profile picture", "not-enough-reputation-min-rep-cover-picture": "You need %1 reputation to add a cover picture", - "not-enough-reputation-custom-field": "You need %1 reputation for %2", - "custom-user-field-value-too-long": "Custom field value too long, %1", - "custom-user-field-select-value-invalid": "Custom field selected option is invalid, %1", - "custom-user-field-invalid-text": "Custom field text is invalid, %1", - "custom-user-field-invalid-link": "Custom field link is invalid, %1", - "custom-user-field-invalid-number": "Custom field number is invalid, %1", - "custom-user-field-invalid-date": "Custom field date is invalid, %1", - "invalid-custom-user-field": "Invalid custom user field, \"%1\" is already used by NodeBB", + "not-enough-reputation-custom-field": "Potřebujete %1 reputace pro %2.", + "custom-user-field-value-too-long": "Hodnota vlastního pole je příliš dlouhá, %1", + "custom-user-field-select-value-invalid": "Vybraná možnost vlastního pole je neplatná, %1", + "custom-user-field-invalid-text": "Text vlastního pole je neplatný, %1", + "custom-user-field-invalid-link": "Odkaz ve vlastním poli je neplatný, %1", + "custom-user-field-invalid-number": "Číslo ve vlastním poli je neplatné, %1", + "custom-user-field-invalid-date": "Datum ve vlastním poli je neplatné, %1", + "invalid-custom-user-field": "Neplatné vlastní uživatelské pole, „%1“ již používá NodeBB.", "post-already-flagged": "You have already flagged this post", "user-already-flagged": "You have already flagged this user", "post-flagged-too-many-times": "This post has been flagged by others already", "user-flagged-too-many-times": "This user has been flagged by others already", - "too-many-post-flags-per-day": "You can only flag %1 post(s) per day", - "too-many-user-flags-per-day": "You can only flag %1 user(s) per day", + "too-many-post-flags-per-day": "Můžete označit maximálně %1 příspěvek/příspěvky za den", + "too-many-user-flags-per-day": "Můžete označit maxilmálně %1 uživatele/uživatelů za den", "cant-flag-privileged": "You are not allowed to flag the profiles or content of privileged users (moderators/global moderators/admins)", - "cant-locate-flag-report": "Cannot locate flag report", + "cant-locate-flag-report": "Nelze najít hlášení o označení.", "self-vote": "U svého vlastního příspěvku nemůžete hlasovat", "too-many-upvotes-today": "You can only upvote %1 times a day", "too-many-upvotes-today-user": "You can only upvote a user %1 times a day", @@ -225,6 +229,7 @@ "no-topics-selected": "Žádná vybraná témata.", "cant-move-to-same-topic": "Není možné přesunout příspěvek do stejného tématu!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Nemůžete zablokovat sebe sama!", "cannot-block-privileged": "Nemůžete zablokovat správce nebo hlavní moderátory", "cannot-block-guest": "Hosté nemohou blokovat ostatní uživatele.", @@ -232,13 +237,14 @@ "already-unblocked": "Tento uživatel již byl odblokován", "no-connection": "Zdá se, že nastal problém s připojením k internetu", "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", - "invalid-plugin-id": "Invalid plugin ID", + "invalid-plugin-id": "Neplatné ID pluginu", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", - "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", + "cannot-toggle-system-plugin": "Nemůžete měnit stav systémového pluginu.", + "plugin-installation-via-acp-disabled": "Instalace pluginů přes ACP je zakázána", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", "topic-event-unrecognized": "Topic event '%1' unrecognized", - "category.handle-taken": "Category handle is already taken, please choose another.", + "category.handle-taken": "Identifikátor kategorie je již použit, vyberte prosím jiný.", "cant-set-child-as-parent": "Can't set child as parent category", "cant-set-self-as-parent": "Can't set self as parent category", "api.master-token-no-uid": "A master token was received without a corresponding `_uid` in the request body", @@ -246,17 +252,18 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", "api.501": "The route you are trying to call is not implemented yet, please try again tomorrow", "api.503": "The route you are trying to call is not currently available due to a server configuration", "api.reauth-required": "The resource you are trying to access requires (re-)authentication.", - "activitypub.not-enabled": "Federation is not enabled on this server", - "activitypub.invalid-id": "Unable to resolve the input id, likely as it is malformed.", - "activitypub.get-failed": "Unable to retrieve the specified resource.", - "activitypub.pubKey-not-found": "Unable to resolve public key, so payload verification cannot take place.", - "activitypub.origin-mismatch": "The received object's origin does not match the sender's origin", - "activitypub.actor-mismatch": "The received activity is being carried out by an actor that is different from expected.", - "activitypub.not-implemented": "The request was denied because it or an aspect of it is not implemented by the recipient server" + "activitypub.not-enabled": "Federace na tomto serveru není povolena", + "activitypub.invalid-id": "Nelze rozpoznat zadané ID, pravděpodobně je poškozené nebo nesprávně zformátované.", + "activitypub.get-failed": "Nelze získat zadaný zdroj.", + "activitypub.pubKey-not-found": "Nelze ověřit veřejný klíč, proto není možné provést ověření obsahu.", + "activitypub.origin-mismatch": "Původ přijatého objektu neodpovídá původu odesílatele", + "activitypub.actor-mismatch": "Přijatou aktivitu provedl někdo jiný, než bylo očekáváno.", + "activitypub.not-implemented": "Požadavek byl zamítnut, protože cílový server tuto funkci nepodporuje" } \ No newline at end of file diff --git a/public/language/cs/global.json b/public/language/cs/global.json index 11bef7ec67..a69cd6e1de 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -24,20 +24,20 @@ "cancel": "Cancel", "close": "Zrušit", "pagination": "Stránkování", - "pagination.previouspage": "Previous Page", - "pagination.nextpage": "Next Page", - "pagination.firstpage": "First Page", - "pagination.lastpage": "Last Page", + "pagination.previouspage": "Předchozí stránka", + "pagination.nextpage": "Další stránka", + "pagination.firstpage": "První stránka", + "pagination.lastpage": "Poslední stránka", "pagination.out-of": "%1 z %2", "pagination.enter-index": "Přejít na n-tý příspěvek", - "pagination.go-to-page": "Go to page", - "pagination.page-x": "Page %1", - "header.brand-logo": "Brand Logo", + "pagination.go-to-page": "Jít na stránku", + "pagination.page-x": "Stránka %1", + "header.brand-logo": "Logo značky", "header.admin": "Administrace", "header.categories": "Kategorie", "header.recent": "Nejnovější", "header.unread": "Nepřečtené", - "header.tags": "Značky", + "header.tags": "Štítky", "header.popular": "Populární", "header.top": "Nejlepší", "header.users": "Uživatelé", @@ -50,7 +50,7 @@ "header.navigation": "Navigace", "header.manage": "Manage", "header.drafts": "Drafts", - "header.world": "World", + "header.world": "Svět", "notifications.loading": "Načítání upozornění", "chats.loading": "Načítání chatů", "drafts.loading": "Loading Drafts", @@ -68,6 +68,7 @@ "users": "Uživatelé", "topics": "Témata", "posts": "Příspěvky", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Nesouhlasů", "views": "Zobrazení", "posters": "Přispěvatelé", + "watching": "Sleduji", "reputation": "Reputace", "lastpost": "Poslední příspěvek", "firstpost": "První příspěvek", @@ -111,7 +113,7 @@ "dnd": "Nevyrušovat", "invisible": "Neviditelný", "offline": "Offline", - "remote-user": "This user is from outside of this forum", + "remote-user": "Tento uživatel není z tohoto fóra", "email": "E-mail", "language": "Jazyk", "guest": "Host", @@ -142,12 +144,12 @@ "edited": "Upraveno", "disabled": "Nepovoleno", "select": "Vyberte", - "selected": "Selected", + "selected": "Vybráno", "copied": "Copied", "user-search-prompt": "Pro hledání uživatelů, zde pište...", "hidden": "Hidden", - "sort": "Sort", + "sort": "Řazení", "actions": "Actions", - "rss-feed": "RSS Feed", - "skip-to-content": "Skip to content" + "rss-feed": "RSS kanál", + "skip-to-content": "Přejít na obsah" } \ No newline at end of file diff --git a/public/language/cs/groups.json b/public/language/cs/groups.json index 2854f1a7d7..7b9dcb151c 100644 --- a/public/language/cs/groups.json +++ b/public/language/cs/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Skupiny", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Zobrazit skupinu", "owner": "Vlastník skupiny", "new-group": "Vytvořit novou skupinu", diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index f8d89bde37..4258f29635 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -1,9 +1,9 @@ { "chat.room-id": "Room %1", "chat.chatting-with": "Konverzace s", - "chat.placeholder": "Type chat message here, drag & drop images", - "chat.placeholder.mobile": "Type chat message", - "chat.placeholder.message-room": "Message #%1", + "chat.placeholder": "Zadejte zprávu do chatu, přetáhněte obrázky sem", + "chat.placeholder.mobile": "Zadejte zprávu do chatu", + "chat.placeholder.message-room": "Zpráva #%1", "chat.scroll-up-alert": "Go to most recent message", "chat.usernames-and-x-others": "%1 & %2 others", "chat.chat-with-usernames": "Chat with %1", @@ -16,8 +16,8 @@ "chat.user-typing-n": "%1, %2 and %3 others are typing ...", "chat.user-has-messaged-you": "%1 Vám napsal.", "chat.replying-to": "Replying to %1", - "chat.see-all": "All chats", - "chat.mark-all-read": "Mark all read", + "chat.see-all": "Všechny konverzace", + "chat.mark-all-read": "Označit vše jako přečtené", "chat.no-messages": "Vyberte příjemce k prohlédnutí historie zpráv.", "chat.no-users-in-room": "Žádní uživatelé v místnosti.", "chat.recent-chats": "Aktuální konverzace", @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Připojit/Opustit zprávy", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -69,8 +70,8 @@ "chat.in-room": "V této místnosti", "chat.kick": "Vykopnout", "chat.show-ip": "Zobrazit IP", - "chat.copy-text": "Copy Text", - "chat.copy-link": "Copy Link", + "chat.copy-text": "Kopírovat text", + "chat.copy-link": "Kopírovat odkaz", "chat.owner": "Majitel místnosti", "chat.grant-rescind-ownership": "Grant/Rescind Ownership", "chat.system.user-join": "%1 has joined the room ", @@ -79,32 +80,32 @@ "composer.compose": "Napsat", "composer.show-preview": "Ukázat náhled", "composer.hide-preview": "Skrýt náhled", - "composer.help": "Help", + "composer.help": "Nápověda", "composer.user-said-in": "%1 řekl v %2:", - "composer.user-said": "%1 řekl:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Jste si jisti, že chcete zrušit tento příspěvek?", "composer.submit-and-lock": "Potvrdit a uzamknout", "composer.toggle-dropdown": "Rozbalovací nabídka", "composer.uploading": "Nahrávám %1", "composer.formatting.bold": "Tučné", "composer.formatting.italic": "Kurzíva", - "composer.formatting.heading": "Heading", - "composer.formatting.heading1": "Heading 1", - "composer.formatting.heading2": "Heading 2", - "composer.formatting.heading3": "Heading 3", - "composer.formatting.heading4": "Heading 4", - "composer.formatting.heading5": "Heading 5", - "composer.formatting.heading6": "Heading 6", + "composer.formatting.heading": "Nadpis", + "composer.formatting.heading1": "Nadpis 1", + "composer.formatting.heading2": "Nadpis 2", + "composer.formatting.heading3": "Nadpis 3", + "composer.formatting.heading4": "Nadpis 4", + "composer.formatting.heading5": "Nadpis 5", + "composer.formatting.heading6": "Nadpis 6", "composer.formatting.list": "Seznam", "composer.formatting.strikethrough": "Přeškrtnutí", "composer.formatting.code": "Kód", "composer.formatting.link": "Odkaz", - "composer.formatting.picture": "Image Link", + "composer.formatting.picture": "Odkaz na obrázek", "composer.upload-picture": "Nahrát obrázek", "composer.upload-file": "Nahrát soubor", "composer.zen-mode": "Režim Zem", "composer.select-category": "Vyberte kategorii", - "composer.textarea.placeholder": "Enter your post content here, drag and drop images", + "composer.textarea.placeholder": "Sem vložte obsah příspěvku nebo přetáhněte obrázky", "composer.post-queue-alert": "Hello👋!
This forum uses a post queue system, since you are a new user your post will be hidden until it is approved by our moderation team.", "composer.schedule-for": "Schedule topic for", "composer.schedule-date": "Date", @@ -115,12 +116,12 @@ "composer.discard-all-drafts": "Discard all drafts", "composer.no-drafts": "You have no drafts", "composer.discard-draft-confirm": "Do you want to discard this draft?", - "composer.remote-pid-editing": "Editing a remote post", - "composer.remote-pid-content-immutable": "The content of remote posts cannot be edited. However, you are able change the topic title and tags.", + "composer.remote-pid-editing": "Úprava vzdáleného příspěvku", + "composer.remote-pid-content-immutable": "Obsah vzdálených příspěvků nelze upravovat. Můžete však změnit název tématu a štítky.", "bootbox.ok": "OK", "bootbox.cancel": "Zrušit", "bootbox.confirm": "Potvrdit", - "bootbox.submit": "Submit", + "bootbox.submit": "Odeslat", "bootbox.send": "Send", "cover.dragging-title": "Umístění fotografie", "cover.dragging-message": "Přesuňte fotku na požadovanou pozici a klikněte na „Uložit”", diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json index ed3838f865..c7b4f30009 100644 --- a/public/language/cs/notifications.json +++ b/public/language/cs/notifications.json @@ -12,51 +12,51 @@ "you-have-unread-notifications": "Máte nepřečtená upozornění.", "all": "Vše", "topics": "Témata", - "tags": "Tags", - "categories": "Categories", + "tags": "Štítky", + "categories": "Kategorie", "replies": "Odpovědi", "chat": "Konverzace", "group-chat": "Skupinová konverzace", - "public-chat": "Public Chats", + "public-chat": "Veřejné konverzace", "follows": "Sledování", "upvote": "Souhlasy", - "awards": "Awards", + "awards": "Odměny", "new-flags": "Nové označení", - "my-flags": "Označení přiřazené mě", + "my-flags": "My Flags", "bans": "Blokace", "new-message-from": "Nová zpráva od %1", - "new-messages-from": "%1 new messages from %2", - "new-message-in": "New message in %1", - "new-messages-in": "%1 new messages in %2", - "user-posted-in-public-room": "%1 wrote in %3", - "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", - "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", - "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 souhlasil s vaším příspěvkem v %2.", - "upvoted-your-post-in-dual": "%1 a %2 souhlasili s vaším příspěvkem v %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "new-messages-from": "%1 nových zpráv od %2", + "new-message-in": "Nová zpráva v %1", + "new-messages-in": "%1 nových zpráv v%2", + "user-posted-in-public-room": "%1 napsal do %3", + "user-posted-in-public-room-dual": "%1 a %2napsali do %4", + "user-posted-in-public-room-triple": "%1, %2 a %3 napsali do %5", + "user-posted-in-public-room-multiple": "%1, %2 a %3 dalších napsali do %5", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 přesunul váš příspěvek do %2", "moved-your-topic": "%1 přesunul %2", - "user-flagged-post-in": "%1 označil příspěvek v %2", - "user-flagged-post-in-dual": "%1 a %2 označil příspěvek v %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 označil uživatelský profil (%2)", "user-flagged-user-dual": "%1 a %2 označili uživatelský profil (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 odpověděl na: %2", - "user-posted-to-dual": "%1%2 odpověděli na: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 založil nové téma: %2", - "user-edited-post": "Příspěvek %2 byl upraven uživatelem %1", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 vás začal sledovat.", "user-started-following-you-dual": "%1 a %2 vás začali sledovat.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Seznam uživatelů v csv byl exportován, klikněte pro stažení", "post-queue-accepted": "Váš příspěvek byl akceptován. Klikněte zde pro jeho zobrazení.", "post-queue-rejected": "Váš příspěvek byl odmítnut.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mail potvrzen", "email-confirmed-message": "Děkujeme za ověření vaší e-mailové adresy. Váš účet je nyní aktivní.", "email-confirm-error-message": "Nastal problém s ověřením vaší e-mailové adresy. Kód je pravděpodobně neplatný nebo jeho platnost vypršela.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Ověřovací e-mail odeslán.", "none": "Nic", "notification-only": "Jen oznámení", @@ -83,14 +83,14 @@ "notification-and-email": "Oznámení a e-mail", "notificationType-upvote": "Jakmile někdo vyjádří souhlas s vaším příspěvkem", "notificationType-new-topic": "Jakmile někdo koho sledujete vytvoří nové téma", - "notificationType-new-topic-with-tag": "When a topic is posted with a tag you follow", - "notificationType-new-topic-in-category": "When a topic is posted in a category you are watching", + "notificationType-new-topic-with-tag": "Jakmile někdo vytvoří téma se štítkem, který sledujete", + "notificationType-new-topic-in-category": "Jakmile někdo vytvoří téma v kategorii, kterou sledujete", "notificationType-new-reply": "Jakmile je přidán nový příspěvek v tématu, které sledujete", "notificationType-post-edit": "Jakmile je upraven příspěvek v tématu, které sledujete", "notificationType-follow": "Jakmile vás někdo začne sledovat", "notificationType-new-chat": "Obdržíte-li novou konverzační zprávu", "notificationType-new-group-chat": "Když obdržíte zprávu ve skupinové konverzaci", - "notificationType-new-public-chat": "When you receive a public group chat message", + "notificationType-new-public-chat": "Jakmile obdržíte zprávu ve veřejné konverzaci", "notificationType-group-invite": "Obdržíte-li pozvání ke skupině", "notificationType-group-leave": "Když uživatel opustí Vaši skupinu", "notificationType-group-request-membership": "Jakmile někdo pošle žádost o připojení se do vaší skupiny", @@ -98,9 +98,9 @@ "notificationType-post-queue": "Bude-li přidán nový příspěvek do fronty", "notificationType-new-post-flag": "Bude-li příspěvek označen", "notificationType-new-user-flag": "Bude-li uživatel označen", - "notificationType-new-reward": "When you earn a new reward", - "activitypub.announce": "%1 shared your post in %2 to their followers.", - "activitypub.announce-dual": "%1 and %2 shared your post in %3 to their followers.", - "activitypub.announce-triple": "%1, %2 and %3 shared your post in %4 to their followers.", - "activitypub.announce-multiple": "%1, %2 and %3 others shared your post in %4 to their followers." + "notificationType-new-reward": "Když získáte novou odměnu", + "activitypub.announce": "%1nasdílel(a) váš příspěvek v %2 svým sledujícím.", + "activitypub.announce-dual": "%1 a %2 nasdíleli své příspěvky v %3 svým sledujícím.", + "activitypub.announce-triple": "%1, %2 a %3 nasdíleli váš příspěvek v %4 svým sledujícím.", + "activitypub.announce-multiple": "%1, %2 a %3 další nasdíleli váš příspěvek v %4 svým sledujícím." } \ No newline at end of file diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json index bcb7d69cc7..1d51c7ef47 100644 --- a/public/language/cs/pages.json +++ b/public/language/cs/pages.json @@ -23,7 +23,7 @@ "users/most-flags": "Nejoznačovanější uživatelé", "users/search": "Hledat uživatele", "notifications": "Upozornění", - "tags": "Značky", + "tags": "Štítky", "tag": "Témata označená "%1"", "register": "Zaregistrovat účet", "registration-complete": "Registrace dokončena", @@ -49,7 +49,7 @@ "account/topics": "Příspěvky vytvořeny uživatelem %1", "account/groups": "%1's skupiny", "account/watched-categories": "%1's sledovaných kategorii", - "account/watched-tags": "%1's Watched Tags", + "account/watched-tags": "%1's Sledované štítky", "account/bookmarks": "%1's zazáložkované příspěvky", "account/settings": "Uživatelské nastavení", "account/settings-of": "Changing settings of %1", diff --git a/public/language/cs/register.json b/public/language/cs/register.json index 356bcc7f2b..f0ccb8e441 100644 --- a/public/language/cs/register.json +++ b/public/language/cs/register.json @@ -21,9 +21,9 @@ "registration-added-to-queue": "Vaše registrace byla přidána do fronty. Obdržíte e-mail až ji správce schválí.", "registration-queue-average-time": "Our average time for approving memberships is %1 hours %2 minutes.", "registration-queue-auto-approve-time": "Your membership to this forum will be fully activated in up to %1 hours.", - "interstitial.intro": "We'd like some additional information in order to update your account…", - "interstitial.intro-new": "We'd like some additional information before we can create your account…", - "interstitial.errors-found": "Please review the entered information:", + "interstitial.intro": "Před vytvořením účtu vyžadujeme některé dodatečné informace.", + "interstitial.intro-new": "Před vytvořením účtu vyžadujeme některé dodatečné informace.", + "interstitial.errors-found": "Prosím zkontrolujte zadané údaje:", "gdpr-agree-data": "Dávám souhlas se sběrem a zpracováním mých osobních údajů na této webové stránce.", "gdpr-agree-email": "Dávám souhlas k dostávání e-mailových přehledů a oznámení z týkající se této webové stránky.", "gdpr-consent-denied": "Musíte dát souhlas této stránce sbírat/zpracovávat informace o vaší činnosti a odesílat vám e-maily.", diff --git a/public/language/cs/search.json b/public/language/cs/search.json index 02fa6a52c5..4a364aefa7 100644 --- a/public/language/cs/search.json +++ b/public/language/cs/search.json @@ -11,12 +11,12 @@ "in-categories": "In categories", "in-users": "In users", "in-tags": "In tags", - "categories": "Categories", + "categories": "Kategorie", "all-categories": "All categories", "categories-x": "Categories: %1", "categories-watched-categories": "Categories: Watched categories", "type-a-category": "Type a category", - "tags": "Tags", + "tags": "Štítky", "tags-x": "Tags: %1", "type-a-tag": "Type a tag", "match-words": "Shodná slova", diff --git a/public/language/cs/social.json b/public/language/cs/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/cs/social.json +++ b/public/language/cs/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/cs/tags.json b/public/language/cs/tags.json index dceab72efc..f6c6f0af11 100644 --- a/public/language/cs/tags.json +++ b/public/language/cs/tags.json @@ -2,9 +2,9 @@ "all-tags": "All tags", "no-tag-topics": "Není zde žádné téma s tímto označením.", "no-tags-found": "No tags found", - "tags": "Označení", + "tags": "Štítky", "enter-tags-here": "Enter tags, %1 - %2 characters.", - "enter-tags-here-short": "Zadejte označení…", + "enter-tags-here-short": "Zadejte štítky…", "no-tags": "Zatím tu není žádné označení.", "select-tags": "Select Tags", "tag-whitelist": "Tag Whitelist", diff --git a/public/language/cs/themes/harmony.json b/public/language/cs/themes/harmony.json index 727a1b0553..0d7d59019a 100644 --- a/public/language/cs/themes/harmony.json +++ b/public/language/cs/themes/harmony.json @@ -1,23 +1,25 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", - "sidebar-toggle": "Sidebar Toggle", - "login-register-to-search": "Login or register to search.", - "settings.title": "Theme settings", - "settings.enableQuickReply": "Enable quick reply", - "settings.enableBreadcrumbs": "Show breadcrumbs in Category and Topic pages", - "settings.enableBreadcrumbs.why": "Breadcrumbs are visible in most pages for ease-of-navigation. The base design of the category and topic pages has alternative means to link back to parent pages, but the breadcrumb can be toggled off to reduce clutter.", + "sidebar-toggle": "Přepínání postranního panelu", + "login-register-to-search": "Přihlaste se nebo zaregistrujte pro hledání.", + "settings.title": "Nastavení motivu", + "settings.enableQuickReply": "Povolit rychlou odpověď", + "settings.enableBreadcrumbs": "Zobrazovat navigační cestu na stránkách kategorií a témat", + "settings.enableBreadcrumbs.why": "Navigační cesta je zobrazena na většině stránek pro snadnější orientaci. Základní design stránek kategorií a témat nabízí i jiné způsoby, jak se vrátit na nadřazené stránky, ale navigační cestu lze vypnout, aby stránky působily přehledněji.", "settings.centerHeaderElements": "Center header elements", "settings.mobileTopicTeasers": "Show topic teasers on mobile", "settings.stickyToolbar": "Sticky toolbar", "settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page", - "settings.topicSidebarTools": "Topic sidebar tools", - "settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop", - "settings.autohideBottombar": "Auto hide mobile navigation bar", - "settings.autohideBottombar.help": "The mobile bar will be hidden when the page is scrolled down", - "settings.topMobilebar": "Move the mobile navigation bar to the top", - "settings.openSidebars": "Open sidebars", + "settings.topicSidebarTools": "Nástroje postranního panelu tématu", + "settings.topicSidebarTools.help": "Tato možnost přesune nástroje tématu do postranního panelu na ploše", + "settings.autohideBottombar": "Automaticky skrýt navigační lištu na mobilu", + "settings.autohideBottombar.help": "Navigační lišta na mobilu bude skrytá při posunu stránky dolů", + "settings.topMobilebar": "Přesunout navigační lištu na mobilu nahoru", + "settings.openSidebars": "Otevřít postranní panely", "settings.chatModals": "Enable chat modals" } \ No newline at end of file diff --git a/public/language/cs/themes/persona.json b/public/language/cs/themes/persona.json index e7d1945303..075fde6f3a 100644 --- a/public/language/cs/themes/persona.json +++ b/public/language/cs/themes/persona.json @@ -1,5 +1,5 @@ { - "settings.title": "Theme settings", + "settings.title": "Nastavení motivu", "settings.intro": "You can customise your theme settings here. Settings are stored on a per-device basis, so you are able to have different settings on different devices (phone, tablet, desktop, etc.)", "settings.mobile-menu-side": "Switch which side each mobile menu is on", "settings.autoHidingNavbar": "Automatically hide the navbar on scroll", diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index fd5c6a50d5..dd2e4da749 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -1,6 +1,6 @@ { "topic": "Téma", - "title": "Title", + "title": "Název", "no-topics-found": "Nebyla nalezena žádná témata.", "no-posts-found": "Nebyly nalezeny žádné příspěvky.", "post-is-deleted": "Tento příspěvek je vymazán.", @@ -15,60 +15,62 @@ "replies-to-this-post": "%1 odpovědí", "one-reply-to-this-post": "1 odpověď", "last-reply-time": "Poslední odpověď", - "reply-options": "Reply options", + "reply-options": "Možnosti odpovědi", "reply-as-topic": "Odpovědět jako Téma", "guest-login-reply": "Přihlásit se pro odpověď", "login-to-view": "Přihlásit se pro zobrazení", "edit": "Upravit", "delete": "Odstranit", - "delete-event": "Delete Event", - "delete-event-confirm": "Are you sure you want to delete this event?", + "delete-event": "Smazat událost", + "delete-event-confirm": "Opravdu chcete smazat tuto událost?", "purge": "Vypráznit", "restore": "Obnovit", "move": "Přesunout", "change-owner": "Změnit vlastníka", - "manage-editors": "Manage Editors", + "manage-editors": "Správa editorů", "fork": "Rozdělit", "link": "Odkaz", "share": "Sdílet", "tools": "Nástroje", "locked": "Uzamknuto", "pinned": "Připnuto", - "pinned-with-expiry": "Pinned until %1", - "scheduled": "Scheduled", - "deleted": "Deleted", + "pinned-with-expiry": "Připnuto dol %1", + "scheduled": "Naplánováno", + "deleted": "Smazané", "moved": "Přesunuto", - "moved-from": "Moved from %1", - "copy-code": "Copy Code", + "moved-from": "Přesunuto z %1", + "copy-code": "Zkopírovat kód", "copy-ip": "Kopírovat IP", "ban-ip": "Zakázat IP", "view-history": "Upravit historii", - "wrote-ago": "wrote ", - "wrote-on": "wrote on ", - "replied-to-user-ago": "replied to %3 ", - "replied-to-user-on": "replied to %3 on ", - "user-locked-topic-ago": "%1 locked this topic %2", - "user-locked-topic-on": "%1 locked this topic on %2", - "user-unlocked-topic-ago": "%1 unlocked this topic %2", - "user-unlocked-topic-on": "%1 unlocked this topic on %2", - "user-pinned-topic-ago": "%1 pinned this topic %2", - "user-pinned-topic-on": "%1 pinned this topic on %2", - "user-unpinned-topic-ago": "%1 unpinned this topic %2", - "user-unpinned-topic-on": "%1 unpinned this topic on %2", - "user-deleted-topic-ago": "%1 deleted this topic %2", - "user-deleted-topic-on": "%1 deleted this topic on %2", - "user-restored-topic-ago": "%1 restored this topic %2", - "user-restored-topic-on": "%1 restored this topic on %2", - "user-moved-topic-from-ago": "%1 moved this topic from %2 %3", - "user-moved-topic-from-on": "%1 moved this topic from %2 on %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "wrote-ago": "napsal ", + "wrote-on": "napsal na ", + "replied-to-user-ago": "odpověděll %3 ", + "replied-to-user-on": "odpověděl %3 na ", + "user-locked-topic-ago": "%1 uzamkl toto téma %2", + "user-locked-topic-on": "%1 uzamkl toto téma na %2", + "user-unlocked-topic-ago": "%1 odemkl toto téma %2", + "user-unlocked-topic-on": "%1 odemkl toto téma na %2", + "user-pinned-topic-ago": "%1 připnul toto téma %2", + "user-pinned-topic-on": "%1 připnul toto téma na %2", + "user-unpinned-topic-ago": "%1 odepnul toto téma %2", + "user-unpinned-topic-on": "%1 odepnul toto téma na %2", + "user-deleted-topic-ago": "%1 smazal toto téma %2", + "user-deleted-topic-on": "%1 smazal toto téma na %2", + "user-restored-topic-ago": "%1 obnovil toto téma %2", + "user-restored-topic-on": "%1 obnovil toto téma na %2", + "user-moved-topic-from-ago": "%1 přesunul toto téma z %2 %3", + "user-moved-topic-from-on": "%1 přesunul toto téma z %2 na %3", + "user-shared-topic-ago": "%1 sdílel(a) toto téma %2", + "user-shared-topic-on": "%1 sdílel(a) toto téma %2", "user-queued-post-ago": "%1 queued post for approval %3", "user-queued-post-on": "%1 queued post for approval on %3", "user-referenced-topic-ago": "%1 referenced this topic %3", "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Pro návrat k poslednímu čtenému příspěvku v tématu, klikněte zde.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,10 +105,11 @@ "thread-tools.lock": "Zamknout téma", "thread-tools.unlock": "Odemknout téma", "thread-tools.move": "Přesunout téma", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Přesunout příspěvky", "thread-tools.move-all": "Přesunout vše", "thread-tools.change-owner": "Změnit vlastníka", - "thread-tools.manage-editors": "Manage Editors", + "thread-tools.manage-editors": "Správa editorů", "thread-tools.select-category": "Vybrat kategorii", "thread-tools.fork": "Větvit téma", "thread-tools.tag": "Tag Topic", @@ -118,7 +121,7 @@ "thread-tools.purge": "Vyčistit téma", "thread-tools.purge-confirm": "Jste si jist/a, že chcete vyčistit toto téma?", "thread-tools.merge-topics": "Sloučit témata", - "thread-tools.merge": "Merge Topic", + "thread-tools.merge": "Sloučit téma", "topic-move-success": "This topic will be moved to \"%1\" shortly. Click here to undo.", "topic-move-multiple-success": "These topics will be moved to \"%1\" shortly. Click here to undo.", "topic-move-all-success": "All topics will be moved to \"%1\" shortly. Click here to undo.", @@ -132,48 +135,54 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Načítání kategorií", "confirm-move": "Přesunout", + "confirm-crosspost": "Cross-post", "confirm-fork": "Rozdělit", "bookmark": "Záložka", "bookmarks": "Záložky", "bookmarks.has-no-bookmarks": "Ještě jste nezazáložkoval žádný příspěvek.", - "copy-permalink": "Copy Permalink", - "go-to-original": "View Original Post", + "copy-permalink": "Zkopírovat odkaz", + "go-to-original": "Zobrazit původní příspěvek", "loading-more-posts": "Načítání více příspěvků", "move-topic": "Přesunout téma", "move-topics": "Přesunout témata", + "crosspost-topic": "Cross-post Topic", "move-post": "Přesunout příspěvek", "post-moved": "Příspěvek přesunut.", "fork-topic": "Rozdělit příspěvek", - "enter-new-topic-title": "Enter new topic title", + "enter-new-topic-title": "Zadejte nový název tématu", "fork-topic-instruction": "Click the posts you want to fork, enter a title for the new topic and click fork topic", "fork-no-pids": "Nebyly vybrány žádné příspěvky.", - "no-posts-selected": "No posts selected!", - "x-posts-selected": "%1 post(s) selected", - "x-posts-will-be-moved-to-y": "%1 post(s) will be moved to \"%2\"", + "no-posts-selected": "Nebyl vybrán žádný příspěvek!", + "x-posts-selected": "Vybráno %1 příspěvků", + "x-posts-will-be-moved-to-y": "%1 přízpěvků bude přesunuto do \"%2\"", "fork-pid-count": "Vybráno %1 příspěvek/ů", "fork-success": "Téma úspěšně rozděleno. Pro přejití na rozdělené téma, zde klikněte.", "delete-posts-instruction": "Klikněte na příspěvek, který chcete odstranit/vyčistit", "merge-topics-instruction": "Click the topics you want to merge or search for them", "merge-topic-list-title": "List of topics to be merged", "merge-options": "Merge options", - "merge-select-main-topic": "Select the main topic", - "merge-new-title-for-topic": "New title for topic", - "topic-id": "Topic ID", + "merge-select-main-topic": "Vybrat hlavní téma", + "merge-new-title-for-topic": "Nový název tématu", + "topic-id": "ID tématu", "move-posts-instruction": "Click the posts you want to move then enter a topic ID or go to the target topic", "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Klikněte na příspěvek u kterého chcete změnit vlastníka", - "manage-editors-instruction": "Manage the users who can edit this post below.", + "manage-editors-instruction": "Spravujte uživatele, kteří mohou tento příspěvek upravovat, níže.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Zadejte název tématu…", - "composer.handle-placeholder": "Enter your name/handle here", - "composer.hide": "Hide", + "composer.handle-placeholder": "Zadejte zde své jméno/přezdívku", + "composer.hide": "Skrýt", "composer.discard": "Zrušit", "composer.submit": "Odeslat", - "composer.additional-options": "Additional Options", - "composer.post-later": "Post Later", - "composer.schedule": "Schedule", + "composer.additional-options": "Další možnosti", + "composer.post-later": "Publikovat později", + "composer.schedule": "Naplánovat", "composer.replying-to": "Odpovídání na %1", "composer.new-topic": "Nové téma", - "composer.editing-in": "Editing post in %1", + "composer.editing-in": "Úprava příspěvku v %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "nahrávání…", "composer.thumb-url-label": "Vložit URL náhledu tématu", "composer.thumb-title": "Přidat k tématu náhled", @@ -188,11 +197,11 @@ "sort-by": "Seřadit dle", "oldest-to-newest": "Od nejstarších po nejnovější", "newest-to-oldest": "Od nejnovějších po nejstarší", - "recently-replied": "Recently Replied", - "recently-created": "Recently Created", + "recently-replied": "Poslední příspěvky", + "recently-created": "Nedávno vytvořené", "most-votes": "S nejvíce hlasy", "most-posts": "S nejvíce příspěvky", - "most-views": "Most Views", + "most-views": "Nejvíce zobrazení", "stale.title": "Raději vytvořit nové téma?", "stale.warning": "Reagujete na starší téma. Nechcete raději vytvořit nové téma a na původní v něm odkázat?", "stale.create": "Vytvořit nové téma", @@ -203,26 +212,29 @@ "diffs.no-revisions-description": "Tento příspěvek má %1 změn.", "diffs.current-revision": "aktuální revize", "diffs.original-revision": "originální revize", - "diffs.restore": "Restore this revision", - "diffs.restore-description": "A new revision will be appended to this post's edit history after restoring.", - "diffs.post-restored": "Post successfully restored to earlier revision", - "diffs.delete": "Delete this revision", - "diffs.deleted": "Revision deleted", + "diffs.restore": "Obnovit tuto verzi", + "diffs.restore-description": "Po obnovení bude k historii úprav tohoto příspěvku přidána nová verze.", + "diffs.post-restored": "Příspěvek byl úspěšně obnoven do předchozí verze.", + "diffs.delete": "Smazat tuto verzi", + "diffs.deleted": "Verze smazána", "timeago-later": "%1 později", "timeago-earlier": "%1 dříve", - "first-post": "First post", - "last-post": "Last post", - "go-to-my-next-post": "Go to my next post", - "no-more-next-post": "You don't have more posts in this topic", - "open-composer": "Open composer", - "post-quick-reply": "Quick reply", - "navigator.index": "Post %1 of %2", - "navigator.unread": "%1 unread", - "upvote-post": "Upvote post", - "downvote-post": "Downvote post", - "post-tools": "Post tools", - "unread-posts-link": "Unread posts link", - "thumb-image": "Topic thumbnail image", - "announcers": "Shares", - "announcers-x": "Shares (%1)" + "first-post": "První příspěvek", + "last-post": "Poslední příspěvek", + "go-to-my-next-post": "Přejít do mého následujícího příspěvku", + "no-more-next-post": "V tomto tématu nemáte další příspěvky", + "open-composer": "Otevřít editor příspěvku", + "post-quick-reply": "Rychlá odpověď", + "navigator.index": "Publikovat %1 of %2", + "navigator.unread": "%1 nepřečteno", + "upvote-post": "Dát příspěvku kladný hlas", + "downvote-post": "Dát příspěvku záporný hlas", + "post-tools": "Nástroje příspěvku", + "unread-posts-link": "Odkaz na nepřečtené příspěvky", + "thumb-image": "Miniatura tématu", + "announcers": "Sdílení", + "announcers-x": "Sdílení (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/cs/unread.json b/public/language/cs/unread.json index 63d013e0f6..1d4fefddcc 100644 --- a/public/language/cs/unread.json +++ b/public/language/cs/unread.json @@ -3,7 +3,7 @@ "no-unread-topics": "Nejsou zde žádné nepřečtené témata.", "load-more": "Načíst další", "mark-as-read": "Označit jako přečtené", - "mark-as-unread": "Mark as Unread", + "mark-as-unread": "Označ jako nepřečtené", "selected": "Vybrané", "all": "Vše", "all-categories": "Všechny kategorie", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 8486f24f05..fa2e2dad41 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -39,7 +39,7 @@ "reputation": "Reputace", "bookmarks": "Záložky", "watched-categories": "Sledované kategorie", - "watched-tags": "Watched tags", + "watched-tags": "Sledované štítky", "change-all": "Změnit vše", "watched": "Sledován", "ignored": "Ignorován", @@ -100,11 +100,15 @@ "remove-cover-picture-confirm": "Jste si jist/a, že chcete smazat obrázek?", "crop-picture": "Oříznout obrázek", "upload-cropped-picture": "Oříznout a nahrát", - "avatar-background-colour": "Avatar background colour", + "avatar-background-colour": "Barva pozadí", "settings": "Nastavení", "show-email": "Zobrazovat můj e-mail", "show-fullname": "Zobrazovat celé jméno", "restrict-chats": "Povolit konverzační zprávy pouze od uživatelů, které sleduji.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Odebírat přehled", "digest-description": "Přihlásit se k odběru e-mailových aktualizací pro toto fórum (nová oznámení a témata), dle stanoveného plánu", "digest-off": "Vypnuto", @@ -130,8 +134,8 @@ "paginate-description": "Stránkovat témata a příspěvky místo použití nekonečného posunování", "topics-per-page": "Témat na stránce", "posts-per-page": "Příspěvků na stránce", - "category-topic-sort": "Category topic sort", - "topic-post-sort": "Topic post sort", + "category-topic-sort": "Řazení podle kategorie", + "topic-post-sort": "Řazení příspěvků v tématu", "max-items-per-page": "Maximum %1", "acp-language": "Jazyk stránky správce", "notifications": "Oznámení", @@ -158,8 +162,8 @@ "order-group-down": "Order group down", "no-group-title": "Žádný nadpis skupiny", "select-skin": "Vybrat vzhled", - "default": "Default (%1)", - "no-skin": "No Skin", + "default": "Výchozí (%1)", + "no-skin": "žádný vzhled", "select-homepage": "Vybrat domovskou stránku", "homepage": "Domovská stránka", "homepage-description": "Vyberte stránku, která má být domovskou stránkou fóra nebo vyberte „Nic” a bude použita výchozí domovská stránka.", diff --git a/public/language/cs/users.json b/public/language/cs/users.json index 195ee52f14..9ebf1bee91 100644 --- a/public/language/cs/users.json +++ b/public/language/cs/users.json @@ -21,6 +21,6 @@ "popular-topics": "Oblíbená témata", "unread-topics": "Nepřečtená témata", "categories": "Kategorie", - "tags": "Značky", + "tags": "Štítky", "no-users-found": "Nebyly nalezeny žádní uživatelé." } \ No newline at end of file diff --git a/public/language/cs/world.json b/public/language/cs/world.json index 3753335278..d1f602f306 100644 --- a/public/language/cs/world.json +++ b/public/language/cs/world.json @@ -1,18 +1,25 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Svět", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "Nápověda", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", - "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.title": "Co je tato stránka?", + "help.intro": "Vítejte ve svém koutku fediverse.", + "help.fediverse": "„Fediverse“ je síť propojených aplikací a webových stránek, které spolu komunikují a jejichž uživatelé se navzájem vidí. Toto fórum je federované a může komunikovat s touto sociální sítí (nebo „fediverse“). Tato stránka je vaším koutkem ve fediverse. Skládá se výhradně z témat vytvořených a sdílených uživateli, které vy sledujete.", + "help.build": "Zpočátku zde nemusí být mnoho témat; je to normální. Postupem času se zde začne objevovat více obsahu, až začnete sledovat další uživatele.", + "help.federating": "Stejně tak, pokud vás začnou sledovat uživatelé mimo toto fórum, vaše příspěvky se začnou zobrazovat i v těchto aplikacích a na webových stránkách.", + "help.next-generation": "Toto je nová generace sociálních sítí – začněte přispívat ještě dnes!", - "onboard.title": "Your window to the fediverse...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.title": "Vaše okno do fediverse…", + "onboard.what": "Toto je vaše personalizovaná kategorie složená pouze z obsahu nalezeného mimo toto fórum. To, zda se něco zobrazí na této stránce, závisí na tom, zda tyto zdroje sledujete, nebo zda byl příspěvek sdílen někým, koho sledujete.", + "onboard.why": "Mimo toto fórum se děje spousta věcí, a ne všechno je relevantní pro vaše zájmy. Proto je sledování lidí nejlepší způsob, jak naznačit, že chcete vidět více obsahu od konkrétní osoby.", + "onboard.how": "Mezitím můžete kliknout na tlačítka zkratek nahoře, abyste viděli o čem dalším toto fórum ví, a začít objevovat nový obsah!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/da/admin/advanced/cache.json b/public/language/da/admin/advanced/cache.json index f8fb35a8f5..d6e355d523 100644 --- a/public/language/da/admin/advanced/cache.json +++ b/public/language/da/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Indlægs Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Fuld", "post-cache-size": "Indlægs Cache Størrelse", "items-in-cache": "Ting i Cache" diff --git a/public/language/da/admin/dashboard.json b/public/language/da/admin/dashboard.json index 98aeb80e34..4774deb49f 100644 --- a/public/language/da/admin/dashboard.json +++ b/public/language/da/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/da/admin/development/info.json b/public/language/da/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/da/admin/development/info.json +++ b/public/language/da/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/da/admin/manage/categories.json b/public/language/da/admin/manage/categories.json index bd3e7b8f22..7ba0e7739a 100644 --- a/public/language/da/admin/manage/categories.json +++ b/public/language/da/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/da/admin/manage/custom-reasons.json b/public/language/da/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/da/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/da/admin/manage/privileges.json b/public/language/da/admin/manage/privileges.json index 1ba88acd76..f0a9ea0778 100644 --- a/public/language/da/admin/manage/privileges.json +++ b/public/language/da/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Etikettér Tråde", "edit-posts": "Edit Posts", diff --git a/public/language/da/admin/manage/users.json b/public/language/da/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/da/admin/manage/users.json +++ b/public/language/da/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/da/admin/settings/activitypub.json b/public/language/da/admin/settings/activitypub.json index 360d4ebfcf..b70d230cf5 100644 --- a/public/language/da/admin/settings/activitypub.json +++ b/public/language/da/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Opslag Ventetid (millisekunder)", "probe-timeout-help": "(Udgangspunkt: 2000) Hvis opslagsforespørgslen ikke modtager et svar inden for den angivne tidsramme, vil vil brugeren blive sendt til linket direkte i stedet for. Justér dette tal højere, hvis sider responderer langsomt og du gerne vil give dem ekstra tid.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtrering", "count": "Denne NodeBB instans er lige nu bevidst om %1 server(e)", "server.filter-help": "Specificér servere, som du gerne vil stoppe fra at føderere med din NodeBB instans. Alternativt, kan du vælge at selektivt tillade føderation med udvalgte servere i stedet. Begge muligheder er understøttet, men man kan kun vælge en metode ad gangen.", diff --git a/public/language/da/admin/settings/chat.json b/public/language/da/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/da/admin/settings/chat.json +++ b/public/language/da/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/da/admin/settings/email.json b/public/language/da/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/da/admin/settings/email.json +++ b/public/language/da/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/da/admin/settings/notifications.json b/public/language/da/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/da/admin/settings/notifications.json +++ b/public/language/da/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/da/admin/settings/uploads.json b/public/language/da/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/da/admin/settings/uploads.json +++ b/public/language/da/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/da/admin/settings/user.json b/public/language/da/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/da/admin/settings/user.json +++ b/public/language/da/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/da/admin/settings/web-crawler.json b/public/language/da/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/da/admin/settings/web-crawler.json +++ b/public/language/da/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/da/aria.json b/public/language/da/aria.json index c508ad09f3..47d81b3e1c 100644 --- a/public/language/da/aria.json +++ b/public/language/da/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "Bruger-fulgte etiketter", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/da/category.json b/public/language/da/category.json index a42e1549a3..330d9739d6 100644 --- a/public/language/da/category.json +++ b/public/language/da/category.json @@ -1,12 +1,13 @@ { "category": "Kategori", "subcategories": "Underkategorier", - "uncategorized": "Ikke-kategoriseret", - "uncategorized.description": "Tråden passer ikke strengt ind i nogen eksisterende kategorier", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "Denne kategori kan følges fra det åbne sociale net gennem grebet %1", "new-topic-button": "Ny tråd", "guest-login-post": "Log ind", "no-topics": "Der er ikke nogen nye tråde i denne kategori.
Hvorfor prøver du ikke at lave et?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "browse", "no-replies": "Ingen har svaret", "no-new-posts": "Ingen nye indlæg", diff --git a/public/language/da/error.json b/public/language/da/error.json index cbe96419f3..4e9bd34219 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Det ser ikke ud til at du er logget ind.", "account-locked": "Din konto er blevet blokeret midlertidigt.", "search-requires-login": "Du skal have en konto for at søge - log venligst ind eller registrer dig.", @@ -146,6 +147,7 @@ "post-already-restored": "Dette indlæg er allerede blevet genskabt", "topic-already-deleted": "Denne tråd er allerede blevet slettet", "topic-already-restored": "Denne tråd er allerede blevet genskabt", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Du kan ikke udradere hoved indlægget, fjern venligt tråden istedet", "topic-thumbnails-are-disabled": "Tråd miniaturebilleder er slået fra.", "invalid-file": "Ugyldig fil", @@ -154,6 +156,8 @@ "about-me-too-long": "Beklager, men din om mig side kan ikke være længere end %1 karakter(er).", "cant-chat-with-yourself": "Du kan ikke chatte med dig selv!", "chat-restricted": "Denne bruger har spæret adgangen til chat beskeder. Brugeren må følge dig før du kan chatte med ham/hende", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system er deaktiveret", "too-many-messages": "Du har sendt for mange beskeder, vent venligt lidt.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/da/global.json b/public/language/da/global.json index 4da3e8b60d..5a9b870534 100644 --- a/public/language/da/global.json +++ b/public/language/da/global.json @@ -68,6 +68,7 @@ "users": "Bruger", "topics": "Emner", "posts": "Indlæg", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Syntes ikke godt om", "views": "Visninger", "posters": "Posters", + "watching": "Watching", "reputation": "Omdømme", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/da/groups.json b/public/language/da/groups.json index f5bc3a47cb..bcac3b168e 100644 --- a/public/language/da/groups.json +++ b/public/language/da/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "grupper", "members": "Members", + "x-members": "%1 member(s)", "view-group": "se gruppe", "owner": "Gruppe ejer", "new-group": "Opret ny gruppe", diff --git a/public/language/da/modules.json b/public/language/da/modules.json index 85a9e8fdfa..76519b0e40 100644 --- a/public/language/da/modules.json +++ b/public/language/da/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Fjern forhåndsvisning", "composer.help": "Help", "composer.user-said-in": "%1 sagde i %2:", - "composer.user-said": "%1 sagde:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Er du sikker på at du vil kassere dette indlæg?", "composer.submit-and-lock": "Send og lås", "composer.toggle-dropdown": "Skift mellem dropdown", diff --git a/public/language/da/notifications.json b/public/language/da/notifications.json index b7ebebded5..c07c3dc8fa 100644 --- a/public/language/da/notifications.json +++ b/public/language/da/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Ny besked fra %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 har upvotet dit indlæg i %2.", - "upvoted-your-post-in-dual": "%1 og %2 har syntes godt om dit indlæg i %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 har flyttet dit indlæg til %2", "moved-your-topic": "%1 har flyttet %2", - "user-flagged-post-in": "%1 har anmeldt et indlæg i %2", - "user-flagged-post-in-dual": "%1 og %2 har anmeldt et indlæg i %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flagged a user profile (%2)", "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 har skrevet et svar til: %2", - "user-posted-to-dual": "%1 og %2 har skrevet svar til: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 har oprettet en ny tråd: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 har delt %2 (etiketteret %3)", - "user-posted-topic-with-tag-dual": "%1 har delt %2 (etiketteret %3 og %4)", - "user-posted-topic-with-tag-triple": "%1 har delt %2 (etiketteret %3, %4 og %5)", - "user-posted-topic-with-tag-multiple": "%1 har delt %2 (etiketteret %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 har valgt at følge dig.", "user-started-following-you-dual": "%1 og %2 har valgt at følge dig.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email bekræftet", "email-confirmed-message": "Tak fordi du validerede din email. Din konto er nu fuldt ud aktiveret.", "email-confirm-error-message": "Der var et problem med valideringen af din emailadresse. Bekræftelses koden var muligvis forkert eller udløbet.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Bekræftelses email afsendt.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/da/social.json b/public/language/da/social.json index d2dec7d2f0..308b0f1065 100644 --- a/public/language/da/social.json +++ b/public/language/da/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log ind med Facebook", "continue-with-facebook": "Fortsæt med Facebook", "sign-in-with-linkedin": "Log ind med LinkedIn", - "sign-up-with-linkedin": "Meld dig ind med LinkedIn" + "sign-up-with-linkedin": "Meld dig ind med LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/da/themes/harmony.json b/public/language/da/themes/harmony.json index 418735a8d8..2f455d37db 100644 --- a/public/language/da/themes/harmony.json +++ b/public/language/da/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmoni Tema", "skins": "Temaer", + "light": "Light", + "dark": "Dark", "collapse": "Kollaps", "expand": "Udvid", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/da/topic.json b/public/language/da/topic.json index 4b3720e0ca..577acf3ab6 100644 --- a/public/language/da/topic.json +++ b/public/language/da/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klik her for at vende tilbage til den sidst læste indlæg i denne tråd.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lås tråd", "thread-tools.unlock": "Lås tråd op", "thread-tools.move": "Flyt tråd", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Flyt alt", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Indlæser kategorier", "confirm-move": "Flyt", + "confirm-crosspost": "Cross-post", "confirm-fork": "Fraskil", "bookmark": "Bogmærke", "bookmarks": "Bogmærker", @@ -141,6 +145,7 @@ "loading-more-posts": "Indlæser flere indlæg", "move-topic": "Flyt tråd", "move-topics": "Flyt tråde", + "crosspost-topic": "Cross-post Topic", "move-post": "Flyt indlæg", "post-moved": "Indlæg flyttet!", "fork-topic": "Fraskil tråd", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Angiv din trådtittel her ...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Svare til %1", "composer.new-topic": "Ny tråd", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "uploader...", "composer.thumb-url-label": "Indsæt en tråd miniature URL", "composer.thumb-title": "Tilføj en miniature til denne tråd", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/da/user.json b/public/language/da/user.json index d61d7c3df2..6a8b1a9cf8 100644 --- a/public/language/da/user.json +++ b/public/language/da/user.json @@ -105,6 +105,10 @@ "show-email": "Vis min emailaddresse", "show-fullname": "Vis mit fulde navn", "restrict-chats": "Tillad kun chat beskeder fra brugere jeg følger", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Abonner på sammendrag", "digest-description": "Abonner på email opdateringer for detta forum (nye notifikationer og indlæg) efter en bestemt køreplan", "digest-off": "Slukket", diff --git a/public/language/da/world.json b/public/language/da/world.json index fbee425865..32e1b2af70 100644 --- a/public/language/da/world.json +++ b/public/language/da/world.json @@ -1,7 +1,12 @@ { "name": "Verden", - "popular": "Populære tråde", - "recent": "Alle tråde", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Hjælp", "help.title": "Hvad er denne side?", @@ -14,5 +19,7 @@ "onboard.title": "Dit vindue til fødiverset...", "onboard.what": "Dette er din personaliserede kategori som består kun af indhold fra udefra dette forum. Om noget dukker op her eller ej afhænger af om følger personen der lavede indlægget, eller om du følger nogen der har fremhævet indlægget.", "onboard.why": "Der foregår en masse udenfor dette forum, og ikke det hele er relevant for dine interesser. At følge folk er derfor den bedste måde at signalere at du gerne vil se mere fra dem.", - "onboard.how": "I mellemtiden kan du klikke på genvejs-knapperne i toppen for at se, hvad forummet kender til og begynd at opdage nyt indhold!" + "onboard.how": "I mellemtiden kan du klikke på genvejs-knapperne i toppen for at se, hvad forummet kender til og begynd at opdage nyt indhold!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/de/admin/advanced/cache.json b/public/language/de/admin/advanced/cache.json index 6aa3604fe4..18ae71a2d0 100644 --- a/public/language/de/admin/advanced/cache.json +++ b/public/language/de/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post-Cache", - "group-cache": "Gruppen-Cache", - "local-cache": "Lokaler Cache", - "object-cache": "Objekt-Cache", "percent-full": "%1% Voll", "post-cache-size": "Post-Cache-Größe", "items-in-cache": "Elemente im Cache" diff --git a/public/language/de/admin/dashboard.json b/public/language/de/admin/dashboard.json index 9229f720aa..4067ac8311 100644 --- a/public/language/de/admin/dashboard.json +++ b/public/language/de/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Registrierte Seitenaufrufe", "graphs.page-views-guest": "Seitenaufrufe von Gästen", "graphs.page-views-bot": "Seitenaufrufe von Bots", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Verschiedene Besucher", "graphs.registered-users": "Registrierte Benutzer", "graphs.guest-users": "Gast-Benutzer", diff --git a/public/language/de/admin/development/info.json b/public/language/de/admin/development/info.json index b5dd27dfe5..062f425547 100644 --- a/public/language/de/admin/development/info.json +++ b/public/language/de/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "Node.js Version", "online": "Online", "git": "git", - "process-memory": "Prozess-Speicher", + "process-memory": "rss/heap used", "system-memory": "System-Speicher", "used-memory-process": "Verwendeter Prozess-Speicher", "used-memory-os": "Verwendeter System-Speicher", diff --git a/public/language/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json index 1cc1282011..c5c971a259 100644 --- a/public/language/de/admin/manage/categories.json +++ b/public/language/de/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Kategorien verwalten", "add-category": "Kategorie hinzufügen", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Springen zu...", "settings": "Kategorieeinstellungen", "edit-category": "Kategorie bearbeiten", "privileges": "Berechtigungen", "back-to-categories": "Zurück zu Kategorien", + "id": "Category ID", "name": "Kategoriename", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategorie-Beschreibung", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Hintergrundfarbe", "text-color": "Textfarbe", "bg-image-size": "Hintergrundbildgröße", @@ -103,6 +107,11 @@ "alert.create-success": "Kategorie erfolgreich erstellt!", "alert.none-active": "Du hast keine aktiven Kategorien.", "alert.create": "Erstelle eine Kategorie", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Möchtest du die Kategorie \"%1\" wirklich löschen?

Warnung! Alle Themen und Beiträge in dieser Kategorie werden gelöscht!

Löschen einer Kategorie wird alle Themen und Beiträge zu entfernen, und die Kategorie aus der Datenbank löschen. Falls du eine Kategorie temporär entfernen möchstest, dann kannst du sie stattdessen \"deaktivieren\".", "alert.purge-success": "Kategorie gelöscht!", "alert.copy-success": "Einstellungen kopiert!", diff --git a/public/language/de/admin/manage/custom-reasons.json b/public/language/de/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/de/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/de/admin/manage/privileges.json b/public/language/de/admin/manage/privileges.json index 622895fa73..40fe8e7fd3 100644 --- a/public/language/de/admin/manage/privileges.json +++ b/public/language/de/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Themenzutritt", "create-topics": "Themen erstellen", "reply-to-topics": "Auf Themen antworten", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Geplante Themen", "tag-topics": "Themen taggen", "edit-posts": "Beiträge editieren", diff --git a/public/language/de/admin/manage/users.json b/public/language/de/admin/manage/users.json index 08a5ae9b45..61aad650d0 100644 --- a/public/language/de/admin/manage/users.json +++ b/public/language/de/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Benutzer und Benutzer-Inhalte löschen", "download-csv": "CSV herunterladen", "custom-user-fields": "Benutzerdefinierte Benutzerfelder", + "custom-reasons": "Custom Reasons", "manage-groups": "Gruppen verwalten", "set-reputation": "Ansehen festlegen", "add-group": "Gruppe hinzufügen", @@ -77,9 +78,11 @@ "temp-ban.length": "Länge", "temp-ban.reason": "Grund (optional)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Stunden", "temp-ban.days": "Tage", "temp-ban.explanation": "Geben die dauer des Bans an. Beachte, dass eine Zeit von 0 als permanent interpretiert wird.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Möchtest Du diesen Nutzer wirklich permanent bannen?", "alerts.confirm-ban-multi": "Möchtest Du diese Nutzer wirklich permanent bannen?", diff --git a/public/language/de/admin/settings/activitypub.json b/public/language/de/admin/settings/activitypub.json index 7475cf5d4b..34f2635f78 100644 --- a/public/language/de/admin/settings/activitypub.json +++ b/public/language/de/admin/settings/activitypub.json @@ -1,26 +1,48 @@ { "intro-lead": "Was ist Föderation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", + "intro-body": "NodeBB kann mit anderen NodeBB-Instanzen kommunizieren, die dies unterstützen. Dies geschieht über ein Protokoll namens ActivityPub. Wenn es aktiviert ist, kann NodeBB auch mit anderen Apps und Websites kommunizieren, die ActivityPub verwenden (z. B. Mastodon, Peertube usw.).", "general": "Allgemein", "pruning": "Inhaltsbereinigung", - "content-pruning": "Days to keep remote content", - "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", - "user-pruning": "Days to cache remote user accounts", - "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", + "content-pruning": "Tage, an denen der Remote-Inhalt aufbewahrt werden soll.", + "content-pruning-help": "Inhalte von extern, die Interaktionen bekommen haben (z. B. eine Antwort oder ein Upvote/Downvote), bleiben erhalten. (0 = deaktiviert)", + "user-pruning": "Tage, um externe Benutzerkonten zwischenzuspeichern", + "user-pruning-help": "Externe Benutzerkonten werden nur gelöscht, wenn sie keine Beiträge haben. Andernfalls werden sie erneut abgerufen. (0 = deaktiviert)", "enabled": "Föderation aktivieren", - "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", - "allowLoopback": "Allow loopback processing", - "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", + "enabled-help": "Wenn aktiviert, kann dieses NodeBB mit allen ActivityPub-fähigen Clients im weiteren Fediverse kommunizieren.", + "allowLoopback": "Loopback-Verarbeitung erlauben", + "allowLoopback-help": "Nur für Debugging-Zwecke nützlich. Sollte am besten deaktiviert bleiben.", "probe": "In App öffnen", - "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", - "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", - "probe-timeout": "Lookup Timeout (milliseconds)", - "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "probe-enabled": "Versuchen, ActivityPub-fähige Ressourcen in NodeBB zu öffnen", + "probe-enabled-help": "Wenn aktiviert, überprüft NodeBB jeden externen Link auf ein ActivityPub-Äquivalent und lädt diesen stattdessen in NodeBB.", + "probe-timeout": "Lookup-Timeout (Millisekunden)", + "probe-timeout-help": "(Standard: 2000) Wenn die Lookup-Anfrage innerhalb des festgelegten Zeitraums keine Antwort erhält, wird der Nutzer stattdessen direkt zum Link weitergeleitet. Erhöhe diesen Wert, wenn Seiten langsam reagieren und du mehr Zeit einräumen möchtest.", + + "rules": "Kategorisierung", + "rules-intro": "Über ActivityPub entdeckte Inhalte können automatisch anhand bestimmter Regeln (z. B. Hashtags) kategorisiert werden.", + "rules.modal.title": "Wie es funktioniert", + "rules.modal.instructions": "Eingehende Inhalte werden mit diesen Kategorisierungsregeln abgeglichen, und passende Inhalte werden automatisch in die gewünschte Kategorie verschoben. Hinweis: Inhalte, die bereits kategorisiert sind (z. B. in einer externen Kategorie), durchlaufen diese Regeln nicht.", + "rules.add": "Neue Regel hinzufügen", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Typ", + "rules.value": "Wert", + "rules.cid": "Kategorie", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", "server-filtering": "Filterung", - "count": "This NodeBB is currently aware of %1 server(s)", - "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", - "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.", - "server.filter-allow-list": "Use this as an Allow List instead" + "count": "Dieses NodeBB kennt derzeit %1 Server", + "server.filter-help": "Gib die Server an, die du von der Föderation mit deinem NodeBB ausschließen möchtest. Alternativ kannst du auch festlegen, dass die Föderation nur mit bestimmten Servern erlaubt ist. Beide Optionen werden unterstützt, schließen sich jedoch gegenseitig aus.", + "server.filter-help-hostname": "Gib unten nur den Instanz-Hostnamen ein (z. B. example.org), jeweils durch Zeilenumbrüche getrennt.", + "server.filter-allow-list": "Stattdessen als Allow-Liste verwenden" } \ No newline at end of file diff --git a/public/language/de/admin/settings/chat.json b/public/language/de/admin/settings/chat.json index 68bee6c18b..7175761f5f 100644 --- a/public/language/de/admin/settings/chat.json +++ b/public/language/de/admin/settings/chat.json @@ -5,13 +5,11 @@ "disable-editing": "Chatnachrichtenbearbeitung/löschung deaktivieren", "disable-editing-help": "Administratoren und globale Moderatoren sind von dieser Einschränkung ausgenommen", "max-length": "Maximale Länge von Chatnachrichten", - "max-length-remote": "Maximum length of remote chat messages", - "max-length-remote-help": "This value is usually set higher than the chat message maximum for local users as remote messages tend to be longer (with @ mentions, etc.)", + "max-length-remote": "Maximale Länge von Remote-Chat-Nachrichten", + "max-length-remote-help": "Dieser Wert wird in der Regel höher als das Chat-Nachrichtenmaximum für lokale Benutzer festgelegt, da Remote-Nachrichten tendenziell länger sind (mit @-Erwähnungen usw.).", "max-chat-room-name-length": "Maximale Länge der Namen von Chaträumen", "max-room-size": "Maximale Anzahl von Benutzern in Chatrooms", "delay": "Zeit zwischen Chat-Nachrichten (ms)", - "notification-delay": "Benachrichtigungsverzögerung für Chat-Nachrichten", - "notification-delay-help": "Zusätzliche Nachrichten, die zwischen dieser Zeit gesendet werden, werden gesammelt, und der Benutzer wird einmal pro Verzögerungszeitraum benachrichtigt. Setze diesen Wert auf 0, um die Verzögerung zu deaktivieren.", "restrictions.seconds-edit-after": "Anzahl der Sekunden, die eine Chat-Nachricht bearbeitbar bleibt.", "restrictions.seconds-delete-after": "Anzahl der Sekunden, die eine Chat-Nachricht löschbar bleibt." } \ No newline at end of file diff --git a/public/language/de/admin/settings/email.json b/public/language/de/admin/settings/email.json index b5691aad10..94c0d8c5b9 100644 --- a/public/language/de/admin/settings/email.json +++ b/public/language/de/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Das Poolen von Verbindungen hindert NodeBB daran für jede neu erstellte E-Mail eine eigene Verbindung aufzubauen. Diese Option ist nur zutreffend, wenn SMTP aktiviert ist.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "E-Mail Vorlage bearbeiten", "template.select": "E-Mail Vorlage auswählen", "template.revert": "Original wiederherstellen", + "test-smtp-settings": "Test SMTP Settings", "testing": "E-Mail Test", + "testing.success": "Test Email Sent.", "testing.select": "E-Mail-Vorlage auswählen", "testing.send": "Test-E-Mail versenden", - "testing.send-help": "Die Test-E-Mail wird an die E-Mail Adresse des momentan eingeloggten Nutzers geschickt.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Zusammenfassungen", "subscriptions.disable": "Deaktivierung der Email Zusammenfassungen", "subscriptions.hour": "Sende Zeit", diff --git a/public/language/de/admin/settings/notifications.json b/public/language/de/admin/settings/notifications.json index 089bd86254..578bb826fb 100644 --- a/public/language/de/admin/settings/notifications.json +++ b/public/language/de/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Willkommensbenachrichtigung", "welcome-notification-link": "Willkommens-Benachrichtigungslink", "welcome-notification-uid": "Begrüßungsbenachrichtigungsbenutzer (UID)", - "post-queue-notification-uid": "Post-Queue-Benutzer (UID)" + "post-queue-notification-uid": "Post-Queue-Benutzer (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/de/admin/settings/uploads.json b/public/language/de/admin/settings/uploads.json index 269bfb8178..a52accfc0e 100644 --- a/public/language/de/admin/settings/uploads.json +++ b/public/language/de/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Breitere Bilder werden abgelehnt.", "reject-image-height": "Maximale Bildhöhe (in Pixeln)", "reject-image-height-help": "Höhere Bilder werden abgelehnt.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Nutzern erlauben Themen Thumbnails hochzuladen", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Thema Thumbnailgröße", "allowed-file-extensions": "Erlaubte Dateiendungen", "allowed-file-extensions-help": "Komma-getrennte Liste der Dateiendungen hier einfügen (z.B. pdf,xls,doc). Eine leere Liste bedeutet, dass alle Dateiendungen erlaubt sind.", diff --git a/public/language/de/admin/settings/user.json b/public/language/de/admin/settings/user.json index 8ba2a4d4d3..633d8e3d6f 100644 --- a/public/language/de/admin/settings/user.json +++ b/public/language/de/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Zeige E-Mail-Adresse", "show-fullname": "Zeige vollen Namen", "restrict-chat": "Erlaube nur Chatnachrichten von Benutzern denen ich folge", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Öffne externe Links in einem neuen Tab", "topic-search": "Suchen innerhalb von Themen aktivieren", "update-url-with-post-index": " URL während Themen durchsuchen mit dem Beitragsindex aktivieren", diff --git a/public/language/de/admin/settings/web-crawler.json b/public/language/de/admin/settings/web-crawler.json index d898fa69ac..3addf20d7c 100644 --- a/public/language/de/admin/settings/web-crawler.json +++ b/public/language/de/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "RSS Feeds deaktivieren", "disable-sitemap-xml": "sitemap.xml deaktivieren", "sitemap-topics": "Anzahl der Themen, die in der Sitemap angezeigt werden sollen", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Sitemap Cache leeren", "view-sitemap": "Sitemap anzeigen" } \ No newline at end of file diff --git a/public/language/de/aria.json b/public/language/de/aria.json index bece04e6d6..a33c879516 100644 --- a/public/language/de/aria.json +++ b/public/language/de/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/de/category.json b/public/language/de/category.json index 61f70e760a..7cac3f3368 100644 --- a/public/language/de/category.json +++ b/public/language/de/category.json @@ -1,12 +1,13 @@ { "category": "Kategorie", "subcategories": "Unterkategorien", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Neues Thema", "guest-login-post": "Melde dich an, um einen Beitrag zu erstellen", "no-topics": "Es gibt noch keine Themen in dieser Kategorie.
Warum beginnst du nicht eins?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "Aktiv", "no-replies": "Niemand hat geantwortet", "no-new-posts": "Keine neuen Beiträge.", diff --git a/public/language/de/error.json b/public/language/de/error.json index 76ea8f38e2..65b3bfef95 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -3,6 +3,7 @@ "invalid-json": "Ungültiges JSON", "wrong-parameter-type": "Für die Eigenschaft „%1“ wurde ein Wert vom Typ %3 erwartet, aber stattdessen wurde %2 empfangen", "required-parameters-missing": "Bei diesem API-Aufruf fehlten erforderliche Parameter: %1", + "reserved-ip-address": "Netzwerkanfragen an reservierte IP-Bereiche sind nicht erlaubt.", "not-logged-in": "Du bist nicht angemeldet.", "account-locked": "Dein Konto wurde vorübergehend gesperrt.", "search-requires-login": "Die Suche erfordert ein Konto, bitte einloggen oder registrieren.", @@ -67,8 +68,8 @@ "no-chat-room": "Der Chatroom existiert nicht", "no-privileges": "Du verfügst nicht über ausreichende Berechtigungen, um die Aktion durchzuführen.", "category-disabled": "Kategorie ist deaktiviert", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Beitrag gelöscht", + "topic-locked": "Thema gesperrt", "post-edit-duration-expired": "Entschuldigung, du darfst Beiträge nur %1 Sekunde(n) nach dem Veröffentlichen editieren.", "post-edit-duration-expired-minutes": "Du darfst Beiträge lediglich innerhalb von %1 Minuten/n nach dem Erstellen editieren", "post-edit-duration-expired-minutes-seconds": "Du darfst Beiträge lediglich innerhalb von %1 Minuten/n und %2 Sekunden nach dem Erstellen editieren", @@ -146,6 +147,7 @@ "post-already-restored": "Dieser Beitrag ist bereits wiederhergestellt worden", "topic-already-deleted": "Dieses Thema ist bereits gelöscht worden", "topic-already-restored": "Dieses Thema ist bereits wiederhergestellt worden", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Du kannst den Hauptbeitrag nicht löschen, bitte lösche stattdessen das Thema", "topic-thumbnails-are-disabled": "Vorschaubilder für Themen sind deaktiviert", "invalid-file": "Ungültige Datei", @@ -154,7 +156,9 @@ "about-me-too-long": "Entschuldigung, dein \"über mich\" kann nicht länger als %1 Zeichen sein.", "cant-chat-with-yourself": "Du kannst nicht mit dir selber chatten!", "chat-restricted": "Dieser Benutzer hat seine Chatfunktion eingeschränkt. Du kannst nur mit diesem Benutzer chatten, wenn er dir folgt.", - "chat-user-blocked": "You have been blocked by this user.", + "chat-allow-list-user-already-added": "Dieser Benutzer befindet sich bereits in deiner Allow-Liste.", + "chat-deny-list-user-already-added": "Dieser Benutzer befindet sich bereits in deiner Deny-Liste.", + "chat-user-blocked": "Du wurdest von diesem Benutzer blockiert.", "chat-disabled": "Das Chatsystem deaktiviert", "too-many-messages": "Du hast zu viele Nachrichten versandt, bitte warte eine Weile.", "invalid-chat-message": "Ungültige Nachricht", @@ -169,7 +173,7 @@ "cant-add-users-to-chat-room": "Kann Benutzer nicht zu Chatroom hinzufügen", "cant-remove-users-from-chat-room": "Kann Benutzer nicht aus Chatroom entfernen.", "chat-room-name-too-long": "Der Name des Chat-Raums ist zu lang. Die Namen dürfen nicht länger als %1 Zeichen sein.", - "remote-chat-received-too-long": "You received a chat message from %1, but it was too long and was rejected.", + "remote-chat-received-too-long": "Du hast eine Chat-Nachricht von %1 erhalten, aber sie war zu lang und wurde abgelehnt.", "already-voting-for-this-post": "Du hast diesen Beitrag bereits bewertet.", "reputation-system-disabled": "Das Reputationssystem ist deaktiviert.", "downvoting-disabled": "Downvotes sind deaktiviert.", @@ -183,20 +187,20 @@ "not-enough-reputation-min-rep-signature": "Du benötigst %1 Reputation, um eine Signatur hinzuzufügen", "not-enough-reputation-min-rep-profile-picture": "Du benötigst %1 Ruf, um ein Profilbild hinzuzufügen", "not-enough-reputation-min-rep-cover-picture": "Du benötigst %1 Ruf, um ein Titelbild hinzuzufügen", - "not-enough-reputation-custom-field": "You need %1 reputation for %2", - "custom-user-field-value-too-long": "Custom field value too long, %1", - "custom-user-field-select-value-invalid": "Custom field selected option is invalid, %1", - "custom-user-field-invalid-text": "Custom field text is invalid, %1", - "custom-user-field-invalid-link": "Custom field link is invalid, %1", - "custom-user-field-invalid-number": "Custom field number is invalid, %1", - "custom-user-field-invalid-date": "Custom field date is invalid, %1", - "invalid-custom-user-field": "Invalid custom user field, \"%1\" is already used by NodeBB", + "not-enough-reputation-custom-field": "Du benötigst %1 Reputation für %2", + "custom-user-field-value-too-long": "Benutzerdefiniertes Feld zu lang, %1", + "custom-user-field-select-value-invalid": "Die ausgewählte Option im benutzerdefinierten Feld ist ungültig, %1", + "custom-user-field-invalid-text": "Der Text im benutzerdefinierten Feld ist ungültig, %1", + "custom-user-field-invalid-link": "Der Link im benutzerdefinierten Feld ist ungültig, %1", + "custom-user-field-invalid-number": "Die Zahl im benutzerdefinierten Feld ist ungültig, %1", + "custom-user-field-invalid-date": "Das Datum im benutzerdefinierten Feld ist ungültig, %1", + "invalid-custom-user-field": "Ungültiges benutzerdefiniertes Feld, %1 wird bereits von NodeBB verwendet", "post-already-flagged": "Du hast diesen Beitrag bereits gemeldet", "user-already-flagged": "Du hast diesen Benutzer bereits gemeldet", "post-flagged-too-many-times": "Dieser Beitrag wurde bereits von anderen Benutzern gemeldet", "user-flagged-too-many-times": "Dieser Benutzer wurde bereits von anderen Benutzern gemeldet", - "too-many-post-flags-per-day": "You can only flag %1 post(s) per day", - "too-many-user-flags-per-day": "You can only flag %1 user(s) per day", + "too-many-post-flags-per-day": "Du kannst pro Tag nur %1 Beitrag/Beiträge melden", + "too-many-user-flags-per-day": "Du kannst pro Tag nur %1 Benutzer melden", "cant-flag-privileged": "Sie dürfen die Profile oder Inhalte von privilegierten Benutzern (Moderatoren/Globalmoderatoren/Admins) nicht kennzeichnen.", "cant-locate-flag-report": "Meldung-Report kann nicht gefunden werden", "self-vote": "Du kannst deine eigenen Beiträge nicht bewerten", @@ -225,6 +229,7 @@ "no-topics-selected": "Keine Beiträge ausgewählt!", "cant-move-to-same-topic": "Du kannst den Beitrag nicht in das selbe Thema schieben!", "cant-move-topic-to-same-category": "Das Thema kann nicht zur selben Kategorie verschoben werden!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Du kannst dich nicht selbst blocken!", "cannot-block-privileged": "Du kannst keine Administratoren bzw. Globale Moderatoren blocken.", "cannot-block-guest": "Gäste können andere Nutzer nicht blockieren.", @@ -232,13 +237,14 @@ "already-unblocked": "Dieser Nutzer ist bereits entsperrt", "no-connection": "Es scheint als gäbe es ein Problem mit deiner Internetverbindung", "socket-reconnect-failed": "Der Server kann zurzeit nicht erreicht werden. Klicken Sie hier, um es erneut zu versuchen, oder versuchen Sie es später erneut", - "invalid-plugin-id": "Invalid plugin ID", + "invalid-plugin-id": "Ungültige Plugin-ID", "plugin-not-whitelisted": "Plugin kann nicht installiert werden – nur Plugins, die vom NodeBB Package Manager in die Whitelist aufgenommen wurden, können über den ACP installiert werden", - "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", + "cannot-toggle-system-plugin": "Du kannst den Status eines System-Plugins nicht umschalten", + "plugin-installation-via-acp-disabled": "Die Plugin-Installation über das ACP ist deaktiviert", "plugins-set-in-configuration": "Du darfst den Status der Plugins nicht ändern, da sie zur Laufzeit definiert werden (config.json, Umgebungsvariablen oder Terminalargumente). Bitte ändere stattdessen die Konfiguration.", "theme-not-set-in-configuration": "Wenn in der Konfiguration aktive Plugins definiert werden, muss bei einem Themenwechsel das neue Thema zur Liste der aktiven Plugins hinzugefügt werden, bevor es im ACP aktualisiert wird.", "topic-event-unrecognized": "Themenereignis „%1“ nicht erkannt", - "category.handle-taken": "Category handle is already taken, please choose another.", + "category.handle-taken": "Kategorie-Handle ist bereits vergeben, bitte wähle ein anderes.", "cant-set-child-as-parent": "Untergeordnete Kategorie kann nicht als übergeordnete Kategorie festgelegt werden", "cant-set-self-as-parent": "Die aktuelle Kategorie kann nicht als übergeordnete Kategorie festgelegt werden", "api.master-token-no-uid": "Ein Master-Token wurde ohne eine entsprechende `_uid` im Anfrage-Body empfangen", @@ -246,17 +252,18 @@ "api.401": "Es wurde keine gültige Anmeldesitzung gefunden. Bitte melden Sie sich an und versuchen Sie es erneut.", "api.403": "Sie sind nicht berechtigt, diesen Anruf zu tätigen", "api.404": "Ungültiger API-Aufruf", + "api.413": "The request payload is too large", "api.426": "HTTPS ist für Anfragen an die Schreib-API erforderlich, bitte senden Sie Ihre Anfrage erneut über HTTPS", "api.429": "Sie haben zu viele Anfragen gestellt, bitte versuchen Sie es später erneut", "api.500": "Beim Versuch, Ihre Anfrage zu bearbeiten, ist ein unerwarteter Fehler aufgetreten.", "api.501": "Die Route, die Sie anrufen möchten, ist noch nicht implementiert. Bitte versuchen Sie es morgen erneut", "api.503": "Die Route, die Sie anrufen möchten, ist derzeit aufgrund einer Serverkonfiguration nicht verfügbar", "api.reauth-required": "Die angeforderte Ressource erfordert eine (Re-)Authentifizierung.", - "activitypub.not-enabled": "Federation is not enabled on this server", - "activitypub.invalid-id": "Unable to resolve the input id, likely as it is malformed.", - "activitypub.get-failed": "Unable to retrieve the specified resource.", - "activitypub.pubKey-not-found": "Unable to resolve public key, so payload verification cannot take place.", - "activitypub.origin-mismatch": "The received object's origin does not match the sender's origin", - "activitypub.actor-mismatch": "The received activity is being carried out by an actor that is different from expected.", - "activitypub.not-implemented": "The request was denied because it or an aspect of it is not implemented by the recipient server" + "activitypub.not-enabled": "Die Föderation ist auf diesem Server nicht aktiviert", + "activitypub.invalid-id": "Die Eingabe-ID kann nicht aufgelöst werden, wahrscheinlich weil sie fehlerhaft ist.", + "activitypub.get-failed": "Die angegebene Ressource kann nicht abgerufen werden.", + "activitypub.pubKey-not-found": "Der öffentliche Schlüssel kann nicht aufgelöst werden, daher ist eine Überprüfung des Payloads nicht möglich.", + "activitypub.origin-mismatch": "Der Ursprung des empfangenen Objekts stimmt nicht mit dem Ursprung des Absenders überein", + "activitypub.actor-mismatch": "Die empfangene Aktivität wird von einem anderen Akteur ausgeführt als erwartet", + "activitypub.not-implemented": "Die Anfrage wurde abgelehnt, weil sie oder ein Teil davon vom empfangenden Server nicht implementiert ist" } \ No newline at end of file diff --git a/public/language/de/global.json b/public/language/de/global.json index 33ebea5759..5b55c71acf 100644 --- a/public/language/de/global.json +++ b/public/language/de/global.json @@ -68,6 +68,7 @@ "users": "Benutzer", "topics": "Themen", "posts": "Beiträge", + "crossposts": "Cross-posts", "x-posts": "%1 Beiträge", "x-topics": "%1 Themen", "x-reputation": "%1 Reputation", @@ -82,6 +83,7 @@ "downvoted": "Negativ bewertet", "views": "Aufrufe", "posters": "Kommentatoren", + "watching": "Watching", "reputation": "Ansehen", "lastpost": "Letzter Beitrag", "firstpost": "Erster Beitrag", diff --git a/public/language/de/groups.json b/public/language/de/groups.json index fc270d3858..18c4a01585 100644 --- a/public/language/de/groups.json +++ b/public/language/de/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Alle Gruppen", "groups": "Gruppen", "members": "Mitglieder", + "x-members": "%1 member(s)", "view-group": "Gruppe zeigen", "owner": "Gruppenbesitzer", "new-group": "Neue Gruppe erstellen", diff --git a/public/language/de/modules.json b/public/language/de/modules.json index 5edc6169e8..38d3f657e0 100644 --- a/public/language/de/modules.json +++ b/public/language/de/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Benutzer hinzufügen", "chat.notification-settings": "Benachrichtigungseinstellungen", "chat.default-notification-setting": "Standardeinstellung für die Benachrichtigung", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Raum Standard", "chat.notification-setting-none": "Keine Benachrichtigungen", "chat.notification-setting-at-mention-only": "@nur Erwähnung", @@ -81,7 +82,7 @@ "composer.hide-preview": "Vorschau ausblenden", "composer.help": "Hilfe", "composer.user-said-in": "%1 sagte in %2:", - "composer.user-said": "%1 sagte:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Bist du sicher, dass du diesen Beitrag verwerfen möchtest?", "composer.submit-and-lock": "Einreichen und Sperren", "composer.toggle-dropdown": "Menu aus-/einblenden", diff --git a/public/language/de/notifications.json b/public/language/de/notifications.json index 47b5cc6e0d..2dc78e1c81 100644 --- a/public/language/de/notifications.json +++ b/public/language/de/notifications.json @@ -22,7 +22,7 @@ "upvote": "Positive Bewertungen", "awards": "Auszeichnungen", "new-flags": "Neue Meldungen", - "my-flags": "Mir zugewiesene Markierungen", + "my-flags": "My Flags", "bans": "Verbannungen", "new-message-from": "Neue Nachricht von %1", "new-messages-from": "%1 neue Nachrichten von %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 und %2 schrieben in %4", "user-posted-in-public-room-triple": "%1, %2 und %3 schrieben in %5", "user-posted-in-public-room-multiple": "%1, %2 und %3 andere schrieben in %5", - "upvoted-your-post-in": "%1 hat deinen Beitrag in %2 positiv bewertet.", - "upvoted-your-post-in-dual": "%1 und %2 haben deinen Beitrag in %3 positiv bewertet.", - "upvoted-your-post-in-triple": "%1, %2 und %3 haben deinen Beitrag in %4 positiv bewertet.", - "upvoted-your-post-in-multiple": "%1, %2 und %3 andere haben deinen Beitrag in %4 positiv bewertet.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 hat deinen Beitrag nach %2 verschoben.", "moved-your-topic": "%1 hat %2 verschoben.", - "user-flagged-post-in": "%1 hat einen Beitrag in %2 gemeldet", - "user-flagged-post-in-dual": "%1 und %2 haben einen Beitrag in %3 gemeldet", - "user-flagged-post-in-triple": "%1, %2 und %3 meldeten einen Beitrag in %4", - "user-flagged-post-in-multiple": "%1, %2 und %3 andere meldeten einen Beitrag in %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 meldete ein Nutzerprofil (%2)", "user-flagged-user-dual": "%1 und %2 meldeten ein Nutzerprofil (%3)", "user-flagged-user-triple": "%1, %2 und %3 meldeten ein Benutzerprofil (%4)", "user-flagged-user-multiple": "%1, %2 und %3 andere meldeten ein Benutzerprofil (%4)", - "user-posted-to": "%1 hat auf %2 geantwortet.", - "user-posted-to-dual": "%1 und %2 haben auf %3 geantwortet.", - "user-posted-to-triple": "%1, %2 und %3 haben geantwortet auf: %4", - "user-posted-to-multiple": "%1, %2 und %3 andere haben geantwortet auf: %4", - "user-posted-topic": "%1 hat ein neues Thema erstellt: %2", - "user-edited-post": "%1 hat einen Post in %2 bearbeitet", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 hat ein neues Thema in %2 erstellt", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 folgt dir jetzt.", "user-started-following-you-dual": "%1 und %2 folgen dir jetzt.", "user-started-following-you-triple": "%1, %2 und %3 folgen dir jetzt.", @@ -71,11 +71,11 @@ "users-csv-exported": "Benutzer im CSV-Format exportiert, zum Herunterladen klicken", "post-queue-accepted": "Ihr Post in der Warteschlange wurde akzeptiert. Klicken Sie hier, um Ihren Beitrag anzuzeigen.", "post-queue-rejected": "Ihr Post in der Warteschlange wurde abgelehnt.", - "post-queue-notify": "Post in der Warteschlange hat eine Benachrichtigung erhalten:
„%1“", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-Mail bestätigt", "email-confirmed-message": "Vielen Dank für Ihre E-Mail-Validierung. Ihr Konto ist nun vollständig aktiviert.", "email-confirm-error-message": "Es gab ein Problem bei der Validierung Ihrer E-Mail-Adresse. Möglicherweise ist der Code ungültig oder abgelaufen.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Bestätigungs-E-Mail gesendet.", "none": "Nichts", "notification-only": "Nur Benachrichtigungen", diff --git a/public/language/de/social.json b/public/language/de/social.json index 0e8ce251b3..1ae8106ef9 100644 --- a/public/language/de/social.json +++ b/public/language/de/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Mit Facebook anmelden", "continue-with-facebook": "Mit Facebook fortsetzen", "sign-in-with-linkedin": "Mit LinkedIn anmelden", - "sign-up-with-linkedin": "Mit LinkedIn registrieren" + "sign-up-with-linkedin": "Mit LinkedIn registrieren", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/de/themes/harmony.json b/public/language/de/themes/harmony.json index dc403bdec3..9a9aa225b2 100644 --- a/public/language/de/themes/harmony.json +++ b/public/language/de/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Einklappen", "expand": "Ausklappen", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/de/topic.json b/public/language/de/topic.json index 67a22712f3..e82f135a39 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -61,14 +61,16 @@ "user-restored-topic-on": "%1 stellte dieses Thema am %2 wieder her", "user-moved-topic-from-ago": "%1 verschob dieses Thema von %2 %3", "user-moved-topic-from-on": "%1 verschob dieses Thema von %2 am %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "user-shared-topic-ago": "%1 hat dieses Thema geteilt %2", + "user-shared-topic-on": "%1 hat dieses Thema am %2 geteilt.", "user-queued-post-ago": "%1 hat Beitrag für Überprüfung markiert %3", "user-queued-post-on": "%1 hat Beitrag am %3 für Überprüfung markiert", "user-referenced-topic-ago": "%1 hat auf dieses Thema verwiesen %3", "user-referenced-topic-on": "%1 hat am %3 auf dieses Thema verwiesen", "user-forked-topic-ago": "%1 hat dieses Thema aufgespalten %3", "user-forked-topic-on": "%1 hat dieses Thema am %3 aufgespalten", + "user-crossposted-topic-ago": "%1 hat dieses Thema auch in %2 und %3 gepostet.", + "user-crossposted-topic-on": "%1 hat dieses Thema am %3 in %2 gepostet.", "bookmark-instructions": "Klicke hier, um zum letzten gelesenen Beitrag des Themas zurückzukehren.", "flag-post": "Diesen Post melden", "flag-user": "Diesen Benutzer melden", @@ -103,6 +105,7 @@ "thread-tools.lock": "Thema schließen", "thread-tools.unlock": "Thema öffnen", "thread-tools.move": "Thema verschieben", + "thread-tools.crosspost": "Crosspost-Thema", "thread-tools.move-posts": "Beiträge verschieben", "thread-tools.move-all": "Alle verschieben", "thread-tools.change-owner": "Besitzer ändern", @@ -132,15 +135,17 @@ "pin-modal-help": "Optional können Sie hier ein Ablaufdatum für das gepinnte Thema (die gepinnten Themen) festlegen. Alternativ können Sie dieses Feld leer lassen, damit das Thema fixiert bleibt, bis es manuell gelöst wird.", "load-categories": "Kategorien laden", "confirm-move": "Verschieben", + "confirm-crosspost": "Cross-post", "confirm-fork": "Aufspalten", "bookmark": "Lesezeichen", "bookmarks": "Lesezeichen", "bookmarks.has-no-bookmarks": "Du hast noch keine Beiträge mit Lesezeichen markiert.", "copy-permalink": "Permalink kopieren", - "go-to-original": "View Original Post", + "go-to-original": "Originalbeitrag anzeigen", "loading-more-posts": "Lade mehr Beiträge", "move-topic": "Thema verschieben", "move-topics": "Themen verschieben", + "crosspost-topic": "Cross-Post-Thema", "move-post": "Beitrag verschieben", "post-moved": "Beitrag wurde verschoben!", "fork-topic": "Thema aufspalten", @@ -162,7 +167,10 @@ "move-posts-instruction": "Klicken Sie auf die Beiträge, die Sie verschieben möchten, und geben Sie dann eine Themen-ID ein oder gehen Sie zum Zielthema", "move-topic-instruction": "Wähle die Ziel-Kategorie und klicke \"Verschieben\"", "change-owner-instruction": "Klicke auf die Beiträge, die einem anderen Benutzer zugeordnet werden sollen", - "manage-editors-instruction": "Manage the users who can edit this post below.", + "manage-editors-instruction": "Verwalte unten die Leute, die diesen Beitrag bearbeiten können.", + "crossposts.instructions": "Wähle eine oder mehrere Kategorien aus, in denen du einen Beitrag veröffentlichen möchtest. Die Themen sind dann in der ursprünglichen Kategorie und in allen Kategorien, in denen der Beitrag veröffentlicht wurde, zugänglich.", + "crossposts.listing": "Dieses Thema wurde auch in den folgenden lokalen Kategorien gepostet:", + "crossposts.none": "Dieses Thema wurde in keine weiteren Kategorien gepostet.", "composer.title-placeholder": "Hier den Titel des Themas eingeben...", "composer.handle-placeholder": "Gib deinen Namen/Nick hier ein", "composer.hide": "Verstecken", @@ -174,6 +182,7 @@ "composer.replying-to": "Antworte auf %1", "composer.new-topic": "Neues Thema", "composer.editing-in": "Bearbeite Beitrag in %1", + "composer.untitled-topic": "Thema ohne Titel", "composer.uploading": "Lade hoch...", "composer.thumb-url-label": "Vorschaubild-URL hier einfügen", "composer.thumb-title": "Vorschaubild zu diesem Thema hinzufügen", @@ -214,15 +223,18 @@ "last-post": "Letzter Beitrag", "go-to-my-next-post": "Zu meinem nächsten Beitrag gehen", "no-more-next-post": "Du hast keine weiteren Beiträge zu diesem Thema", - "open-composer": "Open composer", + "open-composer": "Composer öffnen", "post-quick-reply": "Schnell antworten", "navigator.index": "Beitrag %1 von %2", "navigator.unread": "%1 ungelesen", - "upvote-post": "Upvote post", - "downvote-post": "Downvote post", - "post-tools": "Post tools", - "unread-posts-link": "Unread posts link", - "thumb-image": "Topic thumbnail image", - "announcers": "Shares", - "announcers-x": "Shares (%1)" + "upvote-post": "Beitrag positiv bewerten", + "downvote-post": "Beitrag negativ bewerten", + "post-tools": "Beitrag-Tools", + "unread-posts-link": "Link zu ungelesenen Beiträgen", + "thumb-image": "Miniaturbild zum Thema", + "announcers": "Geteilt", + "announcers-x": "geteilte (1 %)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/de/user.json b/public/language/de/user.json index 4e31967358..4f2fba1550 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -105,6 +105,10 @@ "show-email": "Meine E-Mail anzeigen", "show-fullname": "Zeige meinen kompletten Namen an", "restrict-chats": "Erlaube Chatnachrichten nur von Benutzern, denen ich folge.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Zusammenfassung abonnieren", "digest-description": "Abonniere E-Mail-Benachrichtigungen für dieses Forum (neue Benachrichtigungen und Themen) nach einem festen Zeitplan.", "digest-off": "Aus", diff --git a/public/language/de/world.json b/public/language/de/world.json index 3753335278..33dec67049 100644 --- a/public/language/de/world.json +++ b/public/language/de/world.json @@ -1,8 +1,13 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "Hilfe", "help.title": "What is this page?", "help.intro": "Welcome to your corner of the fediverse.", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/el/admin/advanced/cache.json b/public/language/el/admin/advanced/cache.json index 4e3576c903..d4d1741968 100644 --- a/public/language/el/admin/advanced/cache.json +++ b/public/language/el/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Προσωρινή μνήμη ανάρτησης", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Πλήρες", "post-cache-size": "Μέγεθος προσωρινής μνήμης ανάρτησης", "items-in-cache": "Αντικείμενα στην προσωρινή μνήμη" diff --git a/public/language/el/admin/dashboard.json b/public/language/el/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/el/admin/dashboard.json +++ b/public/language/el/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/el/admin/development/info.json b/public/language/el/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/el/admin/development/info.json +++ b/public/language/el/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/el/admin/manage/categories.json +++ b/public/language/el/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/el/admin/manage/custom-reasons.json b/public/language/el/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/el/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/el/admin/manage/privileges.json b/public/language/el/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/el/admin/manage/privileges.json +++ b/public/language/el/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/el/admin/manage/users.json b/public/language/el/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/el/admin/manage/users.json +++ b/public/language/el/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/el/admin/settings/activitypub.json b/public/language/el/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/el/admin/settings/activitypub.json +++ b/public/language/el/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/el/admin/settings/chat.json b/public/language/el/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/el/admin/settings/chat.json +++ b/public/language/el/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/el/admin/settings/email.json b/public/language/el/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/el/admin/settings/email.json +++ b/public/language/el/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/el/admin/settings/notifications.json b/public/language/el/admin/settings/notifications.json index b38c65a34c..a808394950 100644 --- a/public/language/el/admin/settings/notifications.json +++ b/public/language/el/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Ειδοποίηση καλωσορίσματος", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/el/admin/settings/uploads.json b/public/language/el/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/el/admin/settings/uploads.json +++ b/public/language/el/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/el/admin/settings/user.json b/public/language/el/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/el/admin/settings/user.json +++ b/public/language/el/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/el/admin/settings/web-crawler.json b/public/language/el/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/el/admin/settings/web-crawler.json +++ b/public/language/el/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/el/aria.json b/public/language/el/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/el/aria.json +++ b/public/language/el/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/el/category.json b/public/language/el/category.json index 6e68ece8e6..38d8874796 100644 --- a/public/language/el/category.json +++ b/public/language/el/category.json @@ -1,12 +1,13 @@ { "category": "Κατηγορία", "subcategories": "Υποκατηγορίες", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Νέο Θέμα", "guest-login-post": "Συνδέσου για να δημοσιεύσεις", "no-topics": "Δεν υπάρχουν θέματα σε αυτή την κατηγορία.
Γιατί δεν δοκιμάζεις να δημοσιεύσεις ένα εσύ;", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "περιηγούνται", "no-replies": "Κανείς δεν έχει απαντήσει", "no-new-posts": "Δεν υπάρχουν νέες δημοσιεύσεις", diff --git a/public/language/el/error.json b/public/language/el/error.json index bd0de100c1..8e7c03028a 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Φαίνεται πως δεν είσαι συνδεδεμένος/η.", "account-locked": "Ο λογαριασμός σου έχει κλειδωθεί προσωρινά", "search-requires-login": "Searching requires an account - please login or register.", @@ -146,6 +147,7 @@ "post-already-restored": "This post has already been restored", "topic-already-deleted": "This topic has already been deleted", "topic-already-restored": "This topic has already been restored", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Οι εικόνες θεμάτων είναι απενεργοποιημένες", "invalid-file": "Άκυρο Αρχείο", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "Δεν μπορείς να συνομιλήσεις με τον εαυτό σου!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/el/global.json b/public/language/el/global.json index b1fe2ca73a..bab622f332 100644 --- a/public/language/el/global.json +++ b/public/language/el/global.json @@ -68,6 +68,7 @@ "users": "Χρήστες", "topics": "Θέματα", "posts": "Δημοσιεύσεις", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Εμφανίσεις", "posters": "Posters", + "watching": "Watching", "reputation": "Φήμη", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/el/groups.json b/public/language/el/groups.json index 483a72fccb..28b9f0d948 100644 --- a/public/language/el/groups.json +++ b/public/language/el/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Ομάδες", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Προβολή Ομάδας", "owner": "Κάτοχος Ομάδας", "new-group": "Δημιουργία Νέας Ομάδας", diff --git a/public/language/el/modules.json b/public/language/el/modules.json index a1d1259471..d13931d5a2 100644 --- a/public/language/el/modules.json +++ b/public/language/el/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 said in %2:", - "composer.user-said": "%1 said:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Are you sure you wish to discard this post?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/el/notifications.json b/public/language/el/notifications.json index aa0d06ca53..6ed64ec9e5 100644 --- a/public/language/el/notifications.json +++ b/public/language/el/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "New message from %1", "new-messages-from": "%1 new messages from %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 has upvoted your post in %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", "user-flagged-post-in": "%1 flagged a post in %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 has posted a reply to: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 started following you.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Στάλθηκε email επιβεβαίωσης.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/el/social.json b/public/language/el/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/el/social.json +++ b/public/language/el/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/el/themes/harmony.json b/public/language/el/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/el/themes/harmony.json +++ b/public/language/el/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/el/topic.json b/public/language/el/topic.json index d7acb0c3ae..3b6d5abe15 100644 --- a/public/language/el/topic.json +++ b/public/language/el/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Κλείδωμα Θέματος", "thread-tools.unlock": "Ξεκλείδωμα Θέματος", "thread-tools.move": "Μετακίνηση Θέματος", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Μετακίνηση Όλων", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Οι Κατηγορίες Φορτώνουν", "confirm-move": "Μετακίνηση", + "confirm-crosspost": "Cross-post", "confirm-fork": "Διαχωρισμός", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Φόρτωση περισσότερων δημοσιεύσεων", "move-topic": "Μετακίνηση Θέματος", "move-topics": "Μετακίνηση Θεμάτων", + "crosspost-topic": "Cross-post Topic", "move-post": "Μετακίνηση Δημοσίευσης", "post-moved": "Η δημοσίευση μετακινήθηκε!", "fork-topic": "Διαχωρισμός Θέματος", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Εισαγωγή του τίτλου του θέματος εδώ...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Απάντηση στο %1", "composer.new-topic": "Νέο Θέμα", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "ανέβασμα...", "composer.thumb-url-label": "Επικόλληση του URL της εικόνας του θέματος", "composer.thumb-title": "Προσθήκη μιας εικόνας στο θέμα", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/el/user.json b/public/language/el/user.json index 72b50d4860..c4a0a364bd 100644 --- a/public/language/el/user.json +++ b/public/language/el/user.json @@ -105,6 +105,10 @@ "show-email": "Εμφάνιση του email μου", "show-fullname": "Show My Full Name", "restrict-chats": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Εγγραφή στην Σύνοψη", "digest-description": "Εγγράψου σε ενημερώσεις με email για αυτό το φόρουμ (νεες ειδοποιήσεις και θέματα), βάσει του επιλεγμένου προγράμματος", "digest-off": "Off", diff --git a/public/language/el/world.json b/public/language/el/world.json index 3753335278..e6694bf507 100644 --- a/public/language/el/world.json +++ b/public/language/el/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/en-GB/admin/advanced/cache.json b/public/language/en-GB/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/en-GB/admin/advanced/cache.json +++ b/public/language/en-GB/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/en-GB/admin/dashboard.json b/public/language/en-GB/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/en-GB/admin/dashboard.json +++ b/public/language/en-GB/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/en-GB/admin/development/info.json b/public/language/en-GB/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/en-GB/admin/development/info.json +++ b/public/language/en-GB/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/en-GB/admin/manage/custom-reasons.json b/public/language/en-GB/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/en-GB/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/en-GB/admin/manage/privileges.json b/public/language/en-GB/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/en-GB/admin/manage/privileges.json +++ b/public/language/en-GB/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/en-GB/admin/manage/users.json b/public/language/en-GB/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/en-GB/admin/manage/users.json +++ b/public/language/en-GB/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/en-GB/admin/settings/chat.json b/public/language/en-GB/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/en-GB/admin/settings/chat.json +++ b/public/language/en-GB/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/en-GB/admin/settings/email.json b/public/language/en-GB/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/en-GB/admin/settings/email.json +++ b/public/language/en-GB/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/en-GB/admin/settings/notifications.json b/public/language/en-GB/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/en-GB/admin/settings/notifications.json +++ b/public/language/en-GB/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/en-GB/admin/settings/uploads.json b/public/language/en-GB/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/en-GB/admin/settings/uploads.json +++ b/public/language/en-GB/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/en-GB/admin/settings/user.json b/public/language/en-GB/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/en-GB/admin/settings/user.json +++ b/public/language/en-GB/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/en-GB/admin/settings/web-crawler.json b/public/language/en-GB/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/en-GB/admin/settings/web-crawler.json +++ b/public/language/en-GB/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/en-GB/aria.json b/public/language/en-GB/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/en-GB/aria.json +++ b/public/language/en-GB/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/en-GB/category.json b/public/language/en-GB/category.json index a0498414fb..8d0cde6eae 100644 --- a/public/language/en-GB/category.json +++ b/public/language/en-GB/category.json @@ -1,12 +1,13 @@ { "category": "Category", "subcategories": "Subcategories", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "New Topic", "guest-login-post": "Log in to post", "no-topics": "There are no topics in this category.
Why don't you try posting one?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "browsing", "no-replies": "No one has replied", diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index a8f8bfe732..61231b11a8 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -4,6 +4,8 @@ "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", + "not-logged-in": "You don't seem to be logged in.", "account-locked": "Your account has been locked temporarily", "search-requires-login": "Searching requires an account - please login or register.", @@ -168,6 +170,8 @@ "topic-already-deleted": "This topic has already been deleted", "topic-already-restored": "This topic has already been restored", + "topic-already-crossposted": "This topic has already been cross-posted there.", + "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Topic thumbnails are disabled.", @@ -179,6 +183,8 @@ "cant-chat-with-yourself": "You can't chat with yourself!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -258,6 +264,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", @@ -269,6 +276,7 @@ "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -284,6 +292,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/en-GB/global.json b/public/language/en-GB/global.json index 4ca7816332..8c76edde4c 100644 --- a/public/language/en-GB/global.json +++ b/public/language/en-GB/global.json @@ -81,6 +81,7 @@ "users": "Users", "topics": "Topics", "posts": "Posts", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -95,6 +96,7 @@ "downvoted": "Downvoted", "views": "Views", "posters": "Posters", + "watching": "Watching", "reputation": "Reputation", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/en-GB/groups.json b/public/language/en-GB/groups.json index ccf1590eff..30bf4eafc4 100644 --- a/public/language/en-GB/groups.json +++ b/public/language/en-GB/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Groups", "members": "Members", + "x-members": "%1 member(s)", "view-group": "View Group", "owner": "Group Owner", "new-group": "Create New Group", diff --git a/public/language/en-GB/modules.json b/public/language/en-GB/modules.json index 29ba02726f..44e435ae33 100644 --- a/public/language/en-GB/modules.json +++ b/public/language/en-GB/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -83,7 +84,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 said in %2:", - "composer.user-said": "%1 said:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Are you sure you wish to discard this post?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/en-GB/notifications.json b/public/language/en-GB/notifications.json index f4d04ebe27..43eaa603b3 100644 --- a/public/language/en-GB/notifications.json +++ b/public/language/en-GB/notifications.json @@ -24,7 +24,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", @@ -36,10 +36,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 has upvoted your post in %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", "user-flagged-post-in": "%1 flagged a post in %2", @@ -50,19 +50,19 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to" : "%1 has posted a reply to: %2", - "user-posted-to-dual" : "%1 and %2 have posted replies to: %3", - "user-posted-to-triple" : "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple" : "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post" : "%1 has edited a post in %2", + "user-posted-to" : "%1 posted a reply in %2", + "user-posted-to-dual" : "%1 and %2 replied in %3", + "user-posted-to-triple" : "%1, %2 and %3 replied in %4", + "user-posted-to-multiple" : "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post" : "%1 edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 started following you.", "user-started-following-you-dual": "%1 and %2 started following you.", @@ -78,12 +78,12 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Confirmation email sent.", "none": "None", diff --git a/public/language/en-GB/social.json b/public/language/en-GB/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/en-GB/social.json +++ b/public/language/en-GB/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/en-GB/themes/harmony.json b/public/language/en-GB/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/en-GB/themes/harmony.json +++ b/public/language/en-GB/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index bfb3101c2c..d7533b80f3 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -74,6 +74,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions" : "Click here to return to the last read post in this thread.", @@ -116,6 +118,7 @@ "thread-tools.lock": "Lock Topic", "thread-tools.unlock": "Unlock Topic", "thread-tools.move": "Move Topic", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Move All", "thread-tools.change-owner": "Change Owner", @@ -149,6 +152,7 @@ "load-categories": "Loading Categories", "confirm-move": "Move", + "confirm-crosspost": "Cross-post", "confirm-fork": "Fork", "bookmark": "Bookmark", @@ -161,6 +165,7 @@ "loading-more-posts": "Loading More Posts", "move-topic": "Move Topic", "move-topics": "Move Topics", + "crosspost-topic": "Cross-post Topic", "move-post": "Move Post", "post-moved": "Post moved!", "fork-topic": "Fork Topic", @@ -184,6 +189,10 @@ "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", + "composer.title-placeholder": "Enter your topic title here...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -195,6 +204,7 @@ "composer.replying-to": "Replying to %1", "composer.new-topic": "New Topic", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "uploading...", "composer.thumb-url-label": "Paste a topic thumbnail URL", @@ -254,5 +264,10 @@ "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" + } diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index 42039d997f..3e0fab1e63 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -111,6 +111,10 @@ "show-email": "Show My Email", "show-fullname": "Show My Full Name", "restrict-chats": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Subscribe to Digest", "digest-description": "Subscribe to email updates for this forum (new notifications and topics) according to a set schedule", "digest-off": "Off", diff --git a/public/language/en-GB/world.json b/public/language/en-GB/world.json index 3753335278..e6694bf507 100644 --- a/public/language/en-GB/world.json +++ b/public/language/en-GB/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/en-US/admin/advanced/cache.json b/public/language/en-US/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/en-US/admin/advanced/cache.json +++ b/public/language/en-US/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/en-US/admin/dashboard.json b/public/language/en-US/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/en-US/admin/dashboard.json +++ b/public/language/en-US/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/en-US/admin/development/info.json b/public/language/en-US/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/en-US/admin/development/info.json +++ b/public/language/en-US/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/en-US/admin/manage/categories.json b/public/language/en-US/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/en-US/admin/manage/categories.json +++ b/public/language/en-US/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/en-US/admin/manage/custom-reasons.json b/public/language/en-US/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/en-US/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/en-US/admin/manage/privileges.json b/public/language/en-US/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/en-US/admin/manage/privileges.json +++ b/public/language/en-US/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/en-US/admin/manage/users.json b/public/language/en-US/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/en-US/admin/manage/users.json +++ b/public/language/en-US/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/en-US/admin/settings/activitypub.json b/public/language/en-US/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/en-US/admin/settings/activitypub.json +++ b/public/language/en-US/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/en-US/admin/settings/chat.json b/public/language/en-US/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/en-US/admin/settings/chat.json +++ b/public/language/en-US/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/en-US/admin/settings/email.json b/public/language/en-US/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/en-US/admin/settings/email.json +++ b/public/language/en-US/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/en-US/admin/settings/notifications.json b/public/language/en-US/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/en-US/admin/settings/notifications.json +++ b/public/language/en-US/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/en-US/admin/settings/uploads.json b/public/language/en-US/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/en-US/admin/settings/uploads.json +++ b/public/language/en-US/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/en-US/admin/settings/user.json b/public/language/en-US/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/en-US/admin/settings/user.json +++ b/public/language/en-US/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/en-US/admin/settings/web-crawler.json b/public/language/en-US/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/en-US/admin/settings/web-crawler.json +++ b/public/language/en-US/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/en-US/aria.json b/public/language/en-US/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/en-US/aria.json +++ b/public/language/en-US/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/en-US/category.json b/public/language/en-US/category.json index b20d137139..f4d73eccba 100644 --- a/public/language/en-US/category.json +++ b/public/language/en-US/category.json @@ -1,12 +1,13 @@ { "category": "Category", "subcategories": "Subcategories", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "New Topic", "guest-login-post": "Log in to post", "no-topics": "There are no topics in this category.
Why don't you try posting one?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "browsing", "no-replies": "No one has replied", "no-new-posts": "No new posts.", diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index 7f924fc7ac..0ec4d83c98 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "You don't seem to be logged in.", "account-locked": "Your account has been locked temporarily", "search-requires-login": "Searching requires an account - please login or register.", @@ -146,6 +147,7 @@ "post-already-restored": "This post has already been restored", "topic-already-deleted": "This topic has already been deleted", "topic-already-restored": "This topic has already been restored", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Topic thumbnails are disabled.", "invalid-file": "Invalid File", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "You can't chat with yourself!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/en-US/global.json b/public/language/en-US/global.json index aa95d59bb4..0b95f7d360 100644 --- a/public/language/en-US/global.json +++ b/public/language/en-US/global.json @@ -68,6 +68,7 @@ "users": "Users", "topics": "Topics", "posts": "Posts", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Views", "posters": "Posters", + "watching": "Watching", "reputation": "Reputation", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/en-US/groups.json b/public/language/en-US/groups.json index 25fe9c75e6..4bf47bdd80 100644 --- a/public/language/en-US/groups.json +++ b/public/language/en-US/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Groups", "members": "Members", + "x-members": "%1 member(s)", "view-group": "View Group", "owner": "Group Owner", "new-group": "Create New Group", diff --git a/public/language/en-US/modules.json b/public/language/en-US/modules.json index a1d1259471..d13931d5a2 100644 --- a/public/language/en-US/modules.json +++ b/public/language/en-US/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 said in %2:", - "composer.user-said": "%1 said:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Are you sure you wish to discard this post?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/en-US/notifications.json b/public/language/en-US/notifications.json index 924c0c9490..c445120343 100644 --- a/public/language/en-US/notifications.json +++ b/public/language/en-US/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "New message from %1", "new-messages-from": "%1 new messages from %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 has upvoted your post in %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", "user-flagged-post-in": "%1 flagged a post in %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 has posted a reply to: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 started following you.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Confirmation email sent.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/en-US/social.json b/public/language/en-US/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/en-US/social.json +++ b/public/language/en-US/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/en-US/themes/harmony.json b/public/language/en-US/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/en-US/themes/harmony.json +++ b/public/language/en-US/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/en-US/topic.json b/public/language/en-US/topic.json index 25ff8a6dc9..0e1bcdfa97 100644 --- a/public/language/en-US/topic.json +++ b/public/language/en-US/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lock Topic", "thread-tools.unlock": "Unlock Topic", "thread-tools.move": "Move Topic", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Move All", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Loading Categories", "confirm-move": "Move", + "confirm-crosspost": "Cross-post", "confirm-fork": "Fork", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Loading More Posts", "move-topic": "Move Topic", "move-topics": "Move Topics", + "crosspost-topic": "Cross-post Topic", "move-post": "Move Post", "post-moved": "Post moved!", "fork-topic": "Fork Topic", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Enter your topic title here...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Replying to %1", "composer.new-topic": "New Topic", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "uploading...", "composer.thumb-url-label": "Paste a topic thumbnail URL", "composer.thumb-title": "Add a thumbnail to this topic", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/en-US/user.json b/public/language/en-US/user.json index df8427096c..c716206cd8 100644 --- a/public/language/en-US/user.json +++ b/public/language/en-US/user.json @@ -105,6 +105,10 @@ "show-email": "Show My Email", "show-fullname": "Show My Full Name", "restrict-chats": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Subscribe to Digest", "digest-description": "Subscribe to email updates for this forum (new notifications and topics) according to a set schedule", "digest-off": "Off", diff --git a/public/language/en-US/world.json b/public/language/en-US/world.json index 3753335278..e6694bf507 100644 --- a/public/language/en-US/world.json +++ b/public/language/en-US/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/advanced/cache.json b/public/language/en-x-pirate/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/en-x-pirate/admin/advanced/cache.json +++ b/public/language/en-x-pirate/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/en-x-pirate/admin/dashboard.json b/public/language/en-x-pirate/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/en-x-pirate/admin/dashboard.json +++ b/public/language/en-x-pirate/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/en-x-pirate/admin/development/info.json b/public/language/en-x-pirate/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/en-x-pirate/admin/development/info.json +++ b/public/language/en-x-pirate/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/en-x-pirate/admin/manage/categories.json b/public/language/en-x-pirate/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/en-x-pirate/admin/manage/categories.json +++ b/public/language/en-x-pirate/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/en-x-pirate/admin/manage/custom-reasons.json b/public/language/en-x-pirate/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/en-x-pirate/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/manage/privileges.json b/public/language/en-x-pirate/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/en-x-pirate/admin/manage/privileges.json +++ b/public/language/en-x-pirate/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/en-x-pirate/admin/manage/users.json b/public/language/en-x-pirate/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/en-x-pirate/admin/manage/users.json +++ b/public/language/en-x-pirate/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/en-x-pirate/admin/settings/activitypub.json b/public/language/en-x-pirate/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/en-x-pirate/admin/settings/activitypub.json +++ b/public/language/en-x-pirate/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/en-x-pirate/admin/settings/chat.json b/public/language/en-x-pirate/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/en-x-pirate/admin/settings/chat.json +++ b/public/language/en-x-pirate/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/settings/email.json b/public/language/en-x-pirate/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/en-x-pirate/admin/settings/email.json +++ b/public/language/en-x-pirate/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/en-x-pirate/admin/settings/notifications.json b/public/language/en-x-pirate/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/en-x-pirate/admin/settings/notifications.json +++ b/public/language/en-x-pirate/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/settings/uploads.json b/public/language/en-x-pirate/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/en-x-pirate/admin/settings/uploads.json +++ b/public/language/en-x-pirate/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/en-x-pirate/admin/settings/user.json b/public/language/en-x-pirate/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/en-x-pirate/admin/settings/user.json +++ b/public/language/en-x-pirate/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/en-x-pirate/admin/settings/web-crawler.json b/public/language/en-x-pirate/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/en-x-pirate/admin/settings/web-crawler.json +++ b/public/language/en-x-pirate/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/en-x-pirate/aria.json b/public/language/en-x-pirate/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/en-x-pirate/aria.json +++ b/public/language/en-x-pirate/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/en-x-pirate/category.json b/public/language/en-x-pirate/category.json index 1dcf9b3829..dba822714c 100644 --- a/public/language/en-x-pirate/category.json +++ b/public/language/en-x-pirate/category.json @@ -1,12 +1,13 @@ { "category": "Category", "subcategories": "Subcategories", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "New Topic", "guest-login-post": "Log in to post", "no-topics": "Thar be no topics in 'tis category.
Why don't ye give a go' postin' one?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "browsin'", "no-replies": "No one has replied to ye message", "no-new-posts": "Thar be no new posts.", diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json index 7f924fc7ac..0ec4d83c98 100644 --- a/public/language/en-x-pirate/error.json +++ b/public/language/en-x-pirate/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "You don't seem to be logged in.", "account-locked": "Your account has been locked temporarily", "search-requires-login": "Searching requires an account - please login or register.", @@ -146,6 +147,7 @@ "post-already-restored": "This post has already been restored", "topic-already-deleted": "This topic has already been deleted", "topic-already-restored": "This topic has already been restored", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Topic thumbnails are disabled.", "invalid-file": "Invalid File", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "You can't chat with yourself!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/en-x-pirate/global.json b/public/language/en-x-pirate/global.json index 611e7a0b7d..b90e710f60 100644 --- a/public/language/en-x-pirate/global.json +++ b/public/language/en-x-pirate/global.json @@ -68,6 +68,7 @@ "users": "Users", "topics": "Topics", "posts": "Messages", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Views", "posters": "Posters", + "watching": "Watching", "reputation": "Reputation", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/en-x-pirate/groups.json b/public/language/en-x-pirate/groups.json index 25fe9c75e6..4bf47bdd80 100644 --- a/public/language/en-x-pirate/groups.json +++ b/public/language/en-x-pirate/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Groups", "members": "Members", + "x-members": "%1 member(s)", "view-group": "View Group", "owner": "Group Owner", "new-group": "Create New Group", diff --git a/public/language/en-x-pirate/modules.json b/public/language/en-x-pirate/modules.json index c78a052be8..eddf337f7f 100644 --- a/public/language/en-x-pirate/modules.json +++ b/public/language/en-x-pirate/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 said in %2:", - "composer.user-said": "%1 said:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Are you sure you wish to discard this post?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/en-x-pirate/notifications.json b/public/language/en-x-pirate/notifications.json index ba6111dd39..844e6f9c17 100644 --- a/public/language/en-x-pirate/notifications.json +++ b/public/language/en-x-pirate/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "New message from %1", "new-messages-from": "%1 new messages from %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 has upvoted your post in %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", "user-flagged-post-in": "%1 flagged a post in %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 has posted a reply to: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 started following you.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Confirmation email sent.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/en-x-pirate/social.json b/public/language/en-x-pirate/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/en-x-pirate/social.json +++ b/public/language/en-x-pirate/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/en-x-pirate/themes/harmony.json b/public/language/en-x-pirate/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/en-x-pirate/themes/harmony.json +++ b/public/language/en-x-pirate/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/en-x-pirate/topic.json b/public/language/en-x-pirate/topic.json index 25ff8a6dc9..0e1bcdfa97 100644 --- a/public/language/en-x-pirate/topic.json +++ b/public/language/en-x-pirate/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lock Topic", "thread-tools.unlock": "Unlock Topic", "thread-tools.move": "Move Topic", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Move All", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Loading Categories", "confirm-move": "Move", + "confirm-crosspost": "Cross-post", "confirm-fork": "Fork", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Loading More Posts", "move-topic": "Move Topic", "move-topics": "Move Topics", + "crosspost-topic": "Cross-post Topic", "move-post": "Move Post", "post-moved": "Post moved!", "fork-topic": "Fork Topic", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Enter your topic title here...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Replying to %1", "composer.new-topic": "New Topic", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "uploading...", "composer.thumb-url-label": "Paste a topic thumbnail URL", "composer.thumb-title": "Add a thumbnail to this topic", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/en-x-pirate/user.json b/public/language/en-x-pirate/user.json index 870d5e96fb..3e7886a378 100644 --- a/public/language/en-x-pirate/user.json +++ b/public/language/en-x-pirate/user.json @@ -105,6 +105,10 @@ "show-email": "Show My Email", "show-fullname": "Show My Full Name", "restrict-chats": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Subscribe to Digest", "digest-description": "Subscribe to email updates for this forum (new notifications and topics) according to a set schedule", "digest-off": "Off", diff --git a/public/language/en-x-pirate/world.json b/public/language/en-x-pirate/world.json index 3753335278..e6694bf507 100644 --- a/public/language/en-x-pirate/world.json +++ b/public/language/en-x-pirate/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/es/admin/advanced/cache.json b/public/language/es/admin/advanced/cache.json index ec5f2a5fce..378f5789ef 100644 --- a/public/language/es/admin/advanced/cache.json +++ b/public/language/es/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Publicar Cache", - "group-cache": "Agrupar cache", - "local-cache": "Cache local", - "object-cache": "Cache de objetos", "percent-full": "%1% Completo", "post-cache-size": "Tamaño de cache del post", "items-in-cache": "Artículos en cache" diff --git a/public/language/es/admin/dashboard.json b/public/language/es/admin/dashboard.json index 791546b5ee..211df77bc0 100644 --- a/public/language/es/admin/dashboard.json +++ b/public/language/es/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Vistas de la página registradas", "graphs.page-views-guest": "Vistas de la página visitantes", "graphs.page-views-bot": "Vistas de la página Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Visitantes Unicos", "graphs.registered-users": "Usuarios Registrados", "graphs.guest-users": "Guest Users", diff --git a/public/language/es/admin/development/info.json b/public/language/es/admin/development/info.json index 809d0c85bc..913f52a8bc 100644 --- a/public/language/es/admin/development/info.json +++ b/public/language/es/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "en-linea", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json index 23d64eb58a..2980f59ab2 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Configuración de Categoría", "edit-category": "Edit Category", "privileges": "Privilegios", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Nombre de Categoría", "handle": "Identificador de categoría ", "handle.help": "Tu identificador de categoría está siendo utilizado como representación de esta categoría a través de otras redes, similar al nombre de usuario. El identificador de la categoría no puede ser igual a un nombre de usuario o usuario de grupo existente.", "description": "Descripción de Categoría", - "federatedDescription": "Descripción federada", - "federatedDescription.help": "Este texto será agregado a la descripción de la categoría cuando sea buscado por otros sitios y aplicaciones.", - "federatedDescription.default": "Esta es una categoría de foro que contiene discusiones pasadas. Puedes iniciar nuevas discusiones mencionando esta categoría.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Color de Fondo", "text-color": "Color del Texto", "bg-image-size": "Tamaño de la Imagen de Fondo", @@ -103,6 +107,11 @@ "alert.create-success": "¡Categoría creada con éxito!", "alert.none-active": "No tienes categorías activas.", "alert.create": "Crear una Categoría", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

¿Realmente quieres purgar esta categoría\"%1\"?

¡Cuidado! ¡Todos los temas y respuestas en esta categoría serán purgados!

Purgar una categoría eliminará todos los temas y respuestas, y borrará la categoría de la base de datos. Si quieres eliminar una categoría temporalmente, deberías \"desactivar\" esa categoría en su lugar.

", "alert.purge-success": "¡Categoría purgada!", "alert.copy-success": "¡Configuración Copiada!", diff --git a/public/language/es/admin/manage/custom-reasons.json b/public/language/es/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/es/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/es/admin/manage/privileges.json b/public/language/es/admin/manage/privileges.json index 06f55e6fcd..a0f199bfd7 100644 --- a/public/language/es/admin/manage/privileges.json +++ b/public/language/es/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Acceder Temas", "create-topics": "Crear Temas", "reply-to-topics": "Responder a Temas", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Poner Tags (etiquetas) a Temas", "edit-posts": "Editar Entradas", diff --git a/public/language/es/admin/manage/user-custom-fields.json b/public/language/es/admin/manage/user-custom-fields.json index dab10670d2..49096d7021 100644 --- a/public/language/es/admin/manage/user-custom-fields.json +++ b/public/language/es/admin/manage/user-custom-fields.json @@ -1,28 +1,28 @@ { - "title": "Manage Custom User Fields", - "create-field": "Create Field", - "edit-field": "Edit Field", - "manage-custom-fields": "Manage Custom Fields", - "type-of-input": "Type of input", - "key": "Key", - "name": "Name", - "icon": "Icon", - "type": "Type", - "min-rep": "Minimum Reputation", - "input-type-text": "Input (Text)", - "input-type-link": "Input (Link)", - "input-type-number": "Input (Number)", - "input-type-date": "Input (Date)", - "input-type-select": "Select", - "input-type-select-multi": "Select Multiple", - "select-options": "Options", + "title": "Gestionar campos personalizados del usuario", + "create-field": "Crear campo", + "edit-field": "Editar campo", + "manage-custom-fields": "Gestionar campos personalizados", + "type-of-input": "Tipo de campo", + "key": "Clave", + "name": "Nombre", + "icon": "Icono", + "type": "Tipo", + "min-rep": "Reputación mínima", + "input-type-text": "Campo (Texto)", + "input-type-link": "Campo (Link)", + "input-type-number": "Campo (Número)", + "input-type-date": "Campo (Fecha)", + "input-type-select": "Selector", + "input-type-select-multi": "Selector múltiple", + "select-options": "Opciones", "select-options-help": "Add one option per line for the select element", - "minimum-reputation": "Minimum reputation", + "minimum-reputation": "Reputación mínima", "minimum-reputation-help": "If a user has less than this value they won't be able to use this field", "delete-field-confirm-x": "Do you really want to delete custom field \"%1\"?", - "custom-fields-saved": "Custom fields saved", - "visibility": "Visibility", - "visibility-all": "Everyone can see the field", - "visibility-loggedin": "Only logged in users can see the field", + "custom-fields-saved": "Campos personalizados guardados", + "visibility": "Visibilidad", + "visibility-all": "Todo el mundo puede ver este campo", + "visibility-loggedin": "Solo usuarios logeados pueden ver este campo", "visibility-privileged": "Only privileged users like admins & moderators can see the field" } \ No newline at end of file diff --git a/public/language/es/admin/manage/users.json b/public/language/es/admin/manage/users.json index 20f588179a..4bdf0f632e 100644 --- a/public/language/es/admin/manage/users.json +++ b/public/language/es/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Descargar CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Razón (Opcional)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Horas", "temp-ban.days": "Días", "temp-ban.explanation": "Introduzca la duración de esta expulsión. Ten en cuenta que 0 se considera una expulsión permanente.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "¿Quiere realmente expulsar a este usuario permanentemente?", "alerts.confirm-ban-multi": "¿Quiere realmente expulsar a estos usuarios permanentemente?", diff --git a/public/language/es/admin/settings/activitypub.json b/public/language/es/admin/settings/activitypub.json index 6e6585b295..5bc9a80078 100644 --- a/public/language/es/admin/settings/activitypub.json +++ b/public/language/es/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/es/admin/settings/chat.json b/public/language/es/admin/settings/chat.json index 52d0157ff0..16c393612c 100644 --- a/public/language/es/admin/settings/chat.json +++ b/public/language/es/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Máximo numero de usuarios en las salas de chat", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/es/admin/settings/email.json b/public/language/es/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/es/admin/settings/email.json +++ b/public/language/es/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/es/admin/settings/notifications.json b/public/language/es/admin/settings/notifications.json index 8d2509d75b..ea6d4b5835 100644 --- a/public/language/es/admin/settings/notifications.json +++ b/public/language/es/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Notificación de Bienvenida", "welcome-notification-link": "Enlace de Notificación de Bienvenida", "welcome-notification-uid": "Usuario de Notificación de Bienvenida (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/es/admin/settings/uploads.json b/public/language/es/admin/settings/uploads.json index 60273aa8ac..9e71229f80 100644 --- a/public/language/es/admin/settings/uploads.json +++ b/public/language/es/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Las imágenes más anchas que este valor serán rechazadas.", "reject-image-height": "Altura máxima de la imágen (en píxeles)", "reject-image-height-help": "Las imágenes más altas que este valor serán rechazadas.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Permitir a los usuarios subir imágenes en miniatura para los temas", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tamaño de la Imagen en Miniatura para el Tema", "allowed-file-extensions": "Permitir Extensiones de Archivo", "allowed-file-extensions-help": "Introduzca una lista de extensiones de archivos, separadas por comas, aquí (por ejemplo: pdf,xls,doc). Una lista vacía significa que se permiten todas las extensiones.", diff --git a/public/language/es/admin/settings/user.json b/public/language/es/admin/settings/user.json index 58271d447c..b4337296f1 100644 --- a/public/language/es/admin/settings/user.json +++ b/public/language/es/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Mostrar email", "show-fullname": "Mostrar nombre completo", "restrict-chat": "Solo permitir mensajes de chat de usuarios a los que sigo", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Abrir enlaces externos en una pestaña nueva", "topic-search": "Habilitar Búsqueda Dentro de Tema", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/es/admin/settings/web-crawler.json b/public/language/es/admin/settings/web-crawler.json index 111ab7182b..9f75f6758c 100644 --- a/public/language/es/admin/settings/web-crawler.json +++ b/public/language/es/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Deshabilitar RSS Feeds", "disable-sitemap-xml": "Deshabilitar Sitemap.xml", "sitemap-topics": "Número de Temas para mostrar en el Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Limpiar Caché del Sitemap", "view-sitemap": "Ver Sitemap" } \ No newline at end of file diff --git a/public/language/es/aria.json b/public/language/es/aria.json index fd05ccc7be..0eae0089a4 100644 --- a/public/language/es/aria.json +++ b/public/language/es/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Página de perfil para usuario %1", "user-watched-tags": "Etiquetas de usuario seguidas", "delete-upload-button": "Eliminar botón de subida ", - "group-page-link-for": "Link de página de grupo para %1" + "group-page-link-for": "Link de página de grupo para %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/es/category.json b/public/language/es/category.json index 6445dbc3be..5c08d422d6 100644 --- a/public/language/es/category.json +++ b/public/language/es/category.json @@ -1,12 +1,13 @@ { "category": "Categoría", "subcategories": "Subcategorías", - "uncategorized": "Sin categoría", - "uncategorized.description": "Temas que no encajan estrictamente con alguna categoría", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "Esta categoría puede ser seguida desde open social web con el identificador %1", "new-topic-button": "Nuevo tema", "guest-login-post": "Accede para escribir", "no-topics": "No hay temas en esta categoría.
¿Por qué no te animas y publicas uno?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "viendo ahora", "no-replies": "Nadie ha respondido aún", "no-new-posts": "No hay mensajes nuevos.", diff --git a/public/language/es/error.json b/public/language/es/error.json index 29ea8a5865..4877d507ea 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON no válido", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "No has iniciado sesión.", "account-locked": "Tu cuenta ha sido bloqueada temporalmente.", "search-requires-login": "¡Buscar requiere estar registrado! Por favor, entra o regístrate.", @@ -146,6 +147,7 @@ "post-already-restored": "Esta publicación ya ha sido restaurada", "topic-already-deleted": "Este tema ya ha sido borrado", "topic-already-restored": "Este tema ya ha sido restaurado", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "No puedes purgar el mensaje principal, por favor utiliza borrar tema", "topic-thumbnails-are-disabled": "Las miniaturas de los temas están deshabilitadas.", "invalid-file": "Archivo no válido", @@ -154,6 +156,8 @@ "about-me-too-long": "Lo sentimos, pero tu descripción no puede ser más larga de %1 caractere(s).", "cant-chat-with-yourself": "¡No puedes conversar contigo mismo!", "chat-restricted": "Este usuario tiene restringidos los mensajes de chat. Los usuarios deben seguirte antes de que pueda charlar con ellos", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "Has sido bloqueado por este usuario.", "chat-disabled": "El sistema de chat está deshabilitado", "too-many-messages": "Has enviado demasiados mensajes, por favor espera un poco.", @@ -225,6 +229,7 @@ "no-topics-selected": "¡No se han seleccionado temas!", "cant-move-to-same-topic": "¡No puedes mover el mensaje al mismo tema!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "¡No puedes bloquearte a tí mismo!", "cannot-block-privileged": "No puedes bloquear administradores o moderadores globales", "cannot-block-guest": "Los invitados no pueden bloquear a otros usuarios", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "ID de plugin inválido", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Instalación de extensiones vía ACP está deshabilitada", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/es/global.json b/public/language/es/global.json index 2b7ff143dc..a94ec5471c 100644 --- a/public/language/es/global.json +++ b/public/language/es/global.json @@ -68,6 +68,7 @@ "users": "Usuarios", "topics": "Temas", "posts": "Mensajes", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Votado negativamente", "views": "Visitas", "posters": "Posters", + "watching": "Watching", "reputation": "Reputación", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/es/groups.json b/public/language/es/groups.json index 98c603ece4..87a855e1d6 100644 --- a/public/language/es/groups.json +++ b/public/language/es/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Todos los grupos", "groups": "Grupos", "members": "Miembros", + "x-members": "%1 member(s)", "view-group": "Ver Grupo", "owner": "Propietario del Grupo", "new-group": "Crear Nuevo Grupo", diff --git a/public/language/es/modules.json b/public/language/es/modules.json index 1fd0eda552..6c98632e5c 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Ocultar Previsualización", "composer.help": "Help", "composer.user-said-in": "%1 dijo en %2:", - "composer.user-said": "%1 dijo:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "¿Estás seguro de que deseas descartar este mensaje?", "composer.submit-and-lock": "Enviar y Bloquear", "composer.toggle-dropdown": "Alternar desplegable", diff --git a/public/language/es/notifications.json b/public/language/es/notifications.json index 728af75bdc..7c4cc6d2f5 100644 --- a/public/language/es/notifications.json +++ b/public/language/es/notifications.json @@ -22,7 +22,7 @@ "upvote": "Votos positivos", "awards": "Premios", "new-flags": "Nuevos reportes", - "my-flags": "Reportado asignado a mí", + "my-flags": "My Flags", "bans": "Baneos", "new-message-from": "Nuevo mensaje de %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 ha votado positivamente tu respuesta en %2.", - "upvoted-your-post-in-dual": "%1 y %2 han votado positivamente tu respuesta en %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 su tema ha sido movido a %2", "moved-your-topic": "%1 ha movido %2", - "user-flagged-post-in": "%1 ha reportado una respuesta en %2", - "user-flagged-post-in-dual": "%1 y %2 han reportado un post en %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 reportó el perfil (%2)", "user-flagged-user-dual": "%1 y %2 reportaron el perfil (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 ha respondido a: %2", - "user-posted-to-dual": "%1 y %2 han respondido a %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 ha publicado un nuevo tema: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1ha publicado %2(etiquetado %3)", - "user-posted-topic-with-tag-dual": "%1 ha publicado %2(etiquetado %3 y %4) ", - "user-posted-topic-with-tag-triple": "%1 ha publicado %2 (etiquetado %3, %4 y %5)", - "user-posted-topic-with-tag-multiple": "%1 ha publicado %2 (etiquetado %3)", - "user-posted-topic-in-category": "%1 ha publicado un nuevo tema en %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 comenzó a seguirte.", "user-started-following-you-dual": "%1 y %2 comenzaron a seguirte.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Correo electrónico confirmado", "email-confirmed-message": "Gracias por validar tu correo electrónico. Tu cuenta ya está completamente activa.", "email-confirm-error-message": "Hubo un problema al validar tu cuenta de correo electrónico. Quizá el código era erróneo o expiró...", - "email-confirm-error-message-already-validated": "Tu dirección de correo ya ha sido validada.", "email-confirm-sent": "Correo de confirmación enviado.", "none": "Ninguno/a", "notification-only": "Solo Notificación", diff --git a/public/language/es/social.json b/public/language/es/social.json index 84474c4ecb..bebb366304 100644 --- a/public/language/es/social.json +++ b/public/language/es/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Accede con Facebook", "continue-with-facebook": "Regístrate con Facebook", "sign-in-with-linkedin": "Iniciar sesión con LinkedIn", - "sign-up-with-linkedin": "Registrarse con LinkedIn" + "sign-up-with-linkedin": "Registrarse con LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/es/themes/harmony.json b/public/language/es/themes/harmony.json index c93f1ab2b8..26e24c47b1 100644 --- a/public/language/es/themes/harmony.json +++ b/public/language/es/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Alternar barra lateral", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index 440bc2254a..072e21e832 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Haz click aquí para volver a tu último mensaje leído en este tema", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Cerrar tema", "thread-tools.unlock": "Reabrir tema", "thread-tools.move": "Mover tema", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Mover mensajes", "thread-tools.move-all": "Mover todo", "thread-tools.change-owner": "Cambiar propietario", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Cargando categorías", "confirm-move": "Mover", + "confirm-crosspost": "Cross-post", "confirm-fork": "Dividir", "bookmark": "Marcador", "bookmarks": "Marcadores", @@ -141,6 +145,7 @@ "loading-more-posts": "Cargando más mensajes", "move-topic": "Mover tema", "move-topics": "Mover temas", + "crosspost-topic": "Cross-post Topic", "move-post": "Mover mensaje", "post-moved": "¡Mensaje movido!", "fork-topic": "Dividir tema", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Haz click en los mensajes que quieres asignar a otro usuario", "manage-editors-instruction": "Gestionar abajo los usuarios que pueden editar esta entrada.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Ingresa el título de tu tema...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Ocultar", @@ -174,6 +182,7 @@ "composer.replying-to": "En respuesta a %1", "composer.new-topic": "Nuevo tema", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "subiendo...", "composer.thumb-url-label": "Agrega una URL de miniatura para el hilo", "composer.thumb-title": "Agregar miniatura a este tema", @@ -224,5 +233,8 @@ "unread-posts-link": "Enlace a entradas sin leer", "thumb-image": "Imagen miniatura del tema", "announcers": "Comparte", - "announcers-x": "Comparte (%1)" + "announcers-x": "Comparte (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/es/user.json b/public/language/es/user.json index 1566547851..76e9c3c181 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -105,6 +105,10 @@ "show-email": "Mostrar mi correo electrónico", "show-fullname": "Mostrar mi nombre completo", "restrict-chats": "Solo permitir mensajes de chat de usuarios a los que sigo", + "disable-incoming-chats": "Desactivar mensajes de chat entrantes", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Agregar usuario", "digest-label": "Suscribirse al resumen", "digest-description": "Suscribirse a actualizaciones por correo electrónico a este foro (nuevas notificaciones y temas) de acuerdo a una recurrencia definida", "digest-off": "Apagado", diff --git a/public/language/es/world.json b/public/language/es/world.json index 3753335278..e6694bf507 100644 --- a/public/language/es/world.json +++ b/public/language/es/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/et/admin/advanced/cache.json b/public/language/et/admin/advanced/cache.json index 52dc62df29..9f165676a6 100644 --- a/public/language/et/admin/advanced/cache.json +++ b/public/language/et/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Postituste vahemälu", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Täis", "post-cache-size": "Postituse vahemälu suurus", "items-in-cache": "Esemed vahemälus" diff --git a/public/language/et/admin/dashboard.json b/public/language/et/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/et/admin/dashboard.json +++ b/public/language/et/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/et/admin/development/info.json b/public/language/et/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/et/admin/development/info.json +++ b/public/language/et/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/et/admin/manage/categories.json b/public/language/et/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/et/admin/manage/categories.json +++ b/public/language/et/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/et/admin/manage/custom-reasons.json b/public/language/et/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/et/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/et/admin/manage/privileges.json b/public/language/et/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/et/admin/manage/privileges.json +++ b/public/language/et/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/et/admin/manage/users.json b/public/language/et/admin/manage/users.json index fed5c2083f..0fc43d5896 100644 --- a/public/language/et/admin/manage/users.json +++ b/public/language/et/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Lae alla CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Põhjus (valikuline)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Tunnid", "temp-ban.days": "Päevad", "temp-ban.explanation": "Sisesta keelustuse pikkus. Kui sisestad 0, siis seda loetakse igaveseks keelustuseks.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Kas te tõesti soovite antud kasutajat igaveseks keelustada ?", "alerts.confirm-ban-multi": "Kas te tõesti soovite antud kasutajaid igaveseks keelustada?", diff --git a/public/language/et/admin/settings/activitypub.json b/public/language/et/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/et/admin/settings/activitypub.json +++ b/public/language/et/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/et/admin/settings/chat.json b/public/language/et/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/et/admin/settings/chat.json +++ b/public/language/et/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/et/admin/settings/email.json b/public/language/et/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/et/admin/settings/email.json +++ b/public/language/et/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/et/admin/settings/notifications.json b/public/language/et/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/et/admin/settings/notifications.json +++ b/public/language/et/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/et/admin/settings/uploads.json b/public/language/et/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/et/admin/settings/uploads.json +++ b/public/language/et/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/et/admin/settings/user.json b/public/language/et/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/et/admin/settings/user.json +++ b/public/language/et/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/et/admin/settings/web-crawler.json b/public/language/et/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/et/admin/settings/web-crawler.json +++ b/public/language/et/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/et/aria.json b/public/language/et/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/et/aria.json +++ b/public/language/et/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/et/category.json b/public/language/et/category.json index c0099e4e54..ca0bf4e1a4 100644 --- a/public/language/et/category.json +++ b/public/language/et/category.json @@ -1,12 +1,13 @@ { "category": "Kategooria", "subcategories": "Alamkategooriad", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Uus teema", "guest-login-post": "Postitamiseks logi sisse", "no-topics": "Kahjuks ei leidu siin kategoorias ühtegi teemat.
Soovid postitada?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "vaatab", "no-replies": "Keegi pole vastanud", "no-new-posts": "Uusi postitusi pole", diff --git a/public/language/et/error.json b/public/language/et/error.json index fe75d69395..608f980b5d 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Sa ei ole sisse logitud", "account-locked": "Su kasutaja on ajutiselt lukustatud", "search-requires-login": "Otsing nõuab kasutajat - palun registreeruge või logige sisse.", @@ -146,6 +147,7 @@ "post-already-restored": "Postitus on juba taastatud", "topic-already-deleted": "Teema on juba kustutatud", "topic-already-restored": "Teema on juba taastatud", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Te ei saa eemaldada peamist postitust, pigem kustutage teema ära.", "topic-thumbnails-are-disabled": "Teema thumbnailid on keelatud.", "invalid-file": "Vigane fail", @@ -154,6 +156,8 @@ "about-me-too-long": "Vabandage, teie tutvustus ei saa olaa pikem kui %1 tähemärk(i).", "cant-chat-with-yourself": "Sa ei saa endaga vestelda!", "chat-restricted": "Kasutaja on piiranud sõnumite saatmist. Privaatsõnumi saatmiseks peab kasutaja sind jälgima", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Vestlus süsteem keelatud", "too-many-messages": "Oled saatnud liiga palju sõnumeid, oota natukene.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/et/global.json b/public/language/et/global.json index 74cbf9677a..06966e2234 100644 --- a/public/language/et/global.json +++ b/public/language/et/global.json @@ -68,6 +68,7 @@ "users": "Kasutajad", "topics": "Teemat", "posts": "Postitust", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Hääletas vastu", "views": "Vaatamist", "posters": "Posters", + "watching": "Watching", "reputation": "Reputatsioon", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/et/groups.json b/public/language/et/groups.json index 25267ffd46..62e5e4c45d 100644 --- a/public/language/et/groups.json +++ b/public/language/et/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupid", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Vaata gruppi", "owner": "Grupi omanik", "new-group": "Loo uus grupp", diff --git a/public/language/et/modules.json b/public/language/et/modules.json index 1aa6c0d427..3212afbb9c 100644 --- a/public/language/et/modules.json +++ b/public/language/et/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Peida eelvaade", "composer.help": "Help", "composer.user-said-in": "%1 ütles %2:", - "composer.user-said": "%1 ütles:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Oled kindel, et soovid selle postituse tühistada?", "composer.submit-and-lock": "Kinnita ja Lukusta", "composer.toggle-dropdown": "Aktiveeri rippmenüü", diff --git a/public/language/et/notifications.json b/public/language/et/notifications.json index f23aa29d24..a05d7bca3b 100644 --- a/public/language/et/notifications.json +++ b/public/language/et/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Uus sõnum kasutajalt %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 hääletas sinu postituse poolt teemas %2.", - "upvoted-your-post-in-dual": "%1 ja %2 kiitsid sinu postituse heaks: %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 liigutas sinu postituse %2 'sse", "moved-your-topic": "%1 liigutas %2", - "user-flagged-post-in": "%1 raporteeris postitust %2", - "user-flagged-post-in-dual": "%1 ja %2 märgistasid postituse: %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flagged a user profile (%2)", "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "Kasutaja %1 postitas vastuse teemasse %2", - "user-posted-to-dual": "%1 ja %2 on postitanud vastused: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 on postitanud uue teema: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 hakkas sind jälgima.", "user-started-following-you-dual": "%1 ja %2 hakkasid sind jälgima.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Emaili aadress kinnitatud", "email-confirmed-message": "Täname, et kinnitasite oma emaili aadressi. Teie kasutaja on nüüd täielikult aktiveeritud.", "email-confirm-error-message": "Emaili aadressi kinnitamisel tekkis viga. Võibolla kinnituskood oli vale või aegunud.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Kinnituskiri on saadetud.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/et/social.json b/public/language/et/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/et/social.json +++ b/public/language/et/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/et/themes/harmony.json b/public/language/et/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/et/themes/harmony.json +++ b/public/language/et/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/et/topic.json b/public/language/et/topic.json index a1cede1835..3c5e455644 100644 --- a/public/language/et/topic.json +++ b/public/language/et/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Vajuta siia, et tagasi minna viimati loetud postituse juurde siin teemas.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lukusta teema", "thread-tools.unlock": "Taasava teema", "thread-tools.move": "Liiguta teema", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Liiguta kõik", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Laen kategooriaid", "confirm-move": "Liiguta", + "confirm-crosspost": "Cross-post", "confirm-fork": "Fork", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Laen postitusi", "move-topic": "Liiguta teemat", "move-topics": "Liiguta teemasi", + "crosspost-topic": "Cross-post Topic", "move-post": "Liiguta postitust", "post-moved": "Postitus liigutatud!", "fork-topic": "Fork Topic", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Sisesta teema pealkiri siia...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Vastad %1'le", "composer.new-topic": "Uus teema", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "laen üles...", "composer.thumb-url-label": "Kleebi teema marge.", "composer.thumb-title": "Lisa märge sellele teemale", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/et/user.json b/public/language/et/user.json index b7da379265..2630c16bbe 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -105,6 +105,10 @@ "show-email": "Näita minu emaili", "show-fullname": "Näita minu täisnime", "restrict-chats": "Luba sõnumeid ainult kasutajatelt, keda järgin", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Telli", "digest-description": "Telli kõik teated emaili teel (uued teated ja teemad).", "digest-off": "Väljas", diff --git a/public/language/et/world.json b/public/language/et/world.json index 3753335278..e6694bf507 100644 --- a/public/language/et/world.json +++ b/public/language/et/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/fa-IR/admin/advanced/cache.json b/public/language/fa-IR/admin/advanced/cache.json index 498b0ea71e..6f3b0628fc 100644 --- a/public/language/fa-IR/admin/advanced/cache.json +++ b/public/language/fa-IR/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "نهانگاه", - "post-cache": "نهانگاه دیدگاه ", - "group-cache": "نهانگاه گروه", - "local-cache": "نهانگاه محلی", - "object-cache": "شئ نهانگاه", "percent-full": "%1% تمام شده", "post-cache-size": "اندازه نهانگاه فرسته", "items-in-cache": "موارد موجود در نهانگاه" diff --git a/public/language/fa-IR/admin/dashboard.json b/public/language/fa-IR/admin/dashboard.json index 77ff6d56e8..b5f458d0c0 100644 --- a/public/language/fa-IR/admin/dashboard.json +++ b/public/language/fa-IR/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "ربات بازدید از صفحه", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "بازدیدکنندگان منحصر به فرد", "graphs.registered-users": "کاربران ثبت نام شده", "graphs.guest-users": "کاربران مهمان", diff --git a/public/language/fa-IR/admin/development/info.json b/public/language/fa-IR/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/fa-IR/admin/development/info.json +++ b/public/language/fa-IR/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/fa-IR/admin/manage/categories.json b/public/language/fa-IR/admin/manage/categories.json index 53f79fbd20..923d4defe7 100644 --- a/public/language/fa-IR/admin/manage/categories.json +++ b/public/language/fa-IR/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "تنظیمات دسته‌بندی", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "نام دسته‌بندی", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "توضیحات دسته‌بندی", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/fa-IR/admin/manage/custom-reasons.json b/public/language/fa-IR/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/fa-IR/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/fa-IR/admin/manage/privileges.json b/public/language/fa-IR/admin/manage/privileges.json index 8ca227bb46..89f6e2475f 100644 --- a/public/language/fa-IR/admin/manage/privileges.json +++ b/public/language/fa-IR/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "پاسخ به موضوع‌ها", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/fa-IR/admin/manage/users.json b/public/language/fa-IR/admin/manage/users.json index 6867a3b0d3..d3b6922f10 100644 --- a/public/language/fa-IR/admin/manage/users.json +++ b/public/language/fa-IR/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "علت (اختیاری)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "ساعت", "temp-ban.days": "روز", "temp-ban.explanation": "میزان مدت زمان اخراج را وارد کنید. توجه داشته باشید که مقدار 0 به عنوان اخراج مادام العمر در نظر گرفته خواهد شد.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/fa-IR/admin/settings/activitypub.json b/public/language/fa-IR/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/fa-IR/admin/settings/activitypub.json +++ b/public/language/fa-IR/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/fa-IR/admin/settings/chat.json b/public/language/fa-IR/admin/settings/chat.json index fcd505442d..b1413d19ed 100644 --- a/public/language/fa-IR/admin/settings/chat.json +++ b/public/language/fa-IR/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "بیشترین تعداد کاربران در چت‌روم ", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/fa-IR/admin/settings/email.json b/public/language/fa-IR/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/fa-IR/admin/settings/email.json +++ b/public/language/fa-IR/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/fa-IR/admin/settings/notifications.json b/public/language/fa-IR/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/fa-IR/admin/settings/notifications.json +++ b/public/language/fa-IR/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/fa-IR/admin/settings/uploads.json b/public/language/fa-IR/admin/settings/uploads.json index baf90ba0e4..60cf60e8d7 100644 --- a/public/language/fa-IR/admin/settings/uploads.json +++ b/public/language/fa-IR/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/fa-IR/admin/settings/user.json b/public/language/fa-IR/admin/settings/user.json index e4c4f1f229..15bcb91501 100644 --- a/public/language/fa-IR/admin/settings/user.json +++ b/public/language/fa-IR/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "فقط از کاربرانی که دنبال می کنم پیام خصوصی دریافت کنم", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/fa-IR/admin/settings/web-crawler.json b/public/language/fa-IR/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/fa-IR/admin/settings/web-crawler.json +++ b/public/language/fa-IR/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/fa-IR/aria.json b/public/language/fa-IR/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/fa-IR/aria.json +++ b/public/language/fa-IR/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/fa-IR/category.json b/public/language/fa-IR/category.json index 47687be66d..ecfbe39f60 100644 --- a/public/language/fa-IR/category.json +++ b/public/language/fa-IR/category.json @@ -1,12 +1,13 @@ { "category": "دسته‌بندی", "subcategories": "زیر دسته‌بندی‌", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "تاپیک جدید", "guest-login-post": "برای ارسال پست وارد شوید", "no-topics": "هیچ تاپیکی در این دسته‌بندی نیست.
چرا شما یکی نمی‌فرستید؟", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "بیننده‌ها", "no-replies": "هیچ کسی پاسخ نداده است.", "no-new-posts": "هیچ پست جدیدی وجود ندارد.", diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json index ad19981610..c01739e641 100644 --- a/public/language/fa-IR/error.json +++ b/public/language/fa-IR/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON نامعتبر", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "وارد حساب کاربری نشده‌اید.", "account-locked": "حساب کاربری شما موقتاً مسدود شده است.", "search-requires-login": "استفاده از جستجو نیازمند ورود با نام‌کاربری و رمز‌عبور است. لطفا ابتدا وارد شوید.", @@ -146,6 +147,7 @@ "post-already-restored": "پست قبلا بازگردانی شده است.", "topic-already-deleted": "موضوع قبلا حذف شده است", "topic-already-restored": "موضوع قبلا بازگردانی شده است", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "شما نمی‌توانید پست اصلی را پاک کنید، لطفا موضوع را به جای آن پاک کنید.", "topic-thumbnails-are-disabled": "چهرک‌های موضوع غیرفعال شده است.", "invalid-file": "فایل نامعتبر است.", @@ -154,6 +156,8 @@ "about-me-too-long": "با عرض پوزش محتوای 'درباره ی من' نمی تواند طولانی تر از %1 کاراکتر باشد", "cant-chat-with-yourself": "شما نمی‌توانید با خودتان چت کنید!", "chat-restricted": "این کاربر پیام های چتی خود را محدود کرده است . آنها بایدشما را دنبال کنند تا اینکه شما بتوانید به آنها پیامی بفرستید", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "سیستم گفتمان غیرفعال شده است", "too-many-messages": "شما پیامهای خیلی زیادی فرستاده اید، لطفا مدتی صبر نمایید", @@ -225,6 +229,7 @@ "no-topics-selected": "هیچ موضوعی انتخاب نشده است !", "cant-move-to-same-topic": "نمی توان پست یک موضوع را به همان موضوع انتقال داد !", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "شما نمی توانید خودتان را بلاک کنید!", "cannot-block-privileged": "شما نمی توانید ادمین ها یا مدیر ها را بلاک کنید", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/fa-IR/global.json b/public/language/fa-IR/global.json index 3627e7abee..428903f329 100644 --- a/public/language/fa-IR/global.json +++ b/public/language/fa-IR/global.json @@ -68,6 +68,7 @@ "users": "کاربران", "topics": "موضوع ها", "posts": "دیدگاه‌ها", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "1% موضوع‌ها", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "رای منفی", "views": "بازدیدها", "posters": "کاربران", + "watching": "Watching", "reputation": "اعتبار", "lastpost": "آخرین پست", "firstpost": "اولین پست", diff --git a/public/language/fa-IR/groups.json b/public/language/fa-IR/groups.json index 40d7f83cbf..657248dfdc 100644 --- a/public/language/fa-IR/groups.json +++ b/public/language/fa-IR/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "همه‌ی گروه‌ها", "groups": "گروه‌ها", "members": "اعضا", + "x-members": "%1 member(s)", "view-group": "مشاهده گروه", "owner": "مالک گروه", "new-group": "ساخت گروه جدید", diff --git a/public/language/fa-IR/modules.json b/public/language/fa-IR/modules.json index ddd073ddbf..0a2a344bad 100644 --- a/public/language/fa-IR/modules.json +++ b/public/language/fa-IR/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "مخفی کردن پیش‌نمایش", "composer.help": "Help", "composer.user-said-in": "%1 در %2 گفته است:", - "composer.user-said": "%1 گفته است:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "آیا از دور انداختن این پست اطمینان دارید؟", "composer.submit-and-lock": "ارسال و قفل", "composer.toggle-dropdown": "باز و بسته کردن کرکره", diff --git a/public/language/fa-IR/notifications.json b/public/language/fa-IR/notifications.json index d93f18d530..bf57a3aad9 100644 --- a/public/language/fa-IR/notifications.json +++ b/public/language/fa-IR/notifications.json @@ -22,7 +22,7 @@ "upvote": "رای های مثبت", "awards": "اعلان ها", "new-flags": "گزارش های جدید", - "my-flags": "گزارش های اختصاص یافته به من", + "my-flags": "My Flags", "bans": "اخراجی ها", "new-message-from": "پیام تازه از %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 امتیاز مثبت به پست شما در %2 داده", - "upvoted-your-post-in-dual": "%1 و %2 رای مثبت به پست شما در\n %3.", - "upvoted-your-post-in-triple": "%1, 2% و %3 به پست شما در %4 امتیاز مثبت دادن", - "upvoted-your-post-in-multiple": "%1, 2% و %3 به پست شما در %4 امتیاز مثبت دادن", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 پست شما را به %2 انتقال داده است", "moved-your-topic": "%2 %1 را منتقل کرده است", - "user-flagged-post-in": "%1 پستی را در %2 گزارش کرده", - "user-flagged-post-in-dual": "%1 و %2 پستی را در %3 گزارش کرده اند", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1کاربری را برای بررسی گزارش کرد (%2)", "user-flagged-user-dual": "%1و %2کاربری را برای بررسی گزارش کردند (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "پاسخ دادن به %2 از سوی %1", - "user-posted-to-dual": "%1 و %2 پاسخی در: %3 ارسال کردند", - "user-posted-to-triple": "%1, 2% و %3 پاسخی در %4 ارسال کردن", - "user-posted-to-multiple": "%1, 2% , 3% دیگر پاسخی در %4 ارسال کردند", - "user-posted-topic": "%1 یک موضوع جدید ارسال کرده: %2", - "user-edited-post": "%1 پستی را در %2 ویرایش کرد", - "user-posted-topic-with-tag": "1% ارسال کرده است 2% ( تگ کرده %3)", - "user-posted-topic-with-tag-dual": "1% ارسال کرده است 2% ( تگ کرده %3 و %4 )", - "user-posted-topic-with-tag-triple": "1% ارسال کرده است 2% ( تگ کرده %3, %4 و %5)", - "user-posted-topic-with-tag-multiple": "1% ارسال کرده است 2% ( تگ کرده %3)", - "user-posted-topic-in-category": "%1 یک موضوع جدید در %2 ارسال کرده", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 شروع به دنبال کردن شما کرده", "user-started-following-you-dual": "%1 و %2 شروع به دنبال کردن شما کرده.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "ایمیل تایید شد", "email-confirmed-message": "بابت تایید ایمیلتان سپاس‌گزاریم. حساب کاربری شما اکنون به صورت کامل فعال شده است.", "email-confirm-error-message": "خطایی در تایید آدرس ایمیل شما پیش آمده است. ممکن است کد نا‌معتبر و یا منقضی شده باشد.", - "email-confirm-error-message-already-validated": "آدرس ایمیل شما قبلا تایید شده است", "email-confirm-sent": "ایمیل تایید ارسال شد.", "none": "هیچکدام", "notification-only": "فقط اعلان", diff --git a/public/language/fa-IR/social.json b/public/language/fa-IR/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/fa-IR/social.json +++ b/public/language/fa-IR/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/fa-IR/themes/harmony.json b/public/language/fa-IR/themes/harmony.json index 414e4ecb51..0f7681d691 100644 --- a/public/language/fa-IR/themes/harmony.json +++ b/public/language/fa-IR/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "پوسته‌ها", + "light": "Light", + "dark": "Dark", "collapse": "بستن ", "expand": "باز کردن", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/fa-IR/topic.json b/public/language/fa-IR/topic.json index cb940c2db5..ce21e4587b 100644 --- a/public/language/fa-IR/topic.json +++ b/public/language/fa-IR/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 این تاپیک را در %3 ارجاع داد", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "برای بازگشت به آخرین پست در این موضوع اینجا را کلیک کنید.", "flag-post": "گزارش این پست", "flag-user": "گزارش این کاربر", @@ -103,6 +105,7 @@ "thread-tools.lock": "قفل کردن موضوع", "thread-tools.unlock": "باز کردن موضوع", "thread-tools.move": "جابجا کردن موضوع", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "انتقال پست ها", "thread-tools.move-all": "جابجایی همه", "thread-tools.change-owner": "تغییر مالک پست", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "بارگذاری دسته‌ها", "confirm-move": "جابه‌جا کردن", + "confirm-crosspost": "Cross-post", "confirm-fork": "شاخه ساختن", "bookmark": "نشانک", "bookmarks": "نشانک‌ها", @@ -141,6 +145,7 @@ "loading-more-posts": "بارگذاری پست‌های بیش‌تر", "move-topic": "جابه‌جایی موضوع", "move-topics": "انتقال موضوع", + "crosspost-topic": "Cross-post Topic", "move-post": "جابه‌جایی موضوع", "post-moved": "پست جابه‌جا شد!", "fork-topic": "شاخه ساختن از موضوع", @@ -163,6 +168,9 @@ "move-topic-instruction": "دسته مقصد را انتخاب کنید و سپس روی جابه‌جا کردن کلیک کنید", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "عنوان موضوعتان را اینجا بنویسید...", "composer.handle-placeholder": "نام خود را اینجا وارد کنید", "composer.hide": "پیش نویس", @@ -174,6 +182,7 @@ "composer.replying-to": "پاسخ به %1", "composer.new-topic": "موضوع تازه", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "بارگذاری...", "composer.thumb-url-label": "چسباندن نشانی چهرک یک موضوع", "composer.thumb-title": "افزودن یک چهرک به این موضوع", @@ -224,5 +233,8 @@ "unread-posts-link": "پست های خوانده نشده پیوند", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/fa-IR/user.json b/public/language/fa-IR/user.json index 0b2d556db3..5cb6056f7e 100644 --- a/public/language/fa-IR/user.json +++ b/public/language/fa-IR/user.json @@ -105,6 +105,10 @@ "show-email": "نمایش نشانی رایانامه من", "show-fullname": "نمایش نام کامل من (نام و نام خانوادگی)", "restrict-chats": "فقط از کاربرانی که دنبال می کنم پیام خصوصی دریافت کنم", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "مشترک شدن در چکیده", "digest-description": "مشترک شدن برای دریافت جدیدترین‌های این انجمن (موضوع ها و آکاه‌سازی‌های تازه) با ایمیل روی یک برنامه زمان‌بندی", "digest-off": "خاموش", diff --git a/public/language/fa-IR/world.json b/public/language/fa-IR/world.json index 3753335278..e6694bf507 100644 --- a/public/language/fa-IR/world.json +++ b/public/language/fa-IR/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/fi/admin/advanced/cache.json b/public/language/fi/admin/advanced/cache.json index 4142ffde34..f4e83f3dca 100644 --- a/public/language/fi/admin/advanced/cache.json +++ b/public/language/fi/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Viestivälimuisti", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Täynnä", "post-cache-size": "Viestivälimuistin koko", "items-in-cache": "Asioita välimuistissa" diff --git a/public/language/fi/admin/dashboard.json b/public/language/fi/admin/dashboard.json index 83c4b42c58..57c63fdf9d 100644 --- a/public/language/fi/admin/dashboard.json +++ b/public/language/fi/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Kirjautuneiden sivulatausta", "graphs.page-views-guest": "Vieraiden sivulatausta", "graphs.page-views-bot": "Bottien sivulatausta", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Ainutalaatuista kävijää", "graphs.registered-users": "Rekisteröitynyttä käyttäjää", "graphs.guest-users": "Vieraskäyttäjää", diff --git a/public/language/fi/admin/development/info.json b/public/language/fi/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/fi/admin/development/info.json +++ b/public/language/fi/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/fi/admin/manage/categories.json b/public/language/fi/admin/manage/categories.json index d458c9c5e9..c15a783323 100644 --- a/public/language/fi/admin/manage/categories.json +++ b/public/language/fi/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Hallitse kategorioita", "add-category": "Lisää kategoria", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Siirry...", "settings": "Kategoria-asetukset", "edit-category": "Muokkaa kategoriaa", "privileges": "Privileges", "back-to-categories": "Palaa kategorioihin", + "id": "Category ID", "name": "Kategorian nimi", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategorian kuvaus", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Taustaväri", "text-color": "Tekstin väri", "bg-image-size": "Taustakuvan koko", @@ -103,6 +107,11 @@ "alert.create-success": "Kategoria luotiin!", "alert.none-active": "Sinulla ei ole aktiivisia kategorioita.", "alert.create": "Luo kategoria.", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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": "Kategoria poistettiin!", "alert.copy-success": "Asetukset kopioitiin!", diff --git a/public/language/fi/admin/manage/custom-reasons.json b/public/language/fi/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/fi/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/fi/admin/manage/privileges.json b/public/language/fi/admin/manage/privileges.json index f91379ea70..3295d328b4 100644 --- a/public/language/fi/admin/manage/privileges.json +++ b/public/language/fi/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Luo aiheita", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Ajoita aiheita", "tag-topics": "Merkitse aiheita", "edit-posts": "Edit Posts", diff --git a/public/language/fi/admin/manage/users.json b/public/language/fi/admin/manage/users.json index 5903d1feaf..360c380150 100644 --- a/public/language/fi/admin/manage/users.json +++ b/public/language/fi/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Poista käyttäjät ja sisällöt", "download-csv": "Lataa CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Hallitse ryhmiä", "set-reputation": "Aseta maine", "add-group": "Lisää ryhmä", @@ -77,9 +78,11 @@ "temp-ban.length": "Pituus", "temp-ban.reason": "Syy(valinnainen)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Tuntia", "temp-ban.days": "Päivää", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/fi/admin/settings/activitypub.json b/public/language/fi/admin/settings/activitypub.json index 94f9ad7822..a916af734e 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -1,23 +1,45 @@ { - "intro-lead": "What is Federation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", - "general": "General", - "pruning": "Content Pruning", + "intro-lead": "Mikä on federaatio?", + "intro-body": "NodeBB pystyy yhdistämään muihin NodeBB instansseihin, jotka tukevat tätä ominaisuutta. Tämä on toteutettuActivityPub protokollaa käyttäen ja, jos se on aktivoitu, NodeBB pystyy yhdistämään myös toisten sovellusten ja sivustojen kanssa jotka tukevat ActivityPub Protokollaa( esim. Mastodon, Peertube jne.) ", + "general": "Yleinen", + "pruning": "Sisällön karsiminen", "content-pruning": "Days to keep remote content", "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", "user-pruning": "Days to cache remote user accounts", "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", - "enabled": "Enable Federation", + "enabled": "Ota federointi käyttöön", "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", "allowLoopback": "Allow loopback processing", "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", - "probe": "Open in App", + "probe": "Avaa sovelluksessa", "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/fi/admin/settings/chat.json b/public/language/fi/admin/settings/chat.json index c11a523f34..9fb0cb2110 100644 --- a/public/language/fi/admin/settings/chat.json +++ b/public/language/fi/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Keskusteluhuoneiden nimien enimmäispituus", "max-room-size": "Keskusteluhuoneiden käyttäjien enimmäismäärä", "delay": "Pikaviestien välinen aika (ms)", - "notification-delay": "Pikaviestien ilmoitusviive", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/fi/admin/settings/email.json b/public/language/fi/admin/settings/email.json index 9401be8285..c3f7a83d1c 100644 --- a/public/language/fi/admin/settings/email.json +++ b/public/language/fi/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Muokkaa sähköpostimallia", "template.select": "Valitse sähköpostimalli", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Sähköpostin testaus", + "testing.success": "Test Email Sent.", "testing.select": "Valitse sähköpostimalli", "testing.send": "Lähetä testiviesti", - "testing.send-help": "The test email will be sent to the currently logged in user's email address.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Sähköpostikoosteet", "subscriptions.disable": "Poista sähköpostikoosteet käytöstä", "subscriptions.hour": "Digest Hour", diff --git a/public/language/fi/admin/settings/notifications.json b/public/language/fi/admin/settings/notifications.json index ae280e6ddd..347c871ddd 100644 --- a/public/language/fi/admin/settings/notifications.json +++ b/public/language/fi/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Tervetuloilmoitus", "welcome-notification-link": "Tervetuloilmoituksen linkki", "welcome-notification-uid": "Tervetuloilmoituksen käyttäjä (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/fi/admin/settings/uploads.json b/public/language/fi/admin/settings/uploads.json index a1412b0ed2..4e85cfe10c 100644 --- a/public/language/fi/admin/settings/uploads.json +++ b/public/language/fi/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Images wider than this value will be rejected.", "reject-image-height": "Kuvan suurin sallittu korkeus (kuvapisteinä)", "reject-image-height-help": "Images taller than this value will be rejected.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/fi/admin/settings/user.json b/public/language/fi/admin/settings/user.json index 3c35662fff..c0514f4c26 100644 --- a/public/language/fi/admin/settings/user.json +++ b/public/language/fi/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Näytä sähköpostiosoite", "show-fullname": "Näytä etu- ja sukunimi", "restrict-chat": "Salli pikaviestit vain seuraamiltani käyttäjiltä", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Käytä aiheen sisältöhakua", "update-url-with-post-index": "Päivitä viestin järjestysnumero URL-osoitteeseen aihetta selattaessa", diff --git a/public/language/fi/admin/settings/web-crawler.json b/public/language/fi/admin/settings/web-crawler.json index 00bcbf6ede..102de1e297 100644 --- a/public/language/fi/admin/settings/web-crawler.json +++ b/public/language/fi/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/fi/aria.json b/public/language/fi/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/fi/aria.json +++ b/public/language/fi/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/fi/category.json b/public/language/fi/category.json index 8075401b06..928c349879 100644 --- a/public/language/fi/category.json +++ b/public/language/fi/category.json @@ -1,12 +1,13 @@ { "category": "Kategoria", "subcategories": "Alikategoria", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Uusi aihe", "guest-login-post": "Kirjaudu sisään julkastaksesi", "no-topics": "Kategoriassa ei ole aiheita.
Jospa aloittaisit sellaisen?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "selaamassa", "no-replies": "Kukaan ei ole vastannut", "no-new-posts": "Ei uusia viestejä", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index f39f2b3bce..ffd6a1b423 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Et taida olla kirjautuneena sisään.", "account-locked": "Käyttäjätilisi on lukittu väliaikaisesti", "search-requires-login": "Haku vaatii tunnukset. Kirjaudu sisään tai luo tunnus.", @@ -146,6 +147,7 @@ "post-already-restored": "Tämä viesti on jo palautettu", "topic-already-deleted": "Tämä aihe on jo poistettu", "topic-already-restored": "Tämä aihe on jo palautettu", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Aiheiden kuvakkeet eivät ole käytössä", "invalid-file": "Virheellinen tiedosto", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "Et voi keskustella itsesi kanssa!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Keskustelujärjestelmä on pois käytöstä", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "Ei aiheita valittuna", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/fi/global.json b/public/language/fi/global.json index 33dfadd8e0..899dad2a1b 100644 --- a/public/language/fi/global.json +++ b/public/language/fi/global.json @@ -68,6 +68,7 @@ "users": "Käyttäjät", "topics": "Aiheet", "posts": "Viestejä", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Tarkasteltu", "posters": "Osallistujia", + "watching": "Watching", "reputation": "Maine", "lastpost": "Viimeisin viesti", "firstpost": "Ensimmäinen viesti", diff --git a/public/language/fi/groups.json b/public/language/fi/groups.json index a97db6b641..f6464d5f16 100644 --- a/public/language/fi/groups.json +++ b/public/language/fi/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Kaikki ryhmät", "groups": "Ryhmät", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Tarkaste ryhmää", "owner": "Ryhmän omistaja", "new-group": "Luo uusi ryhmä", diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index d68c1bbaef..4cf554d8f3 100644 --- a/public/language/fi/modules.json +++ b/public/language/fi/modules.json @@ -3,7 +3,7 @@ "chat.chatting-with": "Pikaviesti käyttäjälle", "chat.placeholder": "Kirjoita pikaviesti ja raahaa kuvia tähän", "chat.placeholder.mobile": "Kirjoita pikaviesti", - "chat.placeholder.message-room": "Message #%1", + "chat.placeholder.message-room": "Viesti #%1", "chat.scroll-up-alert": "Siirry uusimpaan viestiin", "chat.usernames-and-x-others": "%1 & %2 others", "chat.chat-with-usernames": "Keskustelu käyttäjän %1 kanssa", @@ -42,12 +42,13 @@ "chat.private-rooms": "Private Rooms (%1)", "chat.create-room": "Luo keskusteluhuone", "chat.private.option": "Private (Only visible to users added to room)", - "chat.public.option": "Public (Visible to every user in selected groups)", + "chat.public.option": "Julkinen (Kaikki valitusta ryhmästä voivat nähdä tämän)", "chat.public.groups-help": "To create a chat room that is visible to all users select registered-users from the group list.", "chat.manage-room": "Hallitse keskusteluhuonetta", "chat.add-user": "Add User", "chat.notification-settings": "Ilmoitusasetukset", "chat.default-notification-setting": "Ilmoitusten oletusasetukset", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Huoneen oletus", "chat.notification-setting-none": "Ilmoituksia ei ole", "chat.notification-setting-at-mention-only": "vain @maininta", @@ -69,8 +70,8 @@ "chat.in-room": "In this room", "chat.kick": "Kick", "chat.show-ip": "Näytä IP-osoite", - "chat.copy-text": "Copy Text", - "chat.copy-link": "Copy Link", + "chat.copy-text": "Kopioi teksti", + "chat.copy-link": "Kopioi linkki", "chat.owner": "Room Owner", "chat.grant-rescind-ownership": "Grant/Rescind Ownership", "chat.system.user-join": "%1 has joined the room ", @@ -81,7 +82,7 @@ "composer.hide-preview": "Piilota esikatselu", "composer.help": "Help", "composer.user-said-in": "%1 kirjoitti aiheeseen %2:", - "composer.user-said": "%1 sanoi:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Oletko varma, että haluat hylätä viestin?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/fi/notifications.json b/public/language/fi/notifications.json index 3d6099fda9..637f3aaec8 100644 --- a/public/language/fi/notifications.json +++ b/public/language/fi/notifications.json @@ -22,7 +22,7 @@ "upvote": "Tykkäykset", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Uusi viesti käyttäjältä %1", "new-messages-from": "%1 uutta viestiä lähteestä %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 käyttäjä tykkäsi viestistäsi aiheessa %2 ", - "upvoted-your-post-in-dual": "%1 ja %2 tykkäsivät viestistäsi aiheesssa %3", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 on siirtänyt viestisi %2", "moved-your-topic": "%1 on siirtänyt %2 alueelle", "user-flagged-post-in": "%1 flagged a post in %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 on vastannut viestiin: %2", - "user-posted-to-dual": "%1 ja %2 ovat vastanneet viestiin: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 on kirjoittanut uuden aiheen: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 aloitti uuden aiheen kategoriassa %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 alkoi seurata sinua.", "user-started-following-you-dual": "%1 ja %2 alkoivat seurata sinua", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Odottavaan viestiin on tullut ilmoitus:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Sähköpostiosoite on vahvistettu", "email-confirmed-message": "Kiitos sähköpostiosoitteesi vahvistamisesta. Käyttäjätilisi on nyt täysin aktivoitu.", "email-confirm-error-message": "Ongelma sähköpostiosoitteen vahvistamisessa. Ehkäpä koodi oli virheellinen tai vanhentunut.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Vahvistusviesti on lähetetty.", "none": "Ei mitään", "notification-only": "ilmoita vain sivustolla", diff --git a/public/language/fi/recent.json b/public/language/fi/recent.json index 93fbd99645..9e8f26e97b 100644 --- a/public/language/fi/recent.json +++ b/public/language/fi/recent.json @@ -8,6 +8,6 @@ "no-recent-topics": "Tuoreita aiheita ei ole.", "no-popular-topics": "Ei päivityksiä suosituimmissa aiheissa", "load-new-posts": "Load new posts", - "uncategorized.title": "All known topics", + "uncategorized.title": "Kaikki aiheet", "uncategorized.intro": "This page shows a chronological listing of every topic that this forum has received.
The views and opinions expressed in the topics below are not moderated and may not represent the views and opinions of this website." } \ No newline at end of file diff --git a/public/language/fi/social.json b/public/language/fi/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/fi/social.json +++ b/public/language/fi/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/fi/themes/harmony.json b/public/language/fi/themes/harmony.json index 971ee1ad91..ade92c67b3 100644 --- a/public/language/fi/themes/harmony.json +++ b/public/language/fi/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony-teema", "skins": "Teemat", + "light": "Light", + "dark": "Dark", "collapse": "Tiivistä", "expand": "Laajenna", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json index 0ebb73285d..246f538d9f 100644 --- a/public/language/fi/topic.json +++ b/public/language/fi/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klikkaa tästä palataksesi viimeisimpään luettuun viestiin tässä aiheessa", "flag-post": "Liputa viesti", "flag-user": "Liputa käyttäjä", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lukitse aihe", "thread-tools.unlock": "Poista aiheen lukitus", "thread-tools.move": "Siirrä aihe", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Siirrä viestit", "thread-tools.move-all": "Siirrä kaikki", "thread-tools.change-owner": "Vaihda omistaja", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Ladataan aihealueita", "confirm-move": "Siirrä", + "confirm-crosspost": "Cross-post", "confirm-fork": "Haaroita", "bookmark": "Lisää/poista krjanmerkki", "bookmarks": "Kirjanmerkit", @@ -141,6 +145,7 @@ "loading-more-posts": "Ladataan lisää viestejä", "move-topic": "Siirrä aihe", "move-topics": "Siirrä aiheet", + "crosspost-topic": "Cross-post Topic", "move-post": "Siirrä viesti", "post-moved": "Viestit siirretty!", "fork-topic": "Haaroita aihe", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Valitse viestit jotka haluat siirtää toiselle henkilölle", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Syötä aiheesi otsikko tähän...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Piilota", @@ -174,6 +182,7 @@ "composer.replying-to": "Vastataan viestiin %1", "composer.new-topic": "Uusi aihe", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "ladataan palvelimelle...", "composer.thumb-url-label": "Liitä aiheen aiheen kuvakkeen URL", "composer.thumb-title": "Lisää kuvake tähän aiheeseen", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/fi/unread.json b/public/language/fi/unread.json index 6e4ce6039d..a1c9b8f408 100644 --- a/public/language/fi/unread.json +++ b/public/language/fi/unread.json @@ -3,7 +3,7 @@ "no-unread-topics": "Ei lukemattomia aiheita.", "load-more": "Lataa lisää", "mark-as-read": "Merkitse luetuksi", - "mark-as-unread": "Mark as Unread", + "mark-as-unread": "Merkitse lukemattomaksi", "selected": "Valitut", "all": "Kaikki", "all-categories": "Kaikki kategoriat", diff --git a/public/language/fi/user.json b/public/language/fi/user.json index ed080f48bf..582b3d363e 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -1,9 +1,9 @@ { - "user-menu": "User menu", + "user-menu": "Käyttäjävalikko", "banned": "Bannattu", - "unbanned": "Unbanned", + "unbanned": "Estot poistettu", "muted": "Muted", - "unmuted": "Unmuted", + "unmuted": "Hiljennys poistettu", "offline": "Offline", "deleted": "Poistettu", "username": "Käyttäjän nimi", @@ -43,11 +43,11 @@ "change-all": "Muuta kaikki", "watched": "Seurataan", "ignored": "Ohitetut", - "read": "Read", + "read": "Lue", "default-category-watch-state": "Kategoriaseurannan oletustaso", "followers": "Seuraajat", "following": "Seuratut", - "shares": "Shares", + "shares": "Jaettu", "blocks": "Estot", "blocked-users": "Estetyt käyttäjät", "block-toggle": "Toggle Block", @@ -59,12 +59,12 @@ "chat": "Keskustele", "chat-with": "Jatka keskustelua käyttäjän %1 kanssa", "new-chat-with": "Aloita keskutelu käyttäjän %1 kanssa", - "view-remote": "View Original", + "view-remote": "Näytä alkuperäinen", "flag-profile": "Flag Profile", - "profile-flagged": "Already flagged", + "profile-flagged": "Liputettu jo aiemmin", "follow": "Seuraa", "unfollow": "Älä seuraa", - "cancel-follow": "Cancel follow request", + "cancel-follow": "Peruuta seuraamispyyntö", "more": "Lisää", "profile-update-success": "Profiili päivitettiin onnistuneesti!", "change-picture": "Vaihda kuva", @@ -83,7 +83,7 @@ "change-password": "Vaihda salasana", "change-password-error": "Virheellinen salasana", "change-password-error-wrong-current": "Nykyinen salasanasi ei ole oikein!", - "change-password-error-same-password": "Your new password matches your current password, please use a new password.", + "change-password-error-same-password": "Uusi salasana on sama kuin aiemmin käyttämäsi, ole hyvä ja käytä eri salasanaa.", "change-password-error-match": "Salasanojen täytyy olla samat!", "change-password-error-privileges": "Sinulla ei ole oikeuksia vaihtaa tätä salasanaa.", "change-password-success": "Salasanasi on päivitetty!", @@ -105,6 +105,10 @@ "show-email": "Näytä sähköpostiosoitteeni", "show-fullname": "Näytä etu- ja sukunimeni", "restrict-chats": "Salli pikaviestit vain seuraamiltani käyttäjiltä", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Tilaa kooste", "digest-description": "Tilaa tämän ajoituksen mukaiset sähköpostitiedotukset keskustelualueen uusista ilmoituksista ja aiheista.", "digest-off": "Ei käytössä", diff --git a/public/language/fi/world.json b/public/language/fi/world.json index 3753335278..4fe18db0c4 100644 --- a/public/language/fi/world.json +++ b/public/language/fi/world.json @@ -1,12 +1,17 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "Apua", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", + "help.title": "Mikä tämä sivu on?", + "help.intro": "Tervetuloa omaan fediverse kulmaasi.", + "help.fediverse": "\"Fediverse\" on verkko, joka mahdollistaa sovellusten ja sivustojen keskustella keskenään ja näiden käyttäjien nähdä toisensa. Tämä foorumi on yhteydessä tähän verkkoon ja pystyy yhdistymään muun sosiaalinen verkon(\"fediverse\") kanssa. Tämä sivusto on sinun henkilökohtainen kulma fediverseä ja se sisältään pelkästään seuraamiesi käyttäjien luomia ja jakamia aiheita.", "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", "help.next-generation": "This is the next generation of social media, start contributing today!", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/fr/admin/advanced/cache.json b/public/language/fr/admin/advanced/cache.json index 06757faf47..aa0136d578 100644 --- a/public/language/fr/admin/advanced/cache.json +++ b/public/language/fr/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Cache des messages", - "group-cache": "Cache de groupe", - "local-cache": "Cache Local", - "object-cache": "Cache d'objets", "percent-full": "Plein à %1%", "post-cache-size": "Taille du cache des messages", "items-in-cache": "Objets en cache" diff --git a/public/language/fr/admin/dashboard.json b/public/language/fr/admin/dashboard.json index eb7bc530e3..6206354f7b 100644 --- a/public/language/fr/admin/dashboard.json +++ b/public/language/fr/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Membres", "graphs.page-views-guest": "Invités", "graphs.page-views-bot": "Robots", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Visiteurs uniques", "graphs.registered-users": "Utilisateurs enregistrés", "graphs.guest-users": "Utilisateurs invités", diff --git a/public/language/fr/admin/development/info.json b/public/language/fr/admin/development/info.json index afcbe555aa..471fa8ede5 100644 --- a/public/language/fr/admin/development/info.json +++ b/public/language/fr/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "en ligne", "git": "git", - "process-memory": "Mémoire de processus", + "process-memory": "rss/heap used", "system-memory": "Mémoire système", "used-memory-process": "Mémoire utilisée par processus", "used-memory-os": "Mémoire système utilisée", diff --git a/public/language/fr/admin/manage/categories.json b/public/language/fr/admin/manage/categories.json index fd10c37800..c84cbc67d2 100644 --- a/public/language/fr/admin/manage/categories.json +++ b/public/language/fr/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Gérer les catégories", "add-category": "Ajouter une catégorie", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Aller à...", "settings": "Paramètres de la catégorie", "edit-category": "Modifier les catégories", "privileges": "Privilèges", "back-to-categories": "Retour aux catégories", + "id": "Category ID", "name": "Nom de la catégorie", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Description de la catégorie", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Couleur d'arrière plan", "text-color": "Couleur du texte", "bg-image-size": "Taille de l'image d'arrière plan", @@ -103,6 +107,11 @@ "alert.create-success": "Catégorie créée avec succès !", "alert.none-active": "Vous n'avez aucune catégorie active.", "alert.create": "Créer une catégorie", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Voulez-vous vraiment purger cette catégorie \"%1\" ?

Attentionc!Tous les sujets et messages dans cette catégorie vont être supprimés

Purger une catégorie va enlever tous les sujets et messages en supprimant la catégorie de la base de données. Si vous voulez seulement enlevez une catégorietemporairement, il faut plutôt \"désactiver\" la catégorie.", "alert.purge-success": "Catégorie purgée !", "alert.copy-success": "Paramètres copiés !", diff --git a/public/language/fr/admin/manage/custom-reasons.json b/public/language/fr/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/fr/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/fr/admin/manage/privileges.json b/public/language/fr/admin/manage/privileges.json index 6cfde4e79d..04d780fea3 100644 --- a/public/language/fr/admin/manage/privileges.json +++ b/public/language/fr/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Droits des sujets", "create-topics": "Créer des sujets", "reply-to-topics": "Répondre aux sujets", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Planifier des sujets", "tag-topics": "Tag des sujets", "edit-posts": "Modifier les messages", diff --git a/public/language/fr/admin/manage/user-custom-fields.json b/public/language/fr/admin/manage/user-custom-fields.json index dab10670d2..cd7ec8a52d 100644 --- a/public/language/fr/admin/manage/user-custom-fields.json +++ b/public/language/fr/admin/manage/user-custom-fields.json @@ -1,8 +1,8 @@ { - "title": "Manage Custom User Fields", - "create-field": "Create Field", - "edit-field": "Edit Field", - "manage-custom-fields": "Manage Custom Fields", + "title": "Gérer les champs utilisateur personnalisés", + "create-field": "Créer un champ", + "edit-field": "Editer un champ", + "manage-custom-fields": "Gérer les champs personnalisés", "type-of-input": "Type of input", "key": "Key", "name": "Name", diff --git a/public/language/fr/admin/manage/users.json b/public/language/fr/admin/manage/users.json index 51a2c62e3b..6aa508b175 100644 --- a/public/language/fr/admin/manage/users.json +++ b/public/language/fr/admin/manage/users.json @@ -22,7 +22,8 @@ "delete-content": "Supprimer le contenu du compte", "purge": "Supprimer le compte et le contenu", "download-csv": "Exporter en CSV", - "custom-user-fields": "Custom User Fields", + "custom-user-fields": "Champs utilisateur personnalisés", + "custom-reasons": "Custom Reasons", "manage-groups": "Gérer les groupes", "set-reputation": "Définir une réputation", "add-group": "Ajouter un groupe", @@ -77,9 +78,11 @@ "temp-ban.length": "Longueur", "temp-ban.reason": "Raison (Optionel)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Heures", "temp-ban.days": "Jours", "temp-ban.explanation": "Entrer la durée du bannissement. Notez qu'une durée de 0 sera considérée comme un bannissement définitif.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Voulez-vous réellement bannir définitivement cet utilisateur ?", "alerts.confirm-ban-multi": "Voulez-vous réellement bannir définitivement ces utilisateurs ?", diff --git a/public/language/fr/admin/settings/activitypub.json b/public/language/fr/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/fr/admin/settings/activitypub.json +++ b/public/language/fr/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/fr/admin/settings/chat.json b/public/language/fr/admin/settings/chat.json index 39fbc31b68..4bf5d22326 100644 --- a/public/language/fr/admin/settings/chat.json +++ b/public/language/fr/admin/settings/chat.json @@ -5,13 +5,11 @@ "disable-editing": "Désactiver l'édition/la suppression des messages des discussions", "disable-editing-help": "Les administrateurs et modérateurs globaux sont dispensés de cette restriction", "max-length": "Longueur maximale des messages de discussion", - "max-length-remote": "Maximum length of remote chat messages", - "max-length-remote-help": "This value is usually set higher than the chat message maximum for local users as remote messages tend to be longer (with @ mentions, etc.)", + "max-length-remote": "Longueur maximale des messages de discussions distante", + "max-length-remote-help": "Cette valeur est généralement fixée à un niveau supérieur à la taille maximale des messages de discussion pour les utilisateurs locaux, car les messages distants ont tendance à être plus longs (avec des mentions @, etc.).", "max-chat-room-name-length": "Longueur maximale des noms de salons", "max-room-size": "Nombre maximum d'utilisateurs dans une même discussion", "delay": "Temps entre chaque message de discussion (en millisecondes)", - "notification-delay": "Délai de notification pour les messages de chat", - "notification-delay-help": "Les messages supplémentaires envoyés pendant cette période sont regroupés et l’utilisateur est averti pendant ce délai. Définissez cette valeur sur 0 pour désactiver le délai.", "restrictions.seconds-edit-after": "Nombre de secondes pendant lesquelles un message de discussion restera modifiable.", "restrictions.seconds-delete-after": "Nombre de secondes pendant lesquelles un message de discussion reste supprimable." } \ No newline at end of file diff --git a/public/language/fr/admin/settings/email.json b/public/language/fr/admin/settings/email.json index 3197117eeb..671d4447b7 100644 --- a/public/language/fr/admin/settings/email.json +++ b/public/language/fr/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Le regroupement des connexions empêche NodeBB de créer une nouvelle connexion pour chaque e-mail. Cette option s'applique uniquement si le transport SMTP est activé.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Modifier le modèle d'e-mail", "template.select": "Sélectionner un modèle d'e-mail ", "template.revert": "Retourner à l'original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Test d'e-mail", + "testing.success": "Test Email Sent.", "testing.select": "Sélectionner un modèle d'e-mail ", "testing.send": "Envoyer un e-mail de test", - "testing.send-help": "Le test d'envoi sera envoyé à l'adresse e-mail de l'utilisateur actuellement connecté.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Actualités du forum ", "subscriptions.disable": "Désactiver les actualités du forum ", "subscriptions.hour": "Heure d'envoi", diff --git a/public/language/fr/admin/settings/notifications.json b/public/language/fr/admin/settings/notifications.json index c80b6b87da..31146324dc 100644 --- a/public/language/fr/admin/settings/notifications.json +++ b/public/language/fr/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Notification de bienvenue", "welcome-notification-link": "Lien de notification de bienvenue", "welcome-notification-uid": "Notification de bienvenue de l'utilisateur (UID)", - "post-queue-notification-uid": "File d'attente utilisateur (UID)" + "post-queue-notification-uid": "File d'attente utilisateur (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/fr/admin/settings/uploads.json b/public/language/fr/admin/settings/uploads.json index 453b6be283..d24ccf8f2a 100644 --- a/public/language/fr/admin/settings/uploads.json +++ b/public/language/fr/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Les images plus larges que cette valeur seront rejetées.", "reject-image-height": "Hauteur maximale des images (en pixels)", "reject-image-height-help": "Les images plus grandes que cette valeur seront rejetées.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Autoriser les utilisateurs à téléverser des miniatures de sujet", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Miniature du sujet", "allowed-file-extensions": "Extensions de fichiers autorisées", "allowed-file-extensions-help": "Entrer une liste d’extensions de fichier séparées par une virgule (ex : pdf,xls,doc). Une liste vide signifie que toutes les extensions sont autorisées.", diff --git a/public/language/fr/admin/settings/user.json b/public/language/fr/admin/settings/user.json index 9a358f5ed5..57bb85c0ad 100644 --- a/public/language/fr/admin/settings/user.json +++ b/public/language/fr/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Afficher l'adresse e-mail", "show-fullname": "Afficher le nom complet", "restrict-chat": "Autoriser uniquement les discussions aux utilisateurs que je suis", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Ouvrir les liens sortants dans un nouvel onglet", "topic-search": "Activer la recherche au sein des sujets", "update-url-with-post-index": "Mettre à jour l'URL avec l'index des articles", diff --git a/public/language/fr/admin/settings/web-crawler.json b/public/language/fr/admin/settings/web-crawler.json index e002970e53..2486ba4d9b 100644 --- a/public/language/fr/admin/settings/web-crawler.json +++ b/public/language/fr/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Désactiver les flux RSS", "disable-sitemap-xml": "Désactiver le fichier sitemap.xml", "sitemap-topics": "Nombre de sujets à afficher dans le sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Effacer le cache du sitemap", "view-sitemap": "Afficher le sitemap" } \ No newline at end of file diff --git a/public/language/fr/aria.json b/public/language/fr/aria.json index b0d6da7437..e4b383b847 100644 --- a/public/language/fr/aria.json +++ b/public/language/fr/aria.json @@ -2,8 +2,9 @@ "post-sort-option": "Option de tri des messages, %1", "topic-sort-option": "Option de tri des sujets, %1", "user-avatar-for": "Avatar de l'utilisateur pour %1", - "profile-page-for": "Profile page for user %1", + "profile-page-for": "Page de profil de l'utilisateur %1", "user-watched-tags": "L'utilisateur a regardé les tags", "delete-upload-button": "Supprimer le bouton de téléchargement", - "group-page-link-for": "Lien vers la page de groupe pour %1" + "group-page-link-for": "Lien vers la page de groupe pour %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/fr/category.json b/public/language/fr/category.json index bfc20d2d95..ec9effd39e 100644 --- a/public/language/fr/category.json +++ b/public/language/fr/category.json @@ -1,12 +1,13 @@ { "category": "Catégorie", "subcategories": "Sous-catégories", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nouveau sujet", "guest-login-post": "Se connecter pour poster", "no-topics": "Il n'y a aucun sujet dans cette catégorie.
Pourquoi ne pas en créer un ?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "parcouru par", "no-replies": "Personne n'a répondu", "no-new-posts": "Pas de nouveau message", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 63fed13a3e..40e339df71 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON invalide", "wrong-parameter-type": "Une valeur de type %3 était attendue pour la propriété `%1`, mais %2 a été reçu à la place", "required-parameters-missing": "Les paramètres requis étaient manquants dans cet appel d'API : %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Vous ne semblez pas être connecté.", "account-locked": "Votre compte a été temporairement suspendu", "search-requires-login": "Rechercher nécessite d'avoir un compte. Veuillez vous identifier ou vous enregistrer.", @@ -67,8 +68,8 @@ "no-chat-room": "Le salon de discussion n'existe pas.", "no-privileges": "Vous n'avez pas les privilèges nécessaires pour effectuer cette action.", "category-disabled": "Catégorie désactivée", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Message supprimé", + "topic-locked": "Sujet verrouillé", "post-edit-duration-expired": "Vous ne pouvez modifier un message que pendant %1 seconde(s) après l'avoir posté.", "post-edit-duration-expired-minutes": "Vous ne pouvez éditer un message que pendant %1 minute(s) après l'avoir posté.", "post-edit-duration-expired-minutes-seconds": "Vous ne pouvez éditer un message que pendant %1 minute(s) et %2 seconde(s) après l'avoir posté.", @@ -146,6 +147,7 @@ "post-already-restored": "Message déjà restauré", "topic-already-deleted": "Sujet déjà supprimé", "topic-already-restored": "Sujet déjà restauré", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Il n'est pas possible d'effacer le message principal, veuillez supprimer le sujet entier à la place.", "topic-thumbnails-are-disabled": "Les miniatures de sujet sont désactivés", "invalid-file": "Fichier invalide", @@ -154,6 +156,8 @@ "about-me-too-long": "Votre texte \"à propos de moi\" ne peut dépasser %1 caractère(s).", "cant-chat-with-yourself": "Vous ne pouvez discuter avec vous-même !", "chat-restricted": "Cet utilisateur a restreint ses messages de chat. Il doit d'abord s'abonner à votre compte avant que vous puissiez discuter avec lui.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "Tu as été bloqué par cet utilisateur", "chat-disabled": "Système de chat désactivé", "too-many-messages": "Vous avez envoyé trop de messages, veuillez patienter un instant.", @@ -225,6 +229,7 @@ "no-topics-selected": "Aucun sujet sélectionné !", "cant-move-to-same-topic": "Impossible de déplacer le message dans le même sujet !", "cant-move-topic-to-same-category": "Impossible de déplacer le sujet dans la même catégorie !", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Vous ne pouvez pas vous bloquer !", "cannot-block-privileged": "Vous ne pouvez pas bloquer les administrateurs ou les modérateurs globaux", "cannot-block-guest": "Les Invités ne peuvent pas bloquer d'autres utilisateurs", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Serveur inaccessible pour le moment. Cliquez ici pour réessayer ou réessayez plus tard", "invalid-plugin-id": "ID de plugin invalide", "plugin-not-whitelisted": "Impossible d'installer le plugin, seuls les plugins mis en liste blanche dans le gestionnaire de packages NodeBB peuvent être installés via l'ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "Vous n'êtes pas autorisé à modifier l'état des plugins car ils sont définis au moment de l'exécution (config.json, variables d'environnement ou arguments de terminal), veuillez plutôt modifier la configuration.", "theme-not-set-in-configuration": "Lors de la définition des plugins actifs, le changement de thème nécessite d'ajouter le nouveau thème à la liste des plugins actifs avant de le mettre à jour dans l'ACP", @@ -246,13 +252,14 @@ "api.401": "Aucune session de connexion valide trouvée. Veuillez vous connecter et réessayer.", "api.403": "Vous n'êtes pas autorisé à réaliser cet appel", "api.404": "Appel de l'API non valide", + "api.413": "The request payload is too large", "api.426": "HTTPS est requis pour les demandes d’écriture via l'API, veuillez renvoyer votre demande via HTTPS", "api.429": "Vous avez fait trop de demandes, veuillez réessayer plus tard", "api.500": "Une erreur inattendue s'est produite lors de la tentative de traitement de votre demande.", "api.501": "L'accès n'est pas encore fonctionnel, veuillez réessayer demain", "api.503": "L'accès n'est pas disponible actuellement en raison de la configuration du serveur", "api.reauth-required": "La ressource à laquelle vous tentez d'accéder nécessite une (ré-)authentification.", - "activitypub.not-enabled": "Federation is not enabled on this server", + "activitypub.not-enabled": "La fédération n'est pas activée sur ce serveur.", "activitypub.invalid-id": "Unable to resolve the input id, likely as it is malformed.", "activitypub.get-failed": "Unable to retrieve the specified resource.", "activitypub.pubKey-not-found": "Unable to resolve public key, so payload verification cannot take place.", diff --git a/public/language/fr/global.json b/public/language/fr/global.json index 7b5cf21869..d440f9e321 100644 --- a/public/language/fr/global.json +++ b/public/language/fr/global.json @@ -50,7 +50,7 @@ "header.navigation": "Navigation", "header.manage": "Gestion", "header.drafts": "Brouillons", - "header.world": "World", + "header.world": "Web", "notifications.loading": "Chargement des notifications", "chats.loading": "Chargement des discussions", "drafts.loading": "Chargement des brouillons", @@ -68,6 +68,7 @@ "users": "Utilisateurs", "topics": "Sujets", "posts": "Messages", + "crossposts": "Cross-posts", "x-posts": "%1 messages", "x-topics": "%1 sujets", "x-reputation": "%1 réputation", @@ -82,6 +83,7 @@ "downvoted": "Vote(s) négatif(s)", "views": "Vues", "posters": "Publieurs", + "watching": "Abonné", "reputation": "Réputation", "lastpost": "Dernier message", "firstpost": "Premier message", @@ -111,7 +113,7 @@ "dnd": "Occupé", "invisible": "Invisible", "offline": "Hors-ligne", - "remote-user": "This user is from outside of this forum", + "remote-user": "Cet utilisateur ne fait pas partie de ce forum.", "email": "Email", "language": "Langue", "guest": "Invité", diff --git a/public/language/fr/groups.json b/public/language/fr/groups.json index e9a0f931e1..f946d20072 100644 --- a/public/language/fr/groups.json +++ b/public/language/fr/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Tous les groupes", "groups": "Groupes", "members": "Membres", + "x-members": "%1 member(s)", "view-group": "Voir le groupe", "owner": "Propriétaire du groupe", "new-group": "Créer un nouveau groupe", diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index 475fd1d297..007cdb8793 100644 --- a/public/language/fr/modules.json +++ b/public/language/fr/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Ajouter un utilisateur", "chat.notification-settings": "Paramètres de notification", "chat.default-notification-setting": "Paramètres de notification par défaut", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Salon par défaut", "chat.notification-setting-none": "Aucune notification", "chat.notification-setting-at-mention-only": "@mention seulement", @@ -81,7 +82,7 @@ "composer.hide-preview": "Masquer l'aperçu", "composer.help": "Aide", "composer.user-said-in": "%1 a dit dans %2 :", - "composer.user-said": "%1 a dit :", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Êtes-vous sûr de bien vouloir supprimer ce message ?", "composer.submit-and-lock": "Envoyer et verrouiller", "composer.toggle-dropdown": "Afficher/masquer le menu", diff --git a/public/language/fr/notifications.json b/public/language/fr/notifications.json index 00cdc357b9..cc08b7b941 100644 --- a/public/language/fr/notifications.json +++ b/public/language/fr/notifications.json @@ -22,7 +22,7 @@ "upvote": "Votes positifs", "awards": "Récompenses", "new-flags": "Nouveaux signalements", - "my-flags": "Signalements qui me sont assignés", + "my-flags": "My Flags", "bans": "Bannissements", "new-message-from": "Nouveau message de %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 et %2 ont écrit dans %4", "user-posted-in-public-room-triple": "%1, %2 et %3 ont écris dans %5", "user-posted-in-public-room-multiple": "%1, %2 et %3 d'autres ont écrit dans %5", - "upvoted-your-post-in": "%1 a voté pour votre message dans %2.", - "upvoted-your-post-in-dual": "%1 et %2 ont voté pour votre message dans %3.", - "upvoted-your-post-in-triple": "%1, %2 et %3 ont voté pour votre message dans %4.", - "upvoted-your-post-in-multiple": "%1, %2 et %3 autres ont voté positivement pour votre message dans %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 a déplacé votre message vers %2", "moved-your-topic": "%1 a déplacé %2.", - "user-flagged-post-in": "%1 a signalé un message dans %2.", - "user-flagged-post-in-dual": "%1 et %2 ont signalé un message dans %3", - "user-flagged-post-in-triple": "%1, %2 et %3 ont signalé un message dans %4", - "user-flagged-post-in-multiple": "%1, %2 et %3 autres ont signalé un message dans %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 a signalé un profil utilisateur (%2)", "user-flagged-user-dual": "%1 et %2 ont signalé un profil utilisateur (%3)", "user-flagged-user-triple": "%1, %2 et %3 ont signalé un profil utilisateur (%4)", "user-flagged-user-multiple": "%1, %2 et %3 autres ont signalé un profil utilisateur (%4)", - "user-posted-to": "%1 a répondu à : %2", - "user-posted-to-dual": "%1 et %2 ont posté une réponse à : %3", - "user-posted-to-triple": "%1, %2 et %3 ont publié des réponses à : %4", - "user-posted-to-multiple": "%1, %2 et %3 autres ont posté des réponses dans : %4", - "user-posted-topic": "%1 a posté un nouveau sujet: %2", - "user-edited-post": "%1 a édité un message dans %2", - "user-posted-topic-with-tag": "%1a publié %2 (tags %3)", - "user-posted-topic-with-tag-dual": "%1a publié%2 (tags %3 and %4)", - "user-posted-topic-with-tag-triple": "%1as publié%2 (tags %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1as publié %2(tags %3)", - "user-posted-topic-in-category": "%1 a posté un nouveau sujet: %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 vous suit.", "user-started-following-you-dual": "%1 et %2 se sont abonnés à votre compte.", "user-started-following-you-triple": "%1, %2 et %3 ont commencé à vous suivre.", @@ -71,11 +71,11 @@ "users-csv-exported": "Utilisateurs exportés en CSV, cliquer pour télécharger", "post-queue-accepted": "Votre message a été accepté. Cliquez ici pour l'afficher.", "post-queue-rejected": "Votre message a été rejeté.", - "post-queue-notify": "Vous avez reçu une notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mail vérifié", "email-confirmed-message": "Merci pour la validation de votre adresse e-mail. Votre compte est désormais activé.", "email-confirm-error-message": "Il y a un un problème dans la vérification de votre adresse e-mail. Le code est peut être invalide ou a expiré.", - "email-confirm-error-message-already-validated": "Votre adresse email a déjà été validée", "email-confirm-sent": "E-mail de vérification envoyé.", "none": "aucun", "notification-only": "Seulement une notification", @@ -99,8 +99,8 @@ "notificationType-new-post-flag": "Lorsque un message est marqué", "notificationType-new-user-flag": "Lorsque un utilisateur est marqué", "notificationType-new-reward": "Lorsque vous gagnez une nouvelle récompense", - "activitypub.announce": "%1 shared your post in %2 to their followers.", - "activitypub.announce-dual": "%1 and %2 shared your post in %3 to their followers.", - "activitypub.announce-triple": "%1, %2 and %3 shared your post in %4 to their followers.", - "activitypub.announce-multiple": "%1, %2 and %3 others shared your post in %4 to their followers." + "activitypub.announce": "%1 a partagé votre publication à %2 personnes.", + "activitypub.announce-dual": "%1 et %2 ont partagé votre publication à %3 personnes.", + "activitypub.announce-triple": "%1, %2 et %3 ont partagé votre publication à %4 personnes.", + "activitypub.announce-multiple": "%1, %2 et %3 autres personnes ont partagé votre publication à %4 personnes." } \ No newline at end of file diff --git a/public/language/fr/social.json b/public/language/fr/social.json index c0a67052a8..b2b66a6cf6 100644 --- a/public/language/fr/social.json +++ b/public/language/fr/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Connectez-vous avec Facebook", "continue-with-facebook": "Continuer avec Facebook", "sign-in-with-linkedin": "Connectez-vous avec LinkedIn", - "sign-up-with-linkedin": "Inscrivez-vous avec LinkedIn" + "sign-up-with-linkedin": "Inscrivez-vous avec LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/fr/themes/harmony.json b/public/language/fr/themes/harmony.json index bd02a12b57..57e925ff5d 100644 --- a/public/language/fr/themes/harmony.json +++ b/public/language/fr/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Thème Harmony", "skins": "Habillages", + "light": "Light", + "dark": "Dark", "collapse": "Réduire", "expand": "Développer", "sidebar-toggle": "Réduire la barre latérale", diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json index 35fe9b1dc4..067f0c50dd 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -22,12 +22,12 @@ "edit": "Éditer", "delete": "Supprimer", "delete-event": "Supprimer l'événement", - "delete-event-confirm": "Êtes-vous sûr de vouloir supprimer cet événement ?", + "delete-event-confirm": "Êtes-vous sûr de vouloir supprimer cet événement ?", "purge": "Supprimer définitivement", "restore": "Restaurer", "move": "Déplacer", "change-owner": "Changer de propriétaire", - "manage-editors": "Manage Editors", + "manage-editors": "Gérer les éditeurs", "fork": "Scinder", "link": "Lien", "share": "Partager", @@ -61,14 +61,16 @@ "user-restored-topic-on": "%1 a restauré ce sujet sur %2", "user-moved-topic-from-ago": "%1 a déplacé ce sujet de %2 %3", "user-moved-topic-from-on": "%1 a déplacé ce sujet de %2 sur %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "user-shared-topic-ago": "%1 a partagé ce sujet %2", + "user-shared-topic-on": "%1 a partagé ce sujet sur %2", "user-queued-post-ago": "%1 message En attente pour approbation %3", "user-queued-post-on": "%1 message En attente pour approbation sur %3", "user-referenced-topic-ago": "%1 a fait référence à ce sujet %3", "user-referenced-topic-on": "%1 a fait référence à ce sujet sur %3", "user-forked-topic-ago": "%1 a scindé ce sujet %3", "user-forked-topic-on": "%1 a scindé ce sujet sur %3", + "user-crossposted-topic-ago": "%1 a publié simultanément ce sujet sur %2 %3", + "user-crossposted-topic-on": "%1 a publié simultanément ce sujet %2 sur %3", "bookmark-instructions": "Cliquer ici pour aller au dernier message lu de ce fil.", "flag-post": "Signaler ce message", "flag-user": "Signaler cet utilisateur", @@ -84,7 +86,7 @@ "login-to-subscribe": "Veuillez vous enregistrer ou vous connecter afin de vous abonner à ce sujet.", "markAsUnreadForAll.success": "Sujet marqué comme non lu pour tout le monde.", "mark-unread": "Marquer comme non lu", - "mark-unread.success": "Sujet marqué comme non lu.", + "mark-unread.success": "Sujet marqué comme non lu", "watch": "Suivre", "unwatch": "Cesser de suivre", "watch.title": "Être notifié des nouvelles réponses dans ce sujet", @@ -93,20 +95,21 @@ "watching": "Suivi", "not-watching": "Suivre", "ignoring": "Ignoré", - "watching.description": "Me notifier les nouvelles réponses.
Afficher le sujet dans l'onglet \"Non lu\".", - "not-watching.description": "Ne pas me notifier les nouvelles réponses.
Afficher le sujet dans l'onglet \"Non lu\" si la catégorie n'est pas ignorée.", - "ignoring.description": "Ne pas me notifier les nouvelles réponses.
Ne pas afficher ce sujet dans l'onglet \"Non lu\".", + "watching.description": "Me notifier les nouvelles réponses.
Afficher le sujet dans l'onglet \"Non lus\".", + "not-watching.description": "Ne pas me notifier les nouvelles réponses.
Afficher le sujet dans l'onglet \"Non lus\" si la catégorie n'est pas ignorée.", + "ignoring.description": "Ne pas me notifier les nouvelles réponses.
Ne pas afficher ce sujet dans l'onglet \"Non lus\".", "thread-tools.title": "Outils pour sujets", - "thread-tools.markAsUnreadForAll": "Marquer non lu pour tous", + "thread-tools.markAsUnreadForAll": "Marquer non lus pour tous", "thread-tools.pin": "Épingler le sujet", "thread-tools.unpin": "Désépingler le sujet", "thread-tools.lock": "Verrouiller le sujet", "thread-tools.unlock": "Déverouiller le sujet", "thread-tools.move": "Déplacer le sujet", + "thread-tools.crosspost": "Sujet publié simultanément", "thread-tools.move-posts": "Déplacer les messages", "thread-tools.move-all": "Déplacer tout", "thread-tools.change-owner": "Changer de propriétaire", - "thread-tools.manage-editors": "Manage Editors", + "thread-tools.manage-editors": "Gérer les éditeurs", "thread-tools.select-category": "Sélectionner une catégorie", "thread-tools.fork": "Scinder le sujet", "thread-tools.tag": "Mot-clé de sujet", @@ -132,15 +135,17 @@ "pin-modal-help": "Vous pouvez éventuellement définir une date d'expiration pour le(s) sujet(s) épinglé(s) ici. Vous pouvez également laisser ce champ vide pour que le sujet reste épinglé jusqu'à ce qu'il soit supprimé manuellement.", "load-categories": "Chargement des catégories en cours", "confirm-move": "Déplacer", + "confirm-crosspost": "Publication simultanée", "confirm-fork": "Scinder", "bookmark": "Marque-page", "bookmarks": "Marque-pages", "bookmarks.has-no-bookmarks": "Vous n'avez encore aucun marque-page.", "copy-permalink": "Copier le permalien", - "go-to-original": "View Original Post", + "go-to-original": "Voir le message original", "loading-more-posts": "Charger plus de messages", "move-topic": "Déplacer le sujet", "move-topics": "Déplacer les sujets", + "crosspost-topic": "Sujet publié simultanément", "move-post": "Déplacer", "post-moved": "Message déplacé !", "fork-topic": "Scinder le sujet", @@ -152,17 +157,20 @@ "x-posts-will-be-moved-to-y": "%1 message(s) seront déplacés vers \"%2\"", "fork-pid-count": "%1 message(s) sélectionné(s)", "fork-success": "Sujet scindé avec succès ! Cliquez ici pour aller au sujet scindé.", - "delete-posts-instruction": "Sélectionner les messages que vous souhaitez supprimer/vider", - "merge-topics-instruction": "Cliquer sur les sujets que vous voulez fusionner", + "delete-posts-instruction": "Sélectionnez les messages que vous souhaitez supprimer/vider", + "merge-topics-instruction": "Cliquez sur les sujets que vous voulez fusionner", "merge-topic-list-title": "Liste des sujets à fusionner", "merge-options": "Options de fusion", - "merge-select-main-topic": "Sélectionner le sujet principal", + "merge-select-main-topic": "Sélectionnez le sujet principal", "merge-new-title-for-topic": "Nouveau titre pour le sujet", "topic-id": "Sujet ID", - "move-posts-instruction": "Cliquer sur les articles que vous souhaitez déplacer, puis entrez un ID de sujet ou accédez au sujet cible", - "move-topic-instruction": "Sélectionner la catégorie cible puis cliquer sur déplacer", - "change-owner-instruction": "Cliquer sur les messages que vous souhaitez attribuer à un autre utilisateur.", - "manage-editors-instruction": "Manage the users who can edit this post below.", + "move-posts-instruction": "Cliquez sur les articles que vous souhaitez déplacer, puis entrez un ID de sujet ou accédez au sujet cible", + "move-topic-instruction": "Sélectionnez la catégorie cible puis cliquer sur déplacer", + "change-owner-instruction": "Cliquez sur les messages que vous souhaitez attribuer à un autre utilisateur.", + "manage-editors-instruction": "Gérez les utilisateurs qui peuvent modifier le message ci-dessous.", + "crossposts.instructions": "Sélectionnez une ou plusieurs catégories pour publier ce sujet en simultané. Le(s) sujet(s) seront accessibles depuis la catégorie d'origine ainsi que toutes les catégories sélectionnées.", + "crossposts.listing": "Ce sujet a été publié simultanément dans les catégories locales suivantes :", + "crossposts.none": "Ce sujet n'a pas été publié simultanément dans d'autres catégories.", "composer.title-placeholder": "Entrer le titre du sujet ici…", "composer.handle-placeholder": "Entrez votre nom/identifiant ici", "composer.hide": "Cacher", @@ -174,6 +182,7 @@ "composer.replying-to": "En réponse à %1", "composer.new-topic": "Nouveau sujet", "composer.editing-in": "Modification du message dans %1", + "composer.untitled-topic": "Sujet sans titre", "composer.uploading": "envoi en cours…", "composer.thumb-url-label": "Coller une URL de vignette du sujet", "composer.thumb-title": "Ajouter une vignette à ce sujet", @@ -198,7 +207,7 @@ "stale.create": "Créer un nouveau sujet", "stale.reply-anyway": "Répondre à ce sujet quand même", "link-back": "Re : [%1](%2)", - "diffs.title": "Historique", + "diffs.title": "Historique des modifications", "diffs.description": "Cet article a %1 révisions. Cliquer sur l'une des révisions ci-dessous pour voir le contenu du message.", "diffs.no-revisions-description": "Cet article a %1 révisions.", "diffs.current-revision": "Révision en cours", @@ -223,6 +232,9 @@ "post-tools": "Outils pour les messages", "unread-posts-link": "Lien pour les messages non lus", "thumb-image": "Vignette du sujet", - "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers": "Partages", + "announcers-x": "Partages (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 3bc4183bed..6999f27044 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -105,6 +105,10 @@ "show-email": "Afficher mon e-mail", "show-fullname": "Afficher mon nom complet", "restrict-chats": "Autoriser la réception de messages ne provenant que des personnes auxquelles je suis abonné", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "S’inscrire aux lettres de suivi d'activités", "digest-description": "S'abonner par e-mail aux mises à jour de ce forum (nouvelles notifications et nouveaux sujets) selon le planning sélectionné.", "digest-off": "Désactivé", diff --git a/public/language/fr/world.json b/public/language/fr/world.json index 3753335278..b238721bf7 100644 --- a/public/language/fr/world.json +++ b/public/language/fr/world.json @@ -1,18 +1,25 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Monde", + "latest": "Dernier", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "Tous", + "help": "Aide", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", - "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.title": "Qu'est-ce que cette page ?", + "help.intro": "Bienvenue dans votre coin du fediverse.", + "help.fediverse": "Le \"fediverse\" est un réseau d'applications et de sites web interconnectés qui communiquent entre eux et dont les utilisateurs peuvent se voir. Ce forum est fédéré et peut interagir avec ce web social (ou \"fediverse\"). Cette page est votre coin du fediverse. Elle contient uniquement des sujets créés par, et partagés par, les utilisateurs que vous suivez.", + "help.build": "Il n'y a peut-être pas encore beaucoup de sujets ici au début, c'est normal. Vous commencerez à voir plus de contenu au fil du temps lorsque vous commencerez à suivre d'autres utilisateurs.", + "help.federating": "De même, si des utilisateurs extérieurs à ce forum commencent à vous suivre, vos publications commenceront à apparaître sur ces applications et sites web également.", + "help.next-generation": "C'est la prochaine génération des réseaux sociaux, commencez à contribuer dès aujourd'hui !", - "onboard.title": "Your window to the fediverse...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.title": "Votre fenêtre sur le fediverse...", + "onboard.what": "Voici votre catégorie personnalisée, composée uniquement de contenu trouvé en dehors de ce forum. La présence de contenu sur cette page dépend de si vous suivez la personne, ou si ce post a été partagé par quelqu'un que vous suivez.", + "onboard.why": "Il se passe beaucoup de choses en dehors de ce forum, et tout n'est pas nécessairement pertinent pour vos intérêts. C'est pourquoi suivre des personnes est le meilleur moyen de signaler que vous souhaitez voir plus de contenu de leur part.", + "onboard.how": "En attendant, vous pouvez cliquer sur les boutons de raccourci en haut pour voir ce que ce forum connaît d'autre et commencer à découvrir de nouveaux contenus !", + + "category-search": "Trouver une catégorie..." } \ No newline at end of file diff --git a/public/language/gl/admin/advanced/cache.json b/public/language/gl/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/gl/admin/advanced/cache.json +++ b/public/language/gl/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/gl/admin/dashboard.json b/public/language/gl/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/gl/admin/dashboard.json +++ b/public/language/gl/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/gl/admin/development/info.json b/public/language/gl/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/gl/admin/development/info.json +++ b/public/language/gl/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/gl/admin/manage/categories.json b/public/language/gl/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/gl/admin/manage/categories.json +++ b/public/language/gl/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/gl/admin/manage/custom-reasons.json b/public/language/gl/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/gl/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/gl/admin/manage/privileges.json b/public/language/gl/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/gl/admin/manage/privileges.json +++ b/public/language/gl/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/gl/admin/manage/users.json b/public/language/gl/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/gl/admin/manage/users.json +++ b/public/language/gl/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/gl/admin/settings/activitypub.json b/public/language/gl/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/gl/admin/settings/activitypub.json +++ b/public/language/gl/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/gl/admin/settings/chat.json b/public/language/gl/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/gl/admin/settings/chat.json +++ b/public/language/gl/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/gl/admin/settings/email.json b/public/language/gl/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/gl/admin/settings/email.json +++ b/public/language/gl/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/gl/admin/settings/notifications.json b/public/language/gl/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/gl/admin/settings/notifications.json +++ b/public/language/gl/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/gl/admin/settings/uploads.json b/public/language/gl/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/gl/admin/settings/uploads.json +++ b/public/language/gl/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/gl/admin/settings/user.json b/public/language/gl/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/gl/admin/settings/user.json +++ b/public/language/gl/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/gl/admin/settings/web-crawler.json b/public/language/gl/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/gl/admin/settings/web-crawler.json +++ b/public/language/gl/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/gl/aria.json b/public/language/gl/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/gl/aria.json +++ b/public/language/gl/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/gl/category.json b/public/language/gl/category.json index 475e6b78c1..6e567e7e6c 100644 --- a/public/language/gl/category.json +++ b/public/language/gl/category.json @@ -1,12 +1,13 @@ { "category": "Categoría", "subcategories": "Subcategoría", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Novo tema", "guest-login-post": "Inicia sesión para poder escribir mensaxes", "no-topics": "Non hai temas nesta categoría.
Por que non abres un?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "vendo agora", "no-replies": "Ninguén respondeu", "no-new-posts": "Non hai publicacións novas.", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 1c958d7c23..404fbefb0b 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Parece que estás desconectado.", "account-locked": "A túa conta foi bloqueada temporalmente.", "search-requires-login": "As buscas requiren unha conta. Por favor inicia sesión ou rexístrate.", @@ -146,6 +147,7 @@ "post-already-restored": "A publicación foi restaurada", "topic-already-deleted": "O tema foi borrado", "topic-already-restored": "O tema foi restaurado", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Non podes purgar a publicación principal, por favor, elimínaa no seu canto.", "topic-thumbnails-are-disabled": "Miniaturas do tema deshabilitadas.", "invalid-file": "Arquivo Inválido", @@ -154,6 +156,8 @@ "about-me-too-long": "Desculpa, o teu \"sobre min\" non pode supera-los %1 caracteres,", "cant-chat-with-yourself": "Non podes falar contigo mesmo!", "chat-restricted": "Este usuario restrinxiu as charlas. Debedes seguirvos antes de que poidas falar con el.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Charlas desactivadas", "too-many-messages": "Estás a enviar moitas mensaxes, por favor, agarda un anaco.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/gl/global.json b/public/language/gl/global.json index b6c164a0f9..28d534d971 100644 --- a/public/language/gl/global.json +++ b/public/language/gl/global.json @@ -68,6 +68,7 @@ "users": "Usuarios", "topics": "Temas", "posts": "Publicacións", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Votado negativamente", "views": "Vistas", "posters": "Posters", + "watching": "Watching", "reputation": "Reputación", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/gl/groups.json b/public/language/gl/groups.json index b283020ee6..09aef85be3 100644 --- a/public/language/gl/groups.json +++ b/public/language/gl/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupos", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Ver grupo", "owner": "Dono do grupo", "new-group": "Crear un novo grupo", diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index 437ec89919..64503a4368 100644 --- a/public/language/gl/modules.json +++ b/public/language/gl/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Agochar vista previa", "composer.help": "Help", "composer.user-said-in": "%1 dixo en %2", - "composer.user-said": "%1 dixo:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Estás seguro de que queres desfacer esta publicación?", "composer.submit-and-lock": "Enviar e bloquear", "composer.toggle-dropdown": "Alternar despregable", diff --git a/public/language/gl/notifications.json b/public/language/gl/notifications.json index 4cafb94b12..bba321c678 100644 --- a/public/language/gl/notifications.json +++ b/public/language/gl/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Nova mensaxe de %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 votoute positivo en %2.", - "upvoted-your-post-in-dual": "%1 e %2 votaron positivamente a túa mensaxe en %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 moveu a túa publicación a%2", "moved-your-topic": "%1 moveu %2", - "user-flagged-post-in": "%1 reportou unha mensaxe en %2", - "user-flagged-post-in-dual": "%1 e %2 reportaron a túa mensaxe en %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flagged a user profile (%2)", "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 publicou unha resposta en: %2", - "user-posted-to-dual": "%1 e %2 responderon a %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 publicou un novo tema: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 comezou a seguirte.", "user-started-following-you-dual": "%1 e %2 comezaron a seguirte.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Correo confirmado", "email-confirmed-message": "Grazas por validar o teu correo. A túa conta agora está activada.", "email-confirm-error-message": "Houbo un problema validando o teu correo. Poida que o código fose inválido ou expirase.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Correo de confirmación enviado.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/gl/social.json b/public/language/gl/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/gl/social.json +++ b/public/language/gl/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/gl/themes/harmony.json b/public/language/gl/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/gl/themes/harmony.json +++ b/public/language/gl/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json index 1422a1cd53..92cc657553 100644 --- a/public/language/gl/topic.json +++ b/public/language/gl/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Pica aquí para volver á última mensaxe lida neste tema", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Pechar Tema", "thread-tools.unlock": "Reabrir Tema", "thread-tools.move": "Mover Tema", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Mover todo", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Cargando categorías", "confirm-move": "Mover", + "confirm-crosspost": "Cross-post", "confirm-fork": "Dividir", "bookmark": "Marcador", "bookmarks": "Marcadores", @@ -141,6 +145,7 @@ "loading-more-posts": "Cargando máis publicacións", "move-topic": "Mover Tema", "move-topics": "Mover Temas", + "crosspost-topic": "Cross-post Topic", "move-post": "Mover publicación", "post-moved": "Publicación movida correctamente!", "fork-topic": "Dividir Tema", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Introduce o título do teu tema", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "En resposta a %1", "composer.new-topic": "Novo tema", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "subindo...", "composer.thumb-url-label": "Agrega unha URL de miniatura para o tema", "composer.thumb-title": "Agregar miniatura a este tema", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/gl/user.json b/public/language/gl/user.json index 1be352f7b1..c5d126726d 100644 --- a/public/language/gl/user.json +++ b/public/language/gl/user.json @@ -105,6 +105,10 @@ "show-email": "Amosa-lo meu Email", "show-fullname": "Amosa-lo meu Nome Completo", "restrict-chats": "Permiti-lo chat só con usuarios aos que sigo.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Subscribirse ao resumo", "digest-description": "Subscribirse as actualizacións por correo deste foro (novas notificacións e temas), segundo un calendario prefixado.", "digest-off": "Desactivado", diff --git a/public/language/gl/world.json b/public/language/gl/world.json index 3753335278..e6694bf507 100644 --- a/public/language/gl/world.json +++ b/public/language/gl/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/he/admin/advanced/cache.json b/public/language/he/admin/advanced/cache.json index c6500c1bd0..f0c0a69819 100644 --- a/public/language/he/admin/advanced/cache.json +++ b/public/language/he/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "מטמון", - "post-cache": "מטמון פוסטים", - "group-cache": "מטמון קבוצות", - "local-cache": "מטמון מקומי", - "object-cache": "מטמון אובייקטים", "percent-full": "%1% מלא", "post-cache-size": "גודל מטמון פוסטים", "items-in-cache": "פריטים במטמון" diff --git a/public/language/he/admin/advanced/database.json b/public/language/he/admin/advanced/database.json index 68c0208cb3..cf3736e51a 100644 --- a/public/language/he/admin/advanced/database.json +++ b/public/language/he/admin/advanced/database.json @@ -22,7 +22,7 @@ "mongo.bytes-out": "ביטים יוצאים", "mongo.num-requests": "מספר בקשות", "mongo.raw-info": "מידע לא מעובד מ-MongoDB", - "mongo.unauthorized": "NodeBB לא הצליחה לקבל את המידע הדרוש מ-MongoDB. אנא בדוק שלמשתמש יש הרשאת clusterMonitor ל-admin database.", + "mongo.unauthorized": "NodeBB לא הצליחה לקבל את המידע הדרוש מ-MongoDB. אנא בדקו שלמשתמש יש הרשאת clusterMonitor ל-admin database.", "redis": "Redis", "redis.version": "גרסת Redis", diff --git a/public/language/he/admin/advanced/errors.json b/public/language/he/admin/advanced/errors.json index ac86dbfdbf..43ec79168d 100644 --- a/public/language/he/admin/advanced/errors.json +++ b/public/language/he/admin/advanced/errors.json @@ -10,6 +10,6 @@ "route": "נתיב", "count": "ספירה", "no-routes-not-found": "הידד! אין שגיאות 404!", - "clear404-confirm": "האם אתה בטוח שאתה רוצה לנקות את לוג שגיאות 404?", + "clear404-confirm": "האם לנקות את לוג שגיאות 404?", "clear404-success": "שגיאות \"404 לא נמצא\" נוקו" } \ No newline at end of file diff --git a/public/language/he/admin/advanced/events.json b/public/language/he/admin/advanced/events.json index 10cfb34164..65d39624ad 100644 --- a/public/language/he/admin/advanced/events.json +++ b/public/language/he/admin/advanced/events.json @@ -3,15 +3,15 @@ "no-events": "אין אירועים", "control-panel": "בקרת אירועים", "delete-events": "מחיקת אירועים", - "confirm-delete-all-events": "האם אתה בטוח שאתה רוצה למחוק את כל האירועים שנרשמו?", + "confirm-delete-all-events": "האם למחוק את כל האירועים שנרשמו?", "filters": "מסננים", - "filters-apply": "החל מסננים", + "filters-apply": "החלת מסננים", "filter-type": "סוג אירוע", "filter-start": "מתאריך", "filter-end": "עד תאריך", "filter-user": "סינון לפי משתמש", - "filter-user.placeholder": "הקלד שם משתמש לסינון...", + "filter-user.placeholder": "הקלידו שם משתמש לסינון...", "filter-group": "סינון לפי קבוצה", - "filter-group.placeholder": "הקלד שם קבוצה לסינון...", + "filter-group.placeholder": "הקלידו שם קבוצה לסינון...", "filter-per-page": "פריטים בכל דף" } \ No newline at end of file diff --git a/public/language/he/admin/advanced/logs.json b/public/language/he/admin/advanced/logs.json index 5c1dd85a15..3c6038df10 100644 --- a/public/language/he/admin/advanced/logs.json +++ b/public/language/he/admin/advanced/logs.json @@ -1,7 +1,7 @@ { "logs": "לוגים", "control-panel": "בקרת לוגים", - "reload": "טען לוג מחדש", - "clear": "נקה לוגים", + "reload": "טעינת לוגים מחדש", + "clear": "ניקוי לוגים", "clear-success": "הלוגים נוקו!" } \ No newline at end of file diff --git a/public/language/he/admin/appearance/customise.json b/public/language/he/admin/appearance/customise.json index fc2c805f5f..fc9ca44159 100644 --- a/public/language/he/admin/appearance/customise.json +++ b/public/language/he/admin/appearance/customise.json @@ -1,8 +1,8 @@ { "customise": "התאמה אישית", "custom-css": "CSS/SASS מותאם אישית", - "custom-css.description": "הזן כאן הצהרות CSS/SASS משלך, שיוחלו לאחר כל הסגנונות האחרים.", - "custom-css.enable": "הפעל CSS/SASS מותאם אישית", + "custom-css.description": "הזינו כאן הצהרות CSS/SASS משלך, שיוחלו לאחר כל הסגנונות האחרים.", + "custom-css.enable": "הפעלת CSS/SASS מותאם אישית", "custom-js": "Javascript מותאם אישית", "custom-js.description": "הכניסו כאן JavaScript משלכם, שיבוצע לאחר טעינת הדף לחלוטין.", @@ -15,6 +15,6 @@ "custom-css.livereload": "הפעלת טעינה מחדש אוטומטית.", "custom-css.livereload.description": "הפעלה זו נועדה כדי לרענן את כל החיבורים מכל מכשיר, כאשר תשמרו את הדף המותאם אישית.", "bsvariables": "_variables.scss", - "bsvariables.description": "ניתן לעקוף משתני Bootstrap כאן. אתה יכול גם להשתמש בכלי כמו bootstrap.build ולהדביק את הפלט כאן.
שינויים דורשים בנייה מחדש והפעלה מחדש.", + "bsvariables.description": "ניתן לעקוף משתני Bootstrap כאן. תוכלו גם להשתמש בכלי כמו bootstrap.build ולהדביק את הפלט כאן.
שינויים דורשים בנייה מחדש והפעלה מחדש.", "bsvariables.enable": "Enable _variables.scss" } \ No newline at end of file diff --git a/public/language/he/admin/appearance/skins.json b/public/language/he/admin/appearance/skins.json index ceb0a6a9b8..39893ab1f6 100644 --- a/public/language/he/admin/appearance/skins.json +++ b/public/language/he/admin/appearance/skins.json @@ -2,15 +2,15 @@ "skins": "עיצובים", "bootswatch-skins": "עיצובי Bootswatch", "custom-skins": "עיצובים מותאמים אישית", - "add-skin": "הוסף עיצוב", - "save-custom-skins": "שמור עיצוב מותאם אישית", + "add-skin": "הוספת עיצוב", + "save-custom-skins": "שמירת עיצוב מותאם אישית", "save-custom-skins-success": "עיצובים מותאמים אישית נשמרו בהצלחה", "custom-skin-name": "שם עיצוב מותאם אישית", "custom-skin-variables": "משתני עיצוב מותאם אישית", "loading": "טוען עיצובים", "homepage": "דף הפרוייקט", - "select-skin": "בחר עיצוב זה", - "revert-skin": "חזור לעיצוב מקורי", + "select-skin": "בחירת עיצוב זה", + "revert-skin": "חזרה לעיצוב מקורי", "current-skin": "עיצוב נוכחי", "skin-updated": "עיצוב עודכן", "applied-success": "עיצוב %1 הוחל בהצלחה", diff --git a/public/language/he/admin/appearance/themes.json b/public/language/he/admin/appearance/themes.json index 507ccd8534..514681e5c8 100644 --- a/public/language/he/admin/appearance/themes.json +++ b/public/language/he/admin/appearance/themes.json @@ -2,12 +2,12 @@ "themes": "ערכות נושא", "checking-for-installed": "בודק ערכות נושא מותקנות...", "homepage": "דף הבית", - "select-theme": "בחר ערכת נושא", - "revert-theme": "חזור לערכת נושא מקורית", + "select-theme": "בחירת ערכת נושא", + "revert-theme": "חזרה לערכת נושא מקורית", "current-theme": "ערכת נושא נוכחית", "no-themes": "לא נמצאו ערכות נושא מותקנות", - "revert-confirm": "האם אתה בטוח שאתה רוצה לשחזר את ערכת הנושא הרגילה של NodeBB?", + "revert-confirm": "האם לשחזר את ערכת הנושא הרגילה של NodeBB?", "theme-changed": "ערכת הנושא שונתה", - "revert-success": "החזרת בהצלחה את הפורום שלך לערכת הנושא ברירת המחדל.", - "restart-to-activate": "אנא בצע בנייה והפעלה מחדש כדי להחיל את ערכת הנושא הזו." + "revert-success": "החזרתם בהצלחה את הפורום שלכם לערכת הנושא ברירת המחדל.", + "restart-to-activate": "אנא בצעו בנייה והפעלה מחדש כדי להחיל את ערכת הנושא הזו." } \ No newline at end of file diff --git a/public/language/he/admin/dashboard.json b/public/language/he/admin/dashboard.json index a1aeceeff0..1304c758a1 100644 --- a/public/language/he/admin/dashboard.json +++ b/public/language/he/admin/dashboard.json @@ -12,8 +12,8 @@ "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", + "page-views-custom-help": "הכניסו טווח תאריכים של התקופה בה תרצו לצפות בתעבורת הפורום. הפורמט הנדרש הוא YYYY-MM-DD", + "page-views-custom-error": "הזינו טווח תאריכים תקין כדלהלן YYYY-MM-DD", "stats.yesterday": "אתמול", "stats.today": "היום", @@ -25,21 +25,21 @@ "updates": "עדכונים", "running-version": "הפורום מעודכן לגרסה %1", - "keep-updated": "לעדכוני אבטחה ותיקוני באגים, וודא שהפורום שלך עדכני לגרסה האחרונה.", - "up-to-date": "אתה מעודכן ", + "keep-updated": "לעדכוני אבטחה ותיקוני באגים, וודאו שהפורום שלכם עדכני לגרסה האחרונה.", + "up-to-date": "הינך מעודכן ", "upgrade-available": "גרסה חדשה (v%1) שוחררה. שקול לעדכן את הפורום שלך.", "prerelease-upgrade-available": "זוהי גרסת טרום-הפצה מיושנת של NodeBB. גרסה חדשה (v%1) שוחררה. שקול לשדרג את ה-NodeBB שלך.", "prerelease-warning": " זוהי גרסת טרום-הפצה של NodeBB. עלולים להתרחש באגים לא מכוונים.", "fallback-emailer-not-found": "Fallback emailer לא נמצא!", - "running-in-development": "הפורום פועל במצב פיתוח. הפורום עשוי להיות חשוף לפגיעויות פוטנציאליות; אנא פנה למנהל המערכת שלך", - "latest-lookup-failed": "לא הצליח לחפש את הגרסה האחרונה הזמינה של NodeBB", + "running-in-development": "הפורום פועל במצב פיתוח. הפורום עשוי להיות חשוף לפגיעויות פוטנציאליות; אנא פנו למנהל המערכת שלכם", + "latest-lookup-failed": "לא הצליח למצוא את הגרסה האחרונה הזמינה של NodeBB", "notices": "התראות", "restart-not-required": "לא נדרשת הפעלה מחדש", "restart-required": "נדרשת הפעלה מחדש", "search-plugin-installed": "תוסף חיפוש הותקן", "search-plugin-not-installed": "תוסף חיפוש לא הותקן", - "search-plugin-tooltip": "התקן את תוסף החיפוש מעמוד התוספים על מנת להפעיל את אפשרות החיפוש", + "search-plugin-tooltip": "התקינו את תוסף החיפוש מעמוד התוספים על מנת להפעיל את אפשרות החיפוש", "control-panel": "שליטת מערכת", "rebuild-and-restart": "בנייה והפעלה מחדש", @@ -47,9 +47,9 @@ "restart-warning": "הפעלה או בניה מחדש של הפורום תנתק את כל החיבורים הקיימים למספר שניות", "restart-disabled": "הפעלה או בניה מחדש של הפורום בוטלה, נראה שאינך מפעיל את הפורום דרך שרת מתאים.", "maintenance-mode": "מצב תחזוקה", - "maintenance-mode-title": "לחץ כאן על מנת להכניס את הפורום למצב תחזוקה", + "maintenance-mode-title": "לחצו כאן על מנת להכניס את הפורום למצב תחזוקה", "dark-mode": "מצב כהה", - "realtime-chart-updates": "עדכן תרשים בזמן אמת", + "realtime-chart-updates": "עדכון תרשים בזמן אמת", "active-users": "משתמשים פעילים", "active-users.users": "משתמשים", @@ -75,6 +75,7 @@ "graphs.page-views-registered": "צפיות בדפים-רשומים", "graphs.page-views-guest": "צפיות בדפים-אורחים", "graphs.page-views-bot": "צפיות בדפים-בוטים", + "graphs.page-views-ap": "ActivityPub תצוגת דפים", "graphs.unique-visitors": "מבקרים ייחודיים", "graphs.registered-users": "משתמשים רשומים", "graphs.guest-users": "משתמשים אורחים", @@ -91,11 +92,11 @@ "start": "התחלה", "end": "סיום", "filter": "סינון", - "view-as-json": "הצג כ-JSON", - "expand-analytics": "הרחב ניתוח", - "clear-search-history": "מחק היסטוריית חיפושים", - "clear-search-history-confirm": "האם אתה בטוח שברצונך למחוק את כל היסטוריית החיפושים?", + "view-as-json": "הצגה כ-JSON", + "expand-analytics": "הרחבת ניתוח", + "clear-search-history": "מחיקת היסטוריית חיפושים", + "clear-search-history-confirm": "האם למחוק את כל היסטוריית החיפושים?", "search-term": "מונח", "search-count": "כמות", - "view-all": "הצג הכל" + "view-all": "הצגת הכל" } diff --git a/public/language/he/admin/development/info.json b/public/language/he/admin/development/info.json index d0b09aa382..c661dbf942 100644 --- a/public/language/he/admin/development/info.json +++ b/public/language/he/admin/development/info.json @@ -1,5 +1,5 @@ { - "you-are-on": "אתה נמצא ב %1:%2", + "you-are-on": "הינכם נמצאים ב %1:%2", "ip": "IP %1", "nodes-responded": "%1 nodes הגיבו בתוך %2 מילי שניות!", "host": "host", @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "מקוון", "git": "git", - "process-memory": "זיכרון תהליך", + "process-memory": "rss/heap used", "system-memory": "זיכרון מערכת", "used-memory-process": "זיכרון בשימוש ע\"י התהליך", "used-memory-os": "זיכרון מערכת בשימוש", @@ -17,7 +17,7 @@ "cpu-usage": "שימוש ב-CPU", "uptime": "משך זמן פעולת המערכת ללא השבתה", - "registered": "רשום", + "registered": "רשומים", "sockets": "Sockets", "connection-count": "כמות חיבורים", "guests": "אורחים", diff --git a/public/language/he/admin/development/logger.json b/public/language/he/admin/development/logger.json index 1bdf92c432..2177b19175 100644 --- a/public/language/he/admin/development/logger.json +++ b/public/language/he/admin/development/logger.json @@ -6,7 +6,7 @@ "enable-http": "הפעלת רישום HTTP", "enable-socket": "הפעלת רישום אירועים ב-socket.io", "file-path": "נתיב קובץ לוג", - "file-path-placeholder": "/path/to/log/file.log ::: השאירו ריק כדי שהלוג ישמר בטרמינל שלך", + "file-path-placeholder": "/path/to/log/file.log ::: השאירו ריק כדי שהלוג ישמר בטרמינל שלכם", "control-panel": "ניהול לוגים", "update-settings": "עידכון הגדרות לוגים" diff --git a/public/language/he/admin/extend/rewards.json b/public/language/he/admin/extend/rewards.json index ccad7caf8b..5b054e4eed 100644 --- a/public/language/he/admin/extend/rewards.json +++ b/public/language/he/admin/extend/rewards.json @@ -5,11 +5,11 @@ "condition-is": "הינו:", "condition-then": "תגמל ב:", "max-claims": "מספר פעמים בה ניתן לדרוש תגמול", - "zero-infinite": "הזן 0 ללא הגבלה", - "select-reward": "בחר תגמול", - "delete": "מחק", - "enable": "הפעל", - "disable": "השבת", + "zero-infinite": "הזינו 0 ללא הגבלה", + "select-reward": "בחירת תגמול", + "delete": "מחיקה", + "enable": "הפעלה", + "disable": "השבתה", "alert.delete-success": "תגמול נמחק בהצלחה", "alert.no-inputs-found": "תגמול לא חוקי - לא נמצא מידע!", diff --git a/public/language/he/admin/extend/widgets.json b/public/language/he/admin/extend/widgets.json index 50bd6ce969..c603426857 100644 --- a/public/language/he/admin/extend/widgets.json +++ b/public/language/he/admin/extend/widgets.json @@ -16,22 +16,22 @@ "container.body": "גוף", "container.alert": "התראה", - "alert.confirm-delete": "האם אתה בטוח שאתה רוצה למחוק את הווידג'ט?", + "alert.confirm-delete": "האם למחוק את הווידג'ט?", "alert.updated": "העלאת ווידג'טים", "alert.update-success": "הווידג'טים הועלו בהצלחה", "alert.clone-success": "הווידג'טים שוכפלו בהצלחה", - "error.select-clone": "בחר דף לשכפל ממנו", + "error.select-clone": "בחרו דף לשכפל ממנו", "title": "כותרת", - "title.placeholder": "כותרת (מוצגת רק בגורמים מכילים מסוימים)", - "container": "גורם מכיל", - "container.placeholder": "גרור ושחרר גורם מכיל (container) או הזן HTML כאן.", + "title.placeholder": "כותרת (מוצגת רק ב-containers מסוימים)", + "container": "גורם מכיל - Container", + "container.placeholder": "גררו ושחררו גורם מכיל (container) או הזינו HTML כאן.", "show-to-groups": "יוצג בקבוצות", "hide-from-groups": "יוסתר מקבוצות", "start-date": "תאריך התחלה", "end-date": "תאריך סיום", - "hide-on-mobile": "הסתר במובייל", - "hide-drafts": "הסתר טיוטות", - "show-drafts": "הצג טיוטות" + "hide-on-mobile": "הסתרה במובייל", + "hide-drafts": "הסתרת טיוטות", + "show-drafts": "הצגת טיוטות" } \ No newline at end of file diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index ed4a9247f9..ace84ca716 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "ניהול קטגוריות", - "add-category": "הוסף קטגוריה", - "jump-to": "קפוץ אל...", + "add-category": "הוספת קטגוריה", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", + "jump-to": "קפיצה אל...", "settings": "הגדרות קטגוריות", - "edit-category": "ערוך קטגוריה", + "edit-category": "עריכת קטגוריה", "privileges": "הרשאות", "back-to-categories": "חזרה לרשימת הקטגוריות", + "id": "Category ID", "name": "שם קטגוריה", "handle": "מקשר קטגוריה", "handle.help": "המקשר לקטגוריה שלך משמשת כייצוג של קטגוריה זו ברשתות אחרות, בדומה לשם משתמש. נקודת אחיזה בקטגוריה אינה יכולה להתאים לשם משתמש או קבוצת משתמשים קיימים.", "description": "תיאור קטגוריה", - "federatedDescription": "תיאור פדרציה", - "federatedDescription.help": "טקסט זה יצורף לתיאור הקטגוריה כאשר הוא יתבקש על ידי אתרים או אפליקציות אחרות.", - "federatedDescription.default": "זוהי קטגוריית פורום המכילה דיון אקטואלי. תוכלו להתחיל דיונים חדשים על ידי אזכור קטגוריה זו.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "צבע רקע", "text-color": "צבע טקסט", "bg-image-size": "גודל תמונת רקע", @@ -23,7 +27,7 @@ "is-section": "הגדר קטגוריה זו כמקטע ללא אפשרות כניסה, רק לתתי קטגוריות.", "post-queue": "תור פוסטים", "tag-whitelist": "רשימה לבנה של תגיות", - "upload-image": "העלה תמונה", + "upload-image": "העלו תמונה", "upload": "העלאה", "delete-image": "הסרה", "category-image": "תמונת קטגוריה", @@ -32,8 +36,8 @@ "optional-parent-category": "קטגוריית הורים (אופציונלי)", "top-level": "רמה עליונה", "parent-category-none": "(ללא)", - "copy-parent": "העתק אב", - "copy-settings": "העתק הגדרות מ:", + "copy-parent": "העתקת אב", + "copy-settings": "העתקת הגדרות מ:", "optional-clone-settings": "שכפול הגדרות מקטגוריה (אופציונלי)", "clone-children": "שכפול קטגוריות והגדרות של צאצאים", "purge": "מחיקת קטגוריה", @@ -59,7 +63,7 @@ "privileges.section-moderation": "הרשאות מנחה", "privileges.section-other": "אחר", "privileges.section-user": "משתמש", - "privileges.search-user": "הוסף משתמש", + "privileges.search-user": "הוספת משתמש", "privileges.no-users": "אין הרשאות ספציפיות למשתמש בקטגוריה זו.", "privileges.section-group": "קבוצה", "privileges.group-private": "קבוצה זו פרטית", @@ -89,7 +93,7 @@ "federation.syncing-intro": "קטגוריה יכולה לעקוב אחר \"שחקן קבוצתי\" באמצעות פרוטוקול ActivityPub. אם יתקבל תוכן מאחד השחקנים המפורטים למטה, הוא יתווסף אוטומטית לקטגוריה זו.", "federation.syncing-caveat": "הגדרת סנכרון N.B. כאן יוצרת סנכרון חד כיווני. NodeBB מנסה להירשם/לעקוב אחרי השחקן, אך לא ניתן להניח את ההיפך.", "federation.syncing-none": "קטגוריה זו אינה עוקבת כעת אחר אף אחד.", - "federation.syncing-add": "סנכרן עם...", + "federation.syncing-add": "סינכרון עם...", "federation.syncing-actorUri": "שחקן", "federation.syncing-follow": "עקוב", "federation.syncing-unfollow": "הפסקת מעקב", @@ -101,8 +105,13 @@ "alert.created": "נוצר", "alert.create-success": "קטגוריה נוצרה בהצלחה!", - "alert.none-active": "אין לך קטגוריות פעילות.", + "alert.none-active": "אין לכם קטגוריות פעילות.", "alert.create": "יצירת קטגוריה", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

האם אתם בטוחים שאתם רוצים למחוק את קטגוריית \"%1\"?

אזהרה! כל הנושאים והפוסטים בקטגוריה זו ימחקו!

מחיקת קטגוריה תסיר את כל הנושאים והפוסטים ותמחק את הקטגוריה ממסד הנתונים. אם ברצונכם להסיר את הקטגוריה באופן זמני, בחרו ב\"השבתת\" הקטגוריה.

", "alert.purge-success": "הקטגוריה נמחקה!", "alert.copy-success": "ההגדרות הועתקו!", diff --git a/public/language/he/admin/manage/custom-reasons.json b/public/language/he/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/he/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/he/admin/manage/privileges.json b/public/language/he/admin/manage/privileges.json index e6d107c3b1..48fa63ed41 100644 --- a/public/language/he/admin/manage/privileges.json +++ b/public/language/he/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "גישה לנושאים", "create-topics": "יצירת נושאים", "reply-to-topics": "תגובה לנושאים", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "תזמון נושאים", "tag-topics": "תיוג נושאים", "edit-posts": "עריכת פוסטים", diff --git a/public/language/he/admin/manage/users.json b/public/language/he/admin/manage/users.json index 461fa2beb6..c01aae29d4 100644 --- a/public/language/he/admin/manage/users.json +++ b/public/language/he/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "מחיקת משתמש(ים) ותוכן", "download-csv": "ייצוא משתמשים כ-CSV", "custom-user-fields": "שדות משתמש מותאמים אישית", + "custom-reasons": "Custom Reasons", "manage-groups": "ניהול קבוצות", "set-reputation": "הגדרת מוניטין", "add-group": "הוספת קבוצה", @@ -77,9 +78,11 @@ "temp-ban.length": "זמן הרחקה", "temp-ban.reason": "סיבה (אופציונאלי)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "שעות", "temp-ban.days": "ימים", "temp-ban.explanation": "הזינו זמן הרחקה. שימו לב הזנת מספר 0 מהווה הרחקה לצמיתות.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "האם אתם רוצים להרחיק משתמש זה לצמיתות?", "alerts.confirm-ban-multi": "האם אתם רוצים להרחיק את המשתמשים לצמיתות?", diff --git a/public/language/he/admin/menu.json b/public/language/he/admin/menu.json index b06f729f77..d32642d723 100644 --- a/public/language/he/admin/menu.json +++ b/public/language/he/admin/menu.json @@ -83,11 +83,11 @@ "search.placeholder": "חיפוש הגדרות", "search.no-results": "אין תוצאות...", "search.search-forum": "חפש בפורום ", - "search.keep-typing": "המשך להקליד על מנת למצוא תוצאות...", - "search.start-typing": "התחל להקליד על מנת לראות תוצאות...", + "search.keep-typing": "המשיכו להקליד על מנת למצוא תוצאות...", + "search.start-typing": "התחילו להקליד על מנת לראות תוצאות...", "connection-lost": "החיבור ל-%1 אבד, מנסה להתחבר מחדש...", "alerts.version": "מעודכן ל-NodeBB v%1", - "alerts.upgrade": "שדרג ל v%1" + "alerts.upgrade": "שדרגו ל v%1" } \ No newline at end of file diff --git a/public/language/he/admin/settings/activitypub.json b/public/language/he/admin/settings/activitypub.json index 912aab7975..784350dfff 100644 --- a/public/language/he/admin/settings/activitypub.json +++ b/public/language/he/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "פסק זמן לחיפוש (מילישניות)", "probe-timeout-help": "(ברירת מחדל: 2000) אם שאילתת החיפוש לא תקבל תגובה בתוך מסגרת הזמן שנקבעה, המשתמש יישלח ישירות לקישור. התאימו מספר זה גבוה יותר אם אתרים מגיבים באיטיות וברצונכם להקדיש זמן נוסף.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "סינון", "count": "NodeBB זה מודע כרגע ל-%1 שרתים", "server.filter-help": "ציין שרתים שברצונך למנוע מהתאחדות עם ה-NodeBB שלך. לחלופין, אתה יכול לבחור באופן סלקטיבי פדרציה מאושרים עם שרתים ספציפיים, במקום זאת. שתי האפשרויות נתמכות, אם כי הן סותרות זו את זו.", diff --git a/public/language/he/admin/settings/chat.json b/public/language/he/admin/settings/chat.json index a5a5b9fa2e..7b2548b9c8 100644 --- a/public/language/he/admin/settings/chat.json +++ b/public/language/he/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "אורך מקסימלי של שם חדר צ'אט", "max-room-size": "מספר המשתמשים המרבי בחדרי צ'אט", "delay": "זמן בין הודעות צ'אט (ms)", - "notification-delay": "עיכוב התראה עבור הודעות צ'אט", - "notification-delay-help": "הודעות נוספות שנשלחות בין הזמן הזה נאספות, והמשתמש מקבל הודעה פעם אחת בכל תקופת עיכוב. הגדר ל-0 כדי לבטל את ההשהיה.", "restrictions.seconds-edit-after": "מספר השניות שהודעת צ'אט תישאר ניתנת לעריכה.", "restrictions.seconds-delete-after": "מספר השניות שהודעת צ'אט תישאר ניתנת למחיקה." } \ No newline at end of file diff --git a/public/language/he/admin/settings/email.json b/public/language/he/admin/settings/email.json index 2ee6f04877..df3fad9df1 100644 --- a/public/language/he/admin/settings/email.json +++ b/public/language/he/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "איחוד חיבורים מונע מ- NodeBB ליצור חיבור חדש לכל דואר אלקטרוני. אפשרות זו חלה רק אם SMTP תחבורה מופעלת.", "smtp-transport.allow-self-signed": "אפשר אישורים עם חתימה עצמית - self-signed", "smtp-transport.allow-self-signed-help": "הפעלת הגדרה זו תאפשר לכם להשתמש בתעודות TLS עם חתימה עצמית או לא חוקית.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "עריכת תבנית דוא\"ל", "template.select": "בחירת תבנית דוא\"ל", "template.revert": "החזרה למקור", + "test-smtp-settings": "Test SMTP Settings", "testing": "דוא\"ל בדיקה", + "testing.success": "Test Email Sent.", "testing.select": "בחרו תבנית דוא\"ל", "testing.send": "שלח דוא\"ל בדיקה", - "testing.send-help": "דוא\"ל הבדיקה יישלח לכתובת הדוא\"ל של המשתמש המחובר כעת.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "תקצירי דואר אלקטרוני", "subscriptions.disable": "הפיכת תקצירי דואר אלקטרוני ללא זמינים", "subscriptions.hour": "שעת תקציר", diff --git a/public/language/he/admin/settings/notifications.json b/public/language/he/admin/settings/notifications.json index 5e25d54217..906bd1f819 100644 --- a/public/language/he/admin/settings/notifications.json +++ b/public/language/he/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "הודעת ברוכים הבאים", "welcome-notification-link": "קישור הודעת ברוכים הבאים", "welcome-notification-uid": "הודעת ברוכים הבאים למשתמש (UID)", - "post-queue-notification-uid": "רשום משתמש בתור (UID)" + "post-queue-notification-uid": "רשום משתמש בתור (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/he/admin/settings/sounds.json b/public/language/he/admin/settings/sounds.json index 57cca017c5..cd6cfadc8b 100644 --- a/public/language/he/admin/settings/sounds.json +++ b/public/language/he/admin/settings/sounds.json @@ -1,9 +1,9 @@ { "notifications": "התראות", "chat-messages": "הודעות צ'אט", - "play-sound": "נגן", + "play-sound": "נגינה", "incoming-message": "הודעה נכנסת", "outgoing-message": "הודעה יוצאת", - "upload-new-sound": "העלה צליל חדש", + "upload-new-sound": "העלאת צליל חדש", "saved": "הגדרות נשמרו" } \ No newline at end of file diff --git a/public/language/he/admin/settings/uploads.json b/public/language/he/admin/settings/uploads.json index 55286bd76d..6ab5f7423f 100644 --- a/public/language/he/admin/settings/uploads.json +++ b/public/language/he/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "תמונות רחבות יותר מערך זה יידחו.", "reject-image-height": "גובה תמונה מקסימלי (בפיקסלים)", "reject-image-height-help": "תמונות גבוהות יותר מערך זה יידחו", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "אפשרו למשתמשים להעלות תמונה ממוזערת לנושא", + "show-post-uploads-as-thumbnails": "הצג העלאות הודעות כתמונות ממוזערות", "topic-thumb-size": "גודל תמונה ממוזערת לנושא", "allowed-file-extensions": "סיומות קבצים מאושרים", "allowed-file-extensions-help": "הכניסו כאן רשימת פורמטי קבצים מאושרים (לדוגמא. pdf,xls,doc). השארת השורה ללא תוכן פירושו שכל הקבצים יהיו מאושרים.", diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json index 0018fa4ade..3f1b6958b6 100644 --- a/public/language/he/admin/settings/user.json +++ b/public/language/he/admin/settings/user.json @@ -64,9 +64,10 @@ "show-email": "הצג כתובת אימייל", "show-fullname": "הצג שם מלא", "restrict-chat": "אשר הודעות צ'אט רק ממשתמשים שאני עוקב אחריהם", + "disable-incoming-chats": "השבתת הודעות צ'אט נכנסות", "outgoing-new-tab": "פתח קישורים חיצוניים בכרטיסייה חדשה", "topic-search": "הפעל חיפוש בתוך נושא", - "update-url-with-post-index": "עדכן את כתובת הURL עם מספר הפוסט הנוכחי בזמן גלישה בנושאים", + "update-url-with-post-index": "עדכן את כתובת ה-URL עם מספר הפוסט הנוכחי בזמן גלישה בנושאים", "digest-freq": "הרשם לקבלת תקציר", "digest-freq.off": "כבוי", "digest-freq.daily": "יומי", diff --git a/public/language/he/admin/settings/web-crawler.json b/public/language/he/admin/settings/web-crawler.json index 10f4d432ea..c5be0cd295 100644 --- a/public/language/he/admin/settings/web-crawler.json +++ b/public/language/he/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "בטל הזנת RSS", "disable-sitemap-xml": "בטל את Sitemap.xml", "sitemap-topics": "מספר הנושאים להצגה במפת האתר", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "נקה את זכרון מפת האתר", "view-sitemap": "צפייה במפת האתר" } \ No newline at end of file diff --git a/public/language/he/aria.json b/public/language/he/aria.json index adc95d19d6..d7102b1e5d 100644 --- a/public/language/he/aria.json +++ b/public/language/he/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "דף פרופיל למשתמש %1", "user-watched-tags": "צפיית משתמש בתגיות", "delete-upload-button": "כפתור מחיקת העלאה", - "group-page-link-for": "%1 קבוצת דפים מקושרים " + "group-page-link-for": "%1 קבוצת דפים מקושרים ", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/he/category.json b/public/language/he/category.json index 4586b71933..202c6ad2e2 100644 --- a/public/language/he/category.json +++ b/public/language/he/category.json @@ -1,12 +1,13 @@ { "category": "קטגוריה", "subcategories": "קטגוריות משנה", - "uncategorized": "לא מסווג", - "uncategorized.description": "נושאים שאינם משתלבים בקפדנות עם קטגוריות קיימות", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "ניתן לעקוב אחר קטגוריה זו מהאינטרנט החברתי הפתוח באמצעות הידית %1", "new-topic-button": "נושא חדש", "guest-login-post": "התחברו כדי לפרסם", "no-topics": "קטגוריה זו ריקה מנושאים.
למה שלא תנסו להוסיף נושא חדש?", + "no-followers": "אף אחד באתר זה לא עוקב אחר או צופה בקטגוריה זו. עקבו אחר או צפו בקטגוריה זו כדי להתחיל לקבל עדכונים.", "browsing": "צופים בנושא זה כעת", "no-replies": "אין תגובות", "no-new-posts": "אין פוסטים חדשים.", diff --git a/public/language/he/email.json b/public/language/he/email.json index 48b1b41c7b..f627a4aba0 100644 --- a/public/language/he/email.json +++ b/public/language/he/email.json @@ -5,29 +5,29 @@ "invite": "הזמנה מ%1", "greeting-no-name": "שלום", "greeting-with-name": "שלום %1", - "email.verify-your-email.subject": "בבקשה אמת את המייל שלך.", + "email.verify-your-email.subject": "נא לאמת את המייל שלך.", "email.verify.text1": "ביקשת לשנות או לאמת את כתובת הדוא\"ל שלך", "email.verify.text2": "לצורכי אבטחה, אנו משנים או מאמתים את כתובת הדוא\"ל רק לאחר שבעלותך מאומת ע\"י דוא\"ל. אם לא אתה ביקשת זאת, תוכל להתעלם ממייל זו.", "email.verify.text3": "לאחר שתאשר את כתובת הדוא\"ל, נשנה את כתובת דוא\"ל הנוכחית בכתובת הזו (%1).", "welcome.text1": "תודה שנרשמת ל%1!", - "welcome.text2": "על מנת להפעיל את החשבון שלך, אנו צריכים לוודא שאתה בעל חשבון המייל שנרשמת איתו.", - "welcome.text3": "מנהל אישר את ההרשמה שלך.\nאתה יכול להתחבר עם השם משתמש והסיסמא שלך מעכשיו.", - "welcome.cta": "לחץ כאן על מנת לאשר את כתובת המייל שלך.", + "welcome.text2": "להפעלת החשבון, נדרש אימות שהאימייל אתו נרשמתם, הוא בבעלותכם.", + "welcome.text3": "מנהל אישר את ההרשמה שלך.\nניתן להתחבר עם השם משתמש והסיסמא שלך מעכשיו.", + "welcome.cta": "לחצו כאן על מנת לאשר את כתובת המייל שלכם.", "invitation.text1": "%1 הזמין אותך להצטרף ל%2", "invitation.text2": "ההזמנה של תפוג ב %1 ימים", - "invitation.cta": "לחץ כאן ליצירת החשבון שלך.", - "reset.text1": "קיבלנו בקשה לאפס את הסיסמה לחשבון שלך, כנראה מפני ששכחת אותה. אם לא ביקשת לאפס את הסיסמה, אנא התעלם ממייל זה.", - "reset.text2": "על מנת להמשיך עם תהליך איפוס הסיסמה, אנא לחץ על הלינק הבא:", - "reset.cta": "לחץ כאן לאפס את הסיסמה שלך.", + "invitation.cta": "לחצו כאן ליצירת החשבון שלכם.", + "reset.text1": "קיבלנו בקשה לאפס את הסיסמה לחשבון שלכם, כנראה מפני ששכחתם אותה. אם לא ביקשתם לאפס את הסיסמה, אנא התעלמו מאימייל זה.", + "reset.text2": "על מנת להמשיך עם תהליך איפוס הסיסמה, אנא לחצו על הלינק הבא:", + "reset.cta": "לחצו כאן לאפס את הסיסמה שלכם.", "reset.notify.subject": "הסיסמה שונתה בהצלחה.", - "reset.notify.text1": "אנו מודיעים לך שב%1, סיסמתך שונתה בהצלחה.", - "reset.notify.text2": "אם לא אישרת בקשה זו, אנא הודע למנהל מיד.", + "reset.notify.text1": "רצינו להסב את תשומת ליבך שב%1, סיסמתך שונתה בהצלחה.", + "reset.notify.text2": "אם לא אישרתם בקשה זו, אנא הודיעו למנהל בהקדם.", "digest.unread-rooms": "חדרים שלא נקראו", "digest.room-name-unreadcount": "%1 (%2 לא נקראו)", "digest.latest-topics": "נושאים אחרונים מ%1", "digest.top-topics": "נושאים עם הכי הרבה הצבעות מ-%1", "digest.popular-topics": "הנושאים הכי פופולריים מ-%1", - "digest.cta": "לחץ כאן כדי לבקר ב %1", + "digest.cta": "לחצו כאן כדי לבקר ב %1", "digest.unsub.info": "תקציר זה נשלח אליך על-פי הגדרות החשבון שלך.", "digest.day": "יום", "digest.week": "שבוע", @@ -36,23 +36,23 @@ "digest.title.day": "התקציר היומי שלך", "digest.title.week": "התקציר השבועי שלך", "digest.title.month": "התקציר החודשי שלך", - "notif.chat.new-message-from-user": "New message from \"%1\"", - "notif.chat.new-message-from-user-in-room": "New message from %1 in room %2", - "notif.chat.cta": "לחץ כאן כדי להמשיך את השיחה", - "notif.chat.unsub.info": "התראה הצ'אט הזו נשלחה אליך על-פי הגדרות החשבון שלך.", - "notif.post.unsub.info": "התראת הפוסט הזו נשלחה אליך על-פי הגדרות החשבון שלך.", - "notif.post.unsub.one-click": "ניתן גם להפסיק את קבלת המיילים כמו זה, בלחיצה על", + "notif.chat.new-message-from-user": "הודעה חדשה מ-\"%1\"", + "notif.chat.new-message-from-user-in-room": "הודעה חדשה מ-%1 בחדר %2", + "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": "זוהי בדיקה של ההתראות במייל.", + "notif.cta-new-reply": "הצגת פוסט", + "notif.cta-new-chat": "הצגת צ'אט", + "notif.test.short": "בדיקת התראות", + "notif.test.long": "זוהי בדיקה של ההתראות באימייל.", "test.text1": "זהו אימייל ניסיון על מנת לוודא שהגדרות המייל בוצעו כהלכה בהגדרות NodeBB.", - "unsub.cta": "לחץ כאן לשנות הגדרות אלו", - "unsubscribe": "בטל רישום", - "unsub.success": "אתה לא תקבל יותר מיילים מרשימת התפוצה של %1", + "unsub.cta": "לחצו כאן לשנות הגדרות אלו", + "unsubscribe": "ביטול רישום", + "unsub.success": "לא תקבלו יותר אימיילים מרשימת התפוצה של %1", "unsub.failure.title": "לא ניתן לבטל את המנוי", - "unsub.failure.message": "למרבה הצער, לא הצלחנו לבטל את הרישום שלך מרשימת התפוצה, מכיוון שהייתה בעיה בקישור. עם זאת, אתה יכול לשנות את העדפות הדוא\"ל שלך על ידי מעבר להגדרות המשתמש שלך.

(שגיאה: %1)", + "unsub.failure.message": "למרבה הצער, לא הצלחנו לבטל את הרישום שלך מרשימת התפוצה, מכיוון שהייתה בעיה בקישור. עם זאת, תוכלו לשנות את העדפות הדוא\"ל שלכם על ידי מעבר להגדרות המשתמש שלכם.

(שגיאה: %1)", "banned.subject": "הורחקת מ %1", "banned.text1": "המשתמש %1 הורחק מ %2.", "banned.text2": "הרחקה זו תמשך עד %1", diff --git a/public/language/he/error.json b/public/language/he/error.json index 1775b7e490..05275e4010 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -3,6 +3,7 @@ "invalid-json": "אובייקט JSON לא תקין", "wrong-parameter-type": "ערך מסוג %3 היה צפוי למאפיין `%1`, אבל %2 התקבל במקום זאת", "required-parameters-missing": "פרמטרים נדרשים היו חסרים בקריאת API זו: %1", + "reserved-ip-address": "אין לבקש בקשות רשת לטווחי IP שמורים.", "not-logged-in": "נראה שאינכם מחוברים למערכת.", "account-locked": "חשבונכם נחסם באופן זמני", "search-requires-login": "חיפוש מצריך חשבון - אנא הירשמו או התחברו.", @@ -67,8 +68,8 @@ "no-chat-room": "חדר צ'אט לא קיים", "no-privileges": "ההרשאות שלכם אינן מספיקות לביצוע פעולה זו.", "category-disabled": "קטגוריה לא פעילה", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "הפוסט נמחק", + "topic-locked": "הנושא נעול", "post-edit-duration-expired": "ניתן לערוך פוסטים עד %1 שניות מרגע כתיבת הפוסט", "post-edit-duration-expired-minutes": "ניתן לערוך פוסטים עד %1 דקות מרגע כתיבת הפוסט", "post-edit-duration-expired-minutes-seconds": "ניתן לערוך פוסטים עד %1 דקות %2 שניות מרגע כתיבת הפוסט", @@ -142,18 +143,21 @@ "group-leave-disabled": "אינכם רשאים לעזוב את הקבוצה כרגע", "group-user-not-pending": "למשתמש אין בקשה ממתינה להצטרף לקבוצה זו.", "gorup-user-not-invited": "המשתמש לא הוזמן להצטרף לקבוצה זו.", - "post-already-deleted": "פוסט זה נמחק כבר", + "post-already-deleted": "פוסט זה כבר נמחק", "post-already-restored": "פוסט זה כבר שוחזר", "topic-already-deleted": "נושא זה כבר נמחק", "topic-already-restored": "נושא זה כבר שוחזר", - "cant-purge-main-post": "אינכם יכולים למחוק את הפוסט הראשי, אנא מחקו את הנושא במקום", + "topic-already-crossposted": "הנושא הזה כבר שויך לקטגוריה זו.", + "cant-purge-main-post": "לא ניתן למחוק את הפוסט הראשי, ניתן למחוק את הנושא במקום זה", "topic-thumbnails-are-disabled": "תמונות ממוזערות לנושא אינן מאופשרות.", "invalid-file": "קובץ לא תקין", "uploads-are-disabled": "העלאת קבצים אינה מאופשרת", - "signature-too-long": "מצטערים, החתימה שלכם אינה יכולה להכיל יותר מ-%1 תווים.", - "about-me-too-long": "מצטערים, דף האודות שלכם אינו יכול להיות ארוך מ-%1 תווים.", + "signature-too-long": "מצטערים, החתימה אינה יכולה להכיל יותר מ-%1 תווים.", + "about-me-too-long": "מצטערים, דף האודות אינו יכול להיות ארוך מ-%1 תווים.", "cant-chat-with-yourself": "לא ניתן לעשות צ'אט עם עצמכם!", "chat-restricted": "משתמש זה חסם את הודעות הצ'אט שלו ממשתמשים זרים. המשתמש חייב לעקוב אחריכם לפני שתוכלו לשוחח איתו בצ'אט", + "chat-allow-list-user-already-added": "משתמש זה כבר נמצא ברשימה הלבנה שלכם", + "chat-deny-list-user-already-added": "משתמש זה כבר נמצא ברשימת הדחייה שלכם", "chat-user-blocked": "נחסמתם על ידי משתמש זה.", "chat-disabled": "מערכת הצ'אט לא פעילה", "too-many-messages": "שלחתם יותר מדי הודעות, אנא המתינו זמן מה.", @@ -169,21 +173,21 @@ "cant-add-users-to-chat-room": "לא ניתן להוסיף משתמשים לחדר הצ'אט.", "cant-remove-users-from-chat-room": "לא ניתן להסיר משתמשים מחדר הצ'אט.", "chat-room-name-too-long": "שם החדר ארוך מדי. השם לא יכול להיות ארוך מ-%1 תווים.", - "remote-chat-received-too-long": "קיבלת הודעת צ'אט מ %1, אבל זה היה ארוך מדי ונדחה.", + "remote-chat-received-too-long": "התקבלה הודעת צ'אט מ %1, אבל זה היה ארוך מדי ונדחה.", "already-voting-for-this-post": "הצבעתם כבר בנושא זה.", "reputation-system-disabled": "מערכת המוניטין לא פעילה.", "downvoting-disabled": "היכולת להצביע נגד מושבתת", "not-enough-reputation-to-chat": "נדרש %1 מוניטין כדי לכתוב בצ'אט", "not-enough-reputation-to-upvote": "נדרש %1 מוניטין כדי להצביע בעד", "not-enough-reputation-to-downvote": "נדרש %1 מוניטין כדי להצביע למטה", - "not-enough-reputation-to-post-links": "אתה צריך %1 מוניטין כדי לפרסם קישורים", + "not-enough-reputation-to-post-links": "נדרש %1 מוניטין כדי לפרסם קישורים", "not-enough-reputation-to-flag": "נדרש %1 מוניטין כדי לדווח על פוסט", "not-enough-reputation-min-rep-website": "נרדש %1 מוניטין כדי להוסיף אתר אינטרנט", "not-enough-reputation-min-rep-aboutme": "נדרש %1 מוניטין כדי להוסיף תיאור", "not-enough-reputation-min-rep-signature": "נדרש %1 מוניטין כדי להוסיף חתימה", "not-enough-reputation-min-rep-profile-picture": "נדרש %1 מוניטין כדי להוסיף תמונת פרופיל", "not-enough-reputation-min-rep-cover-picture": "נדרש %1 מוניטין כדי להוסיף תמונת רקע לפרופיל", - "not-enough-reputation-custom-field": "אתה צריך %1 מוניטין עבור %2", + "not-enough-reputation-custom-field": "נדרש %1 מוניטין עבור %2", "custom-user-field-value-too-long": "ערך שדה מותאם אישית ארוך מדי, %1", "custom-user-field-select-value-invalid": "האפשרות שנבחרה בשדה מותאם אישית אינה חוקית, %1", "custom-user-field-invalid-text": "טקסט שדה מותאם אישית אינו חוקי, %1", @@ -199,12 +203,12 @@ "too-many-user-flags-per-day": "ניתן לדווח רק על %1 משתמשים כל יום", "cant-flag-privileged": "לא ניתן לדווח על מנהלים או על תוכן שנכתב על ידי מנהלים.", "cant-locate-flag-report": "לא ניתן לאתר דוח דיווח", - "self-vote": "אי אפשר להצביע על פוסט שיצרתם", - "too-many-upvotes-today": "ביכולתכם להצביע בעד רק %1 פעמים ביום", - "too-many-upvotes-today-user": "ביכולתכם להצביע בעד משתמש מסוים רק %1 פעמים ביום", - "too-many-downvotes-today": "ביכולתכם להצביע נגד %1 פעמים ביום", - "too-many-downvotes-today-user": "ביכולתכם להצביע נגד משתמש %1 פעמים ביום", - "reload-failed": "אירעה תקלה ב NodeBB בזמן הטעינה של: \"%1\". המערכת תמשיך להגיש דפים קיימים, אבל כדאי שתשחזרו את הפעולות שלכם מהפעם האחרונה בה המערכת עבדה כראוי.", + "self-vote": "לא ניתן להצביע לפוסט שלך", + "too-many-upvotes-today": "ניתן להצביע בעד רק %1 פעמים ביום", + "too-many-upvotes-today-user": "ניתן להצביע בעד משתמש מסוים רק %1 פעמים ביום", + "too-many-downvotes-today": "ניתן להצביע נגד %1 פעמים ביום", + "too-many-downvotes-today-user": "ניתן להצביע נגד משתמש %1 פעמים ביום", + "reload-failed": "אירעה תקלה ב-NodeBB בזמן הטעינה של: \"%1\". המערכת תמשיך להציג דפים קיימים, אבל כדאי שתשחזרו את הפעולות שלכם מהפעם האחרונה בה המערכת עבדה כראוי.", "registration-error": "שגיאה בהרשמה", "parse-error": "אירעה שגיאה בעת ניתוח תגובת השרת", "wrong-login-type-email": "אנא השתמשו בכתובת המייל שלכם להתחברות", @@ -223,10 +227,11 @@ "session-mismatch": "סשן לא תואם", "session-mismatch-text": "נראה שסשן ההתחברות שלכם אינו תואם לשרת. אנא רעננו את הדף.", "no-topics-selected": "לא נבחרו נושאים!", - "cant-move-to-same-topic": "אינכם יכולים להעביר פוסט לאותו נושא!", - "cant-move-topic-to-same-category": "אינכם יכולים להעביר נושא לאותה קטגוריה!", - "cannot-block-self": "אינכם יכולים לחסום את עצמכם!", - "cannot-block-privileged": "אינך יכול לחסום מנהלים או מנחים גלובליים", + "cant-move-to-same-topic": "לא ניתן להעביר פוסט לאותו נושא!", + "cant-move-topic-to-same-category": "לא ניתן להעביר נושא לאותה קטגוריה!", + "cant-move-topic-to-from-remote-categories": "לא ניתן להעביר נושאים אל או מחוץ לקטגוריות מרוחקות; נסו שיוך קטגוריה במקום זאת.", + "cannot-block-self": "לא ניתן לחסום את עצמך!", + "cannot-block-privileged": "לא ניתן לחסום מנהלים או מנחים גלובליים", "cannot-block-guest": "אורחים אינם יכולים לחסום משתמשים אחרים", "already-blocked": "המשתמש חסום כבר", "already-unblocked": "המשתמש שוחרר כבר מהחסימה", @@ -234,8 +239,9 @@ "socket-reconnect-failed": "לא ניתן להגיע לשרת בשלב זה. לחצו כאן כדי לנסות שוב, או נסו שוב במועד מאוחר יותר", "invalid-plugin-id": "מזהה תוסף לא תקין", "plugin-not-whitelisted": "לא ניתן להתקין את התוסף – ניתן להתקין דרך הניהול רק תוספים שנמצאים ברשימה הלבנה של מנהל החבילות של NodeBB.", + "cannot-toggle-system-plugin": "לא ניתן להחליף מצב של תוסף מערכת", "plugin-installation-via-acp-disabled": "התקנת תוסף באמצעות ACP מושבתת", - "plugins-set-in-configuration": "אינך רשאי לשנות את מצב הפלאגין כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), אנא שנה את התצורה במקום זאת.", + "plugins-set-in-configuration": "לא ניתן לשנות את מצב התוסף כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), שנו את התצורה במקום זאת.", "theme-not-set-in-configuration": "כאשר מגדירים תוספים פעילים בתצורה, שינוי ערכות נושא מחייב הוספת ערכת הנושא החדשה לרשימת התוספים הפעילים לפני עדכון שלו ב-ACP", "topic-event-unrecognized": "אירוע הנושא '%1' לא מזוהה", "category.handle-taken": "קישור הקטגוריה כבר נלקחה, אנא בחרו אחרת.", @@ -246,6 +252,7 @@ "api.401": "לא נמצא סשן התחברות פעילה. נא התחברו ונסו שוב.", "api.403": "אינכם מורשים לבצע את החיוג", "api.404": "קריאת API שגויה", + "api.413": "The request payload is too large", "api.426": "HTTPS נדרש לבקשות ל-API של הכתיבה, אנא שלחו מחדש את בקשתכם באמצעות HTTPS", "api.429": "יותר מידי בקשות, אנא נסו שוב מאוחר יותר", "api.500": "שגיאה בלתי צפויה אירעה בעת ניסיון להגיש את בקשתכם.", diff --git a/public/language/he/global.json b/public/language/he/global.json index 6aa371de1d..884b522e0c 100644 --- a/public/language/he/global.json +++ b/public/language/he/global.json @@ -32,7 +32,7 @@ "pagination.enter-index": "עבור למיקום פוסט", "pagination.go-to-page": "ניווט לדף", "pagination.page-x": "עמוד %1", - "header.brand-logo": "לוגו מותג", + "header.brand-logo": "לוגו אתר", "header.admin": "ניהול", "header.categories": "קטגוריות", "header.recent": "פוסטים אחרונים", @@ -68,6 +68,7 @@ "users": "משתמשים", "topics": "נושאים", "posts": "פוסטים", + "crossposts": "Cross-posts", "x-posts": "%1 פוסטים", "x-topics": "%1 נושאים", "x-reputation": "%1 מוניטין", @@ -82,6 +83,7 @@ "downvoted": "הוצבע נגד", "views": "צפיות", "posters": "כותבים", + "watching": "עוקבים", "reputation": "מוניטין", "lastpost": "פוסט אחרון", "firstpost": "פוסט ראשון", @@ -133,8 +135,8 @@ "upload": "העלאה", "uploads": "העלאות", "allowed-file-types": "פורמטי הקבצים המורשים הם %1", - "unsaved-changes": "יש לכם שינויים שלא נשמרו. האם הנכם בטוחים שברצונכם להמשיך?", - "reconnecting-message": "החיבור ל-%1 אבד, אנא המתינו בזמן שאנו מנסים לחבר אתכם מחדש", + "unsaved-changes": "יש שינויים שלא נשמרו. האם אתם בטוחים שברצונכם להמשיך?", + "reconnecting-message": "החיבור ל-%1 אבד, נא להמתין בזמן שאנו מנסים לחבר אתכם מחדש", "play": "נגן", "cookies.message": "אתר זה משתמש ב cookies על מנת לשפר את חוויות המשתמש.", "cookies.accept": "קיבלתי!", diff --git a/public/language/he/groups.json b/public/language/he/groups.json index b97ad4d994..c2cedadc9b 100644 --- a/public/language/he/groups.json +++ b/public/language/he/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "כל הקבוצות", "groups": "קבוצות", "members": "חברים", + "x-members": "%1 member(s)", "view-group": "הצגת קבוצה", "owner": "מנהל קבוצה", "new-group": "יצירת קבוצה חדשה", @@ -39,28 +41,28 @@ "details.description": "תיאור", "details.member-post-cids": "מזהי קטגוריות להצגת פוסטים מהם", "details.badge-preview": "תצוגה מקדימה של התג", - "details.change-icon": "שנה אייקון", - "details.change-label-colour": "שנה צבע תווית", - "details.change-text-colour": "שנה צבע טקסט", + "details.change-icon": "שינוי אייקון", + "details.change-label-colour": "שינוי צבע תווית", + "details.change-text-colour": "שינוי צבע טקסט", "details.badge-text": "טקסט תגית", - "details.userTitleEnabled": "הצג תגית", + "details.userTitleEnabled": "הצגת תגית", "details.private-help": "אם אפשרות זו מופעלת, הצטרפות לקבוצות ידרוש אישור מבעל הקבוצה.", "details.hidden": "מוסתר", "details.hidden-help": "אם אפשרות זו מופעלת, קבוצה זו לא תימצא ברשימת הקבוצות, יהיה ניתן להזמין משתמשים רק באופן ידני", - "details.delete-group": "מחק קבוצה", + "details.delete-group": "מחיקת קבוצה", "details.private-system-help": "קבוצות פרטיות מושבתות ברמת המערכת, אפשרות זו אינה עושה דבר", "event.updated": "פרטי הקבוצה עודכנו", "event.deleted": "קבוצת \"%1\" נמחקה", - "membership.accept-invitation": "קבל הזמנה", - "membership.accept.notification-title": "אתה עכשיו חבר ב%1", + "membership.accept-invitation": "קבלת הזמנה", + "membership.accept.notification-title": "מעכשיו הנך חבר ב%1", "membership.invitation-pending": "הזמנה ממתינה", - "membership.join-group": "הצטרפו לקבוצה", - "membership.leave-group": "עזבו קבוצה", + "membership.join-group": "הצטרפות לקבוצה", + "membership.leave-group": "עזיבת קבוצה", "membership.leave.notification-title": "%1 עזב את קבוצת %2", "membership.reject": "דחייה", "new-group.group-name": "שם קבוצה", "upload-group-cover": "העלאת תמונת נושא לקבוצה", "bulk-invite-instructions": "הזינו רשימה מופרדת בפסיק של משתמשים אותם תרצו להזמין לקבוצה זו.", "bulk-invite": "הזמנת מספר משתמשים", - "remove-group-cover-confirm": "האם להסיר תמונת נושא?" + "remove-group-cover-confirm": "להסיר תמונת נושא?" } \ No newline at end of file diff --git a/public/language/he/modules.json b/public/language/he/modules.json index fb546965c1..5783bdaf3b 100644 --- a/public/language/he/modules.json +++ b/public/language/he/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "הוספת משתמש", "chat.notification-settings": "הגדרות התראות", "chat.default-notification-setting": "הגדרת ברירת מחדל להתראות", + "chat.join-leave-messages": "הצטרפות/השארת הודעות", "chat.notification-setting-room-default": "ברירת המחדל של החדר", "chat.notification-setting-none": "ללא התראות", "chat.notification-setting-at-mention-only": "@אזכור בלבד", @@ -81,7 +82,7 @@ "composer.hide-preview": "הסתרת תצוגה מקדימה", "composer.help": "עזרה", "composer.user-said-in": "%1 כתב ב%2:", - "composer.user-said": "%1 כתב:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "האם לבטל את השינויים שנעשו בפוסט זה?", "composer.submit-and-lock": "אשרו ונעלו", "composer.toggle-dropdown": "הצגת תפריט הנפתח", diff --git a/public/language/he/notifications.json b/public/language/he/notifications.json index ec304b678b..2c2e86d6a6 100644 --- a/public/language/he/notifications.json +++ b/public/language/he/notifications.json @@ -22,7 +22,7 @@ "upvote": "הצבעות בעד", "awards": "פרסים", "new-flags": "דיווחים חדשים", - "my-flags": "דיווחים שהוקצו עבורי", + "my-flags": "My Flags", "bans": "הרחקות", "new-message-from": "הודעה חדשה מ %1", "new-messages-from": "%1 הודעות חדשות מאת %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 ו%2 כתבו ב%4", "user-posted-in-public-room-triple": "%1, %2 ו%3 כתבו ב%5", "user-posted-in-public-room-multiple": "%1, %2 ו-%3 אחרים כתבו ב%5", - "upvoted-your-post-in": "%1 הצביע בעד הפוסט שלך ב %2", - "upvoted-your-post-in-dual": "%1 ו%2 הצביעו בעד הפוסט שלך ב%3", - "upvoted-your-post-in-triple": "%1, %2 ו%3 הצביעו בעד הפוסט שלך ב-%4.", - "upvoted-your-post-in-multiple": "%1, %2 ו-%3 אחרים הצביעו בעד הפוסט שלך ב-%4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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-triple": "%1, %2 ו%3 דיווחו על פוסט ב-%4", - "user-flagged-post-in-multiple": "%1, %2 ו-%3 אחרים דיווחו על פוסט ב-%4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 דיווח על משתמש (%2)", "user-flagged-user-dual": "%1 ו - %2 דיווחו על משתמש (%3)", "user-flagged-user-triple": "%1, %2 ו%3 דיווחו על פרופיל משתמש (%4)", "user-flagged-user-multiple": "%1, %2 ו-%3 אחרים דיווחו על פרופיל משתמש (%4)", - "user-posted-to": "%1 פרסם תגובה ל: %2", - "user-posted-to-dual": "%1 ו%2 הגיבו ל: %3", - "user-posted-to-triple": "%1, %2 ו%3 הגיבו ל: %4", - "user-posted-to-multiple": "%1, %2 ו-%3 אחרים הגיבו ל: %4", - "user-posted-topic": "%1 העלה נושא חדש: %2", - "user-edited-post": "%1 ערך פוסט ב: %2", - "user-posted-topic-with-tag": "%1 פרסם %2 (תייג %3)", - "user-posted-topic-with-tag-dual": "%1 פרסם %2 (תייג %3 ו-%4)", - "user-posted-topic-with-tag-triple": "%1 פרסם %2 (תייג %3, %4, ו-%5)", - "user-posted-topic-with-tag-multiple": "%1 פרסם %2 (תייג %3)", - "user-posted-topic-in-category": "%1 פרסם נושא חדש ב%2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 התחיל לעקוב אחריך.", "user-started-following-you-dual": "%1 ו-%2 התחילו לעקוב אחריך.", "user-started-following-you-triple": "%1, %2 ו%3 התחילו לעקוב אחריך.", @@ -71,11 +71,11 @@ "users-csv-exported": "משתמשים יוצאו כ-csv, לחץ כאן להורדה.", "post-queue-accepted": "הפוסט ששלחת התקבל. לחץ כאן כדי לראות את הפוסט", "post-queue-rejected": "הפוסט ששלחת נדחה", - "post-queue-notify": "פוסט ממתין בתור הפוסטים קיבל הודעה: \"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "כתובת המייל אושרה", "email-confirmed-message": "תודה שאישרת את כתובת המייל שלך. החשבון שלך פעיל כעת.", "email-confirm-error-message": "אירעה שגיאה בעת אישור המייל שלך. ייתכן כי הקוד היה שגוי או פג תוקף.", - "email-confirm-error-message-already-validated": "כתובת האימייל אומת כבר.", "email-confirm-sent": "מייל אישור נשלח.", "none": "אף אחד", "notification-only": "התראות בלבד", diff --git a/public/language/he/search.json b/public/language/he/search.json index 564a160918..bc304838d4 100644 --- a/public/language/he/search.json +++ b/public/language/he/search.json @@ -63,7 +63,7 @@ "time-older-than-15552000": "זמן: ישן מחצי שנה", "time-newer-than-31104000": "זמן: חדש משנה", "time-older-than-31104000": "זמן: ישן משנה", - "sort-by": "סדר על-פי", + "sort-by": "מיון לפי", "sort": "מיון", "last-reply-time": "תאריך תגובה אחרון", "topic-title": "כותרת הנושא", diff --git a/public/language/he/social.json b/public/language/he/social.json index 31633e056a..a08072bc9a 100644 --- a/public/language/he/social.json +++ b/public/language/he/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "היכנס באמצעות Facebook", "continue-with-facebook": "המשך בFacebook", "sign-in-with-linkedin": "היכנס באמצעות LinkedIn", - "sign-up-with-linkedin": "הירשם באמצעות LinkedIn" + "sign-up-with-linkedin": "הירשם באמצעות LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/he/themes/harmony.json b/public/language/he/themes/harmony.json index f71d306f16..bc4ad16264 100644 --- a/public/language/he/themes/harmony.json +++ b/public/language/he/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "ערכת נושא Harmony", "skins": "עיצובים", + "light": "Light", + "dark": "Dark", "collapse": "כיווץ", "expand": "הרחבה", "sidebar-toggle": "הצגת סרגל צד", @@ -14,7 +16,7 @@ "settings.stickyToolbar": "הצמד את סרגל הכלים בעת גלילה", "settings.stickyToolbar.help": "סרגל הכלים בדפי נושאים וקטגוריות ייצמד לראש העמוד בעת גלילה", "settings.topicSidebarTools": "כלי סרגל הצד", - "settings.topicSidebarTools.help": "אפשרות זו תעביר את כלי הנושא לסרגל הצד במחשבים שולחניים", + "settings.topicSidebarTools.help": "אפשרות זו תעביר את כלי הנושא לסרגל הצד במחשבים", "settings.autohideBottombar": "הסתרה אוטומטי של סרגל ניווט בנייד", "settings.autohideBottombar.help": "הסרגל בתצוגת הנייד יוסתר כאשר הדף ייגלל מטה", "settings.topMobilebar": "העברת סרגל הניווט בנייד לראש הדף", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 9e479a2da8..f285904fde 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 התייחס לנושא זה ב%3", "user-forked-topic-ago": "%1 פיצל נושא זה %3", "user-forked-topic-on": "%1 פיצל נושא זה ב%3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "לחצו כאן כדי לחזור לפוסט האחרון שקראתם בנושא זה.", "flag-post": "דיווח על פוסט זה", "flag-user": "דיווח על משתמש זה", @@ -103,6 +105,7 @@ "thread-tools.lock": "נעילת נושא", "thread-tools.unlock": "הסרת נעילה", "thread-tools.move": "הזזת נושא", + "thread-tools.crosspost": "שיוך למספר קטגוריות", "thread-tools.move-posts": "הזזת פוסטים", "thread-tools.move-all": "הזזת הכל", "thread-tools.change-owner": "שינוי שם כותב הפוסט", @@ -132,6 +135,7 @@ "pin-modal-help": "באפשרותכם להגדיר כאן תאריך תפוגה לנושאים המוצמדים. לחלופין, ביכולתכם להשאיר שדה זו ריקה, כדי שהנושא יישאר נעוץ עד לביטול ההצמדה ידנית.", "load-categories": "טוען קטגוריות", "confirm-move": "העברה", + "confirm-crosspost": "שיוך", "confirm-fork": "פיצול", "bookmark": "הוספה למועדפים", "bookmarks": "מועדפים", @@ -141,6 +145,7 @@ "loading-more-posts": "טוען פוסטים נוספים", "move-topic": "העברת נושא", "move-topics": "העברת נושאים", + "crosspost-topic": "שיוך הנושא לקטגוריות נוספות", "move-post": "העבר פוסט", "post-moved": "הפוסט הועבר!", "fork-topic": "פיצול נושא", @@ -163,6 +168,9 @@ "move-topic-instruction": "בחרו את קטגוריית היעד ולאחר מכן לחצו על העברה", "change-owner-instruction": "לחצו על הפוסטים בהם תרצו לשנות את שם כותב ההודעה", "manage-editors-instruction": "נהל את המשתמשים שיכולים לערוך את הפוסט הזה למטה.", + "crossposts.instructions": "יש לסמן את הקטגוריות הנוספות אליהן ישויך הנושא. הנושא יופיע גם בקטגוריה המקורית וגם בקטגוריות שתבחרו כעת.", + "crossposts.listing": "נושא זה שויך לקטגוריות המקומיות הבאות:", + "crossposts.none": "נושא זה לא שויך לאף קטגוריה נוספת", "composer.title-placeholder": "הכניסו את כותרת הנושא כאן...", "composer.handle-placeholder": "הזינו שם / כינוי שלכם כאן", "composer.hide": "הסתרה", @@ -174,6 +182,7 @@ "composer.replying-to": "תגובה ל%1", "composer.new-topic": "נושא חדש", "composer.editing-in": "עריכת פוסט ב-%1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "מעלה...", "composer.thumb-url-label": "הדביקו את כתובת ה-URL לתמונה הממוזערת עבור הנושא", "composer.thumb-title": "הוספת תמונה ממוזערת לנושא", @@ -224,5 +233,8 @@ "unread-posts-link": "קישור לפוסטים שלא נקראו", "thumb-image": "תמונה ממוזערת של נושא", "announcers": "שיתופים", - "announcers-x": "שיתופים (%1)" + "announcers-x": "שיתופים (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/he/uploads.json b/public/language/he/uploads.json index 75476e2a37..00415767b5 100644 --- a/public/language/he/uploads.json +++ b/public/language/he/uploads.json @@ -1,9 +1,9 @@ { "uploading-file": "מעלה את הקובץ...", - "select-file-to-upload": "בחר קובץ להעלאה!", + "select-file-to-upload": "בחרו קובץ להעלאה!", "upload-success": "הקובץ הועלה בהצלחה!", "maximum-file-size": "מקסימום %1 קילובייט", "no-uploads-found": "לא נמצאו העלאות!", - "public-uploads-info": "העלאות הינם ציבוריות. כל מי שיש ברשותו לינק לקובץ יוכל לראות אותו.", + "public-uploads-info": "העלאות הינם ציבוריות. כל מי שיש ברשותו קישור לקובץ יוכל לראות אותו.", "private-uploads-info": "העלאות הינם פרטיות. רק משתמשים מחוברים יוכלו לראותם." } \ No newline at end of file diff --git a/public/language/he/user.json b/public/language/he/user.json index a2ba1891a1..695f2f99c2 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -14,7 +14,7 @@ "account-info": "פרטי חשבון", "admin-actions-label": "פעולות ניהול", "ban-account": "הרחקת חשבון", - "ban-account-confirm": "האם אתהם בטוחים שאתם רוצים להרחיק משתמש זה?", + "ban-account-confirm": "האם להרחיק משתמש זה?", "unban-account": "ביטול הרחקת חשבון", "mute-account": "השתקת חשבון", "unmute-account": "ביטול השתקת חשבון", @@ -105,6 +105,10 @@ "show-email": "הצגת כתובת האימייל שלי", "show-fullname": "הצגת שמי המלא", "restrict-chats": "אפשר רק הודעות צ'אט ממשתמשים שאני עוקב אחריהם", + "disable-incoming-chats": "השבתת הודעות צ'אט נכנסות ", + "chat-allow-list": "אפשור הודעות צ'אט מהמשתמשים הבאים", + "chat-deny-list": "דחיית הודעות צ'אט מהמשתמשים הבאים", + "chat-list-add-user": "הוספת משתמש", "digest-label": "הרשמה לקבלת תקציר", "digest-description": "הרשמה לקבלת עדכונים בדואר אלקטרוני מפורום זה (הודעות ונושאים חדשים) בהתאם ללוח זמנים מוגדר מראש", "digest-off": "כבוי", diff --git a/public/language/he/world.json b/public/language/he/world.json index 3c5b6c3905..60f61b84c4 100644 --- a/public/language/he/world.json +++ b/public/language/he/world.json @@ -1,7 +1,12 @@ { "name": "עולם", - "popular": "נושאים פופולריים", - "recent": "כל הנושאים", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "עזרה", "help.title": "מה זה העמוד הזה?", @@ -14,5 +19,7 @@ "onboard.title": "החלון שלכם אל ה-fediverse...", "onboard.what": "זוהי הקטגוריה המותאמת אישית שלכם המורכבת רק מתוכן שנמצא מחוץ לפורום זה. אם משהו מופיע בדף הזה תלוי אם אתם עוקבים אחריו, או אם הפוסט הזה שותף על ידי מישהו שאתם עוקבים אחריו.", "onboard.why": "יש הרבה דברים שקורים מחוץ לפורום הזה, ולא כל זה רלוונטי לתחומי העניין שלכם. לכן מעקב אחר אנשים הוא הדרך הטובה ביותר לאותת שאתם רוצים לראות יותר ממישהו אחר.", - "onboard.how": "בינתיים, תוכלו ללחוץ על כפתורי הקיצור בחלק העליון כדי לראות על מה עוד הפורום הזה יודע, ולהתחיל לגלות תוכן חדש!" + "onboard.how": "בינתיים, תוכלו ללחוץ על כפתורי הקיצור בחלק העליון כדי לראות על מה עוד הפורום הזה יודע, ולהתחיל לגלות תוכן חדש!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/hr/admin/advanced/cache.json b/public/language/hr/admin/advanced/cache.json index 209a2989ca..b20a43ef8c 100644 --- a/public/language/hr/admin/advanced/cache.json +++ b/public/language/hr/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Objava predmemorija", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Puno", "post-cache-size": "Veličina predmemorije objave", "items-in-cache": "Artikli u predmemoriji" diff --git a/public/language/hr/admin/dashboard.json b/public/language/hr/admin/dashboard.json index 1e4fedf429..e1c060d8b7 100644 --- a/public/language/hr/admin/dashboard.json +++ b/public/language/hr/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Jedninstveni posjetitelji", "graphs.registered-users": "Registrirani korisnici", "graphs.guest-users": "Guest Users", diff --git a/public/language/hr/admin/development/info.json b/public/language/hr/admin/development/info.json index 3b21db6c9a..a3a3f29467 100644 --- a/public/language/hr/admin/development/info.json +++ b/public/language/hr/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "Na mreži", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json index 7375875737..5ae77e77e5 100644 --- a/public/language/hr/admin/manage/categories.json +++ b/public/language/hr/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Postavke kategorije", "edit-category": "Edit Category", "privileges": "Privilegije", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Ime kategorije", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Opis kategorije", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Pozadniska boja", "text-color": "Boja teksta", "bg-image-size": "Veličina pozadinske slike", @@ -103,6 +107,11 @@ "alert.create-success": "Kategorija uspješno kreirana!", "alert.none-active": "Nemate aktivnih kategorija.", "alert.create": "Napravi kategoriju", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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": "Kategorija odbačena!", "alert.copy-success": "Postavke kopirane!", diff --git a/public/language/hr/admin/manage/custom-reasons.json b/public/language/hr/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/hr/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/hr/admin/manage/privileges.json b/public/language/hr/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/hr/admin/manage/privileges.json +++ b/public/language/hr/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/hr/admin/manage/users.json b/public/language/hr/admin/manage/users.json index 3fd93f98b5..b72c26f3c8 100644 --- a/public/language/hr/admin/manage/users.json +++ b/public/language/hr/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Preuzmi CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Razlog (Opcionalno)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Sati", "temp-ban.days": "Dani", "temp-ban.explanation": "Unesite dužinu trajana blokade. Ukoliko je vrijeme 0 smatra se permanentnom blokadom.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Sigurni ste da želite blokirati ovo korisnika trajno?", "alerts.confirm-ban-multi": "Sigurni ste da želite blokirati korisnika permanentno?", diff --git a/public/language/hr/admin/settings/activitypub.json b/public/language/hr/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/hr/admin/settings/activitypub.json +++ b/public/language/hr/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/hr/admin/settings/chat.json b/public/language/hr/admin/settings/chat.json index d72c8fd5d6..76d94f44e1 100644 --- a/public/language/hr/admin/settings/chat.json +++ b/public/language/hr/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maksimalan broj korisnika u sobama za razgovor", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/hr/admin/settings/email.json b/public/language/hr/admin/settings/email.json index b2fd21f756..dc9da068eb 100644 --- a/public/language/hr/admin/settings/email.json +++ b/public/language/hr/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Uredi predložak emaila", "template.select": "Odaberi predložak emaila", "template.revert": "Povrati na original ", + "test-smtp-settings": "Test SMTP Settings", "testing": "Testiranje emaila", + "testing.success": "Test Email Sent.", "testing.select": "Odaberi email predložak ", "testing.send": "Pošalji testni email", - "testing.send-help": "Ovaj test mail će biti poslan svim trenutačno prijavljenim korisnicima na njihovu email adresu.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Pregled Sati.", diff --git a/public/language/hr/admin/settings/notifications.json b/public/language/hr/admin/settings/notifications.json index 9b33fa7397..6222b55216 100644 --- a/public/language/hr/admin/settings/notifications.json +++ b/public/language/hr/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Obavijest dobrodošlice", "welcome-notification-link": "Poveznica objave dobrodošlice", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/hr/admin/settings/uploads.json b/public/language/hr/admin/settings/uploads.json index 1efba861e4..6788661f95 100644 --- a/public/language/hr/admin/settings/uploads.json +++ b/public/language/hr/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Dozvoli korisnicima da učitaju sliku teme", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Veličina slike teme", "allowed-file-extensions": "Dozvoljene ekstenzije datoteka", "allowed-file-extensions-help": "Unesite popis dozvoljenih ekstenzija datoteka sa zarezima između (npr. pdf,xls,doc ).Prazan popis znači da su sve ekstenzije dozvoljene.", diff --git a/public/language/hr/admin/settings/user.json b/public/language/hr/admin/settings/user.json index a1ceaf2720..cd5fe1f3f6 100644 --- a/public/language/hr/admin/settings/user.json +++ b/public/language/hr/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Prikaži email", "show-fullname": "Prikaži puno ime", "restrict-chat": "Dozvoli poruke samo od ljudi koje praim", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Otvori odlazne poveznive u novom prozoru ", "topic-search": "Dopusti pretragu po temama", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/hr/admin/settings/web-crawler.json b/public/language/hr/admin/settings/web-crawler.json index 1bcfcd2409..3fcebbb7bb 100644 --- a/public/language/hr/admin/settings/web-crawler.json +++ b/public/language/hr/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Onemogući RSS", "disable-sitemap-xml": "Onemogući Sitemap.xml", "sitemap-topics": "Broj tema za prikaz u mapi foruma", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Očisti mapu foruma iz predmemorije", "view-sitemap": "Pogledaj mapu foruma" } \ No newline at end of file diff --git a/public/language/hr/aria.json b/public/language/hr/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/hr/aria.json +++ b/public/language/hr/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/hr/category.json b/public/language/hr/category.json index 37254811fb..c8f05fdd2e 100644 --- a/public/language/hr/category.json +++ b/public/language/hr/category.json @@ -1,12 +1,13 @@ { "category": "Kategorija", "subcategories": "Podkategorije", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nova Tema", - "guest-login-post": "Prijavi se za objavu", + "guest-login-post": "Prijavite se da biste objavili", "no-topics": "Nema tema u ovoj kategoriji.
Zašto ne probate napisati novu?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "pregledavanje", "no-replies": "Nema odgovora", "no-new-posts": "Nema novih tema.", diff --git a/public/language/hr/error.json b/public/language/hr/error.json index 9282953c1f..03d3b00117 100644 --- a/public/language/hr/error.json +++ b/public/language/hr/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Izgleda da niste prijavljeni.", "account-locked": "Vaš račun je privremeno blokiran", "search-requires-login": "Pretraga zahtijeva prijavu - prijavite se ili se registrirajte.", @@ -146,6 +147,7 @@ "post-already-restored": "Ova objava je povraćena", "topic-already-deleted": "Ova tema je već obrisana", "topic-already-restored": "Ova tema je povraćena", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Nemožete odbaciti glavnu objavu, obrišite temu za brisanje", "topic-thumbnails-are-disabled": "Slike tema su onemogućene", "invalid-file": "Pogrešna datoteka", @@ -154,6 +156,8 @@ "about-me-too-long": "O vama nemože biti duže od %1 znaka", "cant-chat-with-yourself": "Nemoguće je razgovarati sam sa sobom!", "chat-restricted": "Korisnik je ograničio razgovore. Mora vas pratiti prije nego možete razgovarati", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Razgovor onemogućen", "too-many-messages": "Poslali ste previše poruka, pričekajte.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Ne možete blokirati sami sebe", "cannot-block-privileged": "Ne možete blokirati administratore ni globalne administratore", "cannot-block-guest": "Gosti ne mogu blokirati druge korisnike", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/hr/global.json b/public/language/hr/global.json index a331b1e8d9..385b9df1e7 100644 --- a/public/language/hr/global.json +++ b/public/language/hr/global.json @@ -68,6 +68,7 @@ "users": "Korisnici", "topics": "Teme", "posts": "Objave", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Glasova protiv", "views": "Pregleda", "posters": "Posters", + "watching": "Watching", "reputation": "Reputacija", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/hr/groups.json b/public/language/hr/groups.json index 2e9fa5d694..19cc7ea2a4 100644 --- a/public/language/hr/groups.json +++ b/public/language/hr/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupe", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Pogledaj grupu", "owner": "Vlasnik grupe", "new-group": "Napravi novu grupu", diff --git a/public/language/hr/modules.json b/public/language/hr/modules.json index ae32d6f0d7..8a7f6ccefd 100644 --- a/public/language/hr/modules.json +++ b/public/language/hr/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Sakrij prikaz", "composer.help": "Help", "composer.user-said-in": "%1 je rekao u %2:", - "composer.user-said": "%1 je rekao:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Sigurni ste da želite odbaciti ovu objavu?", "composer.submit-and-lock": "Objavi i zaključaj", "composer.toggle-dropdown": "Promjeni padajuće", diff --git a/public/language/hr/notifications.json b/public/language/hr/notifications.json index 13fce7419a..9aace0d982 100644 --- a/public/language/hr/notifications.json +++ b/public/language/hr/notifications.json @@ -22,7 +22,7 @@ "upvote": "Glasači za", "awards": "Awards", "new-flags": "Nove zastave", - "my-flags": "Zastave označene na mene", + "my-flags": "My Flags", "bans": "Blokirani", "new-message-from": "Poruka od %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 je glasao za u %2.", - "upvoted-your-post-in-dual": "%1 i %2 Glasalo je za Vašu objavu in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 je premjestio Vašu objavu u %2", "moved-your-topic": "%1 je premjestio %2", - "user-flagged-post-in": "%1 je označio objavu u %2", - "user-flagged-post-in-dual": "%1 i %2 označio objavu u %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 označio je profil (%2)", "user-flagged-user-dual": "%1 i %2su označili profil (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 je odgovorio/la na: %2", - "user-posted-to-dual": "%1 i %2 ostalih su odgovorili na objavu u: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 je otvorio novu temu: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 Vas sada prati.", "user-started-following-you-dual": "%1 i %2 vas sada prate.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email potvrđen", "email-confirmed-message": "Hvala na potvrdi emaila. Vaš račun je sada aktivan.", "email-confirm-error-message": "Nastao je problem pri potvrdi Vaše email adrese. Provjerite kod ili zatražite novi.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Provjera korisničkog emaila poslana.", "none": "None", "notification-only": "Obavijest samo", diff --git a/public/language/hr/register.json b/public/language/hr/register.json index 4d52f4ea15..ca8637095b 100644 --- a/public/language/hr/register.json +++ b/public/language/hr/register.json @@ -22,7 +22,7 @@ "registration-queue-average-time": "Our average time for approving memberships is %1 hours %2 minutes.", "registration-queue-auto-approve-time": "Your membership to this forum will be fully activated in up to %1 hours.", "interstitial.intro": "We'd like some additional information in order to update your account…", - "interstitial.intro-new": "We'd like some additional information before we can create your account…", + "interstitial.intro-new": "Željeli bismo neke dodatne informacije prije nego što možemo kreirati vaš račun…", "interstitial.errors-found": "Please review the entered information:", "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.", diff --git a/public/language/hr/social.json b/public/language/hr/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/hr/social.json +++ b/public/language/hr/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/hr/themes/harmony.json b/public/language/hr/themes/harmony.json index 727a1b0553..0c3540be50 100644 --- a/public/language/hr/themes/harmony.json +++ b/public/language/hr/themes/harmony.json @@ -1,12 +1,14 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", "login-register-to-search": "Login or register to search.", "settings.title": "Theme settings", - "settings.enableQuickReply": "Enable quick reply", + "settings.enableQuickReply": "Omogući brzi odgovor", "settings.enableBreadcrumbs": "Show breadcrumbs in Category and Topic pages", "settings.enableBreadcrumbs.why": "Breadcrumbs are visible in most pages for ease-of-navigation. The base design of the category and topic pages has alternative means to link back to parent pages, but the breadcrumb can be toggled off to reduce clutter.", "settings.centerHeaderElements": "Center header elements", diff --git a/public/language/hr/topic.json b/public/language/hr/topic.json index 3b5ca2abc8..5c0e05436e 100644 --- a/public/language/hr/topic.json +++ b/public/language/hr/topic.json @@ -17,8 +17,8 @@ "last-reply-time": "Zadnji odgovor", "reply-options": "Reply options", "reply-as-topic": "Odgovori kao temu", - "guest-login-reply": "Prijavi se za objavu", - "login-to-view": "🔒 Log in to view", + "guest-login-reply": "Prijavite se kako bi odgovorili", + "login-to-view": "🔒 Prijavite se kako bi vidjeli sadržaj", "edit": "Uredi", "delete": "Obriši", "delete-event": "Delete Event", @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klikni ovdje za povratak na zadnji pročitani post.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Zaključaj temu", "thread-tools.unlock": "Odključaj temu", "thread-tools.move": "Premjesti temu", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Premjesti sve", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Učitavam kategorije", "confirm-move": "Pomakni", + "confirm-crosspost": "Cross-post", "confirm-fork": "Dupliraj", "bookmark": "Zabilježi", "bookmarks": "Zabilješke", @@ -141,6 +145,7 @@ "loading-more-posts": "Učitavam više objava", "move-topic": "Pomakni temu", "move-topics": "Pomakni teme", + "crosspost-topic": "Cross-post Topic", "move-post": "Pomakni objavu", "post-moved": "Objava pomaknuta!", "fork-topic": "Dupliraj temu", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Unesite naslov teme ovdje ...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Odgovori na %1", "composer.new-topic": "Nova tema", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "slanje...", "composer.thumb-url-label": "Zaljepite URL slike za temu", "composer.thumb-title": "Dodajte slike ovoj temi", @@ -215,7 +224,7 @@ "go-to-my-next-post": "Go to my next post", "no-more-next-post": "You don't have more posts in this topic", "open-composer": "Open composer", - "post-quick-reply": "Quick reply", + "post-quick-reply": " Brzi odgovor", "navigator.index": "Post %1 of %2", "navigator.unread": "%1 unread", "upvote-post": "Upvote post", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/hr/user.json b/public/language/hr/user.json index 50f807a3e8..f6d5802e04 100644 --- a/public/language/hr/user.json +++ b/public/language/hr/user.json @@ -105,6 +105,10 @@ "show-email": "Prikaži email", "show-fullname": "Prikaži puno ime", "restrict-chats": "Dopusti poruke o korisnika koje pratim", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Pretplati se na izvještaje", "digest-description": "Pretplati se na email izvještaje od ovog foruma (nove obavjesti i teme) prema zadanom rasporedu", "digest-off": "Isključi", @@ -221,10 +225,10 @@ "consent.export-uploads-success": "Exporting uploads, you will get a notification when it is complete.", "consent.export-posts": "Export Posts (.csv)", "consent.export-posts-success": "Exporting posts, you will get a notification when it is complete.", - "emailUpdate.intro": "Please enter your email address below. This forum uses your email address for scheduled digest and notifications, as well as for account recovery in the event of a lost password.", - "emailUpdate.optional": "This field is optional. You are not obligated to provide your email address, but without a validated email you will not be able to recover your account or login with your email.", - "emailUpdate.required": "This field is required.", - "emailUpdate.change-instructions": "A confirmation email will be sent to the entered email address with a unique link. Accessing that link will confirm your ownership of the email address and it will become active on your account. At any time, you are able to update your email on file from within your account page.", - "emailUpdate.password-challenge": "Please enter your password in order to verify account ownership.", - "emailUpdate.pending": "Your email address has not yet been confirmed, but an email has been sent out requesting confirmation. If you wish to invalidate that request and send a new confirmation request, please fill in the form below." + "emailUpdate.intro": "Molimo unesite svoju e-adresu u nastavku. Ovaj forum koristi vašu e-adresu za planirane sažetke i obavijesti, kao i za oporavak računa u slučaju izgubljene lozinke.", + "emailUpdate.optional": "Ovo polje je opcionalno.. Niste obavezni dati svoju e-adresu, ali bez potvrđene e-pošte nećete moći oporaviti svoj račun niti se prijaviti koristeći e-poštu.", + "emailUpdate.required": "Ovo polje je obavezno.", + "emailUpdate.change-instructions": "Na unesenu e-adresu biće poslana e-poruka za potvrdu s jedinstvenim linkom. Pristupom tom linku potvrđuje se vaše vlasništvo nad adresom e-pošte i ona će postati aktivna na vašem računu. U bilo kojem trenutku možete ažurirati e-adresu u svojoj evidenciji putem stranice svog računa.", + "emailUpdate.password-challenge": "Molimo unesite svoju lozinku kako biste potvrdili vlasništvo nad računom.", + "emailUpdate.pending": "Vaša e-adresa još nije potvrđena, ali je poslana e-poruka za potvrdu. Ako želite poništiti taj zahtjev i poslati novi zahtjev za potvrdu, molimo popunite obrazac u nastavku." } \ No newline at end of file diff --git a/public/language/hr/world.json b/public/language/hr/world.json index 3753335278..e6694bf507 100644 --- a/public/language/hr/world.json +++ b/public/language/hr/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/hu/admin/advanced/cache.json b/public/language/hu/admin/advanced/cache.json index b111597365..17f86946ab 100644 --- a/public/language/hu/admin/advanced/cache.json +++ b/public/language/hu/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Hozzászólás gyorsítótár", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Tele", "post-cache-size": "Hozzászólás gyorsítótáras mérete", "items-in-cache": "Elemek a gyorsítótárban" diff --git a/public/language/hu/admin/dashboard.json b/public/language/hu/admin/dashboard.json index 1016b5b833..0427cd36c6 100644 --- a/public/language/hu/admin/dashboard.json +++ b/public/language/hu/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Regisztrált látogatások", "graphs.page-views-guest": "Vendég látogatások", "graphs.page-views-bot": "Bot látogatások", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Egyedi látogatók", "graphs.registered-users": "Regisztrált felhasználók", "graphs.guest-users": "Vendég Felhasználók", diff --git a/public/language/hu/admin/development/info.json b/public/language/hu/admin/development/info.json index 48bf12d34b..e20f740176 100644 --- a/public/language/hu/admin/development/info.json +++ b/public/language/hu/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/hu/admin/manage/categories.json b/public/language/hu/admin/manage/categories.json index 78abec813a..e9b029a849 100644 --- a/public/language/hu/admin/manage/categories.json +++ b/public/language/hu/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Kategória beállítások", "edit-category": "Edit Category", "privileges": "Jogosultságok", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Kategória neve", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategória leírása", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Háttérszín", "text-color": "Szövegszín", "bg-image-size": "Háttérkép mérete", @@ -103,6 +107,11 @@ "alert.create-success": "Kategória sikeresen létrehozva!", "alert.none-active": "Nincsenek aktív kategóriáid.", "alert.create": "Kategória létrehozása", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Biztosan szeretnéd teljesen törölni ezt a kategóriát \"%1\"?

Figyelem! Minden témakör és hozzászólás teljesen törlésre kerül ebben a kategóriában!

Egy kategória teljes törlése eltávolítja a témaköröket és hozzászólásokat, valamint törli a kategóriát az adatbázisból. Amennyiben szeretnél egy kategóriát ideiglenesen törölni, használd a kategória \"kikapcsolása\" funkciót.

", "alert.purge-success": "Kategória törölve!", "alert.copy-success": "Beállítások másolva!", diff --git a/public/language/hu/admin/manage/custom-reasons.json b/public/language/hu/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/hu/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/hu/admin/manage/privileges.json b/public/language/hu/admin/manage/privileges.json index 3ec3b259a3..8cc25e52b5 100644 --- a/public/language/hu/admin/manage/privileges.json +++ b/public/language/hu/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Hozzáférés témakörhöz", "create-topics": "Témakör létrehozása", "reply-to-topics": "Hozzászólás a témakörhöz", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Témakörök időzítése", "tag-topics": "Téma címke hozzáadása", "edit-posts": "Bejegyzés szerkesztése", diff --git a/public/language/hu/admin/manage/users.json b/public/language/hu/admin/manage/users.json index a7480a9b3e..39d41a9fbd 100644 --- a/public/language/hu/admin/manage/users.json +++ b/public/language/hu/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Felhasználó(k) és minden tartalmának törlése", "download-csv": "CSV letöltése", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Csoportok kezelése", "set-reputation": "Set Reputation", "add-group": "Csoport létrehozása", @@ -77,9 +78,11 @@ "temp-ban.length": "Hosszúság", "temp-ban.reason": "Indok (Nem kötelező)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Óra", "temp-ban.days": "Nap", "temp-ban.explanation": "Add meg a kitiltás idejének hosszát. Vedd figyelembe, hogy a 0 végleges kitiltásnak minősül.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Biztosan ki szeretnéd tiltani ezt a felhasználót örökre?", "alerts.confirm-ban-multi": "Biztosan ki szeretnéd tiltani ezt a felhasználókat örökre?", diff --git a/public/language/hu/admin/settings/activitypub.json b/public/language/hu/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/hu/admin/settings/activitypub.json +++ b/public/language/hu/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/hu/admin/settings/chat.json b/public/language/hu/admin/settings/chat.json index 1b0fb418b1..38ca457237 100644 --- a/public/language/hu/admin/settings/chat.json +++ b/public/language/hu/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "A csevegési szobákban lévő felhasználók maximális száma", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/hu/admin/settings/email.json b/public/language/hu/admin/settings/email.json index 44befab01e..9e75b6276f 100644 --- a/public/language/hu/admin/settings/email.json +++ b/public/language/hu/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "A kapcsolat megőrzés megakadályozza a NodeBB-t abban, hogy minden email-hez új kapcsolatot nyisson. Ez a beállítás csak akkor érvényesül, ha az SMTP engedélyezett.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Email sablon szerkesztése", "template.select": "Válassz email sablont", "template.revert": "Eredeti visszaálítása", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email tesztelés", + "testing.success": "Test Email Sent.", "testing.select": "Válassz email sablont", "testing.send": "Teszt email küldése", - "testing.send-help": "A teszt email a most használt felhasználó email címére fog megérkezni.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email összefoglalások", "subscriptions.disable": "Minden email összefoglalás letiltása", "subscriptions.hour": "Összefoglalások küldési időpontja", diff --git a/public/language/hu/admin/settings/notifications.json b/public/language/hu/admin/settings/notifications.json index 1e19679d4a..70680c708f 100644 --- a/public/language/hu/admin/settings/notifications.json +++ b/public/language/hu/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Üdvözlő értesítés", "welcome-notification-link": "Üdvözlő értesítés linkje", "welcome-notification-uid": "Felhasználói üdvözlő értesítés (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/hu/admin/settings/uploads.json b/public/language/hu/admin/settings/uploads.json index fb103aea58..c3676e66dd 100644 --- a/public/language/hu/admin/settings/uploads.json +++ b/public/language/hu/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Azon képek, amik szélesebbek ennél az értéknél visszautasításra kerülnek.", "reject-image-height": "Képek maximális magassága (pixelben)", "reject-image-height-help": "Azon képek, amik magasabbak ennél az értéknél visszautasításra kerülnek.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Kis képek feltöltésének engedélyezése témakörhöz a felhasználók számára", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Témakörkép mérete", "allowed-file-extensions": "Megengedett fájlkiterjesztések", "allowed-file-extensions-help": "Itt adj meg fájlkiterjesztési listát, vesszővel elválasztva (pl. pdf,xls,doc). Az üres lista azt jelenti, hogy minden kiterjesztés megengedett.", diff --git a/public/language/hu/admin/settings/user.json b/public/language/hu/admin/settings/user.json index a901af7a38..d92519dfd3 100644 --- a/public/language/hu/admin/settings/user.json +++ b/public/language/hu/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Email cím megjelenítése", "show-fullname": "Teljes név megjelenítése", "restrict-chat": "Csak olyan felhasználó írhasson csevegő üzenetet nekem, akit követek", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Kimenő linkek megnyitása új lapon", "topic-search": "Témakörön belüli keresés engedélyezése", "update-url-with-post-index": "URL frissítése a hozzászólás indexével témakörök böngészése közben", diff --git a/public/language/hu/admin/settings/web-crawler.json b/public/language/hu/admin/settings/web-crawler.json index b229c2521b..818f8a75ce 100644 --- a/public/language/hu/admin/settings/web-crawler.json +++ b/public/language/hu/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "RSS hírcsatorna kikapcsolása", "disable-sitemap-xml": "Sitemap.xml kikapcsolása", "sitemap-topics": "Oldaltérképen megjelenítendő témakörök száma", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Oldaltérkép gyorsítótár kiűrítése", "view-sitemap": "Oldaltérkép megtekintése" } \ No newline at end of file diff --git a/public/language/hu/aria.json b/public/language/hu/aria.json index ea39fea7e9..e8fee4b22f 100644 --- a/public/language/hu/aria.json +++ b/public/language/hu/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "Felhasználó által figyelt címkék", "delete-upload-button": "Feltöltő gomb törlése", - "group-page-link-for": "Csoport oldal linkje %1" + "group-page-link-for": "Csoport oldal linkje %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/hu/category.json b/public/language/hu/category.json index 9e15691908..aeff3272db 100644 --- a/public/language/hu/category.json +++ b/public/language/hu/category.json @@ -1,12 +1,13 @@ { "category": "Kategória", "subcategories": "Alkategóriák", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Új témakör", "guest-login-post": "Lépj be a hozzászóláshoz", "no-topics": "Nincs témakör a kategóriában.Miért nem próbálsz létrehozni egyet?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "böngészés", "no-replies": "Nem érkezett válasz", "no-new-posts": "Nincs új hozzászólás.", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 6b012e4651..c18e6151b2 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -3,6 +3,7 @@ "invalid-json": "Érvénytelen JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Úgy tűnik, nem vagy bejelentkezve.", "account-locked": "A fiókod ideiglenesen zárolva lett.", "search-requires-login": "A kereséshez fiók szükséges - kérlek, lépj be vagy regisztrálj.", @@ -146,6 +147,7 @@ "post-already-restored": "Ez a bejegyzés már visszaállításra került", "topic-already-deleted": "Ezt a témakör már törlésre került", "topic-already-restored": "Ez a témakör már helyreállításra került", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Nem tisztíthatod ki ezt a témakört, inkább töröld", "topic-thumbnails-are-disabled": "Témakör bélyegképek tíltásra kerültek.", "invalid-file": "Érvénytelen fájl", @@ -154,6 +156,8 @@ "about-me-too-long": "A bemutatkozás nem lehet hosszabb %1 karakternél.", "cant-chat-with-yourself": "Nem cseveghetsz magaddal!", "chat-restricted": "Ez a felhasználó korlátozta a chat beállításait. Csak akkor cseveghetsz vele, miután felvett a követettek közé téged", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Csevegés funkció kikapcsolva", "too-many-messages": "Túl sok üzenetet küldtél, kérlek várj egy picit.", @@ -225,6 +229,7 @@ "no-topics-selected": "Nincs témakör kiválasztva", "cant-move-to-same-topic": "Nem mozgathatsz hozzászólást azonos témakörbe!", "cant-move-topic-to-same-category": "Nem mozgathatod a témakört azonos kategóriába!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Nem tudod letiltani magad!", "cannot-block-privileged": "Nem tilthatsz le adminisztrátort és moderátort", "cannot-block-guest": "Vendégek nem tilthatnak le felhasználókat", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Nem lehet elérni a szervert. Kattints ide az újra próbáláshoz vagy várj egy kicsit", "invalid-plugin-id": "Érvénytelen plugin ID", "plugin-not-whitelisted": "Ez a bővítmény nem telepíthető – csak olyan bővítmények telepíthetőek amiket a NodeBB Package Manager az ACP-n keresztül tud telepíteni", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/hu/global.json b/public/language/hu/global.json index d5bb364e10..ceaf8233f0 100644 --- a/public/language/hu/global.json +++ b/public/language/hu/global.json @@ -68,6 +68,7 @@ "users": "Felhasználók", "topics": "Témakörök", "posts": "Hozzászólások", + "crossposts": "Cross-posts", "x-posts": "%1 bejegyzés", "x-topics": "%1 témakör", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Utálva", "views": "Megtekintések", "posters": "Bejegyzők", + "watching": "Watching", "reputation": "Hírnév", "lastpost": "Utolsó bejegyzés", "firstpost": "Első bejegyzés", diff --git a/public/language/hu/groups.json b/public/language/hu/groups.json index 261552fd30..7c9ae7976c 100644 --- a/public/language/hu/groups.json +++ b/public/language/hu/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Csoportok", "members": "Felhasználók", + "x-members": "%1 member(s)", "view-group": "Csoport megtekintés", "owner": "Csoport tulajdonosa", "new-group": "Új csoport létrehozása", diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json index 8b50449c1d..00bb9849e4 100644 --- a/public/language/hu/modules.json +++ b/public/language/hu/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Előnézet elrejtése", "composer.help": "Segítség", "composer.user-said-in": "%1 válasza, %2:", - "composer.user-said": "%1 válasza:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Biztosan el akarod vetni a hozzászólást?", "composer.submit-and-lock": "Küldés és zárolás", "composer.toggle-dropdown": "Legördülő kapcsoló", diff --git a/public/language/hu/notifications.json b/public/language/hu/notifications.json index 299dcb4e91..ba96a71526 100644 --- a/public/language/hu/notifications.json +++ b/public/language/hu/notifications.json @@ -22,7 +22,7 @@ "upvote": "Kedvelés", "awards": "Jutalmak", "new-flags": "Új megjelölés", - "my-flags": "Hozzám társított megjelölés", + "my-flags": "My Flags", "bans": "Kitiltás", "new-message-from": "Új üzenet, feladó: %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 kedvelte a hozzászólásod itt: %2.", - "upvoted-your-post-in-dual": "%1 és %2 kedvelte a hozzászólásod itt: %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 áthelyezte a hozzászólásod ide: %2", "moved-your-topic": "%1 áthelyezve: %2", - "user-flagged-post-in": "%1 megjelölt egy hozzászólást itt: %2", - "user-flagged-post-in-dual": "%1 és%2 megjelölt egy hozzászólást itt: %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 megjelölt egy felhasználói profilt (%2)", "user-flagged-user-dual": "%1 és %2 megjelölt egy felhasználói profilt (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 választ írt neki: %2", - "user-posted-to-dual": "%1 és%2 választ írt neki: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 új témakört hozott létre: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 új témakört hozott létre: %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 elkezdett követni téged.", "user-started-following-you-dual": "%1 és%2 elkezdett követni téged.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "A Users csv exportálva, kattints ide a letöltéshez", "post-queue-accepted": "A sorba állított bejegyzését elfogadtuk. Kattintson ide a bejegyzés megtekintéséhez", "post-queue-rejected": "A sorba állított bejegyzésedet elutasították.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mail megerősítve", "email-confirmed-message": "Köszönjük az e-mail címed megerősítését. A fiókod mostantól teljesen aktiválva van.", "email-confirm-error-message": "Probléma lépett fel az e-mail címed megerősítésekor. Talán a kód érvénytelen volt vagy lejárt.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Megerősítő e-mail elküldve.", "none": "Nincs", "notification-only": "Csak értesítés", diff --git a/public/language/hu/social.json b/public/language/hu/social.json index b0beb0b1cf..d5765bc529 100644 --- a/public/language/hu/social.json +++ b/public/language/hu/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Belépés LinkedIn-el", - "sign-up-with-linkedin": "Regisztráció LinkedIn-el" + "sign-up-with-linkedin": "Regisztráció LinkedIn-el", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/hu/themes/harmony.json b/public/language/hu/themes/harmony.json index 3be806d84e..9ccad1acc3 100644 --- a/public/language/hu/themes/harmony.json +++ b/public/language/hu/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Oldalsáv kapcsoló", diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json index 9e887e5264..0c77afc488 100644 --- a/public/language/hu/topic.json +++ b/public/language/hu/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 hivatkozott erre a témakörre %3", "user-forked-topic-ago": "%1 elágazta ezt a témakört %3", "user-forked-topic-on": "%1 elágazta ezt a témakört %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Kattints ide a beszélgetés utolsó hozzászólására ugráshoz.", "flag-post": "Jelöld meg ezt a bejegyzést", "flag-user": "Jelöld meg ezt a felhasználót", @@ -103,6 +105,7 @@ "thread-tools.lock": "Témakör zárolása", "thread-tools.unlock": "Témakör feloldása", "thread-tools.move": "Témakör áthelyezése", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Bejegyzések mozgatása", "thread-tools.move-all": "Mind áthelyezése", "thread-tools.change-owner": "Tulaj megváltoztatása", @@ -132,6 +135,7 @@ "pin-modal-help": "Itt beállíthatod a lejárat idejét a kitűzött témaköröknek. Ha a mezőt üresen hagyod akkor témakör kitűzve marad amíg manuálisan le nem szedik.", "load-categories": "Kategóriák betöltése", "confirm-move": "Áthelyezés", + "confirm-crosspost": "Cross-post", "confirm-fork": "Szétszedés", "bookmark": "Könyvjelző", "bookmarks": "Könyvjelzők", @@ -141,6 +145,7 @@ "loading-more-posts": "További hozzászólások betöltése", "move-topic": "Témakör áthelyezése", "move-topics": "Témakörök áthelyezése", + "crosspost-topic": "Cross-post Topic", "move-post": "Hozzászólás áthelyezése", "post-moved": "Hozzászólás áthelyezve!", "fork-topic": "Témakör szétszedése", @@ -163,6 +168,9 @@ "move-topic-instruction": "Válassza ki a célkategóriát, majd kattintson az áthelyezés gombra", "change-owner-instruction": "Kattints a bejegyzésre amelyiket hozzá szeretnéd utalni egy felhasználóhoz", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Add meg a témakör címét...", "composer.handle-placeholder": "Adj meg egy nevet/kezelőt", "composer.hide": "Elrejt", @@ -174,6 +182,7 @@ "composer.replying-to": "Válasz erre: %1", "composer.new-topic": "Új témakör", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "feltöltés...", "composer.thumb-url-label": "Bélyegkép URL beszúrása", "composer.thumb-title": "Bélyegkép hozzáadása a témakörhöz", @@ -224,5 +233,8 @@ "unread-posts-link": "Olvasatlan bejegyzés link", "thumb-image": "Téma bélyegkép", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 8561fd77c5..42acfa7ca4 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -105,6 +105,10 @@ "show-email": "E-mail címem megjelenítése", "show-fullname": "Teljes nevem megjelenítése", "restrict-chats": "Csak az általam követett felhasználók tudnak chat üzenetet küldeni", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Feliratkozás a hírlevélre", "digest-description": "E-mailben kapott frissítésekre (új értesítések, témák esetében) való feliratkozás, a beállított időintervallum szerint", "digest-off": "Ki", diff --git a/public/language/hu/world.json b/public/language/hu/world.json index 3753335278..e6694bf507 100644 --- a/public/language/hu/world.json +++ b/public/language/hu/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/hy/admin/advanced/cache.json b/public/language/hy/admin/advanced/cache.json index 2fdaf641ab..ecaf23336e 100644 --- a/public/language/hy/admin/advanced/cache.json +++ b/public/language/hy/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Քեշ", - "post-cache": "Գրառման քեշ", - "group-cache": "Խմբի քեշ", - "local-cache": "Տեղական քեշ", - "object-cache": "Օբյեկտի քեշ", "percent-full": "%1%Լրիվ", "post-cache-size": "Գրառման քեշի չափը", "items-in-cache": "Նյութեր քեշում" diff --git a/public/language/hy/admin/dashboard.json b/public/language/hy/admin/dashboard.json index 5e4f140474..b6eef0c2ee 100644 --- a/public/language/hy/admin/dashboard.json +++ b/public/language/hy/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Գրանցված Էջի դիտումներ ", "graphs.page-views-guest": "Էջի դիտումներ Հյուր", "graphs.page-views-bot": "Էջի դիտումների բոտ", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Եզակի այցելուներ", "graphs.registered-users": "Գրանցված օգտատերեր", "graphs.guest-users": "Հյուր օգտատերեր", diff --git a/public/language/hy/admin/development/info.json b/public/language/hy/admin/development/info.json index ca2bdf9f66..245a89644f 100644 --- a/public/language/hy/admin/development/info.json +++ b/public/language/hy/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "առցանց", "git": "git", - "process-memory": "գործընթացի հիշողություն", + "process-memory": "rss/heap used", "system-memory": "Համակարգի հիշողություն", "used-memory-process": "Օգտագործված հիշողությունը ըստ գործընթացի", "used-memory-os": "Օգտագործված համակարգի հիշողություն", diff --git a/public/language/hy/admin/manage/categories.json b/public/language/hy/admin/manage/categories.json index 7e07ab83be..a890b90dd9 100644 --- a/public/language/hy/admin/manage/categories.json +++ b/public/language/hy/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Կառավարեք կատեգորիաները", "add-category": "Ավելացնել Կատեգորիա", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Անցնել դեպի․․․", "settings": "Կատեգորիայի կարգավորումներ", "edit-category": "Խմբագրել Կատեգորիան", "privileges": "Արտոնություններ", "back-to-categories": "Վերադառնալ կատեգորիաներ", + "id": "Category ID", "name": "Կատեգորիայի անվանումը", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Կատեգորիայի նկարագրություն", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Ֆոնի գույնը", "text-color": "Տեքստի գույն ", "bg-image-size": "Ֆոնային նկարի չափը", @@ -103,6 +107,11 @@ "alert.create-success": "Կատեգորիան հաջողությամբ ստեղծվեց:", "alert.none-active": "Դուք չունեք ակտիվ կատեգորիաներ:", "alert.create": "Ստեղծել կատեգորիա", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "Վստա՞հ եք, որ ուզում եք մաքրել այս «%1» կատեգորիան: Զգուշացում: Այս կատեգորիայի բոլոր թեմաներն ու գրառումները կջնջվեն: Կատեգորիայի մաքրումը կհեռացնի բոլոր թեմաներն ու գրառումները և կջնջի կատեգորիան տվյալների բազայից: Եթե ցանկանում եք ժամանակավորապես հեռացնել կատեգորիան, փոխարենը կցանկանաք «անջատել» կատեգորիան:", "alert.purge-success": "Կատեգորիան մաքրվել է:", "alert.copy-success": "Կարգավորումները պատճենվեցին:", diff --git a/public/language/hy/admin/manage/custom-reasons.json b/public/language/hy/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/hy/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/hy/admin/manage/privileges.json b/public/language/hy/admin/manage/privileges.json index d3e544e5ca..4b8e9ffee1 100644 --- a/public/language/hy/admin/manage/privileges.json +++ b/public/language/hy/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Մուտք գործել թեմաներ", "create-topics": "Ստեղծել Թեմաներ", "reply-to-topics": "Պատասխանել թեմաներին", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Ժամանակացույցի թեմաներ", "tag-topics": "Նշեք թեմաները", "edit-posts": "Խմբագրել գրառումները", diff --git a/public/language/hy/admin/manage/users.json b/public/language/hy/admin/manage/users.json index 6be20e2b72..cd4ef53a71 100644 --- a/public/language/hy/admin/manage/users.json +++ b/public/language/hy/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Ջնջել օգտատերին(ներ) և բովանդակությունը", "download-csv": "Ներբեռնեք CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Կառավարել Խմբերը", "set-reputation": "Սահմանել հեղինակություն", "add-group": "Ավելացնել խումբ ", @@ -77,9 +78,11 @@ "temp-ban.length": "Երկարություն", "temp-ban.reason": "Պատճառը (ըստ ցանկության)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Ժամեր", "temp-ban.days": "Օրեր", "temp-ban.explanation": "Մուտքագրեք արգելքի ժամկետը: Նկատի ունեցեք, որ 0-ի ժամանակը համարվում է մշտական արգելք:", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Իսկապե՞ս ցանկանում եք ընդմիշտ արգելել այս օգտատիրոջը:", "alerts.confirm-ban-multi": "Իսկապե՞ս ցանկանում եք ընդմիշտ արգելել այս օգտատերերին:", diff --git a/public/language/hy/admin/settings/activitypub.json b/public/language/hy/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/hy/admin/settings/activitypub.json +++ b/public/language/hy/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/hy/admin/settings/chat.json b/public/language/hy/admin/settings/chat.json index f29686af5c..963ce98c4e 100644 --- a/public/language/hy/admin/settings/chat.json +++ b/public/language/hy/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Զրույցի սենյակների անունների առավելագույն երկարությունը", "max-room-size": "Զրուցարաններում օգտատերերի առավելագույն քանակը", "delay": "Ժամանակը զրույցի հաղորդագրությունների միջև (մս)", - "notification-delay": "Զրույցի հաղորդագրությունների ծանուցման հետաձգում", - "notification-delay-help": "Այս ժամանակահատվածում ուղարկված հավելյալ հաղորդագրությունները հավաքվում են, և օգտատերը ծանուցվում է մեկ անգամ ուշացման ժամանակահատվածում: Սահմանեք սա 0՝ ուշացումն անջատելու համար:", "restrictions.seconds-edit-after": "Վայրկյանների քանակը զրույցի հաղորդագրությունը կմնա խմբագրելի:", "restrictions.seconds-delete-after": "Վայրկյանների քանակը զրույցի հաղորդագրությունը կմնա ջնջելի:" } \ No newline at end of file diff --git a/public/language/hy/admin/settings/email.json b/public/language/hy/admin/settings/email.json index 1d01e6a92f..19d0dc5c07 100644 --- a/public/language/hy/admin/settings/email.json +++ b/public/language/hy/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Կապերի միավորումը թույլ չի տալիս NodeBB-ին նոր կապ ստեղծել յուրաքանչյուր էլփոստի համար: Այս տարբերակը կիրառվում է միայն այն դեպքում, եթե SMTP Transport-ը միացված է:", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Խմբագրել էլփոստի ձևանմուշը", "template.select": "Ընտրեք էլփոստի ձևանմուշ", "template.revert": "Վերադառնալ բնօրինակին", + "test-smtp-settings": "Test SMTP Settings", "testing": "Էլփոստի փորձարկում", + "testing.success": "Test Email Sent.", "testing.select": "Ընտրեք էլփոստի ձևանմուշ", "testing.send": "Ուղարկեք փորձնական էլ.նամակ", - "testing.send-help": "Փորձնական նամակը կուղարկվի տվյալ պահին մուտք գործած օգտատերի էլ.փոստի հասցեին:", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Էլփոստի ամփոփագրեր", "subscriptions.disable": "Անջատել էլփոստի ամփոփումները", "subscriptions.hour": "Digest Ժամ", diff --git a/public/language/hy/admin/settings/notifications.json b/public/language/hy/admin/settings/notifications.json index f88d8570ba..ad3c5ecbd5 100644 --- a/public/language/hy/admin/settings/notifications.json +++ b/public/language/hy/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Ողջույնի ծանուցում", "welcome-notification-link": "Ողջույնի ծանուցման հղում", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Գրառումների հերթի օգտատեր (UID)" + "post-queue-notification-uid": "Գրառումների հերթի օգտատեր (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/hy/admin/settings/uploads.json b/public/language/hy/admin/settings/uploads.json index 6fa6f789fa..bd2103abad 100644 --- a/public/language/hy/admin/settings/uploads.json +++ b/public/language/hy/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Այս արժեքից ավելի լայն նկարները կմերժվեն:", "reject-image-height": "Նկարի առավելագույն բարձրությունը (պիքսելներով)", "reject-image-height-help": "Այս արժեքից բարձր նկարները կմերժվեն:", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Թույլ տվեք օգտատերերին վերբեռնել թեմայի մանրապատկերները", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Թեմայի Thumb չափ", "allowed-file-extensions": "Թույլատրված ֆայլերի ընդարձակումներ", "allowed-file-extensions-help": "Մուտքագրեք ստորակետերով բաժանված ֆայլերի ընդարձակման ցանկն այստեղ (օրինակ՝ pdf, xls, doc): Դատարկ ցուցակը նշանակում է, որ բոլոր ընդլայնումները թույլատրված են:", diff --git a/public/language/hy/admin/settings/user.json b/public/language/hy/admin/settings/user.json index efd655c458..5e17ca08b5 100644 --- a/public/language/hy/admin/settings/user.json +++ b/public/language/hy/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Ցույց տալ էլ.նամակը", "show-fullname": "Ցույց տալ լրիվ անունը", "restrict-chat": "Թույլատրել զրույցի հաղորդագրությունները միայն այն օգտվողներից, որոնց ես հետևում եմ", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Բացել ելքային հղումները նոր ներդիրում", "topic-search": "Միացնել թեմայում որոնումը", "update-url-with-post-index": "Թեմաներ զննարկելիս թարմացրեք url-ը գրառումների ինդեքսով", diff --git a/public/language/hy/admin/settings/web-crawler.json b/public/language/hy/admin/settings/web-crawler.json index e756979d73..3fb4fabb8f 100644 --- a/public/language/hy/admin/settings/web-crawler.json +++ b/public/language/hy/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Անջատել RSS Feeds", "disable-sitemap-xml": "Անջատել Sitemap.xml", "sitemap-topics": "Կայքի քարտեզում ցուցադրվող թեմաների քանակը", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Մաքրել կայքի քարտեզի քեշը", "view-sitemap": "Դիտել կայքի քարտեզը" } \ No newline at end of file diff --git a/public/language/hy/aria.json b/public/language/hy/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/hy/aria.json +++ b/public/language/hy/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/hy/category.json b/public/language/hy/category.json index 177ff780a9..a06062497e 100644 --- a/public/language/hy/category.json +++ b/public/language/hy/category.json @@ -1,12 +1,13 @@ { "category": "Կատեգորիա", "subcategories": "Ենթակատեգորիաներ", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Նոր թեմա", "guest-login-post": "Մուտք գործեք՝ գրառում կատարելու համար", "no-topics": "Այս բաժնում ոչ մի թեմա չկա։
Գուցե հենց Դո՞ւք ստեղծեք մեկը։", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "դիտում են", "no-replies": "Ոչ ոք չի պատասխանել", "no-new-posts": "Նոր գրառումներ չկան։", diff --git a/public/language/hy/error.json b/public/language/hy/error.json index c116163f6d..4fba5ab412 100644 --- a/public/language/hy/error.json +++ b/public/language/hy/error.json @@ -3,6 +3,7 @@ "invalid-json": "Անվավեր JSON", "wrong-parameter-type": "«%1» հատկության համար սպասվում էր %3 տիպի արժեք, բայց փոխարենը ստացվեց %2", "required-parameters-missing": "Պահանջվող պարամետրերը բացակայում էին այս API զանգից՝ %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Դուք, կարծես, մուտք չեք գործել:", "account-locked": "Ձեր հաշիվը ժամանակավորապես արգելափակվել է", "search-requires-login": "Որոնումը պահանջում է հաշիվ. խնդրում ենք մուտք գործել կամ գրանցվել:", @@ -146,6 +147,7 @@ "post-already-restored": "Այս գրառումն արդեն վերականգնվել է", "topic-already-deleted": "Այս թեման արդեն ջնջված է", "topic-already-restored": "Այս թեման արդեն վերականգնվել է", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Դուք չեք կարող մաքրել հիմնական գրառումը, փոխարենը ջնջեք թեման", "topic-thumbnails-are-disabled": "Թեմայի մանրապատկերներն անջատված են:", "invalid-file": "Անվավեր ֆայլ", @@ -154,6 +156,8 @@ "about-me-too-long": "Ներեցեք, ձեր իմ մասին չի կարող լինել ավելի քան %1 նիշ(ներ):", "cant-chat-with-yourself": "Դուք չեք կարող զրուցել ինքներդ ձեզ հետ:", "chat-restricted": "Նրանք պետք է հետևեն ձեզ, որպեսզի կարողանաք զրուցել նրանց հետ", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Զրույցի համակարգն անջատված է", "too-many-messages": "Դուք չափազանց շատ հաղորդագրություններ եք ուղարկել, խնդրում ենք սպասել մի քիչ:", @@ -225,6 +229,7 @@ "no-topics-selected": "Ընտրված թեմաներ չկան:", "cant-move-to-same-topic": "Հնարավոր չէ հաղորդագրությունը տեղափոխել նույն թեմա:", "cant-move-topic-to-same-category": "Հնարավոր չէ թեման տեղափոխել նույն կատեգորիա:", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Դուք չեք կարող արգելափակել ինքներդ ձեզ:", "cannot-block-privileged": "Դուք չեք կարող արգելափակել ադմինիստրատորներին կամ ընդհանուր մոդերատորներին", "cannot-block-guest": "Հյուրը չի կարող արգելափակել այլ օգտատերին", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Այս պահին հնարավոր չէ միանալ սերվերին: Սեղմեք այստեղ՝ նորից փորձելու համար, կամ ավելի ուշ նորից փորձեք", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Հնարավոր չէ տեղադրել plugin – ACP-ի միջոցով կարող են տեղադրվել միայն NodeBB Package Manager-ի կողմից սպիտակ ցուցակում ներառված պլագինները", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "Ձեզ չի թույլատրվում փոխել plugin-ի վիճակը, քանի որ դրանք սահմանված են գործարկման ժամանակ (config.json, շրջակա միջավայրի փոփոխականներ կամ տերմինալի արգումենտներ), փոխարենը փոխեք կազմաձևը:", "theme-not-set-in-configuration": "Կազմաձևում ակտիվ պլագիններ սահմանելիս, թեմաները փոխելիս անհրաժեշտ է ավելացնել նոր թեման ակտիվ հավելումների ցանկում՝ նախքան այն թարմացնելը ACP-ում:", @@ -246,6 +252,7 @@ "api.401": "Մուտքի վավեր նիստ չի գտնվել: Խնդրում ենք մուտք գործել և նորից փորձել:", "api.403": "Դուք իրավասու չեք կատարել այս զանգը", "api.404": "Անվավեր API զանգ", + "api.413": "The request payload is too large", "api.426": "HTTPS-ն անհրաժեշտ է գրելու api-ին ուղղված հարցումների համար, խնդրում ենք նորից ուղարկել ձեր հարցումը HTTPS-ի միջոցով", "api.429": "Դուք չափազանց շատ հարցումներ եք կատարել, խնդրում ենք փորձել ավելի ուշ", "api.500": "Ձեր հարցումը սպասարկելիս անսպասելի սխալ է տեղի ունեցել:", diff --git a/public/language/hy/global.json b/public/language/hy/global.json index aaf034c3ae..07593edddb 100644 --- a/public/language/hy/global.json +++ b/public/language/hy/global.json @@ -68,6 +68,7 @@ "users": "Օգտվողներ", "topics": "Թեմաներ", "posts": "Գրառումներ", + "crossposts": "Cross-posts", "x-posts": "%1 գրառումներ", "x-topics": "%1 թեմաներ", "x-reputation": "%1 հեղինակություն", @@ -82,6 +83,7 @@ "downvoted": "Դեմ է քվեարկել", "views": "Դիտումներ", "posters": "Պաստառներ", + "watching": "Watching", "reputation": "Վարկանիշ", "lastpost": "Վերջին գրառում", "firstpost": "Առաջին գրառում", diff --git a/public/language/hy/groups.json b/public/language/hy/groups.json index 72305c5897..31b805e616 100644 --- a/public/language/hy/groups.json +++ b/public/language/hy/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Բոլոր խմբերը", "groups": "Խմբեր", "members": "Անդամներ", + "x-members": "%1 member(s)", "view-group": "Դիտել խումբը", "owner": "Խմբի սեփականատեր", "new-group": "Ստեղծել նոր խումբ", diff --git a/public/language/hy/modules.json b/public/language/hy/modules.json index 2de14aeb1e..f90b958d00 100644 --- a/public/language/hy/modules.json +++ b/public/language/hy/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Ավելացնել օգտատեր", "chat.notification-settings": "Ծանուցման կարգավորումներ", "chat.default-notification-setting": "Ծանուցման հիմնական կարգավորումներ", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Սենյակի հիմնական վիճակ", "chat.notification-setting-none": "Ծանուցումներ չկան", "chat.notification-setting-at-mention-only": "@նշում միայն", @@ -81,7 +82,7 @@ "composer.hide-preview": "Թաքցնել նախադիտումը", "composer.help": "Օգնություն", "composer.user-said-in": "%1-ն ասաց %2-ում:", - "composer.user-said": "%1 -ը ասաց.", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Վստա՞հ եք որ ցանկանում եք հրաժարվել այս գրառումից:", "composer.submit-and-lock": "Ներկայացնել և փակել", "composer.toggle-dropdown": "Փոխարկել բացվող պատուհանը", diff --git a/public/language/hy/notifications.json b/public/language/hy/notifications.json index 862a6e3520..9e5e9ad716 100644 --- a/public/language/hy/notifications.json +++ b/public/language/hy/notifications.json @@ -22,7 +22,7 @@ "upvote": "Կողմ ձայներ", "awards": "Մրցանակներ", "new-flags": "Նոր դրոշներ", - "my-flags": "Ինձ հանձնարարված դրոշներ", + "my-flags": "My Flags", "bans": "Արգելքներ", "new-message-from": "Նոր հաղորդագրություն %1-ից", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 և %2 գրել են%4-ում", "user-posted-in-public-room-triple": "%1, %2 և %3 գրել են %5 - ում", "user-posted-in-public-room-multiple": "%1, %2 և %3 ուրիշները գրել են%5 - ում", - "upvoted-your-post-in": "%1-ը դրական է քվեարկել ձեր գրառմանը %2-ում:", - "upvoted-your-post-in-dual": "%1-ը և %2-ը դրական են քվեարկել ձեր գրառմանը %3-ում:", - "upvoted-your-post-in-triple": "%1, %2 և %3 կողմ են քվեարկել ձեր գրառմանը%4-ում։", - "upvoted-your-post-in-multiple": "%1, %2 և %3 ուրիշները կողմ են քվեարկել ձեր գրառմանը %4- ում։", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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-triple": "%1, %2 և %3 դրոշակավորել են գրառումը %4 -ում։", - "user-flagged-post-in-multiple": "%1, %2 և %3 ուրիշները դրոշակավորել են գրառումը %4 -ում։", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1-ը դրոշակեց օգտվողի պրոֆիլը (% 2)", "user-flagged-user-dual": "%1-ը և %2-ը դրոշակել են օգտվողի պրոֆիլը (%3)", "user-flagged-user-triple": "%1, %2 և %3 դրոշակավորել են օգտատիրոջ հաշիվը (%4)", "user-flagged-user-multiple": "%1, %2 և %3 ուրիշները դրոշակավորել են օգտատիրոջ հաշիվը (%4)", - "user-posted-to": "%1-ը պատասխանել է %2-ին", - "user-posted-to-dual": "%1-ը և %2-ը հրապարակել են պատասխաններ %3-ին", - "user-posted-to-triple": "%1, %2 և %3 պատասխանել են %4 - ին։", - "user-posted-to-multiple": "%1, %2 և %3 ուրիշները պատասխանել են %4 - ին։", - "user-posted-topic": "%1-ը նոր թեմա է տեղադրել՝ %2", - "user-edited-post": "%1-ը խմբագրել է գրառում %2-ում", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 նոր թեմա է տեղադրել %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 սկսեց հետևել ձեզ", "user-started-following-you-dual": "%1 և %2 սկսեցին հետևել ձեզ:", "user-started-following-you-triple": "%1, %2 և %3 սկսել են հետևել Ձեզ։", @@ -71,11 +71,11 @@ "users-csv-exported": "Օգտատերերի csv-ն արտահանվել է, սեղմեք ներբեռնելու համար", "post-queue-accepted": "Ձեր հերթագրված գրառումն ընդունվել է: Սեղմեք այստեղ՝ ձեր գրառումը տեսնելու համար:", "post-queue-rejected": "Ձեր հերթագրված գրառումը մերժվել է:", - "post-queue-notify": "Հերթագրված գրառումը ստացել է ծանուցում. «% 1»", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Էլփոստը հաստատված է", "email-confirmed-message": "Շնորհակալություն ձեր էլփոստը հաստատելու համար: Ձեր հաշիվն այժմ ամբողջությամբ ակտիվացված է:", "email-confirm-error-message": "Ձեր էլփոստի հասցեն վավերացնելիս խնդիր առաջացավ: Հավանաբար կոդը անվավեր է կամ ժամկետանց է:", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Հաստատման էլփոստը ուղարկվել է", "none": "None", "notification-only": "Միայն ծանուցում", diff --git a/public/language/hy/social.json b/public/language/hy/social.json index 1f88d125ba..e54b7c8a69 100644 --- a/public/language/hy/social.json +++ b/public/language/hy/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Մուտք գործեք Facebook-ով", "continue-with-facebook": "Շարունակեք Facebook-ով", "sign-in-with-linkedin": "Մուտք գործեք LinkedIn-ով", - "sign-up-with-linkedin": "Գրանցվեք LinkedIn-ով" + "sign-up-with-linkedin": "Գրանցվեք LinkedIn-ով", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/hy/themes/harmony.json b/public/language/hy/themes/harmony.json index 6bd179fbba..3c40c9e203 100644 --- a/public/language/hy/themes/harmony.json +++ b/public/language/hy/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Ներդաշնակության թեմա", "skins": "Շապիկներ", + "light": "Light", + "dark": "Dark", "collapse": "Փլուզվել", "expand": "Ընդարձակել", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/hy/topic.json b/public/language/hy/topic.json index fc8d08db95..1ef24aba46 100644 --- a/public/language/hy/topic.json +++ b/public/language/hy/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 հղել է այս թեման %3", "user-forked-topic-ago": "%1 քաղել է այս թեման %3", "user-forked-topic-on": "%1 քաղել է այս թեման %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Սեղմեք այստեղ՝ այս թեմայի վերջին ընթերցված գրառմանը վերադառնալու համար:", "flag-post": "Դրոշակել այց գրառումը", "flag-user": "Դրոշակել այս օգտատերին", @@ -103,6 +105,7 @@ "thread-tools.lock": "Փակել թեման", "thread-tools.unlock": "Վերաբացել թեման", "thread-tools.move": "Տեղափոխել թեման", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Տեղափոխել գրառումները", "thread-tools.move-all": "Տեղափոխել բոլորը", "thread-tools.change-owner": "Փոխել սեփականատիրոջը", @@ -132,6 +135,7 @@ "pin-modal-help": "Դուք կարող եք ըստ ցանկության սահմանել ամրացված թեմայի (թեմայի) պիտանելիության ժամկետը այստեղ: Որպես այլընտրանք, դուք կարող եք թողնել այս դաշտը դատարկ, որպեսզի թեման մնա ամրացված, մինչև այն ձեռքով չապամրացվի:", "load-categories": "Կատեգորիաների բեռնում", "confirm-move": "Տեղափոխել", + "confirm-crosspost": "Cross-post", "confirm-fork": "Մասնատել", "bookmark": "Էջանիշ", "bookmarks": "Էջանիշեր", @@ -141,6 +145,7 @@ "loading-more-posts": "Լրացուցիչ գրառումների բեռնում", "move-topic": "Տեղափոխել թեման", "move-topics": "Տեղափոխել թեմաները", + "crosspost-topic": "Cross-post Topic", "move-post": "Տեղափոխել գրառումը", "post-moved": "Գրառումը տեղափոխված է։", "fork-topic": "Մասնատել թեման", @@ -163,6 +168,9 @@ "move-topic-instruction": "Ընտրեք թիրախային կատեգորիան և սեղմեք «Տեղափոխել»:", "change-owner-instruction": "Սեղմեք այն գրառումները, որոնք ցանկանում եք վերագրել մեկ այլ օգտատիրոջ", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Մուտքագրեք ձեր թեմայի վերնագիրը այստեղ...", "composer.handle-placeholder": "Մուտքագրեք ձեր անունը/բռնակը այստեղ", "composer.hide": "Թաքցնել", @@ -174,6 +182,7 @@ "composer.replying-to": "Պատասխանում է %1-ին", "composer.new-topic": "Նոր թեմա", "composer.editing-in": "Խմբագրվում է գրառումը %1 - ում", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "վերբեռնում...", "composer.thumb-url-label": "Տեղադրեք թեմայի մանրապատկերի URL", "composer.thumb-title": "Ավելացրեք մանրապատկեր այս թեմային", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/hy/user.json b/public/language/hy/user.json index 3b76444da6..44ba3bc60c 100644 --- a/public/language/hy/user.json +++ b/public/language/hy/user.json @@ -105,6 +105,10 @@ "show-email": "Ցույց տալ իմ էլ. փոստը", "show-fullname": "Ցույց տալ իմ լրիվ անունը", "restrict-chats": "Թույլատրել զրույցի հաղորդագրությունները միայն այն օգտվողներից, որոնց ես հետևում եմ", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Բաժանորդագրվել Digest-ին", "digest-description": "Բաժանորդագրվեք այս ֆորումի էլեկտրոնային թարմացումներին (նոր ծանուցումներ և թեմաներ) ըստ սահմանված ժամանակացույցի", "digest-off": "Անջատված", diff --git a/public/language/hy/world.json b/public/language/hy/world.json index 3753335278..e6694bf507 100644 --- a/public/language/hy/world.json +++ b/public/language/hy/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/id/admin/advanced/cache.json b/public/language/id/admin/advanced/cache.json index 81ae827ba0..e50e463767 100644 --- a/public/language/id/admin/advanced/cache.json +++ b/public/language/id/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Cache Kiriman", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Penuh", "post-cache-size": "Ukuran Cache Kiriman", "items-in-cache": "Item di Cache" diff --git a/public/language/id/admin/dashboard.json b/public/language/id/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/id/admin/dashboard.json +++ b/public/language/id/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/id/admin/development/info.json b/public/language/id/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/id/admin/development/info.json +++ b/public/language/id/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/id/admin/manage/categories.json b/public/language/id/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/id/admin/manage/categories.json +++ b/public/language/id/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/id/admin/manage/custom-reasons.json b/public/language/id/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/id/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/id/admin/manage/privileges.json b/public/language/id/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/id/admin/manage/privileges.json +++ b/public/language/id/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/id/admin/manage/users.json b/public/language/id/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/id/admin/manage/users.json +++ b/public/language/id/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/id/admin/settings/activitypub.json b/public/language/id/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/id/admin/settings/activitypub.json +++ b/public/language/id/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/id/admin/settings/chat.json b/public/language/id/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/id/admin/settings/chat.json +++ b/public/language/id/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/id/admin/settings/email.json b/public/language/id/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/id/admin/settings/email.json +++ b/public/language/id/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/id/admin/settings/notifications.json b/public/language/id/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/id/admin/settings/notifications.json +++ b/public/language/id/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/id/admin/settings/uploads.json b/public/language/id/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/id/admin/settings/uploads.json +++ b/public/language/id/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/id/admin/settings/user.json b/public/language/id/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/id/admin/settings/user.json +++ b/public/language/id/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/id/admin/settings/web-crawler.json b/public/language/id/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/id/admin/settings/web-crawler.json +++ b/public/language/id/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/id/aria.json b/public/language/id/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/id/aria.json +++ b/public/language/id/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/id/category.json b/public/language/id/category.json index ff76d86428..9d28e5fb35 100644 --- a/public/language/id/category.json +++ b/public/language/id/category.json @@ -1,12 +1,13 @@ { "category": "Kategori", "subcategories": "Subkategori", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Topik Baru", "guest-login-post": "Masuk untuk memposting", "no-topics": "Tidak ada topik dikategori ini
Mengapa anda tidak mencoba membuat yang baru?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "penjelajahan", "no-replies": "Belum ada orang yang menjawab", "no-new-posts": "Tidak ada post terbaru", diff --git a/public/language/id/error.json b/public/language/id/error.json index 08afd887b7..41736ccefe 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Kamu terlihat belum login", "account-locked": "Akun kamu dikunci sementara", "search-requires-login": "Searching requires an account - please login or register.", @@ -146,6 +147,7 @@ "post-already-restored": "Postingan ini sudah direstore", "topic-already-deleted": "Topik ini sudah dihapus", "topic-already-restored": "Topik ini sudah direstore", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Thumbnail di topik ditiadakan", "invalid-file": "File Salah", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "Kamu tidak dapat chat dengan akun sendiri", "chat-restricted": "Pengguna ini telah membatasi percakapa mereka. Mereka harus mengikutimu sebelum kamu dapat melakukan percakapan dengan mereka", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/id/global.json b/public/language/id/global.json index 9b45b25e3b..e73f120804 100644 --- a/public/language/id/global.json +++ b/public/language/id/global.json @@ -68,6 +68,7 @@ "users": "Pengguna", "topics": "Topik", "posts": "Post", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Views", "posters": "Posters", + "watching": "Watching", "reputation": "Reputasi", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/id/groups.json b/public/language/id/groups.json index a41330ca20..5bf0a765a3 100644 --- a/public/language/id/groups.json +++ b/public/language/id/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grup", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Tampilkan Grup", "owner": "Group Owner", "new-group": "Create New Group", diff --git a/public/language/id/modules.json b/public/language/id/modules.json index 5b15156ff3..395b0ccb4c 100644 --- a/public/language/id/modules.json +++ b/public/language/id/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 berkata di %2:", - "composer.user-said": "%1 berkata:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Kamu yakin akan membuang posting ini?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/id/notifications.json b/public/language/id/notifications.json index 3e0c611ed5..b7083c2324 100644 --- a/public/language/id/notifications.json +++ b/public/language/id/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Pesan baru dari %1", "new-messages-from": "%1 new messages from %2", @@ -32,13 +32,13 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 telah melakukan upvote untuk posting kamu di %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", - "user-flagged-post-in": "%1 menandai sebuah posting di %2", + "user-flagged-post-in": "%1 flagged a post in %2", "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 telah mengirim sebuah balasan kepada: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 telah membuat topik baru: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 mulai mengikutimu.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email telah Dikonfirmasi", "email-confirmed-message": "Terimakasih telah melakukan validasi email. Akunmu saat ini telah aktif sepenuhnya.", "email-confirm-error-message": "Terjadi masalah saat melakukan validasi emailmu. Mungkin terjadi kesalahan kode atau waktu habis.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Email konfirmasi telah dikirim.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/id/social.json b/public/language/id/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/id/social.json +++ b/public/language/id/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/id/themes/harmony.json b/public/language/id/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/id/themes/harmony.json +++ b/public/language/id/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/id/topic.json b/public/language/id/topic.json index 7483052649..50c74a8bae 100644 --- a/public/language/id/topic.json +++ b/public/language/id/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klik di sini untuk kembali ke posting yang terakhir kali dibaca pada topik ini.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Kunci Topik", "thread-tools.unlock": "Lepas Topik", "thread-tools.move": "Pindah Topik", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Pindah Semua", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Memuat Kategori", "confirm-move": "Pindah", + "confirm-crosspost": "Cross-post", "confirm-fork": "Cabangkan", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Memuat Lebih Banyak Posting", "move-topic": "Pindahkan Topik", "move-topics": "Pindahkan Beberapa Topik", + "crosspost-topic": "Cross-post Topic", "move-post": "Pindahkan Posting", "post-moved": "Posting dipindahkan!", "fork-topic": "Cabangkan Topik", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Masukkan judul topik di sini...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Membalas ke %1", "composer.new-topic": "Topik Baru", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "mengunggah...", "composer.thumb-url-label": "Tempelkan URL gambar mini topik", "composer.thumb-title": "Tambah gambar mini untuk topik ini", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/id/user.json b/public/language/id/user.json index 4d1872ed0f..6578a9292d 100644 --- a/public/language/id/user.json +++ b/public/language/id/user.json @@ -105,6 +105,10 @@ "show-email": "Tampilkan Email Saya", "show-fullname": "Tampilkan Nama Lengkap Saya", "restrict-chats": "Hanya ijinkan pesan percakapan dari pengguna yang saya ikuti.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Berlangganan Digest", "digest-description": "Berlangganan melalui email untuk forum ini (pemberitahuan baru dan topik) sesuai dengan pengaturan jadwal", "digest-off": "Off", diff --git a/public/language/id/world.json b/public/language/id/world.json index 3753335278..e6694bf507 100644 --- a/public/language/id/world.json +++ b/public/language/id/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/it/admin/advanced/cache.json b/public/language/it/admin/advanced/cache.json index c9585acc5c..accfec4045 100644 --- a/public/language/it/admin/advanced/cache.json +++ b/public/language/it/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Cache post", - "group-cache": "Cache di gruppo", - "local-cache": "Cache locale", - "object-cache": "Cache oggetti", "percent-full": "%1% Pieno", "post-cache-size": "Dimensione cache dei post", "items-in-cache": "Elementi nella Cache" diff --git a/public/language/it/admin/dashboard.json b/public/language/it/admin/dashboard.json index 691b2914e7..ceaff8c06e 100644 --- a/public/language/it/admin/dashboard.json +++ b/public/language/it/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Pagine viste Registrati", "graphs.page-views-guest": "Pagine viste Ospite", "graphs.page-views-bot": "Pagine viste Bot", + "graphs.page-views-ap": "Visualizzazioni della pagina ActivityPub", "graphs.unique-visitors": "Visitatori Unici", "graphs.registered-users": "Utenti Registrati", "graphs.guest-users": "Utenti ospiti", diff --git a/public/language/it/admin/development/info.json b/public/language/it/admin/development/info.json index d5bb340bc5..56d9093d24 100644 --- a/public/language/it/admin/development/info.json +++ b/public/language/it/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "memoria di processo", + "process-memory": "rss/heap used", "system-memory": "memoria di sistema", "used-memory-process": "Memoria usata dal processo", "used-memory-os": "Memoria di sistema usata", diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index a78b7e35ab..5b081acb6a 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Gestisci categorie", "add-category": "Aggiungi categoria", + "add-local-category": "Aggiungi categoria locale", + "add-remote-category": "Aggiungi categoria remota", + "remove": "Rimuovi", + "rename": "Rinomina", "jump-to": "Vai a...", "settings": "Impostazioni Categoria", "edit-category": "Modifica categoria", "privileges": "Privilegi", "back-to-categories": "Torna alle categorie", + "id": "ID Categoria", "name": "Nome Categoria", "handle": "Pseudonimo categoria", - "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", + "handle.help": "Lo pseudonimo della categoria è utilizzato come rappresentazione di questa categoria in altre reti, in modo simile a un nome utente. Lo pseudonimo di una categoria non deve corrispondere a un nome utente o a un gruppo di utenti esistenti.", "description": "Descrizione categoria", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Modello discussione", + "topic-template.help": "Definisci un modello per le nuove discussioni create in questa categoria.", "bg-color": "Colore sfondo", "text-color": "Colore testo", "bg-image-size": "Dimensione dell'immagine di sfondo", @@ -82,27 +86,32 @@ "analytics.topics-daily": "Figura 3 – Discussioni giornaliere create in questa categoria", "analytics.posts-daily": "Figura 4dash; Post giornalieri pubblicati in questa categoria", - "federation.title": "Federation settings for \"%1\" category", - "federation.disabled": "Federation is disabled site-wide, so category federation settings are currently unavailable.", - "federation.disabled-cta": "Federation Settings →", - "federation.syncing-header": "Synchronization", - "federation.syncing-intro": "A category can follow a \"Group Actor\" via the ActivityPub protocol. If content is received from one of the actors listed below, it will be automatically added to this category.", - "federation.syncing-caveat": "N.B. Setting up syncing here establishes a one-way synchronization. NodeBB attempts to subscribe/follow the actor, but the reverse cannot be assumed.", - "federation.syncing-none": "This category is not currently following anybody.", - "federation.syncing-add": "Synchronize with...", - "federation.syncing-actorUri": "Actor", - "federation.syncing-follow": "Follow", - "federation.syncing-unfollow": "Unfollow", - "federation.followers": "Remote users following this category", - "federation.followers-handle": "Handle", + "federation.title": "Impostazioni della federazione per categoria \"%1\"", + "federation.disabled": "La federazione è disattivata in tutto il sito, quindi le impostazioni della federazione di categoria non sono attualmente disponibili.", + "federation.disabled-cta": "Impostazioni della federazione →", + "federation.syncing-header": "Sincronizzazione", + "federation.syncing-intro": "Una categoria può seguire un \"Partecipante del Gruppo\" tramite il protocollo ActivityPub. Se il contenuto è ricevuto da uno dei partecipanti elencati di seguito, sarà automaticamente aggiunto a questa categoria.", + "federation.syncing-caveat": "N.B. Impostando la sincronizzazione qui si stabilisce una sincronizzazione unidirezionale. NodeBB tenta di iscrivere/seguire il partecipante, ma non è possibile presumere il contrario.", + "federation.syncing-none": "Questa categoria non segue attualmente nessuno.", + "federation.syncing-add": "Sincronizzazione con...", + "federation.syncing-actorUri": "Partecipante", + "federation.syncing-follow": "Segui", + "federation.syncing-unfollow": "Non seguire", + "federation.followers": "Utenti remoti che seguono questa categoria", + "federation.followers-handle": "Pseudonimo", "federation.followers-id": "ID", - "federation.followers-none": "No followers.", - "federation.followers-autofill": "Autofill", + "federation.followers-none": "Non seguito.", + "federation.followers-autofill": "Riempimento automatico", "alert.created": "Creato", "alert.create-success": "Categoria creata con successo!", "alert.none-active": "Hai una categoria non attiva.", "alert.create": "Crea una Categoria", + "alert.add": "Aggiungi una categoria", + "alert.add-help": "Le categorie remote possono essere aggiunte all'elenco delle categorie specificando il loro identificatore.

Nota — La categoria remota potrebbe non riflettere tutte le discussioni pubblicate a meno che almeno un utente locale non ne tenga traccia.", + "alert.rename": "Rinomina una categoria remota", + "alert.rename-help": "Inserisci un nuovo nome per questa categoria. Lascialo vuoto per ripristinare il nome originale.", + "alert.confirm-remove": "Vuoi davvero rimuovere questa categoria? Puoi aggiungerla di nuovo in qualsiasi momento.", "alert.confirm-purge": "

Vuoi davvero eliminare definitivamente questa categoria \"%1\"?

Attenzione!Tutte le discussioni e i post in questa categoria saranno eliminati definitivamente!

Eliminare definitivamente una categoria rimuoverà tutte le discussioni e i post ed eliminerà la categoria dal database. Se vuoi rimuovere una categoria temporaneamente, puoi invece \"disabilitare\" la categoria.", "alert.purge-success": "Categoria eliminata definitivamente!", "alert.copy-success": "Impostazioni copiate!", diff --git a/public/language/it/admin/manage/custom-reasons.json b/public/language/it/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/it/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/it/admin/manage/privileges.json b/public/language/it/admin/manage/privileges.json index 319dbdf3b1..fb5498ffc0 100644 --- a/public/language/it/admin/manage/privileges.json +++ b/public/language/it/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Accesso discussioni", "create-topics": "Crea discussioni", "reply-to-topics": "Risposta alle discussioni", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Pianificazione discussioni", "tag-topics": "Tag discussioni", "edit-posts": "Modifica i post", diff --git a/public/language/it/admin/manage/users.json b/public/language/it/admin/manage/users.json index 689d129570..defd7d4fc4 100644 --- a/public/language/it/admin/manage/users.json +++ b/public/language/it/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Elimina Utente(i) e Contenuto", "download-csv": "Scarica CSV", "custom-user-fields": "Campi utente personalizzati", + "custom-reasons": "Custom Reasons", "manage-groups": "Gestisci Gruppi", "set-reputation": "Imposta reputazione", "add-group": "Aggiungi Gruppo", @@ -59,7 +60,7 @@ "users.no-email": "(nessuna email)", "users.validated": "Convalidato", "users.not-validated": "Non convalidato", - "users.validation-pending": "In attesa di convalida", + "users.validation-pending": "Validazione in sospeso", "users.validation-expired": "Convalida scaduta", "users.ip": "IP", "users.postcount": "numero di post", @@ -77,9 +78,11 @@ "temp-ban.length": "Lunghezza", "temp-ban.reason": "Ragione (Opzionale)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Ore", "temp-ban.days": "Giorni", "temp-ban.explanation": "Inserisci la lunghezza del tempo di ban. Nota: quando il tempo è 0 il ban è considerato permanente.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Vuoi realmente bannare questo utente permanentemente?", "alerts.confirm-ban-multi": "Vuoi realmente bannare questi utenti permanentemente?", diff --git a/public/language/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index 94f9ad7822..faaf555e92 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -1,26 +1,48 @@ { - "intro-lead": "What is Federation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", - "general": "General", - "pruning": "Content Pruning", - "content-pruning": "Days to keep remote content", - "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", - "user-pruning": "Days to cache remote user accounts", - "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", - "enabled": "Enable Federation", - "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", - "allowLoopback": "Allow loopback processing", - "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", + "intro-lead": "Cos'è la federazione?", + "intro-body": "NodeBB è in grado di comunicare con altre istanze NodeBB che lo supportano. Ciò avviene attraverso un protocollo chiamato ActivityPub. Se abilitato, NodeBB sarà in grado di comunicare anche con altre applicazioni e siti web che utilizzano ActivityPub (ad es. Mastodon, Peertube, ecc.).", + "general": "Generale", + "pruning": "Sfoltimento contenuto", + "content-pruning": "Giorni per mantenere il contenuto remoto", + "content-pruning-help": "Si noti che i contenuti remoti che hanno ricevuto un coinvolgimento (una risposta o un voto positivo/negativo) saranno conservati. (0 per disabilitarlo)", + "user-pruning": "Giorni di archiviazione degli account degli utenti remoti", + "user-pruning-help": "Gli account degli utenti remoti saranno eliminati solo se non hanno messaggi. In caso contrario, saranno recuperati. (0 per disabilitato)", + "enabled": "Abilita federazione", + "enabled-help": "Se abilitato, consentirà a questo NodeBB di comunicare con tutti i client abilitati ad Activitypub nel più ampio fediverso.", + "allowLoopback": "Consenti l'elaborazione del loopback", + "allowLoopback-help": "Utile solo a scopo di debug. Probabilmente è meglio lasciarlo disattivato.", - "probe": "Open in App", - "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", - "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", - "probe-timeout": "Lookup Timeout (milliseconds)", - "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "probe": "Apri in App", + "probe-enabled": "Prova ad aprire le risorse abilitate ad ActivityPub in NodeBB", + "probe-enabled-help": "Se abilitato, NodeBB controllerà ogni link esterno per trovare un equivalente di ActivityPub e lo caricherà invece in NodeBB.", + "probe-timeout": "Timeout di ricerca (millisecondi)", + "probe-timeout-help": "(Predefinito: 2000) Se la query di ricerca non riceve una risposta entro l'intervallo di tempo impostato, l'utente l'utente sarà indirizzato direttamente al link. Aumenta questo numero se i siti rispondono lentamente e desideri concedere più tempo.", - "server-filtering": "Filtering", - "count": "This NodeBB is currently aware of %1 server(s)", - "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", - "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.", - "server.filter-allow-list": "Use this as an Allow List instead" + "rules": "Categorizzazione", + "rules-intro": "I contenuti scoperti tramite ActivityPub possono essere categorizzati automaticamente in base a determinate regole (ad es. hashtag)", + "rules.modal.title": "Come funziona", + "rules.modal.instructions": "Tutti i contenuti in arrivo sono controllati in base a queste regole di categorizzazione e i contenuti corrispondenti sono automaticamente spostati nella categoria scelta.

N.B. Contenuti già categorizzati (ad es. in una categoria remota) non passerà attraverso queste regole.", + "rules.add": "Aggiungi nuova regola", + "rules.help-hashtag": "Le discussioni contenenti questo hashtag senza distinzione tra maiuscole e minuscole corrisponderanno. Non inserire il simbolo #", + "rules.help-user": "Le discussioni create dall'utente inserito corrisponderanno. Inserisci un nome utente o un ID completo (ad es. bob@example.org or https://example.org/users/bob.", + "rules.type": "Tipo", + "rules.value": "Valore", + "rules.cid": "Categoria", + + "relays": "Relè", + "relays.intro": "Un relè migliora la scoperta dei contenuti da e verso il tuo NodeBB. Iscriversi a un relè significa che i contenuti ricevuti dal relè vengono inoltrati qui, e i contenuti pubblicati qui vengono distribuiti all'esterno dal relè.", + "relays.warning": "Nota: I relè possono inviare grandi quantità di traffico e potrebbero far aumentare i costi di archiviazione ed elaborazione.", + "relays.litepub": "NodeBB segue lo standard del relè in stile LitePub. L'URL inserito deve terminare con /actor.", + "relays.add": "Aggiungi nuovo relè", + "relays.relay": "Relè", + "relays.state": "Stato", + "relays.state-0": "In sospeso", + "relays.state-1": "Solo ricezione", + "relays.state-2": "Attivo", + + "server-filtering": "Filtraggio", + "count": "Questo NodeBB è attualmente a conoscenza di %1 server", + "server.filter-help": "Specifica i server a cui desideri impedire la federazione con il tuo NodeBB. In alternativa, puoi scegliere di consentire in modo selettivo la federazione con server specifici. Entrambe le opzioni sono supportate, anche se si escludono a vicenda.", + "server.filter-help-hostname": "Inserisci solo il nome host dell'istanza sottostante (ad es. example.org), separato da interruzioni di riga.", + "server.filter-allow-list": "Usa invece questo come Elenco permessi" } \ No newline at end of file diff --git a/public/language/it/admin/settings/chat.json b/public/language/it/admin/settings/chat.json index 68e2e0e388..5c012c1dbd 100644 --- a/public/language/it/admin/settings/chat.json +++ b/public/language/it/admin/settings/chat.json @@ -5,13 +5,11 @@ "disable-editing": "Disabilita modifica/cancellazione messaggio chat", "disable-editing-help": "Gli amministratori e i moderatori globali sono esenti da questa restrizione.", "max-length": "Lunghezza massima dei messaggi della chat", - "max-length-remote": "Maximum length of remote chat messages", - "max-length-remote-help": "This value is usually set higher than the chat message maximum for local users as remote messages tend to be longer (with @ mentions, etc.)", + "max-length-remote": "Lunghezza massima dei messaggi di chat remota", + "max-length-remote-help": "Questo valore è generalmente impostato più alto rispetto al massimo del messaggio di chat per gli utenti locali, poiché i messaggi remoti tendono a essere più lunghi (con @ menzioni, ecc.).", "max-chat-room-name-length": "Lunghezza massima dei nomi delle stanze chat", "max-room-size": "Numero massimo di utenti nelle stanza chat", "delay": "Tempo tra i messaggi di chat (ms)", - "notification-delay": "Ritardo nella notifica dei messaggi di chat", - "notification-delay-help": "I messaggi aggiuntivi inviati in questo intervallo di tempo vengono raccolti e l'utente riceve una notifica per ogni periodo di ritardo. Impostalo su 0 per disabilitare il ritardo.", "restrictions.seconds-edit-after": "Numero di secondi in cui un messaggio di chat rimane modificabile.", "restrictions.seconds-delete-after": "Numero di secondi in cui un messaggio di chat rimane cancellabile." } \ No newline at end of file diff --git a/public/language/it/admin/settings/email.json b/public/language/it/admin/settings/email.json index 025018b617..e4f3dc88cd 100644 --- a/public/language/it/admin/settings/email.json +++ b/public/language/it/admin/settings/email.json @@ -28,16 +28,22 @@ "smtp-transport.password": "Password", "smtp-transport.pool": "Abilita le connessioni in pool", "smtp-transport.pool-help": "Il pooling delle connessioni impedisce a NodeBB di creare una nuova connessione per ogni email. Questa opzione si applica solo se è abilitato il trasporto SMTP.", - "smtp-transport.allow-self-signed": "Allow self-signed certificates", - "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.allow-self-signed": "Consenti certificati autofirmati", + "smtp-transport.allow-self-signed-help": "L'attivazione di questa impostazione consente di utilizzare certificati TLS autofirmati o non validi.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Modifica Modello Email", "template.select": "Seleziona Modello Email", "template.revert": "Torna all'originale", + "test-smtp-settings": "Test SMTP Settings", "testing": "Prova Email", + "testing.success": "Test Email Sent.", "testing.select": "Seleziona Modello Email", "testing.send": "Invia Email di prova", - "testing.send-help": "L'email di prova sarà inviata all'indirizzo email dell'utente attualmente connesso.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email riepilogo", "subscriptions.disable": "Disabilita email riepilogo", "subscriptions.hour": "Orario riepilogo", diff --git a/public/language/it/admin/settings/general.json b/public/language/it/admin/settings/general.json index f0a5c1ed0d..6b1fea08ae 100644 --- a/public/language/it/admin/settings/general.json +++ b/public/language/it/admin/settings/general.json @@ -15,7 +15,7 @@ "title-layout": "Layout del Titolo", "title-layout-help": "Definire come sarà strutturato il titolo del browser, ad es. {pageTitle} | {browserTitle}", "description.placeholder": "Una breve descrizione della tua comunità", - "description": "Site Description", + "description": "Descrizione sito", "keywords": "Parole chiave del sito", "keywords-placeholder": "Parole chiave che descrivono la vostra comunità, separate da virgole", "logo-and-icons": "Logo e icone del sito", @@ -51,7 +51,7 @@ "topic-tools": "Strumenti discussione", "home-page": "Pagina Iniziale", "home-page-route": "Percorso Pagina Iniziale", - "home-page-description": "Choose what page is shown when users navigate to the root URL of your forum.", + "home-page-description": "Scegli quale pagina visualizzare quando gli utenti navigano verso l'URL principale del tuo forum.", "custom-route": "Percorso personalizzato", "allow-user-home-pages": "Consenti Pagina Iniziale Utente", "home-page-title": "Titolo della pagina iniziale (impostazione predefinita \"Home\")", diff --git a/public/language/it/admin/settings/notifications.json b/public/language/it/admin/settings/notifications.json index 2230b647ac..b8585b913b 100644 --- a/public/language/it/admin/settings/notifications.json +++ b/public/language/it/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Notifica di benvenuto", "welcome-notification-link": "Link a Notifica di benvenuto", "welcome-notification-uid": "Notifica di benvenuto utente (UID)", - "post-queue-notification-uid": "Coda post utente (UID)" + "post-queue-notification-uid": "Coda post utente (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/it/admin/settings/uploads.json b/public/language/it/admin/settings/uploads.json index a2ba8602e9..b01fe2ffc9 100644 --- a/public/language/it/admin/settings/uploads.json +++ b/public/language/it/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Le immagini più grandi di questo valore saranno rifiutate.", "reject-image-height": "Lunghezza Massima Immagine (in pixel)", "reject-image-height-help": "Le immagini più alte di questo valore saranno rifiutate.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Consenti agli utenti di caricare le miniature degli argomenti", + "show-post-uploads-as-thumbnails": "Mostra i post caricati come miniature", "topic-thumb-size": "Dimensione miniatura Argomento", "allowed-file-extensions": "Abilita Estensioni File", "allowed-file-extensions-help": "Inserisci una lista di estensioni separati da virgola quì (es. pdf,xls,doc). Una lista vuota indica che tutte le estensioni sono abilitate.", diff --git a/public/language/it/admin/settings/user.json b/public/language/it/admin/settings/user.json index f226549e58..29fa59691b 100644 --- a/public/language/it/admin/settings/user.json +++ b/public/language/it/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Mostra email ", "show-fullname": "Mostra nome completo", "restrict-chat": "Permetti messaggi in chat solo da utenti che seguo", + "disable-incoming-chats": "Disabilita i messaggi di chat in arrivo", "outgoing-new-tab": "Apri link esterni in una nuova scheda", "topic-search": "Abilita ricerca nella Discussione", "update-url-with-post-index": "Aggiorna l'url con l'indice dei post durante la navigazione delle discussioni", diff --git a/public/language/it/admin/settings/web-crawler.json b/public/language/it/admin/settings/web-crawler.json index 9fa4708e84..853b6f59f2 100644 --- a/public/language/it/admin/settings/web-crawler.json +++ b/public/language/it/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disabilita Feed RSS", "disable-sitemap-xml": "Disabilita Sitemap.xml", "sitemap-topics": "Numero di Discussioni da visualizzare in Mappa Sito", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Cancella Cache Mappa Sito", "view-sitemap": "Visualizza Mappa Sito" } \ No newline at end of file diff --git a/public/language/it/aria.json b/public/language/it/aria.json index 4f891a009f..9b5eda125f 100644 --- a/public/language/it/aria.json +++ b/public/language/it/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Pagina del profilo dell'utente %1", "user-watched-tags": "Tag seguiti dall'utente", "delete-upload-button": "Pulsante annulla caricamento", - "group-page-link-for": "Link alla pagina del gruppo per %1" + "group-page-link-for": "Link alla pagina del gruppo per %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/it/category.json b/public/language/it/category.json index 2bbd753c3f..55682128c4 100644 --- a/public/language/it/category.json +++ b/public/language/it/category.json @@ -1,12 +1,13 @@ { "category": "Categoria", "subcategories": "Sottocategorie", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", - "handle.description": "This category can be followed from the open social web via the handle %1", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", + "handle.description": "Questa categoria può essere seguita dal social web aperto tramite lo pseudonimo %1", "new-topic-button": "Nuova Discussione", "guest-login-post": "Accedi per postare", "no-topics": "Non ci sono discussioni in questa categoria.
Perché non ne posti una?", + "no-followers": "Nessuno su questo sito web sta monitorando o guardando questa categoria. Tieni traccia o guarda questa categoria per iniziare a ricevere aggiornamenti.", "browsing": "navigazione", "no-replies": "Nessuno ha risposto", "no-new-posts": "Nessun nuovo post.", diff --git a/public/language/it/error.json b/public/language/it/error.json index 58a20d87a8..9658973b27 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -3,12 +3,13 @@ "invalid-json": "JSON non valido", "wrong-parameter-type": "Era previsto un valore di tipo %3 per la proprietà '%1', ma invece è stato ricevuto %2", "required-parameters-missing": "I parametri richiesti sono mancanti in questa chiamata API: %1", + "reserved-ip-address": "Le richieste di rete agli intervalli IP riservati non sono consentite.", "not-logged-in": "Non sembra che tu abbia effettuato l'accesso.", "account-locked": "Il tuo account è stato bloccato temporaneamente", "search-requires-login": "La ricerca richiede un account! Si prega di effettuare l'accesso o registrarsi!", "goback": "Premi indietro per tornare alla pagina precedente", "invalid-cid": "ID Categoria non valido", - "invalid-tid": "ID Topic non valido", + "invalid-tid": "ID discussione non valido", "invalid-pid": "ID Post non valido", "invalid-uid": "ID Utente non valido", "invalid-mid": "ID messaggio chat non valido", @@ -67,8 +68,8 @@ "no-chat-room": "La stanza chat non esiste", "no-privileges": "Non hai abbastanza privilegi per questa azione.", "category-disabled": "Categoria disabilitata", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Post cancellato", + "topic-locked": "Discussione bloccata", "post-edit-duration-expired": "Puoi modificare i post solo per %1 secondo(i) dopo la pubblicazione", "post-edit-duration-expired-minutes": "Puoi modificare i post solo per %1 minuto(i) dopo la pubblicazione", "post-edit-duration-expired-minutes-seconds": "Puoi modificare i post solo per %1 secondo(i) %2 secondo(i) dopo la pubblicazione", @@ -144,8 +145,9 @@ "gorup-user-not-invited": "L'utente non è stato invitato a far parte di questo gruppo.", "post-already-deleted": "Questo post è già stato eliminato", "post-already-restored": "Questo post è già stato ripristinato", - "topic-already-deleted": "Questo topic è già stato eliminato", - "topic-already-restored": "Questo Topic è già stato ripristinato", + "topic-already-deleted": "Questa discussione è già stata eliminata", + "topic-already-restored": "Questa discussione è già stata ripristinata", + "topic-already-crossposted": "Questa discussione è già stata pubblicata anche lì.", "cant-purge-main-post": "Non puoi eliminare definitivamente il post principale, per favore elimina invece la discussione", "topic-thumbnails-are-disabled": "Le miniature della Discussione sono disabilitate.", "invalid-file": "File non valido", @@ -154,6 +156,8 @@ "about-me-too-long": "Spiacenti, il testo non può essere più lungo di %1 caratteri.", "cant-chat-with-yourself": "Non puoi chattare con te stesso!", "chat-restricted": "Questo utente ha ristretto i suoi messaggi in chat alle persone che segue. Per poter chattare con te ti deve prima seguire.", + "chat-allow-list-user-already-added": "Questo utente è già nell'elenco dei consenti", + "chat-deny-list-user-already-added": "Questo utente è già nell'elenco dei rifiutati", "chat-user-blocked": "Sei stato bloccato da questo utente.", "chat-disabled": "Il sistema di chat è stato disabilitato", "too-many-messages": "Hai inviato troppi messaggi, aspetta un attimo.", @@ -169,7 +173,7 @@ "cant-add-users-to-chat-room": "Impossibile aggiungere utenti alla stanza chat.", "cant-remove-users-from-chat-room": "Impossibile rimuovere gli utenti dalla stanza chat.", "chat-room-name-too-long": "Nome della stanza chat troppo lungo. I nomi non possono essere più lunghi di %1 caratteri.", - "remote-chat-received-too-long": "You received a chat message from %1, but it was too long and was rejected.", + "remote-chat-received-too-long": "Hai ricevuto un messaggio di chat da %1, ma era troppo lungo ed è stato rifiutato.", "already-voting-for-this-post": "Hai già votato per questo post", "reputation-system-disabled": "Il sistema di reputazione è disabilitato.", "downvoting-disabled": "Votata negativamente è disabilitato", @@ -225,6 +229,7 @@ "no-topics-selected": "Nessuna discussione selezionata!", "cant-move-to-same-topic": "Non puoi spostare il post nella stessa discussione!", "cant-move-topic-to-same-category": "Non si può spostare la discussione nella stessa categoria!", + "cant-move-topic-to-from-remote-categories": "Non puoi spostare discussioni dentro o fuori alle categorie remote; valuta invece la possibilità di pubblicarle in più sezioni.", "cannot-block-self": "Non puoi auto bloccarti!", "cannot-block-privileged": "Impossibile bloccare amministratori o moderatori globali", "cannot-block-guest": "Gli Ospiti non sono in grado di bloccare altri utenti", @@ -234,7 +239,8 @@ "socket-reconnect-failed": "Impossibile raggiungere il server al momento. Clicca qui per riprovare o riprova in un secondo momento", "invalid-plugin-id": "ID plugin non valido", "plugin-not-whitelisted": "Impossibile installare il plug-in & solo i plugin nella whitelist del Gestione Pacchetti di NodeBB possono essere installati tramite ACP", - "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", + "cannot-toggle-system-plugin": "Non puoi attivare/disattivare lo stato di un plugin di sistema.", + "plugin-installation-via-acp-disabled": "L'installazione dei plugin tramite ACP è disabilitata", "plugins-set-in-configuration": "Non è possibile modificare lo stato dei plugin, poiché sono definiti in fase di esecuzione. (config.json, variabili ambientali o argomenti del terminale); modificare invece la configurazione.", "theme-not-set-in-configuration": "Quando si definiscono i plugin attivi nella configurazione, la modifica dei temi richiede l'aggiunta del nuovo tema all'elenco dei plugin attivi prima di aggiornarlo nell'ACP", "topic-event-unrecognized": "Evento discussione '%1' non riconosciuto", @@ -246,17 +252,18 @@ "api.401": "Non è stata trovata una sessione di accesso valida. Per favore, accedi e riprova.", "api.403": "Non sei autorizzato a fare questa chiamata", "api.404": "Chiamata API non valida", + "api.413": "The request payload is too large", "api.426": "HTTPS è necessario per le richieste all'API di scrittura, si prega di inviare nuovamente la richiesta via HTTPS", "api.429": "Hai fatto troppe richieste, riprova più tardi", "api.500": "È stato riscontrato un errore inaspettato durante il tentativo di soddisfare la tua richiesta.", "api.501": "Il percorso che stai cercando di chiamare non è ancora implementato, riprova domani", "api.503": "Il percorso che stai cercando di chiamare non è attualmente disponibile a causa di una configurazione del server", "api.reauth-required": "La risorsa a cui stai cercando di accedere richiede una (ri)autenticazione.", - "activitypub.not-enabled": "Federation is not enabled on this server", + "activitypub.not-enabled": "La federazione non è abilitata su questo server", "activitypub.invalid-id": "Impossibile risolvere l'ID di input, probabilmente perché non è valido.", "activitypub.get-failed": "Impossibile recuperare la risorsa specificata.", "activitypub.pubKey-not-found": "Impossibile risolvere la chiave pubblica, quindi la verifica del payload non può avvenire.", - "activitypub.origin-mismatch": "The received object's origin does not match the sender's origin", - "activitypub.actor-mismatch": "The received activity is being carried out by an actor that is different from expected.", - "activitypub.not-implemented": "The request was denied because it or an aspect of it is not implemented by the recipient server" + "activitypub.origin-mismatch": "L'origine dell'oggetto ricevuto non corrisponde all'origine del mittente.", + "activitypub.actor-mismatch": "L'attività ricevuta viene eseguita da un partecipante diverso da quello previsto.", + "activitypub.not-implemented": "La richiesta è stata rifiutata perché la richiesta o un suo aspetto non è implementato dal server del destinatario." } \ No newline at end of file diff --git a/public/language/it/flags.json b/public/language/it/flags.json index 2f7a2276a7..bea5411308 100644 --- a/public/language/it/flags.json +++ b/public/language/it/flags.json @@ -84,17 +84,17 @@ "modal-reason-offensive": "Offensivo", "modal-reason-other": "Altro (specifica di seguito)", "modal-reason-custom": "Motivo per cui segnali questo contenuto...", - "modal-notify-remote": "Forward this report to %1", + "modal-notify-remote": "Inoltra questo rapporto a %1", "modal-submit": "Invia segnalazione", "modal-submit-success": "Il contenuto è stato segnalato per la moderazione.", - "modal-confirm-rescind": "Rescind Report?", + "modal-confirm-rescind": "Annullare il rapporto?", "bulk-actions": "Azioni in blocco", "bulk-resolve": "Risolvi segnalazione(i)", - "confirm-purge": "Are you sure you want to permanently delete these flags?", - "purge-cancelled": "Flag Purge Cancelled", - "bulk-purge": "Purge Flag(s)", + "confirm-purge": "Sei sicuro di voler eliminare definitivamente queste segnalazioni?", + "purge-cancelled": "Elimina definitivamente delle segnalazioni eliminate", + "bulk-purge": "Elimina definitivamente segnalazione(i)", "bulk-success": "%1 segnalazioni aggiornate", "flagged-timeago": "Segnalato ", "auto-flagged": "[Contrassegnato automaticamente] Ha ricevuto %1 voti negativi." diff --git a/public/language/it/global.json b/public/language/it/global.json index eebede0702..69d766ee34 100644 --- a/public/language/it/global.json +++ b/public/language/it/global.json @@ -68,6 +68,7 @@ "users": "Utenti", "topics": "Discussioni", "posts": "Post", + "crossposts": "Cross-posts", "x-posts": "%1 post", "x-topics": "%1 discussioni", "x-reputation": "%1 reputazione", @@ -82,6 +83,7 @@ "downvoted": "Votato negativamente", "views": "Visualizzazioni", "posters": "Autori", + "watching": "Seguito", "reputation": "Reputazione", "lastpost": "Ultimo post", "firstpost": "Primo post", diff --git a/public/language/it/groups.json b/public/language/it/groups.json index 184e394d06..aa7e3ed339 100644 --- a/public/language/it/groups.json +++ b/public/language/it/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Tutti i gruppi", "groups": "Gruppi", "members": "Membri", + "x-members": "%1 member(s)", "view-group": "Vedi Gruppo", "owner": "Proprietario del Gruppo", "new-group": "Crea Nuovo Gruppo", diff --git a/public/language/it/modules.json b/public/language/it/modules.json index 2646ad5d5f..11b35b6989 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Aggiungi utente", "chat.notification-settings": "Impostazioni di notifica", "chat.default-notification-setting": "Impostazioni di notifica predefinite", + "chat.join-leave-messages": "Messaggi di iscrizione/uscita", "chat.notification-setting-room-default": "Stanza predefinita", "chat.notification-setting-none": "Nessuna notifica", "chat.notification-setting-at-mention-only": "@solo menzione", @@ -81,7 +82,7 @@ "composer.hide-preview": "Nascondi Anteprima", "composer.help": "Aiuto", "composer.user-said-in": "%1 ha detto in %2:", - "composer.user-said": "%1 ha detto:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Sei sicuro di voler scartare questo post?", "composer.submit-and-lock": "Invia e Blocca", "composer.toggle-dropdown": "Mostra/Nascondi menu a discesa", diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json index 2a1288e834..c639316df3 100644 --- a/public/language/it/notifications.json +++ b/public/language/it/notifications.json @@ -22,7 +22,7 @@ "upvote": "Voti positivi", "awards": "Premi", "new-flags": "Nuove segnalazioni", - "my-flags": "Segnalazioni assegnate a me", + "my-flags": "My Flags", "bans": "Espulsioni", "new-message-from": "Nuovo messaggio da %1", "new-messages-from": "%1 nuovi messaggi da %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 e %2 hanno scritto in %4", "user-posted-in-public-room-triple": "%1, %2 e %3 hanno scritto in %5", "user-posted-in-public-room-multiple": "%1, %2 e %3 altri hanno scritto in %5", - "upvoted-your-post-in": "%1 ha votato positivamente il tuo post in %2.", - "upvoted-your-post-in-dual": "%1 e %2 hanno apprezzato il tuo post in %3.", - "upvoted-your-post-in-triple": "%1, %2 e %3 hanno votato positivamente il tuo post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 e %3 altri hanno votato positivamente il tuo post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 ha spostato il tuo post su %2", "moved-your-topic": "%1 è stato spostato %2", - "user-flagged-post-in": "%1 ha segnalato un post in %2", - "user-flagged-post-in-dual": "%1 e %2 hanno segnalato un post in %3", - "user-flagged-post-in-triple": "%1, %2 e %3 hanno segnalato un post in %4", - "user-flagged-post-in-multiple": "%1, %2 e %3 altri hanno segnalato un post in %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 ha segnalato un utente (%2)", "user-flagged-user-dual": "%1 e %2 hanno segnalato un utente (%3)", "user-flagged-user-triple": "%1, %2 e %3 hanno segnalato un profilo utente (%4)", "user-flagged-user-multiple": "%1, %2 e %3 altri hanno segnalato un profilo utente (%4)", - "user-posted-to": "%1 ha postato una risposta a: %2", - "user-posted-to-dual": "%1 e %2 hanno postato una risposta su: %3", - "user-posted-to-triple": "%1, %2 e %3 hanno postato risposte a: %4", - "user-posted-to-multiple": "%1, %2 e %3 altri hanno postato risposte a: %4", - "user-posted-topic": "%1 ha postato una nuova discussione: %2", - "user-edited-post": "%1 ha modificato un post in %2", - "user-posted-topic-with-tag": "%1 ha postato %2 (taggato %3)", - "user-posted-topic-with-tag-dual": "%1 ha postato %2 (taggato %3 e %4)", - "user-posted-topic-with-tag-triple": "%1 ha postato %2 (taggato %3, %4, e %5)", - "user-posted-topic-with-tag-multiple": "%1 ha postato %2 (taggato %3)", - "user-posted-topic-in-category": "%1 ha postato una nuova discussione in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 ha iniziato a seguirti.", "user-started-following-you-dual": "%1 e %2 hanno iniziato a seguirti.", "user-started-following-you-triple": "%1, %2 e %3 hanno iniziato a seguirti.", @@ -71,11 +71,11 @@ "users-csv-exported": "Utenti esportati in CSV, clicca per scaricare", "post-queue-accepted": "Il tuo post in coda è stato accettato. Clicca qui per vedere il tuo post.", "post-queue-rejected": "Il tuo post in coda è stato rifiutato.", - "post-queue-notify": "Il post in coda ha ricevuto una notifica:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confermata", "email-confirmed-message": "Grazie per aver validato la tua email. Il tuo account è ora completamente attivato.", "email-confirm-error-message": "C'è stato un problema nella validazione del tuo indirizzo email. Potrebbe essere il codice non valido o scaduto.", - "email-confirm-error-message-already-validated": "Il tuo indirizzo email è già stato convalidato.", "email-confirm-sent": "Email di conferma inviata.", "none": "Nessuna", "notification-only": "Solo Notifiche", @@ -99,8 +99,8 @@ "notificationType-new-post-flag": "Quando un post viene segnalato", "notificationType-new-user-flag": "Quando un utente viene segnalato", "notificationType-new-reward": "Quando guadagni una nuova ricompensa", - "activitypub.announce": "%1 shared your post in %2 to their followers.", - "activitypub.announce-dual": "%1 and %2 shared your post in %3 to their followers.", - "activitypub.announce-triple": "%1, %2 and %3 shared your post in %4 to their followers.", - "activitypub.announce-multiple": "%1, %2 and %3 others shared your post in %4 to their followers." + "activitypub.announce": "%1 ha condiviso il tuo post in %2 agli utenti che lo seguono.", + "activitypub.announce-dual": "%1 e %2 hanno condiviso il tuo post in %3 agli utenti che li seguono.", + "activitypub.announce-triple": "%1, %2 e %3 hanno condiviso il tuo post in %4 agli utenti che li seguono.", + "activitypub.announce-multiple": "%1, %2 e %3 hanno condiviso il tuo post in %4 agli utenti che li seguono." } \ No newline at end of file diff --git a/public/language/it/recent.json b/public/language/it/recent.json index 2ff5b581b5..ef34db1994 100644 --- a/public/language/it/recent.json +++ b/public/language/it/recent.json @@ -9,5 +9,5 @@ "no-popular-topics": "Non ci sono discussioni popolari.", "load-new-posts": "Carica nuovi post", "uncategorized.title": "Tutte le discussioni note", - "uncategorized.intro": "This page shows a chronological listing of every topic that this forum has received.
The views and opinions expressed in the topics below are not moderated and may not represent the views and opinions of this website." + "uncategorized.intro": "Questa pagina mostra un elenco cronologico di tutte le discussioni che questo forum ha ricevuto.
I punti di vista e le opinioni espresse nelle seguenti discussioni non sono moderate e potrebbero non rappresentare i punti di vista e le opinioni di questo sito web." } \ No newline at end of file diff --git a/public/language/it/social.json b/public/language/it/social.json index 4fb4c6bd3e..6868cad941 100644 --- a/public/language/it/social.json +++ b/public/language/it/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Accedi con Facebook", "continue-with-facebook": "Continua con Facebook", "sign-in-with-linkedin": "Accedi con LinkedIn", - "sign-up-with-linkedin": "Registrati con LinkedIn" + "sign-up-with-linkedin": "Registrati con LinkedIn", + "sign-in-with-wordpress": "Accedi con WordPress", + "sign-up-with-wordpress": "Iscriviti a WordPress" } \ No newline at end of file diff --git a/public/language/it/themes/harmony.json b/public/language/it/themes/harmony.json index 8ce6b71e02..1875f5fdb6 100644 --- a/public/language/it/themes/harmony.json +++ b/public/language/it/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Tema Armonia", "skins": "Skin", + "light": "Light", + "dark": "Dark", "collapse": "Collassa", "expand": "Espandi", "sidebar-toggle": "Attiva/disattiva barra laterale", @@ -13,11 +15,11 @@ "settings.mobileTopicTeasers": "Mostra le anteprime delle discussioni su mobile", "settings.stickyToolbar": "Barra degli strumenti adesiva", "settings.stickyToolbar.help": "La barra degli strumenti nelle pagine delle discussioni e delle categorie si attacca alla parte superiore della pagina.", - "settings.topicSidebarTools": "Topic sidebar tools", - "settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop", - "settings.autohideBottombar": "Auto hide mobile navigation bar", - "settings.autohideBottombar.help": "The mobile bar will be hidden when the page is scrolled down", - "settings.topMobilebar": "Move the mobile navigation bar to the top", + "settings.topicSidebarTools": "Strumenti barra laterale della discussione", + "settings.topicSidebarTools.help": "Questa opzione sposta gli strumenti della discussione nella barra laterale su desktop.", + "settings.autohideBottombar": "Nascondi automaticamente la barra di navigazione mobile", + "settings.autohideBottombar.help": "La barra mobile sarà nascosta quando la pagina viene fatta scorrere verso il basso.", + "settings.topMobilebar": "Sposta la barra di navigazione mobile in alto", "settings.openSidebars": "Apri le barre laterali", "settings.chatModals": "Abilita i modali della chat" } \ No newline at end of file diff --git a/public/language/it/topic.json b/public/language/it/topic.json index c6a91701b2..0ea72b5f05 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -16,7 +16,7 @@ "one-reply-to-this-post": "1 Risposta", "last-reply-time": "Ultima Risposta", "reply-options": "Opzioni di risposta", - "reply-as-topic": "Topic risposta", + "reply-as-topic": "Risposta alla discussione", "guest-login-reply": "Effettua l'accesso per rispondere", "login-to-view": "Accedi per visualizzare", "edit": "Modifica", @@ -61,14 +61,16 @@ "user-restored-topic-on": "%1 ha ripristinato questa discussione su %2", "user-moved-topic-from-ago": "%1 ha spostato questa discussione da %2 %3", "user-moved-topic-from-on": "%1 ha spostato questa discussione da %2 su %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "user-shared-topic-ago": "%1 ha condiviso questa discussione %2", + "user-shared-topic-on": "%1 ha condiviso questa discussione su %2", "user-queued-post-ago": "%1 post in coda per l'approvazione %3", "user-queued-post-on": "%1 post in coda per l'approvazione su %3", "user-referenced-topic-ago": "%1 ha fatto riferimento a questa discussione %3", "user-referenced-topic-on": "%1 ha fatto riferimento a questa discussione su %3", "user-forked-topic-ago": "%1 ha biforcato questa discussione %3", "user-forked-topic-on": "%1 ha biforcato questa discussione su %3", + "user-crossposted-topic-ago": "%1 ha pubblicato questa discussione anche in %2 %3", + "user-crossposted-topic-on": "%1 ha pubblicato questa discussione anche su %2 il %3", "bookmark-instructions": "Clicca qui per tornare all'ultimo post letto in questa discussione.", "flag-post": "Segnala questo post", "flag-user": "Segnala questo utente", @@ -103,6 +105,7 @@ "thread-tools.lock": "Blocca Discussione", "thread-tools.unlock": "Sblocca Discussione", "thread-tools.move": "Sposta Discussione", + "thread-tools.crosspost": "Discussione postata più volte", "thread-tools.move-posts": "Sposta Post", "thread-tools.move-all": "Sposta Tutto", "thread-tools.change-owner": "Cambia proprietario", @@ -132,15 +135,17 @@ "pin-modal-help": "Facoltativamente, è possibile impostare una data di scadenza per le discussioni fissate qui. In alternativa, è possibile lasciare vuoto questo campo per mantenere la discussione fissata fino a quando non viene liberata manualmente.", "load-categories": "Caricamento Categorie", "confirm-move": "Sposta", + "confirm-crosspost": "Post multiplo", "confirm-fork": "Dividi", "bookmark": "Favorito", "bookmarks": "Segnalibri", "bookmarks.has-no-bookmarks": "Non hai ancora aggiunto alcun post ai segnalibri.", "copy-permalink": "Copia link permanente", - "go-to-original": "View Original Post", + "go-to-original": "Visualizza il post originale", "loading-more-posts": "Caricamento altri post", "move-topic": "Sposta Discussione", "move-topics": "Sposta Discussioni", + "crosspost-topic": "Discussione postata più volte", "move-post": "Sposta Post", "post-moved": "Post spostato!", "fork-topic": "Dividi Discussione", @@ -151,7 +156,7 @@ "x-posts-selected": "%1 post selezionato(i)", "x-posts-will-be-moved-to-y": "%1 post sarà(anno) spostato(i) in \"%2\"", "fork-pid-count": "%1 post selezionati", - "fork-success": "Topic Diviso con successo ! Clicca qui per andare al Topic Diviso.", + "fork-success": "Discussione divisa con successo ! Clicca qui per andare alla discussione divisa.", "delete-posts-instruction": "Clicca sui post che vuoi eliminare/eliminare definitivamente", "merge-topics-instruction": "Clicca sulle discussioni che vuoi unire o cercare", "merge-topic-list-title": "Elenco delle discussioni da unire", @@ -163,6 +168,9 @@ "move-topic-instruction": "Seleziona la categoria di destinazione e fai clic su sposta", "change-owner-instruction": "Clicca sui post che vuoi assegnare ad un altro utente", "manage-editors-instruction": "Gestisci gli utenti che possono modificare questo post qui sotto.", + "crossposts.instructions": "Seleziona una o più categorie in cui pubblicare il post. La discussione(i) sarà accessibile dalla categoria originale e da tutte le categorie in cui è stato pubblicato il post.", + "crossposts.listing": "Questa discussione è stata pubblicata anche nelle seguenti categorie locali:", + "crossposts.none": "Questa discussione non è stata pubblicata in altre categorie.", "composer.title-placeholder": "Inserisci qui il titolo della discussione...", "composer.handle-placeholder": "Inserisci qui il tuo nome/pseudonimo", "composer.hide": "Nascondi", @@ -174,6 +182,7 @@ "composer.replying-to": "Rispondendo a %1", "composer.new-topic": "Nuova Discussione", "composer.editing-in": "Modifica post in %1", + "composer.untitled-topic": "Discussione senza titolo", "composer.uploading": "caricamento...", "composer.thumb-url-label": "Incolla l'URL della miniatura per la discussione", "composer.thumb-title": "Aggiungi una miniatura a questa discussione", @@ -194,7 +203,7 @@ "most-posts": "Più Post", "most-views": "Più visualizzazioni", "stale.title": "Preferisci creare una nuova discussione?", - "stale.warning": "Il topic al quale stai rispondendo è abbastanza vecchio. Vorresti piuttosto creare un nuovo topic in riferimento a questo nella tua risposta?", + "stale.warning": "La discussione alla quale stai rispondendo è piuttosto vecchia. Vorresti invece creare una nuova discussione e fare riferimento a questa nella tua risposta?", "stale.create": "Crea una nuova discussione", "stale.reply-anyway": "Rispondi comunque a questa discussione", "link-back": "Re: [%1](%2)", @@ -223,6 +232,9 @@ "post-tools": "Strumenti post", "unread-posts-link": "Link ai post non letti", "thumb-image": "Immagine anteprima della discussione", - "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers": "Condivisioni", + "announcers-x": "Condivisioni (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/it/user.json b/public/language/it/user.json index df7d8aab78..b5ee25419f 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -59,11 +59,11 @@ "chat": "Chat", "chat-with": "Continua la chat con %1", "new-chat-with": "Inizia una nuova chat con %1", - "view-remote": "View Original", + "view-remote": "Visualizza l'originale", "flag-profile": "Segnala Profilo", "profile-flagged": "Già segnalato", "follow": "Segui", - "unfollow": "Smetti di seguire", + "unfollow": "Non seguire", "cancel-follow": "Annulla richiesta di segui", "more": "Altro", "profile-update-success": "Profilo aggiornato correttamente!", @@ -105,6 +105,10 @@ "show-email": "Mostra la mia Email", "show-fullname": "Mostra il mio nome completo", "restrict-chats": "Abilita messaggi in chat soltanto dagli utenti che seguo", + "disable-incoming-chats": "Disabilita i messaggi di chat in arrivo ", + "chat-allow-list": "Consenti i messaggi di chat dai seguenti utenti", + "chat-deny-list": "Nega i messaggi di chat dai seguenti utenti", + "chat-list-add-user": "Aggiungi utente", "digest-label": "Iscriviti al Riepilogo", "digest-description": "Abbonati agli aggiornamenti via email di questo forum (nuove notifiche e discussioni) secondo una pianificazione impostata", "digest-off": "Spento", diff --git a/public/language/it/world.json b/public/language/it/world.json index 3753335278..335b065b51 100644 --- a/public/language/it/world.json +++ b/public/language/it/world.json @@ -1,18 +1,25 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Mondo", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "Aiuto", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", - "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.title": "Cos'è questa pagina?", + "help.intro": "Benvenuto nel tuo angolo del fediverso.", + "help.fediverse": "Il \"fediverso\" è una rete di applicazioni e siti web interconnessi che comunicano tra loro e i cui utenti possono vedersi. Questo forum è federato e può interagire con quel social web (o \"fediverso\"). Questa pagina è il tuo angolo di fediverso. Si tratta esclusivamente di discussioni create da — e condivisi da — utenti che tu segui.", + "help.build": "Potrebbero non esserci molte discussioni da cui iniziare, è normale. Inizierai a vedere più contenuti qui col tempo, quando inizierai a seguire altri utenti.", + "help.federating": "Allo stesso modo, se gli utenti esterni a questo forum iniziano a seguirti, i tuoi post inizieranno ad apparire anche su quelle app e quei siti web.", + "help.next-generation": "Questa è la prossima generazione di social media, inizia a contribuire oggi!", - "onboard.title": "Your window to the fediverse...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.title": "La tua finestra sul fediverso...", + "onboard.what": "Questa è la tua categoria personalizzata composta solo da contenuti trovati al di fuori di questo forum. La presenza di qualcosa in questa pagina dipende dal fatto che tu la segua o che il post sia stato condiviso da qualcuno che segui.", + "onboard.why": "Ci sono molte cose che accadono al di fuori di questo forum e non tutte sono rilevanti per i tuoi interessi. Ecco perché seguire le persone è il modo migliore per segnalare che vuoi vedere di più da qualcuno.", + "onboard.how": "Nel frattempo, puoi cliccare sui pulsanti di scelta rapida in alto per vedere cos'altro conosce questo forum e iniziare a scoprire nuovi contenuti!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/ja/admin/advanced/cache.json b/public/language/ja/admin/advanced/cache.json index df01c34cf4..a340f89ba1 100644 --- a/public/language/ja/admin/advanced/cache.json +++ b/public/language/ja/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "投稿キャッシュ", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% がフル", "post-cache-size": "投稿キャッシュのサイズ", "items-in-cache": "キャッシュ内のアイテム" diff --git a/public/language/ja/admin/dashboard.json b/public/language/ja/admin/dashboard.json index 60e2fad225..f9354ce880 100644 --- a/public/language/ja/admin/dashboard.json +++ b/public/language/ja/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "ページビュー登録済み", "graphs.page-views-guest": "ページビューゲスト", "graphs.page-views-bot": "ページビューBot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "ユニークな訪問者", "graphs.registered-users": "登録したユーザー", "graphs.guest-users": "Guest Users", diff --git a/public/language/ja/admin/development/info.json b/public/language/ja/admin/development/info.json index 857c419a66..619f886e7b 100644 --- a/public/language/ja/admin/development/info.json +++ b/public/language/ja/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "オンライン", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json index 0044cf2f6d..560eb4b239 100644 --- a/public/language/ja/admin/manage/categories.json +++ b/public/language/ja/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "カテゴリ設定", "edit-category": "Edit Category", "privileges": "特権", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "カテゴリ名", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "カテゴリの説明", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "背景色", "text-color": "テキストカラー", "bg-image-size": "背景画像サイズ", @@ -103,6 +107,11 @@ "alert.create-success": "カテゴリが正常に作成されました!", "alert.none-active": "アクティブなカテゴリがありません。", "alert.create": "カテゴリを作成", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

本当にこのカテゴリ \"%1\"を切り離しますか?

警告!このカテゴリのすべてのスレッドと投稿が削除されます。

カテゴリをパージすると、すべてのスレッドと投稿が削除され、データベースからカテゴリが削除されます。一時的にカテゴリを削除する場合は、代わりにカテゴリを無効にすることをおすすめします。

", "alert.purge-success": "カテゴリが切り離されました!", "alert.copy-success": "設定をコピーしました。", diff --git a/public/language/ja/admin/manage/custom-reasons.json b/public/language/ja/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ja/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ja/admin/manage/privileges.json b/public/language/ja/admin/manage/privileges.json index 3b5b247d04..0f4614cb9a 100644 --- a/public/language/ja/admin/manage/privileges.json +++ b/public/language/ja/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "トピックスにアクセス", "create-topics": "トピックスを作成", "reply-to-topics": "トピックスに返信", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/ja/admin/manage/users.json b/public/language/ja/admin/manage/users.json index 329319c0c8..b862b3ac02 100644 --- a/public/language/ja/admin/manage/users.json +++ b/public/language/ja/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "CSVでダウンロード", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "理由(任意)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "時間", "temp-ban.days": "日", "temp-ban.explanation": "禁止期間の長さを入力します。0にすると永久に禁止と解釈されますのでご注意ください。", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "あなたは本当にこのユーザーを永久に禁止しますか?", "alerts.confirm-ban-multi": "あなたは本当にこれらのユーザーを恒久的に禁止しますか?", diff --git a/public/language/ja/admin/settings/activitypub.json b/public/language/ja/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/ja/admin/settings/activitypub.json +++ b/public/language/ja/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/ja/admin/settings/chat.json b/public/language/ja/admin/settings/chat.json index e0cd6e081c..bd74261bd1 100644 --- a/public/language/ja/admin/settings/chat.json +++ b/public/language/ja/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "チャットルームの最大ユーザー数", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/ja/admin/settings/email.json b/public/language/ja/admin/settings/email.json index 0dede9d9db..5e654cf334 100644 --- a/public/language/ja/admin/settings/email.json +++ b/public/language/ja/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "電子メールテンプレートの編集", "template.select": "電子メールテンプレートを選択", "template.revert": "オリジナルに戻す", + "test-smtp-settings": "Test SMTP Settings", "testing": "Eメールテスト", + "testing.success": "Test Email Sent.", "testing.select": "電子メールテンプレートを選択", "testing.send": "テスト電子メールを送信する", - "testing.send-help": "テスト電子メールは、現在ログインしているユーザーの電子メールアドレスに送信されます。", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "ダイジェストアワー", diff --git a/public/language/ja/admin/settings/notifications.json b/public/language/ja/admin/settings/notifications.json index 9be8707a2b..5f195def57 100644 --- a/public/language/ja/admin/settings/notifications.json +++ b/public/language/ja/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "ウェルカム通知", "welcome-notification-link": "ウェルカム通知のリンク", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/ja/admin/settings/uploads.json b/public/language/ja/admin/settings/uploads.json index 84b216ba93..402f8a86c7 100644 --- a/public/language/ja/admin/settings/uploads.json +++ b/public/language/ja/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "ユーザーがスレッドのサムネイルをアップロードできるようにする", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "スレッドのサムネイルの大きさ", "allowed-file-extensions": "ファイル拡張子が有効になりました。", "allowed-file-extensions-help": "ここにファイル拡張子のカンマ区切りリストを入力します(例: pdf,xls,doc )。空のリストは、すべての拡張が許可されていることを意味します。", diff --git a/public/language/ja/admin/settings/user.json b/public/language/ja/admin/settings/user.json index 97afb95e20..f79b46ffc3 100644 --- a/public/language/ja/admin/settings/user.json +++ b/public/language/ja/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "メールを表示", "show-fullname": "フルネームで表示", "restrict-chat": "フォローしたユーザーからのチャットメッセージだけを許可する", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "外部リンクを新しいタブで開く", "topic-search": "スレッド内検索を有効にする", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/ja/admin/settings/web-crawler.json b/public/language/ja/admin/settings/web-crawler.json index 07f2dc0afb..b87a3d3af4 100644 --- a/public/language/ja/admin/settings/web-crawler.json +++ b/public/language/ja/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "RSSフィードを無効にする", "disable-sitemap-xml": "Sitemap.xmlを無効にする", "sitemap-topics": "サイトマップに表示するスレッドの数", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "サイトマップのキャッシュをクリア", "view-sitemap": "サイトマップを表示" } \ No newline at end of file diff --git a/public/language/ja/aria.json b/public/language/ja/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/ja/aria.json +++ b/public/language/ja/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/ja/category.json b/public/language/ja/category.json index c4d5242b86..80ac25539f 100644 --- a/public/language/ja/category.json +++ b/public/language/ja/category.json @@ -1,12 +1,13 @@ { "category": "カテゴリ", "subcategories": "サブカテゴリ", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "新規スレッド", "guest-login-post": "投稿するにはログインしてください", "no-topics": "まだスレッドはありません
最初のスレッドを書いてみませんか?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "閲覧中", "no-replies": "返事はまだありません", "no-new-posts": "新しい投稿はありません", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index f2530ed286..8d5732b34b 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -3,6 +3,7 @@ "invalid-json": "無効なJSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "ログインしていません。", "account-locked": "あなたのアカウントは一時的にロックされています", "search-requires-login": "検索するにはアカウントが必要です - ログインするかアカウントを作成してください。", @@ -146,6 +147,7 @@ "post-already-restored": "この投稿が既に復元されました", "topic-already-deleted": "このスレッドは既に削除されました", "topic-already-restored": "このスレッドは既に復元されました", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "メインの投稿を削除することはできません。代わりにスレッドを削除してください", "topic-thumbnails-are-disabled": "スレッドのサムネイルが無効された", "invalid-file": "無効なファイル", @@ -154,6 +156,8 @@ "about-me-too-long": "申し訳ありませんが、あなたの私についての項目が%1より長くすることができません。", "cant-chat-with-yourself": "自分にチャットすることはできません!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "スレッドが選択されていません!!", "cant-move-to-same-topic": "同じスレッドに投稿を移動することはできません!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "自分をブロックすることは出来ません!", "cannot-block-privileged": "管理者またはグローバルモデレーターはブロックできません", "cannot-block-guest": "ゲストは他のユーザーをブロックできません", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/ja/global.json b/public/language/ja/global.json index d361377631..68d0b4d88d 100644 --- a/public/language/ja/global.json +++ b/public/language/ja/global.json @@ -68,6 +68,7 @@ "users": "ユーザー", "topics": "スレッド", "posts": "投稿", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "低評価", "views": "閲覧数", "posters": "Posters", + "watching": "Watching", "reputation": "評価", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/ja/groups.json b/public/language/ja/groups.json index 8cd85aa660..abb2a03f43 100644 --- a/public/language/ja/groups.json +++ b/public/language/ja/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "グループ", "members": "Members", + "x-members": "%1 member(s)", "view-group": "グループを閲覧", "owner": "グループ管理人", "new-group": "新しいグループを作成", diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json index e177131774..39a6c57a71 100644 --- a/public/language/ja/modules.json +++ b/public/language/ja/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "プレビュー非表示", "composer.help": "Help", "composer.user-said-in": "%2で%1が発言 :", - "composer.user-said": "%1 の発言:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "本当にこの投稿を破棄しても構いませんか?", "composer.submit-and-lock": "送信してロック", "composer.toggle-dropdown": "ドロップダウンの表示切り替え", diff --git a/public/language/ja/notifications.json b/public/language/ja/notifications.json index bc98168514..6504e2b38b 100644 --- a/public/language/ja/notifications.json +++ b/public/language/ja/notifications.json @@ -22,7 +22,7 @@ "upvote": "高評価", "awards": "Awards", "new-flags": "新しいフラグ", - "my-flags": "あなたにフラグがつきました", + "my-flags": "My Flags", "bans": "Ban", "new-message-from": "%1からの新しいメッセージ", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1さんが%2に高評価をつけました。", - "upvoted-your-post-in-dual": "%1さんと%2さんが%3に高評価をつけました。", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1さんはユーザープロフィールにフラグを付けました(%2)", "user-flagged-user-dual": "%1さんと%2さんは、ユーザープロフィール(%3)にフラグをつけました。", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1さんは %2に返信しました。", - "user-posted-to-dual": "%1%2 は、返信しました: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 が新しいスレッドを投稿しました。: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1があなたをフォローしました。", "user-started-following-you-dual": "%1%2 があなたをフォローしました。", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Eメールが確認されました", "email-confirmed-message": "メールアドレス検証をして頂き、ありがとうございます。あなたのアカウントは完全にアクティブになりました。", "email-confirm-error-message": "あなたのEメールアドレス検証に問題があります。コードが無効か、期限切れです。", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "確認メールが送信されました。", "none": "なし", "notification-only": "通知のみ", diff --git a/public/language/ja/social.json b/public/language/ja/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/ja/social.json +++ b/public/language/ja/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/ja/themes/harmony.json b/public/language/ja/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/ja/themes/harmony.json +++ b/public/language/ja/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index b05507a7f1..fb8fdca9b1 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "ここをクリックすると、このスレッドの最後に読んでいた投稿へ移動します。", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "スレッドをロック", "thread-tools.unlock": "スレッドをアンロック", "thread-tools.move": "スレッドを移動", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "投稿を移動", "thread-tools.move-all": "すべてを移動", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "板をローディング中...", "confirm-move": "移動", + "confirm-crosspost": "Cross-post", "confirm-fork": "フォーク", "bookmark": "ブックマーク", "bookmarks": "ブックマーク", @@ -141,6 +145,7 @@ "loading-more-posts": "もっと見る", "move-topic": "スレッドを移動", "move-topics": "スレッドを移動する", + "crosspost-topic": "Cross-post Topic", "move-post": "投稿を移動", "post-moved": "投稿を移動しました!", "fork-topic": "スレッドをフォーク", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "スレッドのタイトルを入力...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "%1へ返答中", "composer.new-topic": "新規スレッド", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "アップロード中...", "composer.thumb-url-label": "スレッドのサムネイルのURLを入力して", "composer.thumb-title": "スレッドにサムネイルを追加", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/ja/user.json b/public/language/ja/user.json index 603effd90c..d704a99b0c 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -105,6 +105,10 @@ "show-email": "メールアドレスを表示", "show-fullname": "フルネームで表示", "restrict-chats": "フォローしたユーザーからのチャットメッセージだけを許可する", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "お知らせを購読する", "digest-description": "この掲示板のアップデートを受信する", "digest-off": "オフ", diff --git a/public/language/ja/world.json b/public/language/ja/world.json index 3753335278..e6694bf507 100644 --- a/public/language/ja/world.json +++ b/public/language/ja/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/ko/admin/advanced/cache.json b/public/language/ko/admin/advanced/cache.json index 96fda18d6d..9028a95f00 100644 --- a/public/language/ko/admin/advanced/cache.json +++ b/public/language/ko/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "캐시", - "post-cache": "포스트 캐시", - "group-cache": "그룹 캐시", - "local-cache": "로컬 캐시", - "object-cache": "객체 캐시", "percent-full": "%1% 사용됨", "post-cache-size": "포스트 캐시 크기", "items-in-cache": "캐시에 있는 항목" diff --git a/public/language/ko/admin/dashboard.json b/public/language/ko/admin/dashboard.json index b0dd16eb86..8828269697 100644 --- a/public/language/ko/admin/dashboard.json +++ b/public/language/ko/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "등록된 사용자 페이지 뷰", "graphs.page-views-guest": "비회원 페이지 뷰", "graphs.page-views-bot": "봇 페이지 뷰", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "고유 방문자", "graphs.registered-users": "등록된 사용자", "graphs.guest-users": "비회원 사용자", diff --git a/public/language/ko/admin/development/info.json b/public/language/ko/admin/development/info.json index 08cb8a739b..db78a02ef8 100644 --- a/public/language/ko/admin/development/info.json +++ b/public/language/ko/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "Node.js", "online": "온라인", "git": "Git", - "process-memory": "프로세스 메모리", + "process-memory": "rss/heap used", "system-memory": "시스템 메모리", "used-memory-process": "프로세스 사용 메모리", "used-memory-os": "시스템 사용 메모리", diff --git a/public/language/ko/admin/manage/categories.json b/public/language/ko/admin/manage/categories.json index 2afb2d7a89..8eb6c15d16 100644 --- a/public/language/ko/admin/manage/categories.json +++ b/public/language/ko/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "카테고리 관리", "add-category": "카테고리 추가", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "이동...", "settings": "카테고리 설정", "edit-category": "카테고리 수정", "privileges": "권한", "back-to-categories": "카테고리로 돌아가기", + "id": "Category ID", "name": "카테고리 이름", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "카테고리 설명", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "배경 색상", "text-color": "텍스트 색상", "bg-image-size": "배경 이미지 크기", @@ -103,6 +107,11 @@ "alert.create-success": "카테고리를 성공적으로 생성했습니다!", "alert.none-active": "활성화된 카테고리가 없습니다.", "alert.create": "카테고리 만들기", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

정말로 이 카테고리 \"%1\"를 정리하시겠습니까?

경고! 이 카테고리의 모든 토픽과 게시물을 정리합니다!

카테고리를 정리하면 모든 토픽과 게시물이 제거되며 데이터베이스에서 카테고리가 삭제됩니다. 카테고리를 일시적으로 제거하려면 카테고리를 대신 \"비활성화\"해야 합니다.

", "alert.purge-success": "카테고리를 정리했습니다!", "alert.copy-success": "설정을 복사했습니다!", diff --git a/public/language/ko/admin/manage/custom-reasons.json b/public/language/ko/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ko/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ko/admin/manage/privileges.json b/public/language/ko/admin/manage/privileges.json index e7dd842790..8e16429b9c 100644 --- a/public/language/ko/admin/manage/privileges.json +++ b/public/language/ko/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "토픽 접근", "create-topics": "토픽 생성", "reply-to-topics": "토픽에 답장하기", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "토픽 예약", "tag-topics": "토픽 태깅", "edit-posts": "게시물 편집", diff --git a/public/language/ko/admin/manage/users.json b/public/language/ko/admin/manage/users.json index 2b5203603b..44813fe71c 100644 --- a/public/language/ko/admin/manage/users.json +++ b/public/language/ko/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "사용자콘텐츠 삭제", "download-csv": "CSV 다운로드", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "그룹 관리", "set-reputation": "평판 설정", "add-group": "그룹 추가", @@ -77,9 +78,11 @@ "temp-ban.length": "기간", "temp-ban.reason": "사유 (선택 사항)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "시간", "temp-ban.days": "일", "temp-ban.explanation": "차단 기간을 입력하세요. 0을 입력하면 영구적인 차단으로 간주됩니다.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "이 사용자를 영구적으로 차단하시겠습니까?", "alerts.confirm-ban-multi": "이 사용자들을 영구적으로 차단하시겠습니까?", diff --git a/public/language/ko/admin/settings/activitypub.json b/public/language/ko/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/ko/admin/settings/activitypub.json +++ b/public/language/ko/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/ko/admin/settings/chat.json b/public/language/ko/admin/settings/chat.json index 2671da8544..9b4f72509d 100644 --- a/public/language/ko/admin/settings/chat.json +++ b/public/language/ko/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "채팅 방 이름의 최대 길이", "max-room-size": "채팅 방의 최대 사용자 수", "delay": "채팅 메시지 간의 시간 (밀리초)", - "notification-delay": "채팅 메시지에 대한 알림 지연", - "notification-delay-help": "이 시간 동안에 추가 메시지는 모아져서 사용자는 지연 기간 당 한 번씩 알림을 받습니다. 지연을 비활성화하려면 0으로 설정하세요.", "restrictions.seconds-edit-after": "채팅 메시지를 편집할 수 있는 시간(초)", "restrictions.seconds-delete-after": "채팅 메시지를 삭제할 수 있는 시간(초)." } \ No newline at end of file diff --git a/public/language/ko/admin/settings/email.json b/public/language/ko/admin/settings/email.json index 70d6a1de31..91b22b3113 100644 --- a/public/language/ko/admin/settings/email.json +++ b/public/language/ko/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "연결을 풀링하면 모든 이메일마다 새로운 연결을 생성하지 않습니다. 이 옵션은 SMTP 전송이 활성화된 경우에만 적용됩니다.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "이메일 템플릿 수정", "template.select": "이메일 템플릿 선택", "template.revert": "원본으로 되돌리기", + "test-smtp-settings": "Test SMTP Settings", "testing": "이메일 발신 테스트", + "testing.success": "Test Email Sent.", "testing.select": "이메일 템플릿 선택", "testing.send": "테스트 이메일 보내기", - "testing.send-help": "현재 로그인 중인 사용자의 이메일로 테스트 이메일을 보냅니다.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "이메일 다이제스트", "subscriptions.disable": "이메일 다이제스트 비활성화", "subscriptions.hour": "다이제스트 시간", diff --git a/public/language/ko/admin/settings/notifications.json b/public/language/ko/admin/settings/notifications.json index fb3847df34..330232ffc5 100644 --- a/public/language/ko/admin/settings/notifications.json +++ b/public/language/ko/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "환영 알림", "welcome-notification-link": "환영 알림 링크", "welcome-notification-uid": "환영 알림 사용자 (UID)", - "post-queue-notification-uid": "게시 대기 사용자 (UID)" + "post-queue-notification-uid": "게시 대기 사용자 (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/ko/admin/settings/uploads.json b/public/language/ko/admin/settings/uploads.json index f222e5fe80..f91b6a459b 100644 --- a/public/language/ko/admin/settings/uploads.json +++ b/public/language/ko/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "이 값보다 큰 이미지는 등록할 수 없습니다.", "reject-image-height": "최대 이미지 높이(픽셀 단위)", "reject-image-height-help": "이 값보다 큰 이미지는 등록할 수 없습니다.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "사용자가 토픽 썸네일 업로드 허용", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "토픽 썸네일 크기", "allowed-file-extensions": "허용된 파일 확장자", "allowed-file-extensions-help": "허용된 파일 확장자를 쉼표로 구분하여 입력하세요 (예: pdf,xls,doc). 비어 있는 목록은 모든 확장자가 허용됨을 의미합니다.", diff --git a/public/language/ko/admin/settings/user.json b/public/language/ko/admin/settings/user.json index f944fb81e4..a98763fd1d 100644 --- a/public/language/ko/admin/settings/user.json +++ b/public/language/ko/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "이메일 표시", "show-fullname": "전체 이름 표시", "restrict-chat": "팔로우하는 사용자의 채팅 메시지만 허용", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "새 탭에서 나가는 링크 열기", "topic-search": "토픽 내 검색 활성화", "update-url-with-post-index": "토픽을 탐색하는 동안 URL에 게시물 색인 업데이트", diff --git a/public/language/ko/admin/settings/web-crawler.json b/public/language/ko/admin/settings/web-crawler.json index 09ba91ea1a..e1a27436cc 100644 --- a/public/language/ko/admin/settings/web-crawler.json +++ b/public/language/ko/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "RSS 피드 비활성화", "disable-sitemap-xml": "Sitemap.xml 비활성화", "sitemap-topics": "사이트맵에 표시할 토픽 수", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "사이트맵 캐시 지우기", "view-sitemap": "사이트맵 보기" } \ No newline at end of file diff --git a/public/language/ko/aria.json b/public/language/ko/aria.json index dd73d4d8ee..39db1d1695 100644 --- a/public/language/ko/aria.json +++ b/public/language/ko/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "사용자 관심 태그", "delete-upload-button": "업로드 버튼 삭제", - "group-page-link-for": "그룹 페이지 링크, %1" + "group-page-link-for": "그룹 페이지 링크, %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/ko/category.json b/public/language/ko/category.json index bd4d8e8887..f3ffcdbece 100644 --- a/public/language/ko/category.json +++ b/public/language/ko/category.json @@ -1,12 +1,13 @@ { "category": "카테고리", "subcategories": "하위 카테고리", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "새 토픽", "guest-login-post": "게시물을 작성하려면 로그인하세요.", "no-topics": "이 카테고리에는 토픽이 없습니다.
한 가지 올려보는 건 어떨까요?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "탐색 중", "no-replies": "답글이 없습니다", "no-new-posts": "새로운 게시물이 없습니다.", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 9357d2e290..8c99e731ca 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -3,6 +3,7 @@ "invalid-json": "잘못된 JSON", "wrong-parameter-type": "속성 `%1`에 대해 %3 유형의 값이 예상되었지만 대신 %2가 수신되었습니다", "required-parameters-missing": "이 API 호출에서 필수 매개변수가 누락되었습니다: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "로그인되지 않았습니다.", "account-locked": "계정이 일시적으로 잠겼습니다.", "search-requires-login": "검색에는 계정이 필요합니다. 로그인하거나 등록하세요.", @@ -146,6 +147,7 @@ "post-already-restored": "이 게시물은 복원되었습니다", "topic-already-deleted": "이 토픽은 삭제되었습니다", "topic-already-restored": "이 토픽은 복원되었습니다", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "주요 게시물을 정리할 수 없습니다. 대신 토픽을 삭제하세요", "topic-thumbnails-are-disabled": "토픽 썸네일이 비활성화되었습니다.", "invalid-file": "잘못된 파일", @@ -154,6 +156,8 @@ "about-me-too-long": "죄송합니다. 자기 소개는 최대 %1자보다 길 수 없습니다.", "cant-chat-with-yourself": "자기 자신과 채팅할 수 없습니다!", "chat-restricted": "이 사용자는 채팅 메시지를 제한했습니다. 채팅할 수 있도록 팔로우해야 합니다", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "채팅 시스템이 비활성화되었습니다", "too-many-messages": "너무 많은 메시지를 보냈습니다. 잠시 기다려주세요.", @@ -225,6 +229,7 @@ "no-topics-selected": "선택된 토픽이 없습니다!", "cant-move-to-same-topic": "게시물을 동일한 토픽으로 이동할 수 없습니다!", "cant-move-topic-to-same-category": "토픽을 동일한 카테고리로 이동할 수 없습니다!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "자신을 차단할 수 없습니다!", "cannot-block-privileged": "관리자나 전역 중재자를 차단할 수 없습니다", "cannot-block-guest": "비회원는 다른 사용자를 차단할 수 없습니다", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "현재 서버에 연결할 수 없습니다. 여기를 클릭 후 다시 시도하거나 나중에 다시 시도하세요", "invalid-plugin-id": "잘못된 플러그인 ID", "plugin-not-whitelisted": "플러그인을 설치할 수 없습니다 - NodeBB 패키지 관리자에서 허용목록에 등록된 플러그인만 ACP를 통해 설치할 수 있습니다", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "실행 중에 정의된 플러그인 상태를 변경할 수 없습니다 (config.json, 환경 변수 또는 터미널 인수). 대신 구성을 수정하세요.", "theme-not-set-in-configuration": "구성에서 활성 플러그인을 정의할 때 새 테마를 추가하기 전에 ACP에서 테마를 업데이트해야 합니다", @@ -246,6 +252,7 @@ "api.401": "유효한 로그인 세션이 없습니다. 로그인한 후 다시 시도하세요.", "api.403": "이 호출을 수행할 권한이 없습니다", "api.404": "잘못된 API 호출", + "api.413": "The request payload is too large", "api.426": "쓰기 API에 대한 요청은 HTTPS로 보내야 합니다. HTTPS를 통해 다시 요청하세요", "api.429": "너무 많은 요청을 보냈습니다. 나중에 다시 시도하세요", "api.500": "요청을 처리하는 중 예기치 않은 오류가 발생했습니다.", diff --git a/public/language/ko/global.json b/public/language/ko/global.json index 2aebde9b11..56a4ed4e48 100644 --- a/public/language/ko/global.json +++ b/public/language/ko/global.json @@ -68,6 +68,7 @@ "users": "사용자", "topics": "토픽", "posts": "게시물", + "crossposts": "Cross-posts", "x-posts": "%1 개의 게시물", "x-topics": "%1 개의 토픽", "x-reputation": "%1 평판", @@ -82,6 +83,7 @@ "downvoted": "반대함", "views": "조회수", "posters": "작성자", + "watching": "Watching", "reputation": "평판", "lastpost": "마지막 게시물", "firstpost": "첫 게시물", diff --git a/public/language/ko/groups.json b/public/language/ko/groups.json index 19d27ea323..0a7eed2db6 100644 --- a/public/language/ko/groups.json +++ b/public/language/ko/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "모든 그룹", "groups": "그룹", "members": "멤버", + "x-members": "%1 member(s)", "view-group": "그룹 보기", "owner": "그룹 소유자", "new-group": "새 그룹 만들기", diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index 090984bfd0..d5f47be8cf 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "사용자 추가", "chat.notification-settings": "알림 설정", "chat.default-notification-setting": "기본 알림 설정", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "방 기본값", "chat.notification-setting-none": "알림 없음", "chat.notification-setting-at-mention-only": "@언급만", @@ -81,7 +82,7 @@ "composer.hide-preview": "미리보기 숨기기", "composer.help": "도움말", "composer.user-said-in": "%2 에서 %1 님이 말했습니다:", - "composer.user-said": "%1 님이 말했습니다:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "이 게시물을 삭제하시겠습니까?", "composer.submit-and-lock": "제출 및 잠금", "composer.toggle-dropdown": "드롭다운 전환", diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json index a97f8068bd..841a45c2d6 100644 --- a/public/language/ko/notifications.json +++ b/public/language/ko/notifications.json @@ -22,7 +22,7 @@ "upvote": "추천", "awards": "수상", "new-flags": "새로운 신고", - "my-flags": "내가 신고한 항목", + "my-flags": "My Flags", "bans": "차단", "new-message-from": "%1님의 새로운 메시지", "new-messages-from": "%2님의 %1개의 새로운 메시지", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1%2님이 %4에 게시했습니다.", "user-posted-in-public-room-triple": "%1, %2%3님이 %5에 게시했습니다.", "user-posted-in-public-room-multiple": "%1, %2 및 다른 %3명이 %5에 게시했습니다.", - "upvoted-your-post-in": "%1님이 %2에서 나의 게시물을 추천했습니다.", - "upvoted-your-post-in-dual": "%1%2님이 %3에서 나의 게시물을 추천했습니다.", - "upvoted-your-post-in-triple": "%1, %2%3님이 %4에서 나의 게시물을 추천했습니다.", - "upvoted-your-post-in-multiple": "%1, %2 및 다른 %3명이 %4에서 나의 게시물을 추천했습니다.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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-triple": "%1, %2%3님이 %4에서 게시물을 신고했습니다.", - "user-flagged-post-in-multiple": "%1, %2 및 다른 %3명이 %4에서 게시물을 신고했습니다.", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1님이 사용자 프로필을 신고했습니다 (%2)", "user-flagged-user-dual": "%1%2님이 사용자 프로필을 신고했습니다 (%3)", "user-flagged-user-triple": "%1, %2%3님이 사용자 프로필을 신고했습니다 (%4)", "user-flagged-user-multiple": "%1, %2 및 다른 %3명이 사용자 프로필을 신고했습니다 (%4)", - "user-posted-to": "%1님이 답글을 게시했습니다: %2", - "user-posted-to-dual": "%1%2님이 답글을 게시했습니다: %3", - "user-posted-to-triple": "%1, %2%3님이 답글을 게시했습니다: %4", - "user-posted-to-multiple": "%1, %2 및 다른 %3명이 답글을 게시했습니다: %4", - "user-posted-topic": "%1님이 새 토픽을 게시했습니다: %2", - "user-edited-post": "%1님이 %2에서 게시물을 편집했습니다.", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1님이 카테고리 %2에 새 토픽을 게시했습니다.", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1님이 나를 팔로우하기 시작했습니다.", "user-started-following-you-dual": "%1%2님이 나를 팔로우하기 시작했습니다.", "user-started-following-you-triple": "%1, %2%3님이 나를 팔로우하기 시작했습니다.", @@ -71,11 +71,11 @@ "users-csv-exported": "사용자 CSV를 내보냈습니다. 다운로드하려면 클릭하세요.", "post-queue-accepted": "대기 중인 게시물이 승인되었습니다. 여기를 클릭하여 게시물을 확인하세요.", "post-queue-rejected": "대기 중인 게시물이 거부되었습니다.", - "post-queue-notify": "대기 중인 게시물이 알림을 받았습니다:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "이메일 확인됨", "email-confirmed-message": "이메일 확인에 감사드립니다. 계정이 이제 완전히 활성화되었습니다.", "email-confirm-error-message": "이메일 주소를 확인하는 데 문제가 발생했습니다. 코드가 잘못되었거나 만료되었을 수 있습니다.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "확인 이메일이 전송되었습니다.", "none": "없음", "notification-only": "알림만", diff --git a/public/language/ko/social.json b/public/language/ko/social.json index 49b91bdf69..2e38a019d2 100644 --- a/public/language/ko/social.json +++ b/public/language/ko/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Facebook으로 로그인", "continue-with-facebook": "Facebook으로 계속하기", "sign-in-with-linkedin": "LinkedIn으로 로그인", - "sign-up-with-linkedin": "LinkedIn으로 가입" + "sign-up-with-linkedin": "LinkedIn으로 가입", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/ko/themes/harmony.json b/public/language/ko/themes/harmony.json index d5ba1daaee..8e4ca896b7 100644 --- a/public/language/ko/themes/harmony.json +++ b/public/language/ko/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "하모니 테마", "skins": "스킨", + "light": "Light", + "dark": "Dark", "collapse": "축소", "expand": "확장", "sidebar-toggle": "사이드바 토글", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index f626bea9d8..223196a7ca 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%3에 %1님이 이 토픽을 참조함", "user-forked-topic-ago": "%3에 %1님이 이 토픽을 포크함", "user-forked-topic-on": "%3에 %1님이 이 토픽을 포크함", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "마지막 읽은 위치로 돌아가려면 클릭하세요.", "flag-post": "이 게시물 신고", "flag-user": "이 사용자 신고", @@ -103,6 +105,7 @@ "thread-tools.lock": "토픽 잠금", "thread-tools.unlock": "토픽 잠금 해제", "thread-tools.move": "토픽 이동", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "게시물 이동", "thread-tools.move-all": "모두 이동", "thread-tools.change-owner": "소유자 변경", @@ -132,6 +135,7 @@ "pin-modal-help": "여기에서 고정된 토픽에 대한 만료일을 선택적으로 설정할 수 있습니다. 또는 토픽이 수동으로 고정 해제될 때까지 이 필드를 비워 둘 수도 있습니다.", "load-categories": "카테고리 로드 중", "confirm-move": "이동", + "confirm-crosspost": "Cross-post", "confirm-fork": "포크", "bookmark": "북마크", "bookmarks": "북마크", @@ -141,6 +145,7 @@ "loading-more-posts": "게시물 더 불러오는 중", "move-topic": "토픽 이동", "move-topics": "토픽 이동", + "crosspost-topic": "Cross-post Topic", "move-post": "게시물 이동", "post-moved": "게시물이 이동되었습니다!", "fork-topic": "토픽 포크", @@ -163,6 +168,9 @@ "move-topic-instruction": "대상 카테고리를 선택한 다음 이동을 클릭하세요", "change-owner-instruction": "다른 사용자에게 할당할 게시물을 클릭하세요", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "여기에 토픽 제목을 입력하세요...", "composer.handle-placeholder": "여기에 이름/핸들을 입력하세요", "composer.hide": "숨기기", @@ -174,6 +182,7 @@ "composer.replying-to": "%1에 답글 작성 중", "composer.new-topic": "새 토픽", "composer.editing-in": "%1에서 게시물 편집 중", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "업로드 중...", "composer.thumb-url-label": "토픽 썸네일 URL 붙여넣기", "composer.thumb-title": "이 토픽에 대한 썸네일 추가", @@ -224,5 +233,8 @@ "unread-posts-link": "읽지 않은 게시물 링크", "thumb-image": "토픽 썸네일 이미지", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/ko/user.json b/public/language/ko/user.json index a44e096677..3bbcc340d3 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -105,6 +105,10 @@ "show-email": "내 이메일 표시", "show-fullname": "내 전체 이름 표시", "restrict-chats": "내가 팔로우하는 사용자의 채팅 메시지만 허용", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "다이제스트 구독", "digest-description": "구독 설정에 따라 이 포럼의 이메일 업데이트를 받아보세요. (새로운 알림과 주제)", "digest-off": "끄기", diff --git a/public/language/ko/world.json b/public/language/ko/world.json index 3753335278..e6694bf507 100644 --- a/public/language/ko/world.json +++ b/public/language/ko/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/lt/admin/advanced/cache.json b/public/language/lt/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/lt/admin/advanced/cache.json +++ b/public/language/lt/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/lt/admin/dashboard.json b/public/language/lt/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/lt/admin/dashboard.json +++ b/public/language/lt/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/lt/admin/development/info.json b/public/language/lt/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/lt/admin/development/info.json +++ b/public/language/lt/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/lt/admin/manage/categories.json b/public/language/lt/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/lt/admin/manage/categories.json +++ b/public/language/lt/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/lt/admin/manage/custom-reasons.json b/public/language/lt/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/lt/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/lt/admin/manage/privileges.json b/public/language/lt/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/lt/admin/manage/privileges.json +++ b/public/language/lt/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/lt/admin/manage/users.json b/public/language/lt/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/lt/admin/manage/users.json +++ b/public/language/lt/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/lt/admin/settings/activitypub.json b/public/language/lt/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/lt/admin/settings/activitypub.json +++ b/public/language/lt/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/lt/admin/settings/chat.json b/public/language/lt/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/lt/admin/settings/chat.json +++ b/public/language/lt/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/lt/admin/settings/email.json b/public/language/lt/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/lt/admin/settings/email.json +++ b/public/language/lt/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/lt/admin/settings/notifications.json b/public/language/lt/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/lt/admin/settings/notifications.json +++ b/public/language/lt/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/lt/admin/settings/uploads.json b/public/language/lt/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/lt/admin/settings/uploads.json +++ b/public/language/lt/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/lt/admin/settings/user.json b/public/language/lt/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/lt/admin/settings/user.json +++ b/public/language/lt/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/lt/admin/settings/web-crawler.json b/public/language/lt/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/lt/admin/settings/web-crawler.json +++ b/public/language/lt/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/lt/aria.json b/public/language/lt/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/lt/aria.json +++ b/public/language/lt/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/lt/category.json b/public/language/lt/category.json index 82c55e3ec8..bd5d99d785 100644 --- a/public/language/lt/category.json +++ b/public/language/lt/category.json @@ -1,12 +1,13 @@ { "category": "Kategorija", "subcategories": "Subkategorijos", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nauja tema", "guest-login-post": "Prisijungti įrašų paskelbimui", "no-topics": "Šioje kategorijoje temų nėra.
Kodėl gi jums nesukūrus naujos?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "naršo", "no-replies": "Nėra atsakymų", "no-new-posts": "Nėra naujų pranešimų.", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index 79e5fe05ef..135ae70882 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -3,6 +3,7 @@ "invalid-json": "Nevalidus JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Atrodo, kad jūs neesate prisijungęs.", "account-locked": "Jūsų paskyra buvo laikinai užrakinta", "search-requires-login": "Paieška reikalauja vartotojo - prašome prisijungti arba užsiregistruoti", @@ -146,6 +147,7 @@ "post-already-restored": "Šis įrašas jau atstatytas", "topic-already-deleted": "Ši tema jau ištrinta", "topic-already-restored": "Ši tema jau atkurta", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Jūs negalite išvalyti pagrindinio pranešimo, prašome ištrinkite temą nedelsiant", "topic-thumbnails-are-disabled": "Temos paveikslėliai neleidžiami.", "invalid-file": "Klaidingas failas", @@ -154,6 +156,8 @@ "about-me-too-long": "Atsiprašome, bet aprašymas apie jus negali būti ilgesnis negu %1 simbolis (ių)", "cant-chat-with-yourself": "Jūs negalite susirašinėti su savimi!", "chat-restricted": "Šis vartotojas apribojo savo žinutes. Jie turi sekti jus kad jūs galėtumėte pradėti bendrauti su jais", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Susirašinėjimų sistema išjungta", "too-many-messages": "Išsiuntėte per daug pranešimų, kurį laiką prašome palaukti.", @@ -225,6 +229,7 @@ "no-topics-selected": "Nepasirinkta jokia tema!", "cant-move-to-same-topic": "Negalima perkelti įrašo į tą pačią temą!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Savęs užblokuoti negalima!", "cannot-block-privileged": "Negalima blokuoti administratorių arba visuotinių moderatorių", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/lt/global.json b/public/language/lt/global.json index 8b64134197..4d54fec520 100644 --- a/public/language/lt/global.json +++ b/public/language/lt/global.json @@ -68,6 +68,7 @@ "users": "Vartotojai", "topics": "Temos", "posts": "Pranešimai", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Peržiūros", "posters": "Posters", + "watching": "Watching", "reputation": "Reputacija", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/lt/groups.json b/public/language/lt/groups.json index c57f1e7132..120b728eef 100644 --- a/public/language/lt/groups.json +++ b/public/language/lt/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupės", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Grupės peržiūra", "owner": "Grupės savininkas", "new-group": "Kurti naują grupę", diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json index 6a1a9c3c7a..11afdd6626 100644 --- a/public/language/lt/modules.json +++ b/public/language/lt/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Slėpti pavyzdį", "composer.help": "Help", "composer.user-said-in": "%1 parašė į %2:", - "composer.user-said": "%1 parašė:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Ar tikrai norite sunaikinti šį pranešimą?", "composer.submit-and-lock": "Pateikti ir užrakinti", "composer.toggle-dropdown": "Perjungti Nukritimą", diff --git a/public/language/lt/notifications.json b/public/language/lt/notifications.json index 5cffb2ba8b..85bdd407eb 100644 --- a/public/language/lt/notifications.json +++ b/public/language/lt/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Nauja žinutė nuo %1", "new-messages-from": "%1 new messages from %2", @@ -32,13 +32,13 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 užbalsavo už jūsų pranešima čia %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", - "user-flagged-post-in": "%1pagrįso nuomone čia %2", + "user-flagged-post-in": "%1 flagged a post in %2", "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 parašė atsaką %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 paskelbė naują temą: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 pradėjo sekti tave", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "El. paštas patvirtintas", "email-confirmed-message": "Dėkojame už el. pašto patvirtinimą. Jūsų paskyra pilnai aktyvuota.", "email-confirm-error-message": "Įvyko klaida mėginant patvirtinti Jūsų el. pašto adresą. Galbūt kodas yra neteisingas, arba nebegalioajantis.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Patvirtinimo laiškas išsiųstas.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/lt/social.json b/public/language/lt/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/lt/social.json +++ b/public/language/lt/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/lt/themes/harmony.json b/public/language/lt/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/lt/themes/harmony.json +++ b/public/language/lt/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json index 4146e1bce6..948e320587 100644 --- a/public/language/lt/topic.json +++ b/public/language/lt/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Užrakinti temą", "thread-tools.unlock": "Atrakinti temą", "thread-tools.move": "Perkelti temą", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Perkelti visus", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Įkeliamos kategorijos", "confirm-move": "Perkelti", + "confirm-crosspost": "Cross-post", "confirm-fork": "Išskaidyti", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Įkeliama daugiau įrašų", "move-topic": "Perkelti temą", "move-topics": "Perkelti temas", + "crosspost-topic": "Cross-post Topic", "move-post": "Perkelti įrašą", "post-moved": "Pranešimas perkeltas!", "fork-topic": "Išskaidyti temą", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Įrašykite temos pavadinimą...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Atsakymas %1", "composer.new-topic": "Nauja tema", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "įkeliama...", "composer.thumb-url-label": "Įklijuokite temos paveikslėlio URL", "composer.thumb-title": "Pridėti paveikslėlį šiai temai", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 453b329046..5525842949 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -105,6 +105,10 @@ "show-email": "Rodyti mano el. paštą viešai", "show-fullname": "Rodyti mano vardą ir pavardę", "restrict-chats": "Gauti pokalbių žinutes tik iš tų narių, kuriuos seku", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Prenumeruoti įvykių santrauką", "digest-description": "Gauti naujienas apie naujus pranešimus ir temas į el. paštą pagal nustatytą grafiką", "digest-off": "Išjungta", diff --git a/public/language/lt/world.json b/public/language/lt/world.json index 3753335278..e6694bf507 100644 --- a/public/language/lt/world.json +++ b/public/language/lt/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/lv/admin/advanced/cache.json b/public/language/lv/admin/advanced/cache.json index 0567c561c4..db1afdd8be 100644 --- a/public/language/lv/admin/advanced/cache.json +++ b/public/language/lv/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Rakstu kešatmiņa", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Aizņemts", "post-cache-size": "Rakstu kešatmiņas lielums", "items-in-cache": "Rakstu skaits kešatmiņā" diff --git a/public/language/lv/admin/dashboard.json b/public/language/lv/admin/dashboard.json index ee1358bb7b..4f55f41c5d 100644 --- a/public/language/lv/admin/dashboard.json +++ b/public/language/lv/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Lapu skatījumi no lietotājiem", "graphs.page-views-guest": "Lapu skatījumi no viesiem", "graphs.page-views-bot": "Lapu skatījumi no botiem", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unikālie apmeklētāji", "graphs.registered-users": "Reģistrētie lietotāji", "graphs.guest-users": "Guest Users", diff --git a/public/language/lv/admin/development/info.json b/public/language/lv/admin/development/info.json index 87cb89d8cf..9066f7ae27 100644 --- a/public/language/lv/admin/development/info.json +++ b/public/language/lv/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "tiešsaistē", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/lv/admin/manage/categories.json b/public/language/lv/admin/manage/categories.json index 1358f338ac..20933eec31 100644 --- a/public/language/lv/admin/manage/categories.json +++ b/public/language/lv/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Kategorijas iestatījumi", "edit-category": "Edit Category", "privileges": "Privilēģijas", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Kategorijas nosaukums", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategorijas apraksts", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Fona krāsa", "text-color": "Teksta krāsa", "bg-image-size": "Fona bildes lielums", @@ -103,6 +107,11 @@ "alert.create-success": "Kategorija veiksmīgi izveidota", "alert.none-active": "Nav aktīvo kategoriju", "alert.create": "Izveidot kategoriju", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Vai tiešām vēlies iztīrīt šo kategoriju \"%1\"?

Brīdinājums!Visi temati un raksti šajā kategorijā tiks iztīrīti!

Iztukšojot kategoriju, tiks noņemti visi temati un raksti un kategorija tiks izdzēsta no datu bāzes. Ja vēlies īslaicīgi noņemt kategoriju, \"atspējo\" to.

", "alert.purge-success": "Kategorija iztīrīta!", "alert.copy-success": "Iestatījumi kopēti!", diff --git a/public/language/lv/admin/manage/custom-reasons.json b/public/language/lv/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/lv/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/lv/admin/manage/privileges.json b/public/language/lv/admin/manage/privileges.json index 8be16662ed..fe6eca81f2 100644 --- a/public/language/lv/admin/manage/privileges.json +++ b/public/language/lv/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Piekļūt tematiem", "create-topics": "Izveidot tematus", "reply-to-topics": "Atbildēt tematos", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Pievienot birkas", "edit-posts": "Rediģēt rakstus", diff --git a/public/language/lv/admin/manage/users.json b/public/language/lv/admin/manage/users.json index 4ca2a1a7d0..cf07ce377c 100644 --- a/public/language/lv/admin/manage/users.json +++ b/public/language/lv/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Lejupielādēt .csv", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Iemesls (neobligāts)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Stundas", "temp-ban.days": "Dienas", "temp-ban.explanation": "Ievadīt bloķēšanas termiņu, ņemot vērā, ka 0 tiks uzskatīts par pastāvīgu bloķēšanu.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Vai tiešām vēlies pastāvīgi bloķēt šo lietotāju?", "alerts.confirm-ban-multi": "Vai tiešām vēlies pastāvīgi bloķēt šos lietotājus?", diff --git a/public/language/lv/admin/settings/activitypub.json b/public/language/lv/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/lv/admin/settings/activitypub.json +++ b/public/language/lv/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/lv/admin/settings/chat.json b/public/language/lv/admin/settings/chat.json index c6a6250e70..a4bc312b49 100644 --- a/public/language/lv/admin/settings/chat.json +++ b/public/language/lv/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maksimālais lietotāju skaits tērzētavā", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/lv/admin/settings/email.json b/public/language/lv/admin/settings/email.json index 6107313c6f..c0e179f42c 100644 --- a/public/language/lv/admin/settings/email.json +++ b/public/language/lv/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Rediģēt e-pasta veidni", "template.select": "Atlasīt e-pasta veidni", "template.revert": "Atgriezt uz oriģinālo", + "test-smtp-settings": "Test SMTP Settings", "testing": "E-pasta testēšana", + "testing.success": "Test Email Sent.", "testing.select": "Atlasīt e-pasta veidni", "testing.send": "Sūtīt parauga e-pastu", - "testing.send-help": "Testa e-pasts tiks nosūtīts uz pašlaik ielogojušā lietotāja e-pasta adresi.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Kopsavilkumu nosūtīšanas stunda", diff --git a/public/language/lv/admin/settings/notifications.json b/public/language/lv/admin/settings/notifications.json index d2f44f18d6..76c7328ec4 100644 --- a/public/language/lv/admin/settings/notifications.json +++ b/public/language/lv/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Sveicināšanas paziņojums", "welcome-notification-link": "Sveicināšanas paziņojuma saite", "welcome-notification-uid": "Sveicināšanas paziņojuma lietotājs (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/lv/admin/settings/uploads.json b/public/language/lv/admin/settings/uploads.json index 0b79f6bb9a..2ac9818602 100644 --- a/public/language/lv/admin/settings/uploads.json +++ b/public/language/lv/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Bildes, kas ir platākas par šo vērtību, tiks noraidītas.", "reject-image-height": "Maksimālais bildes augstums (pikseļos)", "reject-image-height-help": "Bildes, kas ir augstākas par šo vērtību, tiks noraidītas.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Atļaut lietotājiem augšupielādēt tematu sīktēlus", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tematu sīktēlu lielums", "allowed-file-extensions": "Atļautie failu paplašinājumi", "allowed-file-extensions-help": "Ievadīt ar komatu atdalītu failu paplašinājumu sarakstu (piemērām pdf,xls,doc). Tukšais saraksts nozīmē, ka visi failu paplašinājumi ir atļauti.", diff --git a/public/language/lv/admin/settings/user.json b/public/language/lv/admin/settings/user.json index 57b6fcac28..cd4ec29f3f 100644 --- a/public/language/lv/admin/settings/user.json +++ b/public/language/lv/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Rādīt e-pasta adresi", "show-fullname": "Rādīt vārdu un uzvārdu", "restrict-chat": "Atļaut sarunas tikai no tiem lietotājiem, kurus es sekoju", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Atvērt izejošās saites jaunā cilnē", "topic-search": "Iespējot meklēšanu tematu saturā", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/lv/admin/settings/web-crawler.json b/public/language/lv/admin/settings/web-crawler.json index 24dd40c821..dfd028d15c 100644 --- a/public/language/lv/admin/settings/web-crawler.json +++ b/public/language/lv/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Atspējot RSS plūsmu", "disable-sitemap-xml": "Atspējot sitemap.xml", "sitemap-topics": "Cik tematus rādīt vietnes kartē", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Notīrīt vietnes kartes kešatmiņu", "view-sitemap": "Skatīt vietnes karti" } \ No newline at end of file diff --git a/public/language/lv/aria.json b/public/language/lv/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/lv/aria.json +++ b/public/language/lv/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/lv/category.json b/public/language/lv/category.json index 84242bf2f2..5f88f128e9 100644 --- a/public/language/lv/category.json +++ b/public/language/lv/category.json @@ -1,12 +1,13 @@ { "category": "Kategorija", "subcategories": "Apakškategorijas", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Izveidot jaunu tematu", "guest-login-post": "Ielogojies lai rakstītu", "no-topics": "Šinī kategorijā nav rakstu.
Vēlies izveidot kādu rakstu?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "pārlūko", "no-replies": "Nav atbilžu", "no-new-posts": "Nav jaunu rakstu.", diff --git a/public/language/lv/error.json b/public/language/lv/error.json index d1263bc1a4..03edf18edf 100644 --- a/public/language/lv/error.json +++ b/public/language/lv/error.json @@ -3,6 +3,7 @@ "invalid-json": "Nederīgs JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Šķiet, ka neesi ielogojies.", "account-locked": "Tavs konts ir uz laiku bloķēts", "search-requires-login": "Meklēšanai nepieciešams konts - lūdzu, ielogojies vai reģistrējies.", @@ -146,6 +147,7 @@ "post-already-restored": "Raksts jau ir atjaunots", "topic-already-deleted": "Temats jau ir izdzēsts", "topic-already-restored": "Temats jau ir atjaunots", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Nevar iztīrīt galveno rakstu, lūdzu, tā vietā izdzēsi tematu", "topic-thumbnails-are-disabled": "Tematu sīktēli ir atspējoti.", "invalid-file": "Nederīgs fails", @@ -154,6 +156,8 @@ "about-me-too-long": "Atvaino, Tavā \"Par mani\" nevar būt vairāk kā %1 rakstzīmes.", "cant-chat-with-yourself": "Nevar sarunāties pats ar sevi!", "chat-restricted": "Šis lietotājs ir ierobežojis savas sarunas. Viņam ir Tev jāseko, pirms vari sarunāties ar viņu", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Sarunu sistēma ir atspējota", "too-many-messages": "Tu esi publicējis pārāk daudz rakstu, lūdzu, kādu laiku uzgaidi.", @@ -225,6 +229,7 @@ "no-topics-selected": "Nav atlasīts neviens temats", "cant-move-to-same-topic": "Nevar pārnest uz savu pašu tematu!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Nevar pats sevi bloķēt!", "cannot-block-privileged": "Nevar bloķēt administratorus vai globālos moderatorus", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/lv/global.json b/public/language/lv/global.json index 285eaa8bb1..88445d05f3 100644 --- a/public/language/lv/global.json +++ b/public/language/lv/global.json @@ -68,6 +68,7 @@ "users": "Lietotāji", "topics": "Temati", "posts": "Raksti", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Balsojis \"pret\"", "views": "Skatījumi", "posters": "Posters", + "watching": "Watching", "reputation": "Ranga punkti", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/lv/groups.json b/public/language/lv/groups.json index 24fff635ca..c3660ab328 100644 --- a/public/language/lv/groups.json +++ b/public/language/lv/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupas", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Skatīt grupas", "owner": "Grupas īpašnieks", "new-group": "Izveidot jaunu grupu", diff --git a/public/language/lv/modules.json b/public/language/lv/modules.json index 0c1a36c576..488a73b112 100644 --- a/public/language/lv/modules.json +++ b/public/language/lv/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Slēpt priekšskatu", "composer.help": "Help", "composer.user-said-in": "%1 sacīja %2:", - "composer.user-said": "%1 sacīja:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Vai tiešām vēlies atmest šo rakstu?", "composer.submit-and-lock": "Iesniegt un aizslēgt", "composer.toggle-dropdown": "Pārslēgt izvēlni", diff --git a/public/language/lv/notifications.json b/public/language/lv/notifications.json index ec6864d134..86a9d7267f 100644 --- a/public/language/lv/notifications.json +++ b/public/language/lv/notifications.json @@ -22,7 +22,7 @@ "upvote": "Par balsojumiem \"par\"", "awards": "Awards", "new-flags": "Jaunās atzīmes", - "my-flags": "Atzīmes piešķirtas man", + "my-flags": "My Flags", "bans": "Bloķēšanas", "new-message-from": "Jauns raksts no %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 ir balsojis \"par\" Tavu rakstu%2.", - "upvoted-your-post-in-dual": "%1 un %2 ir balsojuši \"par\" Tavu rakstu %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 ir pārvietojis Tavu rakstu %2", "moved-your-topic": "%1 ir pārvietojis %2", - "user-flagged-post-in": "%1 ir atzīmējis rakstu %2", - "user-flagged-post-in-dual": "%1 un %2 ir atzīmējuši rakstu %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 ir atzīmējis lietotāja profilu (%2)", "user-flagged-user-dual": "%1 un %2 ir atzīmējuši lietotāja profilu (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 ir atbildējis: %2", - "user-posted-to-dual": "%1 un %2 ir atbildējuši %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 ir ievietojis jaunu tematu: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 sāka Tev sekot.", "user-started-following-you-dual": "%1 un %2 sāka Tev sekot.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-pasta adrese ir apstiprināta", "email-confirmed-message": "Paldies, ka apstiprināji e-pasta adresi. Tavs konts tagad ir pilnībā aktivizēts.", "email-confirm-error-message": "Tavā e-pasta adreses apstiprināšanā radās problēma. Iespējams, kods ir nederīgs vai ir beidzies derīguma termiņš.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Apstiprinājuma e-pasts ir nosūtīts.", "none": "Neko nedarīt", "notification-only": "Tikai paziņot", diff --git a/public/language/lv/social.json b/public/language/lv/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/lv/social.json +++ b/public/language/lv/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/lv/themes/harmony.json b/public/language/lv/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/lv/themes/harmony.json +++ b/public/language/lv/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/lv/topic.json b/public/language/lv/topic.json index d55b6c985c..72b136f2a5 100644 --- a/public/language/lv/topic.json +++ b/public/language/lv/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Noklikšķināt, lai atgrieztos pēdējā lasītā rakstā šajā pavedienā.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Slēgt tematu", "thread-tools.unlock": "Atslēgt tematu", "thread-tools.move": "Pārvietot tematu", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Pārvietot rakstus", "thread-tools.move-all": "Pārvietot visus", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Ielādē kategorijas", "confirm-move": "Pārvietot", + "confirm-crosspost": "Cross-post", "confirm-fork": "Nozarot", "bookmark": "Atzīme", "bookmarks": "Atzīmētie", @@ -141,6 +145,7 @@ "loading-more-posts": "Ielādē vēl rakstus", "move-topic": "Pārvietot tematu", "move-topics": "Pārvietot tematus", + "crosspost-topic": "Cross-post Topic", "move-post": "Pārvietot rakstu", "post-moved": "Raksts pārvietots!", "fork-topic": "Nozarot tematu", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Ievadīt temata virsrakstu...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Atbild %1", "composer.new-topic": "Izveidot jaunu tematu", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "augšupielādē...", "composer.thumb-url-label": "Ielīmēt temata sīktēla URL", "composer.thumb-title": "Pievienot tematam sīktēlu", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/lv/user.json b/public/language/lv/user.json index 61a32d32ac..a35bdf7b23 100644 --- a/public/language/lv/user.json +++ b/public/language/lv/user.json @@ -105,6 +105,10 @@ "show-email": "Atklāt savu e-pasta adresi", "show-fullname": "Atklāt savu vārdu un uzvārdu", "restrict-chats": "Atļaut sarunas tikai no tiem lietotājiem, kurus es sekoju", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Sakopojumu abonements", "digest-description": "Abonēt e-pasta paziņojumus no šī foruma (par jauniem tematiem un rakstiem) uz noteiktu grafiku", "digest-off": "Izslēgts", diff --git a/public/language/lv/world.json b/public/language/lv/world.json index 3753335278..e6694bf507 100644 --- a/public/language/lv/world.json +++ b/public/language/lv/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/ms/admin/advanced/cache.json b/public/language/ms/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/ms/admin/advanced/cache.json +++ b/public/language/ms/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/ms/admin/dashboard.json b/public/language/ms/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/ms/admin/dashboard.json +++ b/public/language/ms/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/ms/admin/development/info.json b/public/language/ms/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/ms/admin/development/info.json +++ b/public/language/ms/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/ms/admin/manage/categories.json b/public/language/ms/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/ms/admin/manage/categories.json +++ b/public/language/ms/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/ms/admin/manage/custom-reasons.json b/public/language/ms/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ms/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ms/admin/manage/privileges.json b/public/language/ms/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/ms/admin/manage/privileges.json +++ b/public/language/ms/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/ms/admin/manage/users.json b/public/language/ms/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/ms/admin/manage/users.json +++ b/public/language/ms/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/ms/admin/settings/activitypub.json b/public/language/ms/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/ms/admin/settings/activitypub.json +++ b/public/language/ms/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/ms/admin/settings/chat.json b/public/language/ms/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/ms/admin/settings/chat.json +++ b/public/language/ms/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/ms/admin/settings/email.json b/public/language/ms/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/ms/admin/settings/email.json +++ b/public/language/ms/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/ms/admin/settings/notifications.json b/public/language/ms/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/ms/admin/settings/notifications.json +++ b/public/language/ms/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/ms/admin/settings/uploads.json b/public/language/ms/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/ms/admin/settings/uploads.json +++ b/public/language/ms/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/ms/admin/settings/user.json b/public/language/ms/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/ms/admin/settings/user.json +++ b/public/language/ms/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/ms/admin/settings/web-crawler.json b/public/language/ms/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/ms/admin/settings/web-crawler.json +++ b/public/language/ms/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/ms/aria.json b/public/language/ms/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/ms/aria.json +++ b/public/language/ms/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/ms/category.json b/public/language/ms/category.json index fd2b9e6cf9..915870607e 100644 --- a/public/language/ms/category.json +++ b/public/language/ms/category.json @@ -1,12 +1,13 @@ { "category": "Kategori", "subcategories": "Subkategori", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Topik Baru", "guest-login-post": "Log masuk untuk kirim", "no-topics": "Tiada topik dalam kategori ini.
Cuba hantar topik yang baru?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "melihat", "no-replies": "Tiada jawapan", "no-new-posts": "Tiada kiriman baru.", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 575a766932..802d89d1fa 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Anda tidak log masuk.", "account-locked": "Akaun anda telah dikunci untuk seketika", "search-requires-login": "Fungsi Carian perlukan akaun - sila log masuk atau daftar.", @@ -146,6 +147,7 @@ "post-already-restored": "Kiriman ini telah dipulihkan", "topic-already-deleted": "Topik ini telah dipadam", "topic-already-restored": "Kiriman ini telah dipulihkan", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Anda tidak boleh memadam, kiriman utama, sebaliknya sila pada topik", "topic-thumbnails-are-disabled": "Topik kecil dilumpuhkan.", "invalid-file": "Fail tak sah", @@ -154,6 +156,8 @@ "about-me-too-long": "Maaf, penerangan tentang anda tidak boleh lebih %1 aksara().", "cant-chat-with-yourself": "Anda tidak boleh sembang dengan diri sendiri!", "chat-restricted": "Pengguna ini menyekat ruangan sembangnya. Dia hendaklah mengikut anda sebelum kalian dapat bersembang", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Sistem borak tidak diaktifkan", "too-many-messages": "Anda menghantar terlalu banyak pesanan, sila tunggu seketika.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/ms/global.json b/public/language/ms/global.json index b06133513d..e40ae347ba 100644 --- a/public/language/ms/global.json +++ b/public/language/ms/global.json @@ -68,6 +68,7 @@ "users": "Pengguna", "topics": "Topik", "posts": "Kiriman", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Lihat", "posters": "Posters", + "watching": "Watching", "reputation": "Reputasi", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/ms/groups.json b/public/language/ms/groups.json index 12c0615336..1ea9de0b49 100644 --- a/public/language/ms/groups.json +++ b/public/language/ms/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Kumpulan", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Lihat Kumpulan", "owner": "Pemilik Kumpulan", "new-group": "Buat Kumpulan Baru", diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json index a2de6c8f89..268a6fe258 100644 --- a/public/language/ms/modules.json +++ b/public/language/ms/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Sorok pra-lihat", "composer.help": "Help", "composer.user-said-in": "%1 disebut di %2:", - "composer.user-said": "%1 berkata:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Anda yakin untuk membuang kiriman ini?", "composer.submit-and-lock": "Hantar dan Kunci", "composer.toggle-dropdown": "Togol Kebawah", diff --git a/public/language/ms/notifications.json b/public/language/ms/notifications.json index 0758b4e30a..6faf4e069b 100644 --- a/public/language/ms/notifications.json +++ b/public/language/ms/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Pesanan baru daripada %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 telah mengundi naik kiriman and di %2.", - "upvoted-your-post-in-dual": "%1dan %2 telah menambah undi pada kiriman anda di %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 telah memindahkan kiriman anda ke %2", "moved-your-topic": "%1 telah memindahkan %2", - "user-flagged-post-in": "%1 menanda kiriman anda di %2", - "user-flagged-post-in-dual": "%1 dan %2 telah menanda kiriman anda pada %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flagged a user profile (%2)", "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 telah membalas kiriman kepada: %2", - "user-posted-to-dual": "%1 dan %2 membalas kiriman : %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 membuka topik baru : %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 mula mengikut anda.", "user-started-following-you-dual": "%1 dan %2 mula mengikuti anda.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Emel Disahkan", "email-confirmed-message": "Terima kasih kerana mengesahkan emel anda. Akaun anda telah diaktifkan sepenuhnya.", "email-confirm-error-message": "Berlaku masalah semasa mengesahkan emel anda. Mungkin kod tidak sah atau tamat tempoh.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Pengesahan emel telah dihantar.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/ms/social.json b/public/language/ms/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/ms/social.json +++ b/public/language/ms/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/ms/themes/harmony.json b/public/language/ms/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/ms/themes/harmony.json +++ b/public/language/ms/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json index 0493a3172b..af1c8e01c4 100644 --- a/public/language/ms/topic.json +++ b/public/language/ms/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Kunci topik", "thread-tools.unlock": "Buka kekunci topik", "thread-tools.move": "Pindahkan topik", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Pindahkan Semua", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Memuatkan kategori", "confirm-move": "Pindahkan", + "confirm-crosspost": "Cross-post", "confirm-fork": "Salin", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Memuatkan lagi kiriman", "move-topic": "Pindahkan topik", "move-topics": "Pindahkan topik-topik", + "crosspost-topic": "Cross-post Topic", "move-post": "Pindahkan kiriman", "post-moved": "Kiriman dipindahkan", "fork-topic": "Salin topik", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Masukkan tajuk topik disini", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Balas ke %1", "composer.new-topic": "Topik baru", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "Memuat naik ...", "composer.thumb-url-label": "Tampalkan gambaran URL", "composer.thumb-title": "Letakkan gambaran kepada topik ini", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/ms/user.json b/public/language/ms/user.json index 4d97615f21..12a77b875d 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -105,6 +105,10 @@ "show-email": "Tunjukkan emel saya", "show-fullname": "Tunjukkan Nama Penuh", "restrict-chats": "Hanya benarkan sembang mesej dari pengguna yang saya ikut sahaja", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Langgan berita", "digest-description": "Langgan berita terkini untuk forum ini melalui emel (Makluman dan topik) menurut jadual yang ditetapkan", "digest-off": "Tutup", diff --git a/public/language/ms/world.json b/public/language/ms/world.json index 3753335278..e6694bf507 100644 --- a/public/language/ms/world.json +++ b/public/language/ms/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/nb/admin/advanced/cache.json b/public/language/nb/admin/advanced/cache.json index fcfc1f1b03..ead48d8ec5 100644 --- a/public/language/nb/admin/advanced/cache.json +++ b/public/language/nb/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Innleggsbuffer", - "group-cache": "Gruppebuffer", - "local-cache": "Lokalbuffer", - "object-cache": "Objektbuffer", "percent-full": "%1% fullt", "post-cache-size": "Størrelse på innleggsbuffer", "items-in-cache": "Element i buffer" diff --git a/public/language/nb/admin/advanced/logs.json b/public/language/nb/admin/advanced/logs.json index ba29274563..7f08b66fc6 100644 --- a/public/language/nb/admin/advanced/logs.json +++ b/public/language/nb/admin/advanced/logs.json @@ -3,5 +3,5 @@ "control-panel": "Kontrollpanel for logg", "reload": "Last inn logg på nytt", "clear": "Tøm logg", - "clear-success": "Logg er tømt!" + "clear-success": "Logg er tømt" } \ No newline at end of file diff --git a/public/language/nb/admin/dashboard.json b/public/language/nb/admin/dashboard.json index 8662f49f25..2a2e0ee67b 100644 --- a/public/language/nb/admin/dashboard.json +++ b/public/language/nb/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Sidevisninger for registrerte", "graphs.page-views-guest": "Sidevisninger for gjester", "graphs.page-views-bot": "Sidevisninger for botter", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unike besøkende", "graphs.registered-users": "Registrerte brukere", "graphs.guest-users": "Gjestebrukere", diff --git a/public/language/nb/admin/development/info.json b/public/language/nb/admin/development/info.json index deb1975761..4d4e4b3fbb 100644 --- a/public/language/nb/admin/development/info.json +++ b/public/language/nb/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "Node.js", "online": "Pålogget", "git": "Git", - "process-memory": "Prosessminne", + "process-memory": "rss/heap used", "system-memory": "Systemminne", "used-memory-process": "Brukt minne av prosess", "used-memory-os": "Brukt systemminne", diff --git a/public/language/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json index 472daa0fd0..2031f16e36 100644 --- a/public/language/nb/admin/manage/categories.json +++ b/public/language/nb/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Administrer kategorier", "add-category": "Legg til kategori", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Hopp til...", "settings": "Kategoriinnstillinger", "edit-category": "Rediger kategori", "privileges": "Rettigheter", "back-to-categories": "Tilbake til kategorier", + "id": "Category ID", "name": "Kategorinavn", "handle": "Kategoristi", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategoribeskrivelse", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Bakgrunnsfarge", "text-color": "Tekstfarge", "bg-image-size": "Størrelse på bakgrunnsbilde", @@ -103,6 +107,11 @@ "alert.create-success": "Kategori opprettet!", "alert.none-active": "Du har ingen aktive kategorier.", "alert.create": "Opprett en kategori", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Vil du virkelig renske kategorien \"%1\"?

Advarsel! Alle tråder og innlegg i denne kategorien vil bli rensket!

Rensking av en kategori vil fjerne alle tråder og innlegg, og slette kategorien fra databasen. Hvis du vil fjerne en kategori midlertidig, vil du \"deaktivere\" kategorien i stedet.

", "alert.purge-success": "Kategori renset!", "alert.copy-success": "Innstillinger kopiert!", diff --git a/public/language/nb/admin/manage/custom-reasons.json b/public/language/nb/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/nb/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/nb/admin/manage/privileges.json b/public/language/nb/admin/manage/privileges.json index 28702994b2..6ec8301549 100644 --- a/public/language/nb/admin/manage/privileges.json +++ b/public/language/nb/admin/manage/privileges.json @@ -29,14 +29,15 @@ "access-topics": "Tilgang til emner", "create-topics": "Opprett emner", "reply-to-topics": "Svar på emner", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Planlegg emner", "tag-topics": "Legg til emneord", "edit-posts": "Rediger innlegg", "view-edit-history": "Vis redigeringshistorikk", "delete-posts": "Slett innlegg", "view-deleted": "Vis slettede innlegg", - "upvote-posts": "Tilrå innlegg", - "downvote-posts": "Ikke tilrå innlegg", + "upvote-posts": "Anbefal innlegg", + "downvote-posts": "Ikke anbefal innlegg", "delete-topics": "Slett emner", "purge": "Rensk", "moderate": "Moderere", diff --git a/public/language/nb/admin/manage/users.json b/public/language/nb/admin/manage/users.json index 3a7c855594..7e2db252c0 100644 --- a/public/language/nb/admin/manage/users.json +++ b/public/language/nb/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Slett bruker(e) og innhold", "download-csv": "Last ned CSV", "custom-user-fields": "Egendefinerte brukerfelt", + "custom-reasons": "Custom Reasons", "manage-groups": "Administrer grupper", "set-reputation": "Sett omdømme", "add-group": "Legg til gruppe", @@ -77,9 +78,11 @@ "temp-ban.length": "Lengde", "temp-ban.reason": "Årsak (Valgfritt)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Timer", "temp-ban.days": "Dager", "temp-ban.explanation": "Angi varigheten for utestengningen. Merk at en varighet på 0 vil bli betraktet som en permanent utestengning.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Vil du virkelig utestenge denne brukeren permanent?", "alerts.confirm-ban-multi": "Vil du virkelig utestenge disse brukerne permanent?", diff --git a/public/language/nb/admin/menu.json b/public/language/nb/admin/menu.json index 6c51ad8ccf..d62ddede52 100644 --- a/public/language/nb/admin/menu.json +++ b/public/language/nb/admin/menu.json @@ -25,7 +25,7 @@ "settings/general": "Generelt", "settings/homepage": "Hjemmeside", "settings/navigation": "Navigasjon", - "settings/reputation": "Omdømme og flagg", + "settings/reputation": "Omdømme ", "settings/email": "E-post", "settings/user": "Brukere", "settings/group": "Grupper", @@ -33,7 +33,7 @@ "settings/uploads": "Opplastinger", "settings/languages": "Språk", "settings/post": "Innlegg", - "settings/chat": "Chatter", + "settings/chat": "Chat", "settings/pagination": "Sidetelling", "settings/tags": "Emneord", "settings/notifications": "Varsler", diff --git a/public/language/nb/admin/settings/activitypub.json b/public/language/nb/admin/settings/activitypub.json index 70161ed834..bab848c368 100644 --- a/public/language/nb/admin/settings/activitypub.json +++ b/public/language/nb/admin/settings/activitypub.json @@ -4,7 +4,7 @@ "general": "Generelt", "pruning": "Content Pruning", "content-pruning": "Days to keep remote content", - "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", + "content-pruning-help": "Merk at eksternt innhold som har fått respons (et svar eller en anbefaling), vil bli bevart. (0 for å deaktivere)", "user-pruning": "Days to cache remote user accounts", "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", "enabled": "Enable Federation", @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtrering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/nb/admin/settings/chat.json b/public/language/nb/admin/settings/chat.json index 5d52fbd613..ecf7998faf 100644 --- a/public/language/nb/admin/settings/chat.json +++ b/public/language/nb/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maksimal lengde på navn på chat", "max-room-size": "Maksimalt antall brukere i chat", "delay": "Tid mellom chatmeldinger (ms)", - "notification-delay": "Varslingsforsinkelse for chatmeldinger", - "notification-delay-help": "Ytterligere meldinger sendt innen denne tiden blir samlet, og brukeren varsles én gang per forsinkelsesperiode. Sett denne til 0 for å deaktivere forsinkelsen.", "restrictions.seconds-edit-after": "Antall sekunder en chatmelding forblir redigerbar.", "restrictions.seconds-delete-after": "Antall sekunder en chatmelding forblir slettbar." } \ No newline at end of file diff --git a/public/language/nb/admin/settings/email.json b/public/language/nb/admin/settings/email.json index c4a13cc6be..b5b23065fc 100644 --- a/public/language/nb/admin/settings/email.json +++ b/public/language/nb/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling forhindrer NodeBB fra å opprette en ny tilkobling for hver e-post. Dette gjelder bare hvis SMTP Transport er aktivert.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Endre e-postmal", "template.select": "Velg e-postmal", "template.revert": "Gjenopprett til original", + "test-smtp-settings": "Test SMTP Settings", "testing": "E-posttesting", + "testing.success": "Test Email Sent.", "testing.select": "Velg e-postmal", "testing.send": "Send test-e-post", - "testing.send-help": "Test-e-posten vil bli sendt til e-postadressen til den påloggede brukeren.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "E-postsammendrag", "subscriptions.disable": "Deaktiver e-postsammendrag", "subscriptions.hour": "Tidspunkt for sammendrag", diff --git a/public/language/nb/admin/settings/notifications.json b/public/language/nb/admin/settings/notifications.json index 989afda78d..6bee8adc05 100644 --- a/public/language/nb/admin/settings/notifications.json +++ b/public/language/nb/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Velkomstvarsel", "welcome-notification-link": "Lenke for velkomstvarsel", "welcome-notification-uid": "Bruker-ID for velkomstvarsel (UID)", - "post-queue-notification-uid": "Bruker-ID for innleggskø (UID)" + "post-queue-notification-uid": "Bruker-ID for innleggskø (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/nb/admin/settings/post.json b/public/language/nb/admin/settings/post.json index 548446786d..61f21ea532 100644 --- a/public/language/nb/admin/settings/post.json +++ b/public/language/nb/admin/settings/post.json @@ -5,8 +5,8 @@ "sorting.oldest-to-newest": "Eldste til nyeste", "sorting.newest-to-oldest": "Nyeste til eldste", "sorting.recently-replied": "Sist besvart", - "sorting.recently-created": "Nylig opprettet", - "sorting.most-votes": "Flest stemmer", + "sorting.recently-created": "Sist opprettet", + "sorting.most-votes": "Flest anbefalinger", "sorting.most-posts": "Flest innlegg", "sorting.most-views": "Flest visninger", "sorting.topic-default": "Standard trådsortering", diff --git a/public/language/nb/admin/settings/reputation.json b/public/language/nb/admin/settings/reputation.json index 9612efe855..ad1df9d37a 100644 --- a/public/language/nb/admin/settings/reputation.json +++ b/public/language/nb/admin/settings/reputation.json @@ -2,18 +2,18 @@ "reputation": "Omdømmeinnstillinger", "disable": "Deaktiver omdømmesystem", "disable-down-voting": "Deaktiver nedstemning", - "upvote-visibility": "Synlighet for oppstemmer", - "upvote-visibility-all": "Alle kan se oppstemmer", - "upvote-visibility-loggedin": "Bare innloggede brukere kan se oppstemmer", - "upvote-visibility-privileged": "Bare privilegerte brukere kan se oppstemmer", - "downvote-visibility": "Synlighet for nedstemmer", + "upvote-visibility": "Synlighet for anbefalinger", + "upvote-visibility-all": "Alle kan se anbefalinger", + "upvote-visibility-loggedin": "Bare innloggede brukere kan se anbefalinger", + "upvote-visibility-privileged": "Bare privilegerte brukere kan se anbefalinger", + "downvote-visibility": "Synlighet for anbefalinger", "downvote-visibility-all": "Alle kan se nedstemmer", "downvote-visibility-loggedin": "Bare innloggede brukere kan se nedstemmer", "downvote-visibility-privileged": "Bare privilegerte brukere kan se nedstemmer", "thresholds": "Aktivitetsterskler", - "min-rep-upvote": "Minimum omdømme for å stemme opp innlegg", - "upvotes-per-day": " Tilrådinger per dag (sett til 0 for ubegrensede tilrådinger)", - "upvotes-per-user-per-day": " Tilrådinger per bruker per dag (sett til 0 for ubegrensede tilrådinger)", + "min-rep-upvote": "Minimum omdømme for å anbefale innlegg", + "upvotes-per-day": "Anbefalinger per dag (sett til 0 for ubegrensede tilrådinger)", + "upvotes-per-user-per-day": "Anbefalinger per bruker per dag (sett til 0 for ubegrensede tilrådinger)", "min-rep-downvote": "Minimum omdømme for å stemme ned innlegg", "downvotes-per-day": "Nedstemmer per dag (sett til 0 for ubegrensede nedstemmer)", "downvotes-per-user-per-day": "Nedstemmer per bruker per dag (sett til 0 for ubegrensede nedstemmer)", @@ -25,11 +25,11 @@ "min-rep-profile-picture": "Minimum omdømme for å legge til \"Profilbilde\" i brukerprofil", "min-rep-cover-picture": "Minimum omdømme for å legge til \"Omslagsbilde\" i brukerprofil", - "flags": "Flagginnstillinger", - "flags.limit-per-target": "Maksimalt antall ganger noe kan flagges", + "flags": "Raporteringsinnstillinger", + "flags.limit-per-target": "Maksimalt antall ganger noe kan rapporterest", "flags.limit-per-target-placeholder": "Standard: 0", "flags.limit-per-target-help": "Når et innlegg eller en bruker blir flagget flere ganger, regnes hvert ekstra flagg som en \"rapport\" og legges til det opprinnelige flagget. Sett dette alternativet til et tall større enn null for å begrense antall rapporter et element kan motta.", - "flags.limit-post-flags-per-day": "Maksimalt antall innlegg som kan flagges per dag", + "flags.limit-post-flags-per-day": "Maksimalt antall innlegg som kan rapporteres per dag", "flags.limit-post-flags-per-day-help": "Angi antall innlegg som kan flagges av en bruker innen en dag.", "flags.limit-user-flags-per-day": "Maksimalt antall brukere som kan flagges per dag", "flags.limit-user-flags-per-day-help": "Angi antall brukere som kan flagges av en bruker innen en dag.", diff --git a/public/language/nb/admin/settings/uploads.json b/public/language/nb/admin/settings/uploads.json index ff5386fedb..e69069d936 100644 --- a/public/language/nb/admin/settings/uploads.json +++ b/public/language/nb/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Bilder bredere enn denne verdien vil bli avvist.", "reject-image-height": "Maksimal bildehøyde (i piksler)", "reject-image-height-help": "Bilder høyere enn denne verdien vil bli avvist.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Tillat brukere å laste opp emneminiatyrbilder", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Størrelse på emneminiatyrbilder", "allowed-file-extensions": "Tillatte filtyper", "allowed-file-extensions-help": "Skriv inn kommaseparerte filtyper her (f.eks. pdf,xls,doc). En tom liste betyr at alle filtyper er tillatt.", diff --git a/public/language/nb/admin/settings/user.json b/public/language/nb/admin/settings/user.json index 9b73670df6..608724d2ea 100644 --- a/public/language/nb/admin/settings/user.json +++ b/public/language/nb/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Vis e-post", "show-fullname": "Vis fullt navn", "restrict-chat": "Tillat kun chatmeldinger fra brukere jeg følger", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Åpne utgående lenker i ny fane", "topic-search": "Aktiver søk i tråder", "update-url-with-post-index": "Oppdater URL med innleggindeks under navigering i tråder", diff --git a/public/language/nb/admin/settings/web-crawler.json b/public/language/nb/admin/settings/web-crawler.json index 5e89a97d12..42cf2337e0 100644 --- a/public/language/nb/admin/settings/web-crawler.json +++ b/public/language/nb/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Deaktiver RSS-feeder", "disable-sitemap-xml": "Deaktiver Sitemap.xml", "sitemap-topics": "Antall emner som vises i nettstedskartet", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Tøm hurtigbuffer for nettstedskart", "view-sitemap": "Vis nettstedskart" } \ No newline at end of file diff --git a/public/language/nb/aria.json b/public/language/nb/aria.json index bfe7416709..e5700b7667 100644 --- a/public/language/nb/aria.json +++ b/public/language/nb/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "Emneord fulgt av bruker", "delete-upload-button": "Slett opplastingsknapp", - "group-page-link-for": "Gruppesidelink for %1" + "group-page-link-for": "Gruppesidelink for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/nb/category.json b/public/language/nb/category.json index 26b4457339..a3da7a42d8 100644 --- a/public/language/nb/category.json +++ b/public/language/nb/category.json @@ -1,12 +1,13 @@ { "category": "Kategori", "subcategories": "Underkategorier", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", - "new-topic-button": "Nytt emne", + "new-topic-button": "Nytt innlegg", "guest-login-post": "Logg inn for å publisere innlegg", "no-topics": "Denne kategorien er foreløpig tom.
Har du noe å dele? Opprett et innlegg her!", + "no-followers": "Ingen følger denne kategorien ennå. Følg den for å få oppdateringer om endringer.", "browsing": "leser", "no-replies": "Ingen har svart", "no-new-posts": "Ingen nye innlegg.", @@ -16,10 +17,10 @@ "tracking": "Følg", "not-watching": "Følger ikke", "ignoring": "Ignorerer", - "watching.description": "Varsle meg om nye emner.
Vis emner i ulest og nylig", - "tracking.description": "Følg emner i ulest og nylig", - "not-watching.description": "Ikke vis emner i ulest, vis i nylig", - "ignoring.description": "Ikke vis emner i ulest & nylig", + "watching.description": "Varsle meg om nye innlegg.
Vis innlegg i ulest og nylig", + "tracking.description": "Viser innlegg i Ulest og Siste", + "not-watching.description": "Ikke vis innlegg i Uleste, vis i Siste", + "ignoring.description": "Ikke vis innlegg i Uleste eller Siste", "watching.message": "Du ser nå på oppdateringer fra denne kategorien og alle underkategorier", "tracking.message": "Du følger nå oppdateringer fra denne kategorien og alle underkategorier", "notwatching.message": "Du ser ikke på oppdateringer fra denne kategorien og alle underkategorier", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index a5d920185c..66809774c4 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -3,6 +3,7 @@ "invalid-json": "Ugyldig JSON", "wrong-parameter-type": "En verdi av typen %3 var forventet for egenskapen `%1`, men %2 ble mottatt i stedet", "required-parameters-missing": "Nødvendige parametere manglet fra dette API-kallet: %1", + "reserved-ip-address": "Nettverksforespørsler til reservert IP-område er ikke tillatt.", "not-logged-in": "Du ser ikke ut til å være logget inn.", "account-locked": "Kontoen din har blitt midlertidig låst", "search-requires-login": "Søking krever en konto - vennligst logg inn eller registrer deg.", @@ -63,12 +64,12 @@ "no-group": "Gruppe eksisterer ikke", "no-user": "Bruker eksisterer ikke", "no-teaser": "Teaseren eksisterer ikke", - "no-flag": "Flagg eksisterer ikke", + "no-flag": "Rapport eksisterer ikke", "no-chat-room": "Chatten eksisterer ikke", "no-privileges": "Du har ikke nok rettigheter til å utføre denne handlingen.", "category-disabled": "Kategori deaktivert", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Innlegget er slettet.", + "topic-locked": "Innlegget er låst", "post-edit-duration-expired": "Du har bare lov til å redigere innlegg i %1 sekund(er) etter at det er sendt", "post-edit-duration-expired-minutes": "Du har bare lov til å redigere innlegg i %1 sekund(er) etter at det er sendt", "post-edit-duration-expired-minutes-seconds": "Du har bare lov til å redigere innlegg i %1 minutt(er), %2 sekund(er) etter at det er sendt", @@ -146,6 +147,7 @@ "post-already-restored": "Dette innlegget har allerede blitt gjenopprettet", "topic-already-deleted": "Dette emnet har allerede blitt slettet", "topic-already-restored": "Dette emnet har allerede blitt gjenopprettet", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Du kan ikke slette hovedinnlegget. Vennligst slett emnet i stedet.", "topic-thumbnails-are-disabled": "Emne-minatyrbilder har blitt deaktivert", "invalid-file": "Ugyldig fil", @@ -154,6 +156,8 @@ "about-me-too-long": "Beklager, om meg kan ikke være lengre enn %1 tegn.", "cant-chat-with-yourself": "Du kan ikke chatte med deg selv!", "chat-restricted": "Denne brukeren har begrenset sine samtalemeldinger. De må følge deg før du kan chatte med dem", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "Du har blitt blokkert av denne brukeren.", "chat-disabled": "Chattesystem er deaktivert", "too-many-messages": "Du har sendt for mange meldinger, vennligst vent en stund.", @@ -170,11 +174,11 @@ "cant-remove-users-from-chat-room": "Kan ikke fjerne brukere fra chat.", "chat-room-name-too-long": "Navnet på chat er for langt. Navn kan ikke være lengre enn %1 tegn.", "remote-chat-received-too-long": "You received a chat message from %1, but it was too long and was rejected.", - "already-voting-for-this-post": "Du har allerede stemt på dette innlegget", + "already-voting-for-this-post": "Du har allerede anbefalt dette innlegget", "reputation-system-disabled": "Omdømmesystemet er deaktivert.", "downvoting-disabled": "Nedstemming er deaktivert", "not-enough-reputation-to-chat": "Du trenger %1 omdømme for å chatte", - "not-enough-reputation-to-upvote": "Du trenger %1 omdømme for å tilrå.", + "not-enough-reputation-to-upvote": "Du trenger %1 omdømme for å anbefale.", "not-enough-reputation-to-downvote": "Du trenger %1 omdømme for å stemme ned.", "not-enough-reputation-to-post-links": "Du trenger %1 omdømme for å poste lenker", "not-enough-reputation-to-flag": "Du trenger %1 omdømme for å flagge dette innlegget.", @@ -191,17 +195,17 @@ "custom-user-field-invalid-number": "Nummeret for egendefinert felt er ugyldig, %1", "custom-user-field-invalid-date": "Dato for egendefiner felt er ugyldig, %1", "invalid-custom-user-field": "Ugyldig egendefinert feltnavn, \"%1\" er allerede i bruk av NodeBB", - "post-already-flagged": "Du har allerede flagget dette innlegget", - "user-already-flagged": "Du har allerede flagget denne brukeren", - "post-flagged-too-many-times": "Dette innlegget har allerede blitt flagget av andre", - "user-flagged-too-many-times": "Denne brukeren har allerede blitt flagget av andre", - "too-many-post-flags-per-day": "Du kan bare flagge %1 innlegg per dag", - "too-many-user-flags-per-day": "Du kan bare flagge %1 brukere per dag", - "cant-flag-privileged": "Du har ikke lov til å flagge profiler eller innhold fra priveligerte burkere (moderatorer/ globale moderatorer/ administratorer)", + "post-already-flagged": "Du har allerede rapportert dette innlegget", + "user-already-flagged": "Du har allerede rapportert denne brukeren", + "post-flagged-too-many-times": "Dette innlegget har allerede blitt rapportert av andre", + "user-flagged-too-many-times": "Denne brukeren har allerede blitt rapportert av andre", + "too-many-post-flags-per-day": "Du kan bare rapportere %1 innlegg per dag", + "too-many-user-flags-per-day": "Du kan bare rapportere %1 brukere per dag", + "cant-flag-privileged": "Du har ikke lov til å rapportere profiler eller innhold fra priveligerte burkere (moderatorer/ globale moderatorer/ administratorer)", "cant-locate-flag-report": "Kan ikke finne flaggrapporten", - "self-vote": "Du kan ikke stemme på ditt eget innlegg", - "too-many-upvotes-today": "Du kan bare gi oppstemme %1 ganger pr. dag", - "too-many-upvotes-today-user": "Du kan bare gi oppstemme til en bruker %1 ganger pr. dag", + "self-vote": "Du kan ikke anbefale ditt eget innlegg", + "too-many-upvotes-today": "Du kan bare anbefale %1 ganger pr. dag", + "too-many-upvotes-today-user": "Du kan bare anbefale en bruker %1 ganger pr. dag", "too-many-downvotes-today": "Du kan bare nedstemme %1 gang om dagen", "too-many-downvotes-today-user": "Du kan bare nedstemme en bruker %1 ganger om dagen", "reload-failed": "NodeBB støtte på et problem under lasting på nytt: \"%1\". NodeBB vil fortsette å servere eksisterende klientside ressurser, selv om du burde angre endringene du gjorde før du lastet på nytt.", @@ -225,6 +229,7 @@ "no-topics-selected": "Ingen tråder valgt!", "cant-move-to-same-topic": "Du kan ikke flytte innlegg til samme tråd!", "cant-move-topic-to-same-category": "Du kan ikke flytte tråd til samme kategori!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Du kan ikke blokkere deg selv!", "cannot-block-privileged": "Du kan ikke blokkere administratorer eller globale moderatorer", "cannot-block-guest": "Gjester kan ikke blokkere andre brukere", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Får ikke tilgang til serveren for øyeblikket. Klikk her for å prøve igjen, eller prøv igjen senere", "invalid-plugin-id": "Ugyldig innstikk-ID", "plugin-not-whitelisted": "Ute av stand til å installere tillegget – bare tillegg som er hvitelistet av NodeBB sin pakkebehandler kan bli installert via administratorkontrollpanelet", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "Du har ikke tillatelse til å endre plugin-status da de er definert under kjøring (config.json, miljøvariabler eller terminalargumenter)Vennligst endre konfigurasjonen i stedet.", "theme-not-set-in-configuration": "Når aktive plugins er definert i konfigurasjonen, krever endring av tema at det nye temaet legges til i listen over aktive plugins før det oppdateres i ACP.", @@ -246,6 +252,7 @@ "api.401": "En gyldig innloggingssesjon ble ikke funnet. Logg inn og prøv igjen.", "api.403": "Du er ikke autorisert til å gjøre denne forespørselen", "api.404": "Ugyldig API-kall", + "api.413": "The request payload is too large", "api.426": "HTTPS er påkrevd for forespørsler til skrive-api. Ver vennlig å sende forespørselen på nytt via HTTPS", "api.429": "Du har gjort for mange forespørsler. Prøv igjen senere.", "api.500": "En uventet feil oppstod mens vi prøvde å betjene forespørsel din.", diff --git a/public/language/nb/flags.json b/public/language/nb/flags.json index 305a699267..d0d971a832 100644 --- a/public/language/nb/flags.json +++ b/public/language/nb/flags.json @@ -35,7 +35,7 @@ "fewer-filters": "Færre filtre", "quick-actions": "Hurtighandlinger", - "flagged-user": "Flagget bruker", + "flagged-user": "Rapportert bruker", "view-profile": "Vis profil", "start-new-chat": "Start ny chat", "go-to-target": "Vis flaggmålet", @@ -52,8 +52,8 @@ "add-note": "Legg til notat", "edit-note": "Rediger notat", "no-notes": "Ingen delte notater", - "delete-note-confirm": "Er du sikker på at du ønsker å slette dette flaggnotatet?", - "delete-flag-confirm": "Er du sikker på at du vil slette dette flagget?", + "delete-note-confirm": "Er du sikker på at du ønsker å slette denne rapporteringen?", + "delete-flag-confirm": "Er du sikker på at du vil slette denne rapporteringen?", "note-added": "Notat lagt til", "note-deleted": "Notat slettet", "flag-deleted": "Flagg slettet", @@ -75,7 +75,7 @@ "sort-all": "Alle flaggtyper", "sort-posts-only": "Kun innlegg", "sort-downvotes": "Flest nedstemminger", - "sort-upvotes": "Flest tilrådinger", + "sort-upvotes": "Flest anbefalinger", "sort-replies": "Flest kommentarer", "modal-title": "Rapporter innhold", diff --git a/public/language/nb/global.json b/public/language/nb/global.json index 9895572516..54b9f793ab 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -68,20 +68,22 @@ "users": "Brukere", "topics": "Emner", "posts": "Innlegg", + "crossposts": "Cross-posts", "x-posts": "%1 innlegg", "x-topics": "%1 emner", "x-reputation": "%1 omdømme", "best": "Best", "controversial": "Kontroversiell", - "votes": "Stemmer", - "x-votes": "%1 stemmer", - "voters": "Velgere", - "upvoters": "Tilrår", - "upvoted": "Tilrådde", + "votes": "Anbefalinger", + "x-votes": "%1 anbefalinger", + "voters": "Anbefaler", + "upvoters": "Anbefaler", + "upvoted": "Anbefalt", "downvoters": "Nedstemmere", "downvoted": "Nedstemt", "views": "Visninger", "posters": "Innlegg", + "watching": "Følger", "reputation": "Omdømme", "lastpost": "Siste innlegg", "firstpost": "Første innlegg", @@ -100,8 +102,8 @@ "guest-posted-ago": "Gjest skrev den %1", "last-edited-by": "Sist endret av %1", "edited-timestamp": "Endret %1", - "norecentposts": "Ingen nylige innlegg", - "norecenttopics": "Ingen nye tråder", + "norecentposts": "Ingen nye svar", + "norecenttopics": "Ingen nye innlegg", "recentposts": "Nye innlegg", "recentips": "Siste innloggede IPer", "moderator-tools": "Moderatorverktøy", diff --git a/public/language/nb/groups.json b/public/language/nb/groups.json index 2be3bc7921..ce3a3d5221 100644 --- a/public/language/nb/groups.json +++ b/public/language/nb/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Alle grupper", "groups": "Grupper", "members": "Medlemmer", + "x-members": "%1 member(s)", "view-group": "Vis gruppe", "owner": "Gruppeeier", "new-group": "Opprett ny gruppe", diff --git a/public/language/nb/login.json b/public/language/nb/login.json index 7b71016b86..a95e58c37b 100644 --- a/public/language/nb/login.json +++ b/public/language/nb/login.json @@ -5,7 +5,7 @@ "forgot-password": "Glemt passord?", "alternative-logins": "Alternativ innlogging", "failed-login-attempt": "Innlogging mislyktes", - "login-successful": "Du har blitt logget inn!", + "login-successful": "Du er innlogget!", "dont-have-account": "Har du ikke en konto?", "logged-out-due-to-inactivity": "Du har blitt logget ut av administratorsidene fordi du har vært inaktiv for lenge", "caps-lock-enabled": "Caps Lock er skrudd på" diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index 08e42a051e..e42d213c50 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -1,15 +1,15 @@ { "chat.room-id": "Rom %1", - "chat.chatting-with": "Chat med", - "chat.placeholder": "Skriv chat-melding her, dra og slipp bilder", - "chat.placeholder.mobile": "Skriv chat-melding", + "chat.chatting-with": "Send melding til", + "chat.placeholder": "Skriv melding", + "chat.placeholder.mobile": "Skriv melding", "chat.placeholder.message-room": "Melding #%1", "chat.scroll-up-alert": "Gå til siste melding", "chat.usernames-and-x-others": "%1 & %2 andre", "chat.chat-with-usernames": "Chat med %1", "chat.chat-with-usernames-and-x-others": "Chat med %1 & %2 andre", - "chat.send": "Send", - "chat.no-active": "Du har ingen aktive chatter.", + "chat.send": "Publiser", + "chat.no-active": "Du har ingen aktive samtaler", "chat.user-typing-1": "%1 skriver ...", "chat.user-typing-2": "%1 og %2 skriver ...", "chat.user-typing-3": "%1, %2 og %3 skriver ...", @@ -20,11 +20,11 @@ "chat.mark-all-read": "Marker alle som lest", "chat.no-messages": "Velg en mottaker for å vise meldingshistorikk", "chat.no-users-in-room": "Ingen brukere i dette rommet", - "chat.recent-chats": "Nylige chatter", + "chat.recent-chats": "Nylige samtaler", "chat.contacts": "Kontakter", "chat.message-history": "Meldingshistorikk", "chat.message-deleted": "Melding slettet", - "chat.options": "Alternativer for chat", + "chat.options": "Alternativer for meldinger", "chat.pop-out": "Pop-ut chat", "chat.minimize": "Minimer", "chat.maximize": "Maksimer", @@ -40,7 +40,7 @@ "chat.unpin-message": "Fjern festing", "chat.public-rooms": "Offentlige rom (%1)", "chat.private-rooms": "Private rom (%1)", - "chat.create-room": "Opprett chat", + "chat.create-room": "Opprett melding", "chat.private.option": "Privat (Bare synlig for brukere lagt til i rommet)", "chat.public.option": "Offentlig (Synlig for alle brukere i valgte grupper)", "chat.public.groups-help": "For å opprette en chat synlig for alle brukere, velg \"registrerte brukere\" fra gruppelisten.", @@ -48,20 +48,21 @@ "chat.add-user": "Legg til bruker", "chat.notification-settings": "Varslingsinnstillinger", "chat.default-notification-setting": "Standard varslingsinnstilling", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Romstandard", "chat.notification-setting-none": "Ingen varsler", "chat.notification-setting-at-mention-only": "Kun ved @nevning", "chat.notification-setting-all-messages": "Alle meldinger", "chat.select-groups": "Velg grupper", - "chat.add-user-help": "Søk etter brukere her. Når valgt, vil brukeren legges til i chatten. Nye brukere vil ikke se meldinger skrevet før de ble lagt til. Bare romeiere () kan fjerne brukere fra rom.", - "chat.confirm-chat-with-dnd-user": "Denne brukeren har satt statusen sin til \"Ikke forstyrr\". Vil du fortsatt chatte med dem?", + "chat.add-user-help": "Søk etter brukere her. Når valgt, vil brukeren legges til i meldingstråden. Nye brukere vil ikke se meldinger skrevet før de ble lagt til. Bare romeiere () kan fjerne brukere fra rom.", + "chat.confirm-chat-with-dnd-user": "Denne brukeren har satt statusen sin til \"Ikke forstyrr\". Vil du fortsatt sende en melding?", "chat.room-name-optional": "Romnavn (valgfritt)", "chat.rename-room": "Endre navn på rom", "chat.rename-placeholder": "Skriv inn romnavnet her", "chat.rename-help": "Romnavnet vil være synlig for alle deltakere.", "chat.leave": "Forlat", "chat.leave-room": "Forlat rom", - "chat.leave-prompt": "Er du sikker på at du vil forlate dette rommet?", + "chat.leave-prompt": "Er du sikker på at du vil forlate denne chatten?", "chat.leave-help": "Hvis du forlater, fjernes du fra fremtidige samtaler. Ved gjeninntreden vil du ikke se meldingshistorikk fra før du ble lagt til.", "chat.delete": "Slett", "chat.delete-room": "Slett rom", @@ -81,7 +82,7 @@ "composer.hide-preview": "Skjul forhåndsvisning", "composer.help": "Hjelp", "composer.user-said-in": "%1 sa i %2:", - "composer.user-said": "%1 sa:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Er du sikker på at du vil forkaste dette innlegget?", "composer.submit-and-lock": "Send og lås", "composer.toggle-dropdown": "Veksle nedtrekksfelt", @@ -121,7 +122,7 @@ "bootbox.cancel": "Avbryt", "bootbox.confirm": "Bekreft", "bootbox.submit": "Send inn", - "bootbox.send": "Send", + "bootbox.send": "Publiser", "cover.dragging-title": "Posisjoner bilde", "cover.dragging-message": "Dra omslagsbildet til ønsket posisjon og klikk \"Lagre\"", "cover.saved": "Omslagsbilde og posisjon lagret", diff --git a/public/language/nb/notifications.json b/public/language/nb/notifications.json index 94425dba95..6f968c6fce 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -11,18 +11,18 @@ "new-notification": "Du har en ny varsling", "you-have-unread-notifications": "Du har uleste varsler.", "all": "Alle", - "topics": "Emner", + "topics": "Innlegg", "tags": "Emneord", "categories": "Kategorier", "replies": "Svar", - "chat": "Chatter", - "group-chat": "Gruppechat", + "chat": "Chat", + "group-chat": "Gruppemelding", "public-chat": "Offentlig chat", "follows": "Følger", - "upvote": " Tilrår", + "upvote": "Anbefaler", "awards": "Tildelninger", - "new-flags": "Nye flagg", - "my-flags": "Flagg som er tildelt meg", + "new-flags": "Nye rapporteringer", + "my-flags": "My Flags", "bans": "Forbud", "new-message-from": "Ny melding fra %1", "new-messages-from": "%1 nye meldinger fra %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 og %2 skrev i %4", "user-posted-in-public-room-triple": "%1, %2 og %3 skrev i %5", "user-posted-in-public-room-multiple": "%1, %2 og %3 andre skrev i %5", - "upvoted-your-post-in": "%1 har tilrådd innlegget ditt i %2.", - "upvoted-your-post-in-dual": "%1 og %2 har tilrådd innlegget ditt i %3.", - "upvoted-your-post-in-triple": "%1, %2 og %3 har tilrådd innlegget ditt i %4.", - "upvoted-your-post-in-multiple": "%1, %2 og %3 andre har tilrådd innlegget ditt i %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 har flyttet innlegget ditt til %2.", "moved-your-topic": "%1 har flyttet %2", - "user-flagged-post-in": "%1 har flagget et innlegg i %2", - "user-flagged-post-in-dual": "%1 og %2 flagget et innlegg i %3", - "user-flagged-post-in-triple": "%1, %2 og %3 flagget et innlegg i %4", - "user-flagged-post-in-multiple": "%1, %2 og %3 andre flagget et innlegg i %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flagget en brukerprofil (%2)", "user-flagged-user-dual": "%1 og %2 har flagget en brukerprofil (%3)", "user-flagged-user-triple": "%1, %2 og %3 har flagget en brukerprofil (%4)", "user-flagged-user-multiple": "%1, %2 og %3 andre har flagget en brukerprofil (%4)", - "user-posted-to": "%1 har skrevet et svar til: %2", - "user-posted-to-dual": "%1 og %2 har svart på innlegget ditt i %3", - "user-posted-to-triple": "%1, %2 og %3 har svart til: %4", - "user-posted-to-multiple": "%1, %2 og %3 andre har svart til: %4", - "user-posted-topic": "%1 har skrevet en ny tråd: %2", - "user-edited-post": "%1 har redigert et innlegg i %2", - "user-posted-topic-with-tag": "%1 har publisert %2 (merket %3)", - "user-posted-topic-with-tag-dual": "%1 har publisert %2 (merket %3 og %4)", - "user-posted-topic-with-tag-triple": "%1 har publisert %2 (merket %3, %4 og %5)", - "user-posted-topic-with-tag-multiple": "%1 har publisert %2 (merket %3)", - "user-posted-topic-in-category": "%1 har publisert et nytt innlegg i %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 begynte å følge deg.", "user-started-following-you-dual": "%1 og 2% har begynt å følge deg.", "user-started-following-you-triple": "%1, %2 og %3 begynte å følge deg.", @@ -71,24 +71,24 @@ "users-csv-exported": "Bruker csv eksportert, klikk for å laste ned", "post-queue-accepted": "Innlegget ditt i køen er godtatt. Klikk her for å se innlegget ditt.", "post-queue-rejected": "Innlegget dit i køen har blitt avvist", - "post-queue-notify": "Varsel mottatt for innlegg i kø:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-post bekreftet", "email-confirmed-message": "Takk for at du har validert din e-post. Kontoen din er nå fullstendig aktivert.", "email-confirm-error-message": "Det oppsto et problem under validering av e-posten din. Koden kan ha vært ugyldig eller ha utløpt.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Bekreftelses-e-post sendt.", "none": "Ingen", "notification-only": "Kun varsel", "email-only": "Kun e-post", "notification-and-email": "Varsel og e-post", - "notificationType-upvote": "Når noen tilrår innlegget ditt", - "notificationType-new-topic": "Når noen du følger legger ut et emne", - "notificationType-new-topic-with-tag": "Når et emne publiseres med et stikkord du følger", - "notificationType-new-topic-in-category": "Når et emne er lagt ut i en kategori du ser på", - "notificationType-new-reply": "Når et nytt svar er lagt ut i et emne du overvåker", - "notificationType-post-edit": "Når et innlegg er redigert i et emne du overvåker", + "notificationType-upvote": "Når noen anbefaler innlegget ditt", + "notificationType-new-topic": "Når noen du følger legger ut et innlegg", + "notificationType-new-topic-with-tag": "Når et innlegg publiseres med et stikkord du følger", + "notificationType-new-topic-in-category": "Når et innlegg er lagt ut i en kategori du følger", + "notificationType-new-reply": "Når et nytt svar er lagt ut i innlegg du følger", + "notificationType-post-edit": "Når et svar er redigert i et innlegg du følger", "notificationType-follow": "Når noen starter å følge deg", - "notificationType-new-chat": "Når du mottar en melding i chat", + "notificationType-new-chat": "Når du mottar en melding", "notificationType-new-group-chat": "Når du mottar en gruppemelding i chat", "notificationType-new-public-chat": "Når du mottar en melding i en offentlig chat", "notificationType-group-invite": "Når du får tilsendt en gruppeinvitasjon", @@ -96,7 +96,7 @@ "notificationType-group-request-membership": "Når noen sender en forespørsel om å bli med i en gruppe du eier", "notificationType-new-register": "Når noen blir lag til i kø for å registrere", "notificationType-post-queue": "Når et nytt innlegg er satt i kø", - "notificationType-new-post-flag": "Når ett innlegg er flagget", + "notificationType-new-post-flag": "Når ett innlegg er rapportert", "notificationType-new-user-flag": "Når en bruker er flagget", "notificationType-new-reward": "Når du får en ny tildelning", "activitypub.announce": "%1 delte din post i %2 til sine følgere.", diff --git a/public/language/nb/pages.json b/public/language/nb/pages.json index 90436898a9..09c6a6b616 100644 --- a/public/language/nb/pages.json +++ b/public/language/nb/pages.json @@ -5,13 +5,13 @@ "popular-week": "Populære emner denne uken", "popular-month": "Populære emner denne måneden", "popular-alltime": "Mest populære emner for all tid", - "recent": "Nylige emner", - "top-day": "Dagens emne med flest stemmer", - "top-week": "Emne med flest stemmer denne uken", - "top-month": "Emne med flest stemmer denne måneden", - "top-alltime": "Emner med flest stemmer", + "recent": "Siste innlegg", + "top-day": "Dagens emne med flest anbefalinger", + "top-week": "Emne med flest anbefalinger denne uken", + "top-month": "Emne med flest anbefalinger denne måneden", + "top-alltime": "Emner med flest anbefalinger", "moderator-tools": "Moderatorverktøy", - "flagged-content": "Flagget innhold", + "flagged-content": "Rapportert innhold", "ip-blacklist": "IP-svarteliste", "post-queue": "Innleggskø", "registration-queue": "Registreringskø", @@ -34,7 +34,7 @@ "group": "%1 gruppe", "chats": "Samtaler", "chat": "Samtale med %1", - "flags": "Flagg", + "flags": "Rapporteringer", "flag-details": "Flagg %1 detaljer", "world": "Verden", "account/edit": "Endrer \"%1\"", @@ -56,7 +56,7 @@ "account/watched": "Innlegg overvåket av %1", "account/ignored": "Emner ignorert av %1", "account/read": "Emner lest av %1", - "account/upvoted": "Innlegg tilrådd av %1", + "account/upvoted": "Innlegg anbefalt av %1", "account/downvoted": "Innlegg nedstemt av %1", "account/best": "Beste innlegg skrevet av %1", "account/controversial": "Kontroversielle innlegg skrevet av %1", diff --git a/public/language/nb/recent.json b/public/language/nb/recent.json index e8ec1af77b..0403e627a9 100644 --- a/public/language/nb/recent.json +++ b/public/language/nb/recent.json @@ -1,11 +1,11 @@ { - "title": "Nylige", + "title": "Siste", "day": "Dag", "week": "Uke", "month": "Måned", "year": "År", "alltime": "All tid", - "no-recent-topics": "Det er ingen nye emner.", + "no-recent-topics": "Det er ingen nye innlegg.", "no-popular-topics": "Det er ingen populære emner.", "load-new-posts": "Last inn nye innlegg", "uncategorized.title": "Alle kjente emner", diff --git a/public/language/nb/search.json b/public/language/nb/search.json index 498ab1f9a5..146febd8da 100644 --- a/public/language/nb/search.json +++ b/public/language/nb/search.json @@ -1,7 +1,7 @@ { "type-to-search": "Skriv for å søke", "results-matching": "%1 resultat(er) samsvarer med \"%2\", (%3 sekunder)", - "no-matches": "Ingen treff funnet", + "no-matches": "Ingen treff", "advanced-search": "Avansert søk", "in": "I", "in-titles": "I titler", @@ -38,7 +38,7 @@ "relevance": "Relevanse", "time": "Tid", "post-time": "Innleggstid", - "votes": "Stemmer", + "votes": "Anbefalinger", "newer-than": "Nyere enn", "older-than": "Eldre enn", "any-date": "Alle datoer", @@ -67,7 +67,7 @@ "sort": "Sorter", "last-reply-time": "Siste svartid", "topic-title": "Trådtittel", - "topic-votes": "Stemmer på tråd", + "topic-votes": "Anbefalinger av innlegg", "number-of-replies": "Antall svar", "number-of-views": "Antall visninger", "topic-start-date": "Startdato for tråd", @@ -79,8 +79,8 @@ "sort-by-relevance-asc": "Sorter etter: Relevanse (stigende rekkefølge)", "sort-by-timestamp-desc": "Sorter etter: Innleggstid (synkende rekkefølge)", "sort-by-timestamp-asc": "Sorter etter: Innleggstid (stigende rekkefølge)", - "sort-by-votes-desc": "Sorter etter: Stemmer (synkende rekkefølge)", - "sort-by-votes-asc": "Sorter etter: Stemmer (stigende rekkefølge)", + "sort-by-votes-desc": "Sorter etter: Anbefalinger (synkende rekkefølge)", + "sort-by-votes-asc": "Sorter etter: Anbefalinger (stigende rekkefølge)", "sort-by-topic.lastposttime-desc": "Sorter etter: Siste svartid (synkende rekkefølge)", "sort-by-topic.lastposttime-asc": "Sorter etter: Siste svartid (stigende rekkefølge)", "sort-by-topic.title-desc": "Sorter etter: Emnetittel (synkende rekkefølge)", @@ -89,8 +89,8 @@ "sort-by-topic.postcount-asc": "Sorter etter: Antall svar (stigende rekkefølge)", "sort-by-topic.viewcount-desc": "Sorter etter: Antall visninger (synkende rekkefølge)", "sort-by-topic.viewcount-asc": "Sorter etter: Antall visninger (stigende rekkefølge)", - "sort-by-topic.votes-desc": "Sorter etter: Trådstemmer (synkende rekkefølge)", - "sort-by-topic.votes-asc": "Sorter etter: Trådstemmer (stigende rekkefølge)", + "sort-by-topic.votes-desc": "Sorter etter: Anbefalinger (synkende rekkefølge)", + "sort-by-topic.votes-asc": "Sorter etter: Anbefalinger (stigende rekkefølge)", "sort-by-topic.timestamp-desc": "Sorter etter: Trådstartdato (synkende rekkefølge)", "sort-by-topic.timestamp-asc": "Sorter etter: Trådstartdato (stigende rekkefølge)", "sort-by-user.username-desc": "Sorter etter: Brukernavn (synkende rekkefølge)", diff --git a/public/language/nb/social.json b/public/language/nb/social.json index c8133e4f11..41fc62d703 100644 --- a/public/language/nb/social.json +++ b/public/language/nb/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Logg inn med Facebook", "continue-with-facebook": "Fortsett med Facebook", "sign-in-with-linkedin": "Logg inn med LinkedIn", - "sign-up-with-linkedin": "Registrer deg med LinkedIn" + "sign-up-with-linkedin": "Registrer deg med LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/nb/success.json b/public/language/nb/success.json index f1d8830fa0..ec1ca0f243 100644 --- a/public/language/nb/success.json +++ b/public/language/nb/success.json @@ -1,7 +1,7 @@ { "success": "Suksess", "topic-post": "Du har nå publisert.", - "post-queued": "Innlegget ditt er satt i kø for godkjenning. Du vil få en melding når den har blitt godkjent eller avvist.", + "post-queued": "Innlegget ditt er satt i kø for godkjenning. Du vil få en melding når det har blitt godkjent eller avvist.", "authentication-successful": "Innlogging vellykket!", "settings-saved": "Innstillinger lagret!" } \ No newline at end of file diff --git a/public/language/nb/tags.json b/public/language/nb/tags.json index 300852b7ab..571ec1055c 100644 --- a/public/language/nb/tags.json +++ b/public/language/nb/tags.json @@ -10,8 +10,8 @@ "tag-whitelist": "Hviteliste for emneord", "watching": "Følger", "not-watching": "Følger ikke", - "watching.description": "Varsle meg om nye emner.", - "not-watching.description": "Ikke varsle meg om nye emner.", + "watching.description": "Varsle meg om nye innlegg", + "not-watching.description": "Ikke varsle meg om nye innlegg.", "following-tag.message": "Du vil nå motta varsler når noen legger ut et emne med dette emneordet.", "not-following-tag.message": "Du vil ikke motta varsler når noen legger ut et emne med dette emneordet." } \ No newline at end of file diff --git a/public/language/nb/themes/harmony.json b/public/language/nb/themes/harmony.json index f9c66e050b..83a4893ae8 100644 --- a/public/language/nb/themes/harmony.json +++ b/public/language/nb/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony-tema", "skins": "Utseende", + "light": "Light", + "dark": "Dark", "collapse": "Slå sammen", "expand": "Utvid", "sidebar-toggle": "Vis/skjul sidepanelet", diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 5321f0ec4b..c3177f2487 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -1,22 +1,22 @@ { - "topic": "Emne", + "topic": "Innlegg", "title": "Tittel", - "no-topics-found": "Ingen tråder funnet!", - "no-posts-found": "Ingen innlegg funnet!", - "post-is-deleted": "Dette innlegget er slettet!", - "topic-is-deleted": "Denne tråden er slettet!", + "no-topics-found": "Ingen innlegg funnet!", + "no-posts-found": "Ingen poster funnet!", + "post-is-deleted": "Dette svaret er slettet!", + "topic-is-deleted": "Dette innlegget er slettet!", "profile": "Profil", "posted-by": "Opprettet av %1", "posted-by-guest": "Opprettet av Gjest", "chat": "Chat", - "notify-me": "Bli varslet om nye svar i denne tråden", + "notify-me": "Varsle meg om nye svar på dette innlegget", "quote": "Siter", "reply": "Svar", "replies-to-this-post": "%1 svar", "one-reply-to-this-post": "1 svar", "last-reply-time": "Siste svar", "reply-options": "Alternativer for svar", - "reply-as-topic": "Svar som tråd", + "reply-as-topic": "Svar som innlegg", "guest-login-reply": "Logg inn for å besvare", "login-to-view": "🔒 Logg inn for å se", "edit": "Endre", @@ -58,7 +58,7 @@ "user-deleted-topic-ago": "%1 slett dette emnet %2", "user-deleted-topic-on": "%1 slett dette emnet på %2", "user-restored-topic-ago": "%1 gjenopprettet dette emnet %2", - "user-restored-topic-on": "%1 gjenopprettet dette menet på %2", + "user-restored-topic-on": "%1 gjenopprettet dette emnet på %2", "user-moved-topic-from-ago": "%1 flyttet dette emnet fra %2 %3", "user-moved-topic-from-on": "%1 flyttet dette emnet fra %2 på %3", "user-shared-topic-ago": "%1 delte dette emnet %2", @@ -66,36 +66,38 @@ "user-queued-post-ago": "%1 i kø post til godkjenning %3", "user-queued-post-on": "%1 i køpost til godkjenning %3", "user-referenced-topic-ago": "%1 refererte dette emnet %3", - "user-referenced-topic-on": "%1 refererte dette emnet dette emnet på %3", - "user-forked-topic-ago": "%1 gaflet dette emnet %3", - "user-forked-topic-on": "%1 gaflet dette emnet på %3", - "bookmark-instructions": "Klikk her for å gå tilbake til det siste innlegget i denne tråden.", - "flag-post": "Flagg denne posten", - "flag-user": "Flagg denne brukeren", - "already-flagged": "Allerede flagget", - "view-flag-report": "Vis flaggrapport", - "resolve-flag": "Løs flagg", + "user-referenced-topic-on": "%1 refererte dette innlegget dette innlegget på %3", + "user-forked-topic-ago": "%1 forgrenet dette emnet %3", + "user-forked-topic-on": "%1 forgrenet dette emnet på %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", + "bookmark-instructions": "Klikk her for å gå tilbake til det siste svaret i denne tråden.", + "flag-post": "Rapporter denne posten", + "flag-user": "Rapporter denne brukeren", + "already-flagged": "Allerede rapportert", + "view-flag-report": "Vis rapporteringsoversikt", + "resolve-flag": "Behandle rapport", "merged-message": "Dette emnet er slått sammen med %2", - "forked-message": "This topic was forked from %2", - "deleted-message": "Denne tråden har blitt slettet. Bare brukere med trådhåndterings-privilegier kan se den.", - "following-topic.message": "Du vil nå motta varsler når noen skriver i denne tråden.", - "not-following-topic.message": "Du vil se denne tråden i trådlisten, men du vil ikke motta varslinger når noen skriver i den.", - "ignoring-topic.message": "Du vil ikke lenger se denne tråden blant de uleste trådene. Du vil få et varsel når du blir nevnt eller din tråd blir tilrådd.", + "forked-message": "Dette emnet vart forgrenet fra %2", + "deleted-message": "Denne innlegget har blitt slettet. ", + "following-topic.message": "Du vil nå motta varsler når noen svarer på dette innlegget.", + "not-following-topic.message": "Du vil se dette innlegget i oversikten over uleste innlegg, men du vil ikke motta varslinger når noen skriver et svar.", + "ignoring-topic.message": "Du vil ikke lenger se dette innlegget blant uleste innlegg. Du vil få et varsel når du blir nevnt eller innlegget blir anbefalt.", "login-to-subscribe": "Vennligst registrer deg eller logg inn for å abonnere på denne tråden.", "markAsUnreadForAll.success": "Tråd markert som ulest for alle.", "mark-unread": "Merk som ulest", "mark-unread.success": "Tråd merket som ulest.", "watch": "Følg", "unwatch": "Ikke følg", - "watch.title": "Bli varslet om nye svar i denne tråden", + "watch.title": "Varlse meg om nye svar på dette innlegget", "unwatch.title": "Slutt å følge denne tråden", "share-this-post": "Del ditt innlegg", "watching": "Følger", "not-watching": "Følger ikke", "ignoring": "Ignorerer", "watching.description": "Varlse meg om nye svar.
Vis tråd i ulest.", - "not-watching.description": "Ikke varsle meg om nye svar.
Vis tråd i ulest hvis ikke kategori er ignorert.", - "ignoring.description": "Ikke varsle meg om nye svar.
Ikke vis tråd i ulest.", + "not-watching.description": "Ikke varsle meg om nye svar.
Vis innlegg i ulest hvis ikke kategori er ignorert.", + "ignoring.description": "Ikke varsle meg om nye svar.
Ikke vis innlegg i ulest.", "thread-tools.title": "Trådverktøy", "thread-tools.markAsUnreadForAll": "Merk som ulest for alle", "thread-tools.pin": "Fest tråd", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lås tråd", "thread-tools.unlock": "Lås opp tråd", "thread-tools.move": "Flytt tråd", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Flytt innlegg", "thread-tools.move-all": "Flytt alle", "thread-tools.change-owner": "Bytt eier", @@ -132,6 +135,7 @@ "pin-modal-help": "Du kan eventuelt angi en utløpsdato for de festede emne(ne) her. Alternativt kan du la dette feltet stå tomt for å holde emnet festet til det manuelt løsnes.", "load-categories": "Laster kategorier", "confirm-move": "Flytt", + "confirm-crosspost": "Cross-post", "confirm-fork": "Forgren", "bookmark": "Bokmerke", "bookmarks": "Bokmerker", @@ -141,11 +145,12 @@ "loading-more-posts": "Laster flere innlegg", "move-topic": "Flytt tråd", "move-topics": "Flytt tråder", + "crosspost-topic": "Cross-post Topic", "move-post": "Flytt innlegg", "post-moved": "Innlegg flyttet!", "fork-topic": "Forgren tråd", - "enter-new-topic-title": "Tast inn tittel på emne", - "fork-topic-instruction": "Klikk på innleggene du vil dele, skriv inn en tittel for det nye emnet og klikk på emnet", + "enter-new-topic-title": "Skriv tittel på innlegg", + "fork-topic-instruction": "Velg innleggene du vil flytte til nytt forgrenet emne, skriv tittel for det nye emnet og klikk forgren", "fork-no-pids": "Ingen innlegg valgt!", "no-posts-selected": "Ingen innlegg valgt.", "x-posts-selected": "%1 innlegg valgt", @@ -157,12 +162,15 @@ "merge-topic-list-title": "Liste over emner som skal slås sammen", "merge-options": "Slå sammen alternativer", "merge-select-main-topic": "Velg hovedemne", - "merge-new-title-for-topic": "Ny tittel for emne", + "merge-new-title-for-topic": "Ny tittel for innlegg", "topic-id": "Emne ID", "move-posts-instruction": "Klikk på innleggene du vil flytte, og skriv deretter inn en emne-ID, eller gå til målemnet", "move-topic-instruction": "Velg målkategorien og klikk deretter flytt", "change-owner-instruction": "Klikk på innleggene du vil tildele til en annen bruker", "manage-editors-instruction": "Administrer brukere som kan redigere dette innlegget nedenfor.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Skriv din tråd-tittel her", "composer.handle-placeholder": "Skriv inn navnet ditt / signatur her", "composer.hide": "Skjul", @@ -172,8 +180,9 @@ "composer.post-later": "Publiser senere", "composer.schedule": "Timeplan", "composer.replying-to": "Svarer i %1", - "composer.new-topic": "Ny tråd", + "composer.new-topic": "Nytt innleg", "composer.editing-in": "Redigerer post i %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "laster opp...", "composer.thumb-url-label": "Lim inn som tråd-minatyr URL", "composer.thumb-title": "Legg til minatyr til denne tråden", @@ -190,12 +199,12 @@ "newest-to-oldest": "Nyeste til eldste", "recently-replied": "Nye svar", "recently-created": "Siste innlegg", - "most-votes": "Flest tilrådinger", + "most-votes": "Flest anbefalinger", "most-posts": "Flest innlegg", "most-views": "Flest visninger", - "stale.title": "Lag en ny tråd i stedet?", - "stale.warning": "Tråden du svarer på er ganske gammel. Vil du heller lage en ny tråd og referere til denne?", - "stale.create": "Lag en ny tråd", + "stale.title": "Opprett nytt innlegg i stedet?", + "stale.warning": "Innlegget du svarer på er ganske gammelt. Vil du heller lage et nytt innlegg og referere til dette?", + "stale.create": "Lag et nytt innlegg", "stale.reply-anyway": "Svar på denne tråden likevel", "link-back": "Sv: [%1](%2)", "diffs.title": "Redigeringshistorikk for innlegg", @@ -215,14 +224,17 @@ "go-to-my-next-post": "Gå til mitt neste innlegg", "no-more-next-post": "Du har ikke flere innlegg i dette emnet", "open-composer": "Åpne editor", - "post-quick-reply": "Raskt svar", + "post-quick-reply": "Svar", "navigator.index": "Innlegg %1 av %2", "navigator.unread": "%1 ulest", - "upvote-post": "Tilrå innlegg", - "downvote-post": "Ikke tilrå innlegg", + "upvote-post": "Anbefal innlegg", + "downvote-post": "Ikke anbefal innlegg", "post-tools": "Innleggsverktøy", "unread-posts-link": "Lenke til uleste innlegg", "thumb-image": "Miniatyrbilde for emne", "announcers": "Delinger", - "announcers-x": "Delinger (%1)" + "announcers-x": "Delinger (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/nb/unread.json b/public/language/nb/unread.json index 9ac5779198..64b5d53d47 100644 --- a/public/language/nb/unread.json +++ b/public/language/nb/unread.json @@ -9,7 +9,7 @@ "all-categories": "Alle kategorier", "topics-marked-as-read.success": "Emner merket som lest!", "all-topics": "Alle emner", - "new-topics": "Nye emner", + "new-topics": "Nye innlegg", "watched-topics": "Fulgte emner", "unreplied-topics": "Emner som ikke er svart på", "multiple-categories-selected": "Flere valg" diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 6908553b0c..0f2cc20fa5 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -59,9 +59,9 @@ "chat": "Chat", "chat-with": "Fortsett å chatte med %1", "new-chat-with": "Start ny chat med %1", - "view-remote": "View Original", + "view-remote": "Vis opprinnelig versjon", "flag-profile": "Rapporter profil", - "profile-flagged": "Allerede flagget", + "profile-flagged": "Allerede rapportert", "follow": "Følg", "unfollow": "Avfølg", "cancel-follow": "Avbryt forespørsel om å følge", @@ -104,9 +104,13 @@ "settings": "Brukerinnstillinger", "show-email": "Vis min e-post", "show-fullname": "Vis mitt fulle navn", - "restrict-chats": "Bare tillat chat-meldinger fra brukere jeg følger", + "restrict-chats": "Bare tillat meldinger fra brukere jeg følger", + "disable-incoming-chats": "Slå av meldinger fra chat ", + "chat-allow-list": "Bare tillat meldinger fra følgende brukere", + "chat-deny-list": "Ikke tillat meldinger fra følgende brukere", + "chat-list-add-user": "Legg til bruker", "digest-label": "Abonner på sammendrag", - "digest-description": "Abonner på e-post-oppdateringer for dette forumet (nye varsler og emner) i samsvar med valgte tidspunkt", + "digest-description": "Abonner på e-post-oppdateringer for dette forumet (nye varsler og innlegg) i samsvar med valgte tidspunkt", "digest-off": "Av", "digest-daily": "Daglig", "digest-weekly": "Ukentlig", @@ -115,12 +119,12 @@ "has-no-follower": "Denne brukeren har ingen følgere :(", "follows-no-one": "Denne brukeren følger ingen :(", "has-no-posts": "Denne brukeren har ikke skrevet noe enda.", - "has-no-best-posts": "Denne brukeren har ingen tilrådde innlegg ennå.", + "has-no-best-posts": "Denne brukeren har ingen anbefalte innlegg ennå.", "has-no-topics": "Denne brukeren har ikke skrevet noen tråder enda.", "has-no-watched-topics": "Denne brukeren har ikke fulgt noen tråder enda.", "has-no-ignored-topics": "Denne brukeren har ikke ignorert noen emner ennå", "has-no-read-topics": "Denne brukeren har ikke lest noen tråder enda.", - "has-no-upvoted-posts": "Denne brukeren har ikke tilrådd noen innlegg ennå.", + "has-no-upvoted-posts": "Denne brukeren har ikke anbefalt noen innlegg ennå.", "has-no-downvoted-posts": "Denne brukeren har ikke stemt ned noen innlegg ennå.", "has-no-controversial-posts": "Denne brukeren har ikke noen nedstemte innlegg ennå.", "has-no-blocks": "Du har ingen blokkerte brukere.", @@ -135,10 +139,10 @@ "max-items-per-page": "Maksimum %1", "acp-language": "Administrer sidespråk", "notifications": "Varsler", - "upvote-notif-freq": "Varslingsfrekvens for opp-stemmer", - "upvote-notif-freq.all": "Alle tilrådinger", + "upvote-notif-freq": "Varslingsfrekvens for anbefalinger", + "upvote-notif-freq.all": "Alle anbefalinger", "upvote-notif-freq.first": "Først per innlegg", - "upvote-notif-freq.everyTen": "Hver tiende tilråding", + "upvote-notif-freq.everyTen": "Hver tiende anbefaling", "upvote-notif-freq.threshold": "På 1, 5, 10, 25, 50, 100, 150, 200 ...", "upvote-notif-freq.logarithmic": "På 10, 100, 1000 ...", "upvote-notif-freq.disabled": "Noe er galt med funksjonen", @@ -171,12 +175,12 @@ "sso.dissociate": "Separer", "sso.dissociate-confirm-title": "Bekreft seperasjon", "sso.dissociate-confirm": "Er du sikker på at du vil separere kontoen din fra %1?", - "info.latest-flags": "Siste flagg", + "info.latest-flags": "Siste rapporteringer", "info.profile": "Profil", "info.post": "Post", - "info.view-flag": "Vis flagg", + "info.view-flag": "Vis rapportering", "info.reported-by": "Rapportert av:", - "info.no-flags": "Ingen flaggede innlegg funnet", + "info.no-flags": "Ingen rapporterte innlegg funnet", "info.ban-history": "Nylig utestengingshistorikk", "info.no-ban-history": "Denne brukeren har aldri blitt utestengt", "info.banned-until": "Utestengt til %1", diff --git a/public/language/nb/users.json b/public/language/nb/users.json index 7dbae83a4b..94953fb54e 100644 --- a/public/language/nb/users.json +++ b/public/language/nb/users.json @@ -4,7 +4,7 @@ "latest-users": "Siste brukere", "top-posters": "Flest innlegg", "most-reputation": "Best omdømme", - "most-flags": "Flest flagg", + "most-flags": "Flest rapporteringer", "search": "Søk", "enter-username": "Skriv inn et brukernavn for å søke", "search-user-for-chat": "Søk etter en bruker for å starte chat", @@ -17,7 +17,7 @@ "groups-to-join": "Grupper som en kan bli med i når invitasjonen godtas:", "invitation-email-sent": "En invitasjons-e-post ble sendt til %1", "user-list": "Brukerliste", - "recent-topics": "Nye tråder", + "recent-topics": "Nye innlegg", "popular-topics": "Populære tråder", "unread-topics": "Uleste tråder", "categories": "Kategorier", diff --git a/public/language/nb/world.json b/public/language/nb/world.json index 54ab26f15e..598234cf03 100644 --- a/public/language/nb/world.json +++ b/public/language/nb/world.json @@ -1,18 +1,25 @@ { "name": "Verden", - "popular": "Populære tema", - "recent": "Alle tema", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Hjelp", "help.title": "Hva er denne siden?", "help.intro": "Welcome to your corner of the fediverse.", "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", + "help.build": "Det kan hende det ikke er så mange innlegg her i starten, det er helt normalt. Du vil begynne å se mer innhold her etter hvert som du begynner å følge andre brukere.", "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.next-generation": "Dette er neste generasjon sosiale medier, begynn å bidra i dag!", "onboard.title": "Ditt vindu til fødiverset...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.what": "Dette er din personlige kategori, som kun består av innhold funnet utenfor dette forumet. Om noe vises på denne siden, avhenger av om du følger dem, eller om innlegget ble delt av noen du følger.", + "onboard.why": "Det skjer mye utenfor dette forumet, og ikke alt er relevant for dine interesser. Derfor er det å følge folk den beste måten å vise at du vil se mer fra noen.", + "onboard.how": "I mellomtiden kan du klikke på snarveisknappene øverst for å se hva annet dette forumet inneholder, og begynne å oppdage nytt innhold!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/nl/admin/advanced/cache.json b/public/language/nl/admin/advanced/cache.json index 926f30b0d0..67841095b1 100644 --- a/public/language/nl/admin/advanced/cache.json +++ b/public/language/nl/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Onderwerpcache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1%vol", "post-cache-size": "Onderwerpcache grootte", "items-in-cache": "Items in cache" diff --git a/public/language/nl/admin/dashboard.json b/public/language/nl/admin/dashboard.json index 63bae0694f..d0b9c8db04 100644 --- a/public/language/nl/admin/dashboard.json +++ b/public/language/nl/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unieke bezoekers", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/nl/admin/development/info.json b/public/language/nl/admin/development/info.json index b5ee1a8838..3643fab261 100644 --- a/public/language/nl/admin/development/info.json +++ b/public/language/nl/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index 6988838842..470ed0209d 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/nl/admin/manage/custom-reasons.json b/public/language/nl/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/nl/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/nl/admin/manage/privileges.json b/public/language/nl/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/nl/admin/manage/privileges.json +++ b/public/language/nl/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/nl/admin/manage/users.json b/public/language/nl/admin/manage/users.json index 73f21f0caf..84c76092ef 100644 --- a/public/language/nl/admin/manage/users.json +++ b/public/language/nl/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/nl/admin/settings/activitypub.json b/public/language/nl/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/nl/admin/settings/activitypub.json +++ b/public/language/nl/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/nl/admin/settings/chat.json b/public/language/nl/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/nl/admin/settings/chat.json +++ b/public/language/nl/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/nl/admin/settings/email.json b/public/language/nl/admin/settings/email.json index 0eeb33fb89..7779b69b52 100644 --- a/public/language/nl/admin/settings/email.json +++ b/public/language/nl/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Aanpassen E-mail Template", "template.select": "Selecteer E-mail Template", "template.revert": "Veranderingen ongedaan maken", + "test-smtp-settings": "Test SMTP Settings", "testing": "E-mail Testen", + "testing.success": "Test Email Sent.", "testing.select": "Selecteer E-mail Template", "testing.send": "Verzend Test E-mail", - "testing.send-help": "De test mail zal worden verstuurd naar het email adres van de op dit moment ingelogde gebruiker.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "E-mail digests", "subscriptions.disable": "Schakel e-mail digests uit", "subscriptions.hour": "Uur van Digest", diff --git a/public/language/nl/admin/settings/notifications.json b/public/language/nl/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/nl/admin/settings/notifications.json +++ b/public/language/nl/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/nl/admin/settings/uploads.json b/public/language/nl/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/nl/admin/settings/uploads.json +++ b/public/language/nl/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/nl/admin/settings/user.json b/public/language/nl/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/nl/admin/settings/user.json +++ b/public/language/nl/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/nl/admin/settings/web-crawler.json b/public/language/nl/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/nl/admin/settings/web-crawler.json +++ b/public/language/nl/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/nl/aria.json b/public/language/nl/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/nl/aria.json +++ b/public/language/nl/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/nl/category.json b/public/language/nl/category.json index c990a0c98f..cfb237822d 100644 --- a/public/language/nl/category.json +++ b/public/language/nl/category.json @@ -1,12 +1,13 @@ { "category": "Categorie", "subcategories": "Subcategorieën", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nieuw onderwerp", "guest-login-post": "Log in om een reactie te plaatsen", "no-topics": "Er zijn geen onderwerpen in deze categorie.
Waarom maak je er niet een aan?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "browsing", "no-replies": "Niemand heeft gereageerd", "no-new-posts": "Geen nieuwe berichten.", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index e0ed7837ed..1201e79937 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -3,6 +3,7 @@ "invalid-json": "Ongeldige JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Het lijkt erop dat je niet ingelogd bent.", "account-locked": "Je account is tijdelijk vergrendeld", "search-requires-login": "Zoeken vereist een account - meld je aan of registreer je om te zoeken.", @@ -146,6 +147,7 @@ "post-already-restored": "Dit bericht is al hersteld", "topic-already-deleted": "Dit onderwerp is al verwijderd", "topic-already-restored": "Dit onderwerp is al hersteld", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Het is niet mogelijk het eerste bericht te verwijderen. Hiervoor dient het gehele onderwerp verwijderd te worden.", "topic-thumbnails-are-disabled": "Miniatuurweergaven bij onderwerpen uitgeschakeld.", "invalid-file": "Ongeldig bestand", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, je beschrijving kan niet langer zijn dan %1 karakter(s).", "cant-chat-with-yourself": "Het is niet mogelijk om met jezelf een chatgesprek te houden.", "chat-restricted": "Deze gebruiker heeft beperkingen aan de chatfunctie opgelegd waardoor deze eerst iemand moet volgen voordat deze persoon een nieuwe chat mag initiëren.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat systeem uitgeschakeld", "too-many-messages": "Je hebt in korte tijd veel berichten verstuurd, als je even wacht mag je weer berichten sturen.", @@ -225,6 +229,7 @@ "no-topics-selected": "Geen onderwerpen geselecteerd!", "cant-move-to-same-topic": "Een bericht kan niet naar hetzelfde onderwerp worden verplaatst!", "cant-move-topic-to-same-category": "Kan onderwerp niet verplaatsen naar dezelfde categorie", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Je kan jezelf niet blokkeren!", "cannot-block-privileged": "Je kan geen administrators of global moderators blokkeren", "cannot-block-guest": "Gasten kunnen geen andere gebruikers blokkeren", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Kan plugin niet installeren – alleen plugins toegestaan door de NodeBB Package Manager kunnen via de ACP geinstalleerd worden", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/nl/global.json b/public/language/nl/global.json index ba055584b7..7e4e5a0181 100644 --- a/public/language/nl/global.json +++ b/public/language/nl/global.json @@ -68,6 +68,7 @@ "users": "Gebruikers", "topics": "Onderwerpen", "posts": "Berichten", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Omlaag gestemd", "views": "Weergaven", "posters": "Plaatsers", + "watching": "Watching", "reputation": "Reputatie", "lastpost": "Laatste bericht", "firstpost": "Eerste bericht", diff --git a/public/language/nl/groups.json b/public/language/nl/groups.json index c479c65543..aa7b8393db 100644 --- a/public/language/nl/groups.json +++ b/public/language/nl/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Groepen", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Bekijk groep", "owner": "Groepseigenaar", "new-group": "Nieuwe groep aanmaken", diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index cbe6bc5603..3f4da9f96a 100644 --- a/public/language/nl/modules.json +++ b/public/language/nl/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Verberg voorbeeld", "composer.help": "Help", "composer.user-said-in": "%1 zegt in %2:", - "composer.user-said": "%1 zegt:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Bericht plaatsen annuleren?", "composer.submit-and-lock": "Bericht plaatsen en sluiten", "composer.toggle-dropdown": "Keuzelijst schakelen", diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json index 63f9ed5a4e..6642cb8798 100644 --- a/public/language/nl/notifications.json +++ b/public/language/nl/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "Nieuwe markeringen", - "my-flags": "Markeringen toegewezen aan mij", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Nieuw bericht van %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 heeft voor je bericht gestemd in %2.", - "upvoted-your-post-in-dual": "%1 en %2 hebben voor je bericht gestemd in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 heeft je bericht verplaatst naar %2", "moved-your-topic": "%1 heeft %2 verplaatst", - "user-flagged-post-in": "%1 rapporteerde een bericht in %2", - "user-flagged-post-in-dual": "%1 en %2 rapporteerden een bericht in %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 markeerde een gebruikersprofiel (%2)", "user-flagged-user-dual": "%1 en %2 markeerden een gebruikersprofiel (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 heeft een reactie geplaatst in: %2", - "user-posted-to-dual": "%1 en %2 hebben een reactie geplaatst in: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 heeft een nieuw onderwerp geplaatst: %2", - "user-edited-post": "%1 heeft een bericht aangepast in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 volgt jou nu.", "user-started-following-you-dual": "%1 en %2 volgen jou nu.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Csv met gebruikers geëxporteerd, klik om te downloaden", "post-queue-accepted": "Je bericht in de wachtrij is geaccepteerd. Klik hier om je bericht te bekijken.", "post-queue-rejected": "Je bericht in de wachtrij is afgekeurd.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mailadres bevestigd", "email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Je account is nu volledig geactiveerd.", "email-confirm-error-message": "Er was een probleem met het bevestigen van dit e-mailadres. Misschien is de code niet goed ingevoerd of was de beschikbare tijd inmiddels verstreken.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Bevestigingsmail verstuurd.", "none": "Geen", "notification-only": "Alleen notificatie", diff --git a/public/language/nl/social.json b/public/language/nl/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/nl/social.json +++ b/public/language/nl/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/nl/themes/harmony.json b/public/language/nl/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/nl/themes/harmony.json +++ b/public/language/nl/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index ff3e53c91a..7564fee664 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klik hier om terug te keren naar de laatst gelezen post in deze thread.", "flag-post": "Rapporteer dit bericht", "flag-user": "Rapporteer deze gebruiker", @@ -103,6 +105,7 @@ "thread-tools.lock": "Onderwerp sluiten", "thread-tools.unlock": "Onderwerp openen", "thread-tools.move": "Onderwerp verplaatsen", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Verplaats berichten", "thread-tools.move-all": "Verplaats alles", "thread-tools.change-owner": "Wijzig eigenaar", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Categorieën laden", "confirm-move": "Verplaatsen", + "confirm-crosspost": "Cross-post", "confirm-fork": "Splits", "bookmark": "Favoriet", "bookmarks": "Favorieten", @@ -141,6 +145,7 @@ "loading-more-posts": "Meer berichten laden...", "move-topic": "Onderwerp verplaatsen", "move-topics": "Verplaats onderwerpen", + "crosspost-topic": "Cross-post Topic", "move-post": "Bericht verplaatsen", "post-moved": "Bericht verplaatst!", "fork-topic": "Afgesplitst onderwerp", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Klik op de berichten die je wilt toewijzen aan een andere gebruiker", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Voer hier de titel van het onderwerp in...", "composer.handle-placeholder": "Voer je naam/pseudoniem hier in", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Reactie op %1", "composer.new-topic": "Nieuw onderwerp", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "uploaden...", "composer.thumb-url-label": "Plak een URL naar een miniatuurweergave voor dit onderwerp", "composer.thumb-title": "Voeg een miniatuurweergave toe aan dit onderwerp", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/nl/user.json b/public/language/nl/user.json index d01ceade22..48f743a8a8 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -105,6 +105,10 @@ "show-email": "E-mailadres weergeven", "show-fullname": "Laat mijn volledige naam zien", "restrict-chats": "Sta alleen chatsessies toe van gebruikers die ik zelf volg", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Abonneer op een samenvatting", "digest-description": "Abonneer op periodieke e-mail updates van onderwerpen in dit forum", "digest-off": "Uit", diff --git a/public/language/nl/world.json b/public/language/nl/world.json index 3753335278..e6694bf507 100644 --- a/public/language/nl/world.json +++ b/public/language/nl/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/nn-NO/admin/advanced/cache.json b/public/language/nn-NO/admin/advanced/cache.json index 5b9a812b64..905df90c85 100644 --- a/public/language/nn-NO/admin/advanced/cache.json +++ b/public/language/nn-NO/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Innleggscache", - "group-cache": "Gruppecache", - "local-cache": "Lokal cache", - "object-cache": "Objektcache", "percent-full": "%1% fylt", "post-cache-size": "Storleik på innleggscache", "items-in-cache": "Element i cache" diff --git a/public/language/nn-NO/admin/dashboard.json b/public/language/nn-NO/admin/dashboard.json index c852084c8f..2f31eb632b 100644 --- a/public/language/nn-NO/admin/dashboard.json +++ b/public/language/nn-NO/admin/dashboard.json @@ -65,7 +65,7 @@ "on-categories": "I kategoriar", "reading-posts": "Les innlegg", "browsing-topics": "Blar gjennom emne", - "recent": "Nyleg", + "recent": "Siste", "unread": "Uleste", "high-presence-topics": "Emne med høg aktivitet", @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Diagram over sidevisningar (registrerte)", "graphs.page-views-guest": "Diagram over sidevisningar (gjestar)", "graphs.page-views-bot": "Diagram over sidevisningar (botar)", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Diagram over unike besøkande", "graphs.registered-users": "Diagram over registrerte brukarar", "graphs.guest-users": "Diagram over gjestar", diff --git a/public/language/nn-NO/admin/development/info.json b/public/language/nn-NO/admin/development/info.json index 2d28c9fcfb..93f7e59c85 100644 --- a/public/language/nn-NO/admin/development/info.json +++ b/public/language/nn-NO/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "Node.js", "online": "Pålogga", "git": "Git", - "process-memory": "Prosessminne", + "process-memory": "rss/heap used", "system-memory": "Systemminne", "used-memory-process": "Brukt minne (prosess)", "used-memory-os": "Brukt minne (OS)", diff --git a/public/language/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index 6d947aacb2..ef3cfa44c2 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Administrer kategoriar", "add-category": "Legg til kategori", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Hopp til", "settings": "Innstillingar", "edit-category": "Rediger kategori", "privileges": "Rettar", "back-to-categories": "Tilbake til kategoriar", + "id": "Category ID", "name": "Namn", "handle": "Kategori-sti", "handle.help": " Kategori-stien din blir brukt som ein representasjon av denne kategorien på andre nettverk, som eit brukarnamn. Ein kategori-sti må ikkje samsvare med eit eksisterande brukarnamn eller ei brukargruppe.", "description": "Skildring", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Bakgrunnsfarge", "text-color": "Tekstfarge", "bg-image-size": "Storleik på bakgrunnsbilete", @@ -86,23 +90,28 @@ "federation.disabled": "Føderasjon er deaktivert for heile nettstaden, så innstillingar for kategori-føderasjon er for tida ikkje tilgjengelege.", "federation.disabled-cta": "Føderasjonsinnstillingar →", "federation.syncing-header": "Synkronisering", - "federation.syncing-intro": "Ein kategori kan følgje ein “Gruppeaktør” via ActivityPub-protokollen. Dersom innhald blir mottatt frå ein av aktørane som er lista opp nedanfor, vil det automatisk bli lagt til i denne kategorien.", - "federation.syncing-caveat": " Merk: Å konfigurere synkronisering her opprettar ein einvegs synkronisering. NodeBB prøver å abonnere på/følgje aktøren, men det motsette kan ikkje føresetjast.", - "federation.syncing-none": "Denne kategorien følgjer for tida ingen.", + "federation.syncing-intro": "Ein kategori kan følge ein “Gruppeaktør” via ActivityPub-protokollen. Dersom innhald blir mottatt frå ein av aktørane som er lista opp nedanfor, vil det automatisk bli lagt til i denne kategorien.", + "federation.syncing-caveat": " Merk: Å konfigurere synkronisering her opprettar ein einvegs synkronisering. NodeBB prøver å abonnere på/følge aktøren, men det motsette kan ikkje føresetjast.", + "federation.syncing-none": "Denne kategorien følger for tida ingen.", "federation.syncing-add": "Synkronser med...", "federation.syncing-actorUri": "Aktør", "federation.syncing-follow": "Følg", "federation.syncing-unfollow": "Avfølg", - "federation.followers": "Eksterne brukarar som følgjer denne kategorien", + "federation.followers": "Eksterne brukarar som følger denne kategorien", "federation.followers-handle": "Sti", "federation.followers-id": "ID", - "federation.followers-none": "Ingen følgjarar.", + "federation.followers-none": "Ingen følgarar.", "federation.followers-autofill": "Autofill", "alert.created": "Oppretta", "alert.create-success": "Kategori oppretta med suksess", "alert.none-active": "Ingen aktive kategoriar", "alert.create": "Opprett kategori", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "Er du sikker på at du vil rense denne kategorien?", "alert.purge-success": "Kategorien vart rensa med suksess", "alert.copy-success": "Innstillingar kopiert med suksess", diff --git a/public/language/nn-NO/admin/manage/custom-reasons.json b/public/language/nn-NO/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/nn-NO/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/nn-NO/admin/manage/privileges.json b/public/language/nn-NO/admin/manage/privileges.json index 42dd969247..a07d75d881 100644 --- a/public/language/nn-NO/admin/manage/privileges.json +++ b/public/language/nn-NO/admin/manage/privileges.json @@ -29,13 +29,14 @@ "access-topics": "Tilgang til emne", "create-topics": "Opprett emne", "reply-to-topics": "Svar på emne", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Planlegg emne", "tag-topics": "Legg til emneord", "edit-posts": "Rediger innlegg", "view-edit-history": "Vis redigeringshistorikk", "delete-posts": "Slett innlegg", "view-deleted": "Vis sletta", - "upvote-posts": "Tilrå innlegg", + "upvote-posts": "Anbefal innlegg", "downvote-posts": "Stem ned innlegg", "delete-topics": "Slett emne", "purge": "Rensk", diff --git a/public/language/nn-NO/admin/manage/users.json b/public/language/nn-NO/admin/manage/users.json index 7013a7c0df..83cd442251 100644 --- a/public/language/nn-NO/admin/manage/users.json +++ b/public/language/nn-NO/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Rensk", "download-csv": "Last ned CSV", "custom-user-fields": "Tilpassa brukerfelt", + "custom-reasons": "Custom Reasons", "manage-groups": "Administrer grupper", "set-reputation": "Sett omdømme", "add-group": "Legg til gruppe", @@ -77,9 +78,11 @@ "temp-ban.length": "Lengd på utestenging", "temp-ban.reason": "Årsak til utestenging", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Timar", "temp-ban.days": "Dagar", "temp-ban.explanation": "Forklaring på utestenging", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Er du sikker på at du vil utestenge denne brukaren?", "alerts.confirm-ban-multi": "Er du sikker på at du vil utestenge dei valde brukarane?", @@ -135,8 +138,8 @@ "export-field-postcount": "Innleggstal", "export-field-topiccount": "Emnetal", "export-field-profileviews": "Profilvisningar", - "export-field-followercount": "Følgjarar", - "export-field-followingcount": "Følgjer", + "export-field-followercount": "Følgarar", + "export-field-followingcount": "Følger", "export-field-fullname": "Fullt namn", "export-field-website": "Nettstad", "export-field-location": "Stad", diff --git a/public/language/nn-NO/admin/settings/activitypub.json b/public/language/nn-NO/admin/settings/activitypub.json index 96d0526ecc..79f6f05cd6 100644 --- a/public/language/nn-NO/admin/settings/activitypub.json +++ b/public/language/nn-NO/admin/settings/activitypub.json @@ -4,7 +4,7 @@ "general": "Generelt", "pruning": "Innhaldsbeskjæring", "content-pruning": "Dagar å behalde eksternt innhald", - "content-pruning-help": " Merk at eksternt innhald som har fått engasjement (eit svar eller ei opp-/nedstemming) vil bli bevart. (0 for deaktivert)", + "content-pruning-help": " Merk at eksternt innhald som har fått engasjement (eit svar eller ei anbefaling) vil bli bevart. (0 for deaktivert)", "user-pruning": "Dagar å mellomlagre eksterne brukarkontoar", "user-pruning-help": "Eksterne brukarkontoar vil berre bli fjerna dersom dei ikkje har innlegg. Elles vil dei bli henta på nytt. (0 for deaktivert)", "enabled": "Aktiver føderering", @@ -18,6 +18,28 @@ "probe-timeout": "Oppslagstimeout (millisekund)", "probe-timeout-help": "(Standard: 2000) Dersom oppslagsførespurnaden ikkje får eit svar innan den angitte tidsramma, vil brukaren bli sendt direkte til lenkja i staden. Juster dette talet oppover dersom nettsider responderer sakte, og du ønskjer å gi ekstra tid.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtrer etter", "count": "Denne NodeBB-en er for tida klar over %1 server(ar)", "server.filter-help": "Spesifiser serverar du ønskjer å hindre frå å føderere med din NodeBB. Alternativt kan du velje å tillate føderasjon berre med spesifikke serverar. Begge alternativ er støtta, men dei er gjensidig utelukkande.", diff --git a/public/language/nn-NO/admin/settings/chat.json b/public/language/nn-NO/admin/settings/chat.json index 06097dc1d5..9a6f261dc2 100644 --- a/public/language/nn-NO/admin/settings/chat.json +++ b/public/language/nn-NO/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maksimum lengd på namn på chat", "max-room-size": "Maksimum tal på deltakarar i chat", "delay": "Forsinkelse", - "notification-delay": "Varslingsforsinkelse", - "notification-delay-help": "Angi kor lang forsinkelse som skal vere før varsel vert sendt.", "restrictions.seconds-edit-after": "Sekund til redigering er mogleg etter sending", "restrictions.seconds-delete-after": "Sekund til sletting er mogleg etter sending" } \ No newline at end of file diff --git a/public/language/nn-NO/admin/settings/email.json b/public/language/nn-NO/admin/settings/email.json index 674202fd54..51282789cd 100644 --- a/public/language/nn-NO/admin/settings/email.json +++ b/public/language/nn-NO/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Bruk pool for å redusere oppstartstid ved fleire e-postutsendingar.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Mal", "template.select": "Vel mal", "template.revert": "Tilbakestill til standard", + "test-smtp-settings": "Test SMTP Settings", "testing": "Testing", + "testing.success": "Test Email Sent.", "testing.select": "Vel ein e-post for testing", "testing.send": "Send test-e-post", - "testing.send-help": "Skriv inn e-posten for å sende ein test.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Abonnement", "subscriptions.disable": "Deaktiver abonnement", "subscriptions.hour": "Timar mellom utsendingar", diff --git a/public/language/nn-NO/admin/settings/general.json b/public/language/nn-NO/admin/settings/general.json index f14f5c6598..2157dd08fb 100644 --- a/public/language/nn-NO/admin/settings/general.json +++ b/public/language/nn-NO/admin/settings/general.json @@ -32,7 +32,7 @@ "pwa": "Progressiv webapp", "touch-icon": "Touch-ikon", "touch-icon.upload": "Last opp touch-ikon", - "touch-icon.help": "Bruk touch-ikonet for å visast på mobile einingar.", + "touch-icon.help": "Bruk touch-ikonet for å vise på mobile einingar.", "maskable-icon": "Maskerbart ikon", "maskable-icon.help": "Maskerbare ikon vert brukt for å tilpasse webappen til ulike skjermstorleikar.", "outgoing-links": "Utgåande lenkjer", diff --git a/public/language/nn-NO/admin/settings/notifications.json b/public/language/nn-NO/admin/settings/notifications.json index e5551a7780..3046535e50 100644 --- a/public/language/nn-NO/admin/settings/notifications.json +++ b/public/language/nn-NO/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Velkomstvarsel", "welcome-notification-link": "Lenkje til velkomstvarsel", "welcome-notification-uid": "Velkomstvarsel brukar-ID", - "post-queue-notification-uid": "Innleggskø-varsel brukar-ID (UID)" + "post-queue-notification-uid": "Innleggskø-varsel brukar-ID (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/nn-NO/admin/settings/post.json b/public/language/nn-NO/admin/settings/post.json index 10d4b92671..3534e580a9 100644 --- a/public/language/nn-NO/admin/settings/post.json +++ b/public/language/nn-NO/admin/settings/post.json @@ -6,7 +6,7 @@ "sorting.newest-to-oldest": "Nyaste til eldste", "sorting.recently-replied": "Sist svart", "sorting.recently-created": "Nyleg oppretta", - "sorting.most-votes": "Fleire stemmer", + "sorting.most-votes": "Flest anbefalingar", "sorting.most-posts": "Fleire innlegg", "sorting.most-views": "Fleire visningar", "sorting.topic-default": "Standard sortering for emne", @@ -39,7 +39,7 @@ "teaser.last-reply": "Siste svar", "teaser.first": "Fyrste innlegg", "showPostPreviewsOnHover": "Vis innlegg ved førhandsvising", - "unread-and-recent": "Uleste og nylege", + "unread-and-recent": "Innstillingar for Uleste og Siste", "unread.cutoff": "Avskjering for uleste", "unread.min-track-last": "Minste sporingslengd for siste innlegg", "recent.max-topics": "Maksimum tal emne nyleg", diff --git a/public/language/nn-NO/admin/settings/reputation.json b/public/language/nn-NO/admin/settings/reputation.json index bcf7b0cf51..d3ac095bc3 100644 --- a/public/language/nn-NO/admin/settings/reputation.json +++ b/public/language/nn-NO/admin/settings/reputation.json @@ -2,18 +2,18 @@ "reputation": "Omdømme", "disable": "Deaktiver", "disable-down-voting": "Deaktiver nedstemming", - "upvote-visibility": "Synlegheit for oppstemmer", - "upvote-visibility-all": "Alle kan sjå oppstemmer", - "upvote-visibility-loggedin": "Berre innlogga brukarar kan sjå oppstemmer", - "upvote-visibility-privileged": "Berre privilegerte brukarar kan sjå oppstemmer", + "upvote-visibility": "Synlegheit for anbefalingar", + "upvote-visibility-all": "Alle kan sjå anbefalingar", + "upvote-visibility-loggedin": "Berre innlogga brukarar kan sjå anbefalingar", + "upvote-visibility-privileged": "Berre privilegerte brukarar kan sjå anbefalingar", "downvote-visibility": "Synlegheit for nedstemmer", "downvote-visibility-all": "Alle kan sjå nedstemmer", "downvote-visibility-loggedin": "Berre innlogga brukarar kan sjå nedstemmer", "downvote-visibility-privileged": "Berre privilegerte brukarar kan sjå nedstemmer", "thresholds": "Grenseverdiar", - "min-rep-upvote": "Minimum omdømme for å tilrå", - "upvotes-per-day": "Tilrådingar per dag", - "upvotes-per-user-per-day": "Tilrådingar per brukar per dag", + "min-rep-upvote": "Minimum omdømme for å anbefale", + "upvotes-per-day": "Anbefalingar per dag", + "upvotes-per-user-per-day": "Anbefalingar per brukar per dag", "min-rep-downvote": "Minimum omdømme for å stemme ned", "downvotes-per-day": "Nedstemmer per dag", "downvotes-per-user-per-day": "Nedstemmer per brukar per dag", diff --git a/public/language/nn-NO/admin/settings/sounds.json b/public/language/nn-NO/admin/settings/sounds.json index f83f14d0f6..71970eccc7 100644 --- a/public/language/nn-NO/admin/settings/sounds.json +++ b/public/language/nn-NO/admin/settings/sounds.json @@ -1,6 +1,6 @@ { "notifications": "Varsel", - "chat-messages": "Chatmeldingar", + "chat-messages": "Meldingar", "play-sound": "Spel av lyd", "incoming-message": "Innkommande melding", "outgoing-message": "Utgåande melding", diff --git a/public/language/nn-NO/admin/settings/uploads.json b/public/language/nn-NO/admin/settings/uploads.json index c729541aff..3859edb2ac 100644 --- a/public/language/nn-NO/admin/settings/uploads.json +++ b/public/language/nn-NO/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Angi maksimal breidde for bilete som vert avvist ved opplasting.", "reject-image-height": "Avvis bilete høgde", "reject-image-height-help": "Angi maksimal høgde for bilete som vert avvist ved opplasting.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Tillat emne-miniatyrbilete", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Storleik på emne-miniatyr", "allowed-file-extensions": "Tillatne filtypar", "allowed-file-extensions-help": "Angi kva filtypar som er tillatne ved opplasting.", diff --git a/public/language/nn-NO/admin/settings/user.json b/public/language/nn-NO/admin/settings/user.json index 1aa3555133..77b367a63b 100644 --- a/public/language/nn-NO/admin/settings/user.json +++ b/public/language/nn-NO/admin/settings/user.json @@ -63,7 +63,8 @@ "default-user-settings": "Standardinnstillingar for brukar", "show-email": "Vis e-post", "show-fullname": "Vis fullt namn", - "restrict-chat": "Avgrens chat", + "restrict-chat": "Tillat berre meldingar frå brukarar eg følger", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Utgåande lenkjer i ny fane", "topic-search": "Emnesøk", "update-url-with-post-index": "Oppdater URL med innleggindeks", @@ -80,7 +81,7 @@ "default-notification-settings": "Standard varslingsinnstillingar", "categoryWatchState": "Kategori-overvåking", "categoryWatchState.tracking": "Sporing", - "categoryWatchState.notwatching": "Ikkje overvåking", + "categoryWatchState.notwatching": "Følger ikkje", "categoryWatchState.ignoring": "Ignorerer", "restrictions-new": "Nye restriksjonar", "restrictions.rep-threshold": "Omdømmegrense", @@ -89,7 +90,7 @@ "restrictions.seconds-edit-after-new": "Sekund til redigering for nye brukarar", "restrictions.milliseconds-between-messages": "Millisekund mellom meldingar", "restrictions.groups-exempt-from-new-user-restrictions": "Grupper unnateke frå nye brukarrestriksjonar", - "guest-settings": "Gjestinnstillingar", + "guest-settings": "Gjesteinnstillingar", "handles.enabled": "Aktiver handtering av gjestar", "handles.enabled-help": "Aktiver for å la gjestar kunne sjå og interagere med forumet innanfor visse grenser.", "topic-views.enabled": "Aktiver visning av emne for gjestar", diff --git a/public/language/nn-NO/admin/settings/web-crawler.json b/public/language/nn-NO/admin/settings/web-crawler.json index 1f5d14334c..ec2c74cdb0 100644 --- a/public/language/nn-NO/admin/settings/web-crawler.json +++ b/public/language/nn-NO/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Deaktiver RSS-feedar", "disable-sitemap-xml": "Deaktiver sitemap.xml", "sitemap-topics": "Sitemap-emne", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Tøm sitemap-cache", "view-sitemap": "Vis sitemap" } \ No newline at end of file diff --git a/public/language/nn-NO/aria.json b/public/language/nn-NO/aria.json index a89012d5a8..517122ab4c 100644 --- a/public/language/nn-NO/aria.json +++ b/public/language/nn-NO/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profilside for brukar %1", "user-watched-tags": "Emneord følgt av brukar", "delete-upload-button": "Slett opplasting-knapp", - "group-page-link-for": "Gruppeside-lenkje for, %1" + "group-page-link-for": "Gruppeside-lenkje for, %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/nn-NO/category.json b/public/language/nn-NO/category.json index df3374395f..393f310792 100644 --- a/public/language/nn-NO/category.json +++ b/public/language/nn-NO/category.json @@ -1,29 +1,30 @@ { "category": "Kategori", "subcategories": "Underkategoriar", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", - "new-topic-button": "Nytt emne", + "new-topic-button": "Nytt innlegg", "guest-login-post": "Logg inn for å legge inn innlegg", "no-topics": "Denne kategorien er foreløpig tom.
Har du noko å dele? Opprett eit innlegg her!", + "no-followers": "Ingen følger denne kategorien enno. Følg den for å få oppdateringer.", "browsing": "blar gjennom", "no-replies": "Ingen har svart", "no-new-posts": "Ingen nye innlegg.", "watch": "Følg", "ignore": "Ignorer", - "watching": "Følgjer", + "watching": "Følger", "tracking": "Følgjer med", - "not-watching": "Følgjer ikkje", + "not-watching": "Følger ikkje", "ignoring": "Ignorerer", - "watching.description": "Varsle meg om nye emne.
Vis emne i Uleste og nye", - "tracking.description": "Viser emne som uleste og nye", - "not-watching.description": "Vis ikkje emne som uleste, vis i nye", - "ignoring.description": "Vis ikkje emne som uleste og nye", - "watching.message": "Du følgjer no oppdateringar frå denne kategorien og alle underkategoriar", + "watching.description": "Varsle meg om nye innlegg.
Vis innlegg i Uleste og Siste", + "tracking.description": "Viser innlegg i Uleste og Siste", + "not-watching.description": "Ikkje vis innlegg i Uleste, vis i Siste", + "ignoring.description": "Ikkje vis innlegg i Uleste eller Siste", + "watching.message": "Du følger no oppdateringar frå denne kategorien og alle underkategoriar", "tracking.message": "Du følgjer no med på oppdateringar frå denne kategorien og alle underkategoriar", - "notwatching.message": "Du følgjer ikkje oppdateringar frå denne kategorien og alle underkategoriar", + "notwatching.message": "Du følger ikkje oppdateringar frå denne kategorien og alle underkategoriar", "ignoring.message": "Du ignorerer no oppdateringar frå denne kategorien og alle underkategoriar", - "watched-categories": "Kategoriar du følgjer", + "watched-categories": "Kategoriar du følger", "x-more-categories": "%1 fleire kategoriar" } \ No newline at end of file diff --git a/public/language/nn-NO/email.json b/public/language/nn-NO/email.json index 78d9456253..f6bcc0b6e4 100644 --- a/public/language/nn-NO/email.json +++ b/public/language/nn-NO/email.json @@ -17,7 +17,7 @@ "invitation.text2": "Invitasjonen din vil utløpe om %1 dagar.", "invitation.cta": "Klikk her for å opprette kontoen din.", "reset.text1": "Vi har mottatt ein førespurnad om å tilbakestille passordet ditt, moglegvis fordi du har gløymt det. Om dette ikkje er tilfelle, ver venleg å ignorere denne e-posten.", - "reset.text2": "For å halde fram med tilbakestillinga av passordet, ver venleg å klikk på lenkja nedanfor:", + "reset.text2": "For å halde fram med tilbakestillinga av passordet, ver venleg å klikk på lenka nedanfor:", "reset.cta": "Klikk her for å tilbakestille passordet ditt", "reset.notify.subject": "Passord endra med suksess", "reset.notify.text1": "Vi informerer deg om at passordet ditt vart endra med suksess den %1.", diff --git a/public/language/nn-NO/error.json b/public/language/nn-NO/error.json index 3b7434cd7e..27034348a3 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -3,6 +3,7 @@ "invalid-json": "Ugyldig JSON", "wrong-parameter-type": "Ein verdi av typen %3 var venta for eigenskapen `%1`, men %2 vart mottatt i staden", "required-parameters-missing": "Naudsynt parameter mangla i denne API-kallinga: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Du ser ikkje ut til å vere logga inn.", "account-locked": "Kontoen din har blitt midlertidig låst", "search-requires-login": "Søk krev ein konto - ver venleg å logge inn eller registrer deg.", @@ -36,9 +37,9 @@ "email-nochange": "Den oppgitte e-posten er den same som e-posten som allereie er registrert.", "email-invited": "E-post vart allereie invitert", "email-not-confirmed": "Posting i nokre kategoriar eller emne er mogleg når e-posten din er stadfesta, ver venleg å klikke her for å sende ein stadfestings-e-post.", - "email-not-confirmed-chat": "Du kan ikkje chatte før e-posten din er stadfesta, ver venleg å klikke her for å stadfeste e-posten din.", - "email-not-confirmed-email-sent": "E-posten din har ikkje blitt stadfesta enno, ver venleg å sjekke innboksen din for stadfestings-e-posten. Du kan kanskje ikkje poste i nokre kategoriar eller chatte før e-posten er stadfesta.", - "no-email-to-confirm": "Kontoen din har ikkje ein registrert e-post. Ein e-post er naudsynt for kontogjenoppretting, og kan vere naudsynt for å chatte og poste i nokre kategoriar. Ver venleg å klikke her for å registrere ein e-post.", + "email-not-confirmed-chat": "Du kan ikkje sende meldingar før e-posten din er stadfesta, ver venleg å klikke her for å stadfeste e-posten din.", + "email-not-confirmed-email-sent": "E-posten din har ikkje blitt stadfesta enno, ver venleg å sjekke innboksen din for stadfestings-e-posten. Du kan kanskje ikkje poste i nokre kategoriar eller sende meldingar før e-posten er stadfesta.", + "no-email-to-confirm": "Kontoen din har ikkje ein registrert e-post. Ein e-post er naudsynt for kontogjenoppretting, og kan vere naudsynt for å sende meldingar og poste i nokre kategoriar. Ver venleg å klikke her for å registrere ein e-post.", "user-doesnt-have-email": "Brukaren \"%1\" har ikkje ein registrert e-post.", "email-confirm-failed": "Vi kunne ikkje stadfeste e-posten din, prøv igjen seinare.", "confirm-email-already-sent": "Stadfestings-e-post er allereie sendt, ver venleg å vente %1 minutt før du sender ein ny.", @@ -146,6 +147,7 @@ "post-already-restored": "Dette innlegget har allereie blitt gjenoppretta", "topic-already-deleted": "Dette emnet har allereie blitt sletta", "topic-already-restored": "Dette emnet har allereie blitt gjenoppretta", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Du kan ikkje rense hovudinnlegget, ver venleg å slette emnet i staden", "topic-thumbnails-are-disabled": "Miniatyrbilete for emne er deaktivert.", "invalid-file": "Ugyldig fil", @@ -153,28 +155,30 @@ "signature-too-long": "Beklager, signaturen din kan ikkje vere lengre enn %1 teikn.", "about-me-too-long": "Beklager, 'Om meg' kan ikkje vere lengre enn %1 teikn.", "cant-chat-with-yourself": "Du kan ikkje chatte med deg sjølv!", - "chat-restricted": "Denne brukaren har avgrensa chatmeldingar. Dei må følgje deg før du kan chatte med dei", + "chat-restricted": "Denne brukaren tillèt berre meldingar frå følgarar. Dei må følge før du kan sende melding.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "Du har blitt blokkert av denne brukaren.", "chat-disabled": "Chatsystem deaktivert", "too-many-messages": "Du har sendt for mange meldingar, ver venleg å vente litt.", - "invalid-chat-message": "Ugyldig chatmelding", + "invalid-chat-message": "Ugyldig melding", "chat-message-too-long": "Chatmeldingar kan ikkje vere lengre enn %1 teikn.", "cant-edit-chat-message": "Du har ikkje løyve til å redigere denne meldinga", "cant-delete-chat-message": "Du har ikkje løyve til å slette denne meldinga", - "chat-edit-duration-expired": "Du kan berre redigere chatmeldingar i %1 sekund etter posting", - "chat-delete-duration-expired": "Du kan berre slette chatmeldingar i %1 sekund etter posting", - "chat-deleted-already": "Denne chatmeldinga har allereie blitt sletta.", - "chat-restored-already": "Denne chatmeldinga har allereie blitt gjenoppretta.", + "chat-edit-duration-expired": "Du kan berre redigere meldingar i %1 sekund etter posting", + "chat-delete-duration-expired": "Du kan berre slette meldingar i %1 sekund etter posting", + "chat-deleted-already": "Denne meldinga har allereie blitt sletta.", + "chat-restored-already": "Denne meldinga har allereie blitt gjenoppretta.", "chat-room-does-not-exist": "Chatten eksisterer ikkje.", "cant-add-users-to-chat-room": "Kan ikkje legge til brukarar i chat.", "cant-remove-users-from-chat-room": "Kan ikkje fjerne brukarar frå chat.", "chat-room-name-too-long": "Chatnamn er for langt. Namnet kan ikkje vere lengre enn %1 teikn.", "remote-chat-received-too-long": "You received a chat message from %1, but it was too long and was rejected.", - "already-voting-for-this-post": "Du har allereie stemt på dette innlegget.", + "already-voting-for-this-post": "Du har allereie anbefalt dette innlegget.", "reputation-system-disabled": "Omdømmesystemet er deaktivert.", "downvoting-disabled": "Nedstemming er deaktivert", "not-enough-reputation-to-chat": "Du treng %1 omdømme for å chatte", - "not-enough-reputation-to-upvote": "Du treng %1 omdømme for å tilrå", + "not-enough-reputation-to-upvote": "Du treng %1 omdømme for å anbefale", "not-enough-reputation-to-downvote": "Du treng %1 omdømme for å stemme ned", "not-enough-reputation-to-post-links": "Du treng %1 omdømme for å poste lenkjer", "not-enough-reputation-to-flag": "Du treng %1 omdømme for å rapportere dette innlegget", @@ -186,7 +190,7 @@ "not-enough-reputation-custom-field": "Du treng %1 i omdømme for å %2", "custom-user-field-value-too-long": "Tilpassa felt er er for langt, %1", "custom-user-field-select-value-invalid": "Alternativet i tilpassa felt er ugyldig, %1 ", - "custom-user-field-invalid-text": "Tilpassa tekst felt er ugyldig, %1", + "custom-user-field-invalid-text": "Tilpassa tekstfelt er ugyldig, %1", "custom-user-field-invalid-link": "Tilpassa lenke felt er ugyldig, %1", "custom-user-field-invalid-number": "Tilpassa felt for tal er ugyldig, %1", "custom-user-field-invalid-date": "Tilpassa felt for dato er ugyldig, %1", @@ -199,9 +203,9 @@ "too-many-user-flags-per-day": "Du kan berre rapportere %1 brukar(ar) per dag", "cant-flag-privileged": "Du har ikkje løyve til å rapportere profilar eller innhald frå privilegerte brukarar (moderatorar/globale moderatorar/administratorar)", "cant-locate-flag-report": "Kan ikkje finne rapport", - "self-vote": "Du kan ikkje stemme på ditt eige innlegg", - "too-many-upvotes-today": "Du kan berre tilrå %1 gong(ar) per dag", - "too-many-upvotes-today-user": "Du kan berre tilrå ein brukar %1 gong(ar) per dag", + "self-vote": "Du kan ikkje anbefale ditt eige innlegg", + "too-many-upvotes-today": "Du kan berre anbefale %1 gong(er) per dag", + "too-many-upvotes-today-user": "Du kan berre anbefale ein brukar %1 gong(er) per dag", "too-many-downvotes-today": "Du kan berre stemme ned %1 gong(ar) per dag", "too-many-downvotes-today-user": "Du kan berre stemme ned ein brukar %1 gong(ar) per dag", "reload-failed": "NodeBB møtte eit problem ved oppdatering: \"%1\". NodeBB vil fortsette å bruke eksisterande klientressursar, men du bør oppheve det du gjorde før oppdateringa.", @@ -225,6 +229,7 @@ "no-topics-selected": "Ingen emne valt!", "cant-move-to-same-topic": "Kan ikkje flytte innlegg til same emne!", "cant-move-topic-to-same-category": "Kan ikkje flytte emne til same kategori!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Du kan ikkje blokkere deg sjølv!", "cannot-block-privileged": "Du kan ikkje blokkere administratorar eller globale moderatorar", "cannot-block-guest": "Gjestar kan ikkje blokkere andre brukarar", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Kan ikkje nå serveren for augneblinken. Klikk her for å prøve igjen, eller prøv seinare", "invalid-plugin-id": "Ugyldig plugin-ID", "plugin-not-whitelisted": "Kan ikkje installere plugin – berre pluginar som er kvitelistet av NodeBB Package Manager kan installerast via ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "Du har ikkje løyve til å endre plugin-status sidan dei er definert ved oppstart (config.json, miljøvariablar eller terminalargument), ver venleg å endre konfigurasjonen i staden.", "theme-not-set-in-configuration": "Når ein definerer aktive pluginar i konfigurasjonen, krev endring av tema at det nye temaet vert lagt til i lista over aktive pluginar før det oppdaterast i ACP", @@ -246,6 +252,7 @@ "api.401": "Ein gyldig innloggingssesjon vart ikkje funne. Ver venleg å logge inn og prøv igjen.", "api.403": "Du har ikkje autorisasjon til å gjere denne handlinga", "api.404": "Ugyldig API-kall", + "api.413": "The request payload is too large", "api.426": "HTTPS er naudsynt for førespurnader til skrive-API-et, ver venleg å sende førespurnaden din via HTTPS", "api.429": "Du har gjort for mange førespurnader, prøv igjen seinare", "api.500": "Ein uventa feil oppstod medan vi prøvde å handsame førespurnaden din.", diff --git a/public/language/nn-NO/flags.json b/public/language/nn-NO/flags.json index 0ab74af824..8199619302 100644 --- a/public/language/nn-NO/flags.json +++ b/public/language/nn-NO/flags.json @@ -75,7 +75,7 @@ "sort-all": "Sorter alle", "sort-posts-only": "Berre innlegg", "sort-downvotes": "Sorter etter nedstemmer", - "sort-upvotes": "Sorter etter tilrådingar", + "sort-upvotes": "Sorter etter anbefalingar", "sort-replies": "Sorter etter svar", "modal-title": "Rapporter innlegg", diff --git a/public/language/nn-NO/global.json b/public/language/nn-NO/global.json index e66aa148f4..383dc061ab 100644 --- a/public/language/nn-NO/global.json +++ b/public/language/nn-NO/global.json @@ -35,14 +35,14 @@ "header.brand-logo": "Logo", "header.admin": "Admin", "header.categories": "Kategoriar", - "header.recent": "Nyleg", + "header.recent": "Siste", "header.unread": "Uleste", "header.tags": "Emneord", "header.popular": "Populære", "header.top": "Topp", "header.users": "Brukarar", "header.groups": "Grupper", - "header.chats": "Chattar", + "header.chats": "Chat", "header.notifications": "Varsel", "header.search": "Søk", "header.profile": "Profil", @@ -52,7 +52,7 @@ "header.drafts": "Utkast", "header.world": "Verda", "notifications.loading": "Laster varsel", - "chats.loading": "Laster chattar", + "chats.loading": "Lastar meldingar", "drafts.loading": "Laster utkast", "motd.welcome": "Velkomen til NodeBB, diskusjonsplattformen for framtida.", "alert.success": "Suksess", @@ -63,25 +63,27 @@ "alert.banned.message": "Du har blitt utestengd, tilgangen din er no avgrensa.", "alert.unbanned": "Ikkje lenger utestengd", "alert.unbanned.message": "Utestenginga di har blitt oppheva.", - "alert.unfollow": "Du følgjer ikkje lenger %1!", - "alert.follow": "Du følgjer no %1!", + "alert.unfollow": "Du følger ikkje lenger %1!", + "alert.follow": "Du følger no %1!", "users": "Brukarar", "topics": "Emne", "posts": "Innlegg", + "crossposts": "Cross-posts", "x-posts": "%1 innlegg", "x-topics": "%1 emne", "x-reputation": "%1 omdømme", "best": "Best", "controversial": "Kontroversiell", - "votes": "Stemmer", - "x-votes": "%1 stemmer", - "voters": "Veljarar", - "upvoters": "Tilrår", - "upvoted": "Tilrådde", + "votes": "Anbefalinger", + "x-votes": "%1 anbefalinger", + "voters": "Anbefaler", + "upvoters": "Anbefaler", + "upvoted": "Anbefalt", "downvoters": "Nedstemmarar", "downvoted": "Stemte ned", "views": "Visningar", "posters": "Innleggsskrivarar", + "watching": "Følger", "reputation": "Omdømme", "lastpost": "Siste innlegg", "firstpost": "Fyrste innlegg", @@ -100,9 +102,9 @@ "guest-posted-ago": "Gjest posta %1", "last-edited-by": "sist redigert av %1", "edited-timestamp": "Redigert %1", - "norecentposts": "Ingen nylege innlegg", - "norecenttopics": "Ingen nylege emne", - "recentposts": "Nylege innlegg", + "norecentposts": "Ingen nye svar", + "norecenttopics": "Ingen nye innlegg", + "recentposts": "Siste innlegg", "recentips": "Nyleg logga IP-ar", "moderator-tools": "Moderatorverktøy", "status": "Status", diff --git a/public/language/nn-NO/groups.json b/public/language/nn-NO/groups.json index c93187fba6..ec9c2a633e 100644 --- a/public/language/nn-NO/groups.json +++ b/public/language/nn-NO/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Alle grupper", "groups": "Grupper", "members": "Medlemmar", + "x-members": "%1 member(s)", "view-group": "Sjå gruppe", "owner": "Gruppeeigar", "new-group": "Opprett ny gruppe", diff --git a/public/language/nn-NO/ip-blacklist.json b/public/language/nn-NO/ip-blacklist.json index 12cfeb8050..8baad9e7bb 100644 --- a/public/language/nn-NO/ip-blacklist.json +++ b/public/language/nn-NO/ip-blacklist.json @@ -5,7 +5,7 @@ "validate": "Valider", "apply": "Bruk", "hints": "Tips", - "hint-1": "Definer éi IP-adresse per linje. Du kan leggje til IP-blokker så lenge dei følgjer CIDR-formatet (t.d. 192.168.100.0/22).", + "hint-1": "Definer éi IP-adresse per linje. Du kan leggje til IP-blokker så lenge dei følger CIDR-formatet (t.d. 192.168.100.0/22).", "hint-2": "Pass på å validere reglane før du tek dei i bruk.", "validate.x-valid": "%1 av %2 reglar er gyldige.", diff --git a/public/language/nn-NO/modules.json b/public/language/nn-NO/modules.json index 353020cc78..5df6ce31ff 100644 --- a/public/language/nn-NO/modules.json +++ b/public/language/nn-NO/modules.json @@ -1,26 +1,26 @@ { "chat.room-id": "Rom %1", - "chat.chatting-with": "Chat med", - "chat.placeholder": "Skriv chatmelding her, dra og slepp bilete", - "chat.placeholder.mobile": "Skriv chatmelding", + "chat.chatting-with": "Send melding til", + "chat.placeholder": "Skriv melding her", + "chat.placeholder.mobile": "Skriv melding", "chat.placeholder.message-room": "Melding #%1", - "chat.scroll-up-alert": "Gå til nyaste melding", + "chat.scroll-up-alert": "Gå til siste innlegg", "chat.usernames-and-x-others": "%1 og %2 andre", "chat.chat-with-usernames": "Chat med %1", "chat.chat-with-usernames-and-x-others": "Chat med %1 og %2 andre", - "chat.send": "Send", - "chat.no-active": "Du har ingen aktive chattar", + "chat.send": "Publiser", + "chat.no-active": "Du har ingen aktive meldingar.", "chat.user-typing-1": "%1 skriv ...", "chat.user-typing-2": "%1 og %2 skriv ...", "chat.user-typing-3": "%1, %2 og %3 skriv ...", "chat.user-typing-n": "%1, %2 og %3 andre skriv ...", "chat.user-has-messaged-you": "%1 har sendt deg ei melding.", "chat.replying-to": "Svarar til %1", - "chat.see-all": "Alle chattar", + "chat.see-all": "Alle meldingar", "chat.mark-all-read": "Merk alle som lese", "chat.no-messages": "Vel ein mottakar for å sjå meldingshistorikk", "chat.no-users-in-room": "Ingen brukarar i dette rommet", - "chat.recent-chats": "Nylege chattar", + "chat.recent-chats": "Siste meldingar", "chat.contacts": "Kontaktar", "chat.message-history": "Meldingshistorikk", "chat.message-deleted": "Melding sletta", @@ -40,7 +40,7 @@ "chat.unpin-message": "Fjern festing av melding", "chat.public-rooms": "Offentlege rom (%1)", "chat.private-rooms": "Private rom (%1)", - "chat.create-room": "Opprett chat-rom", + "chat.create-room": "Opprett melding", "chat.private.option": "Privat (berre synleg for brukarar lagt til i rommet)", "chat.public.option": "Offentleg (synleg for alle brukarar i valde grupper)", "chat.public.groups-help": "For å opprette ein chat synleg for alle brukarar, vel registered-users frå gruppelista.", @@ -48,20 +48,21 @@ "chat.add-user": "Legg til brukar", "chat.notification-settings": "Varslingsinnstillingar", "chat.default-notification-setting": "Standard varslingsinnstillinger", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Standard for rommet", "chat.notification-setting-none": "Ingen varsel", "chat.notification-setting-at-mention-only": "Berre @nemning", "chat.notification-setting-all-messages": "Alle meldingar", "chat.select-groups": "Vel grupper", "chat.add-user-help": "Søk etter brukarar her. Når vald, vert brukaren lagt til i chatten. Den nye brukaren vil ikkje sjå meldingar skrivne før dei vart lagt til i samtalen. Berre chat-eigar () kan fjerne brukarar frå chat.", - "chat.confirm-chat-with-dnd-user": "Denne brukaren har sett statusen til Ikkje forstyrr. Ønskjer du framleis å chatte med dei?", + "chat.confirm-chat-with-dnd-user": "Denne brukaren har sett statusen til Ikkje forstyrr. Ønskjer du framleis å sende ein melding?", "chat.room-name-optional": "Romnamn (valfritt)", "chat.rename-room": "Endre namn på rom", "chat.rename-placeholder": "Skriv inn romnamnet her", "chat.rename-help": "Romnamnet du set her vil vere synleg for alle deltakarar i rommet.", "chat.leave": "Forlat", "chat.leave-room": "Forlat rom", - "chat.leave-prompt": "Er du sikker på at du vil forlate denne chatten?", + "chat.leave-prompt": "Er du sikker på at du vil forlate denne meldingstråden?", "chat.leave-help": "Å forlate denne chatten vil fjerne deg frå framtidige samtalar i denne chatten. Om du vert lagt til igjen i framtida, vil du ikkje sjå tidlegare meldingshistorikk.", "chat.delete": "Slett", "chat.delete-room": "Slett rom", @@ -81,7 +82,7 @@ "composer.hide-preview": "Skjul førehandsvising", "composer.help": "Hjelp", "composer.user-said-in": "%1 sa i %2:", - "composer.user-said": "%1 sa:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Er du sikker på at du vil forkaste dette innlegget?", "composer.submit-and-lock": "Send inn og lås", "composer.toggle-dropdown": "Vis/skjul nedtrekksmeny", @@ -121,7 +122,7 @@ "bootbox.cancel": "Avbryt", "bootbox.confirm": "Stadfest", "bootbox.submit": "Send inn", - "bootbox.send": "Send", + "bootbox.send": "Publiser", "cover.dragging-title": "Posisjonering av omslagsbilete", "cover.dragging-message": "Dra omslagsbiletet til ønska posisjon og klikk \"Lagre\"", "cover.saved": "Omslagsbilete og posisjon lagra", diff --git a/public/language/nn-NO/notifications.json b/public/language/nn-NO/notifications.json index d7602b43b4..dceaec1cfd 100644 --- a/public/language/nn-NO/notifications.json +++ b/public/language/nn-NO/notifications.json @@ -11,18 +11,18 @@ "new-notification": "Du har eit nytt varsel", "you-have-unread-notifications": "Du har uleste varsel.", "all": "Alle", - "topics": "Emne", + "topics": "Innlegg", "tags": "Emneord", "categories": "Kategoriar", "replies": "Svar", "chat": "Meldingar", - "group-chat": "Gruppechat", + "group-chat": "Gruppemelding", "public-chat": "Offentleg chat", - "follows": "Følgjarar", - "upvote": "Tilrår", + "follows": "Følgarar", + "upvote": "Anbefaler", "awards": "Utmerkingar", "new-flags": "Nye rapporteringar", - "my-flags": "Rapporteringar tilordna meg", + "my-flags": "My Flags", "bans": "Utestengingar", "new-message-from": "Ny melding frå %1", "new-messages-from": "%1 nye meldingar frå %2", @@ -32,35 +32,35 @@ "user-posted-in-public-room-dual": "%1 og %2 skreiv i %4", "user-posted-in-public-room-triple": "%1, %2 og %3 skreiv i %5", "user-posted-in-public-room-multiple": "%1, %2 og %3 andre skreiv i %5", - "upvoted-your-post-in": "%1 har tilrådd innlegget ditt i %2.", - "upvoted-your-post-in-dual": "%1 og %2 har tilrådd innlegget ditt i %3.", - "upvoted-your-post-in-triple": "%1, %2 og %3 har tilrådd innlegget ditt i %4.", - "upvoted-your-post-in-multiple": "%1, %2 og %3 andre har tilrådd innlegget ditt i %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 har flytta innlegget ditt til %2", "moved-your-topic": "%1 har flytta %2", - "user-flagged-post-in": "%1 rapporterte eit innlegg i %2", - "user-flagged-post-in-dual": "%1 og %2 rapporterte eit innlegg i %3", - "user-flagged-post-in-triple": "%1, %2 og %3 rapporterte eit innlegg i %4", - "user-flagged-post-in-multiple": "%1, %2 og %3 andre rapporterte eit innlegg i %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 rapporterte ein brukarpresentasjon (%2)", "user-flagged-user-dual": "%1 og %2 rapporterte ein brukarpresentasjon (%3)", "user-flagged-user-triple": "%1, %2 og %3 rapporterte ein brukarpresentasjon (%4)", "user-flagged-user-multiple": "%1, %2 og %3 andre rapporterte ein brukarpresentasjon (%4)", - "user-posted-to": "%1 har posta eit svar til: %2", - "user-posted-to-dual": "%1 og %2 har posta svar til: %3", - "user-posted-to-triple": "%1, %2 og %3 har posta svar til: %4", - "user-posted-to-multiple": "%1, %2 og %3 andre har posta svar til: %4", - "user-posted-topic": "%1 har posta eit nytt emne: %2", - "user-edited-post": "%1 har redigert eit innlegg i %2", - "user-posted-topic-with-tag": "%1 har posta %2 (merka %3)", - "user-posted-topic-with-tag-dual": "%1 har posta %2 (merka %3 og %4)", - "user-posted-topic-with-tag-triple": "%1 har posta %2 (merka %3, %4, og %5)", - "user-posted-topic-with-tag-multiple": "%1 har posta %2 (merka %3)", - "user-posted-topic-in-category": "%1 har posta eit nytt emne i %2", - "user-started-following-you": "%1 starta å følgje deg.", - "user-started-following-you-dual": "%1 og %2 starta å følgje deg.", - "user-started-following-you-triple": "%1, %2 og %3 starta å følgje deg.", - "user-started-following-you-multiple": "%1, %2 og %3 andre starta å følgje deg.", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", + "user-started-following-you": "%1 starta å følge deg.", + "user-started-following-you-dual": "%1 og %2 starta å følge deg.", + "user-started-following-you-triple": "%1, %2 og %3 starta å følge deg.", + "user-started-following-you-multiple": "%1, %2 og %3 andre starta å følge deg.", "new-register": "%1 sende ein registreringsførespurnad.", "new-register-multiple": "Det er %1 registreringsførespurnadar som ventar på gjennomgang.", "flag-assigned-to-you": "Rapport %1 er tilordna deg", @@ -71,26 +71,26 @@ "users-csv-exported": "Brukar-csv eksportert, klikk for å laste ned", "post-queue-accepted": "Innlegget ditt i køen er godkjent. Klikk her for å sjå innlegget.", "post-queue-rejected": "Innlegget ditt i køen er avvist.", - "post-queue-notify": "Innlegg i kø fekk ein varsel:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-post stadfesta", "email-confirmed-message": "Takk for at du stadfesta e-posten din. Kontoen din er no fullt aktivert.", "email-confirm-error-message": "Det oppstod eit problem med å stadfeste e-postadressa di. Kanskje koden var ugyldig eller utløpt.", - "email-confirm-error-message-already-validated": "E-postadressa di var allereie stadfesta.", "email-confirm-sent": "Stadfestings-e-post sendt.", "none": "Ingen", "notification-only": "Berre varsel", "email-only": "Berre e-post", "notification-and-email": "Varsel og e-post", - "notificationType-upvote": "Når nokon tilrår innlegget ditt", - "notificationType-new-topic": "Når nokon du følgjer opprettar eit emne", - "notificationType-new-topic-with-tag": "Når eit emne vert oppretta med eit emneord du følgjer", - "notificationType-new-topic-in-category": "Når eit emne vert oppretta i ein kategori du følgjer", - "notificationType-new-reply": "Når eit nytt svar vert posta i eit emne du følgjer", - "notificationType-post-edit": "Når eit innlegg vert redigert i eit emne du følgjer", - "notificationType-follow": "Når nokon startar å følgje deg", - "notificationType-new-chat": "Når du mottek ei chatmelding", - "notificationType-new-group-chat": "Når du mottek ei gruppemelding", - "notificationType-new-public-chat": "Når du mottek ei offentleg gruppemelding", + "notificationType-upvote": "Når nokon anbefaler innlegget ditt", + "notificationType-new-topic": "Når nokon du følger opprettar eit innlegg", + "notificationType-new-topic-with-tag": "Når eit innlegg blir oppretta med eit emneord du følger", + "notificationType-new-topic-in-category": "Når eit innlegg vert oppretta i ein kategori du følger", + "notificationType-new-reply": "Når eit nytt svar vert posta i eit innlegg du følger", + "notificationType-post-edit": "Når eit svar blir redigert i eit innlegg du følger", + "notificationType-follow": "Når nokon startar å følge deg", + "notificationType-new-chat": "Når du mottar ei melding", + "notificationType-new-group-chat": "Når du mottar ei gruppemelding", + "notificationType-new-public-chat": "Når du mottar ei offentleg gruppemelding", "notificationType-group-invite": "Når du mottek ei gruppeinvitasjon", "notificationType-group-leave": "Når ein brukar forlèt gruppa di", "notificationType-group-request-membership": "Når nokon ber om å bli med i ei gruppe du eig", @@ -99,8 +99,8 @@ "notificationType-new-post-flag": "Når eit innlegg vert rapportert", "notificationType-new-user-flag": "Når ein brukar vert rapportert", "notificationType-new-reward": "Når du får ein ny utmerking", - "activitypub.announce": "%1 delte din post i %2 til sine følgjarar.", - "activitypub.announce-dual": "%1 og %2 delte din post i %3til sine følgjarar.", - "activitypub.announce-triple": "%1, %2 og %3 delte din post i %4 til sine følgjarar.", - "activitypub.announce-multiple": "%1, %2 og %3 andre delte din post i %4 til sine følgjarar." + "activitypub.announce": "%1 delte din post i %2 til sine følgarar.", + "activitypub.announce-dual": "%1 og %2 delte innlegget ditt i %3til sine følgarar.", + "activitypub.announce-triple": "%1, %2 og %3 delte din post i %4 til sine følgarar.", + "activitypub.announce-multiple": "%1, %2 og %3 andre delte din post i %4 til sine følgarar." } \ No newline at end of file diff --git a/public/language/nn-NO/pages.json b/public/language/nn-NO/pages.json index 4d1cdb955d..596398d6cc 100644 --- a/public/language/nn-NO/pages.json +++ b/public/language/nn-NO/pages.json @@ -5,11 +5,11 @@ "popular-week": "Populære emne denne veka", "popular-month": "Populære emne denne månaden", "popular-alltime": "Mest populære emne gjennom tidene", - "recent": "Nylege emne", - "top-day": "Mest stemte emne i dag", - "top-week": "Mest stemte emne denne veka", - "top-month": "Mest stemte emne denne månaden", - "top-alltime": "Mest stemte emne gjennom tidene", + "recent": "Siste innlegg", + "top-day": "Mest anbefalte innlegg i dag", + "top-week": "Mest anbefalte innlegg denne veka", + "top-month": "Mest anbefalte innlegg denne månaden", + "top-alltime": "Mest anbefalte innlegg", "moderator-tools": "Moderatorverktøy", "flagged-content": "Rapportert innhald", "ip-blacklist": "IP svarteliste", @@ -32,7 +32,7 @@ "categories": "Kategoriar", "groups": "Grupper", "group": "%1 gruppe", - "chats": "Meldingar", + "chats": "Samtalar", "chat": "Chattar med %1", "flags": "Rapportar", "flag-details": "Detaljar for rapport %1", @@ -42,8 +42,8 @@ "account/edit/username": "Endrar brukarnamn for \"%1\"", "account/edit/email": "Endrar e-post for \"%1\"", "account/info": "Kontoinformasjon", - "account/following": "Personar %1 følgjer", - "account/followers": "Personar som følgjer %1", + "account/following": "Personar %1 følger", + "account/followers": "Personar som følger %1", "account/posts": "Innlegg skrive av %1", "account/latest-posts": "Siste innlegg skrive av %1", "account/topics": "Emne oppretta av %1", @@ -56,7 +56,7 @@ "account/watched": "Emne følgde av %1", "account/ignored": "Emne ignorert av %1", "account/read": "Emne lest av %1", - "account/upvoted": "Innlegg tilrådd av %1", + "account/upvoted": "Innlegg anbefalt av %1", "account/downvoted": "Innlegg stemt ned av %1", "account/best": "Beste innlegg skrive av %1", "account/controversial": "Kontroversielle innlegg skrive av %1", diff --git a/public/language/nn-NO/recent.json b/public/language/nn-NO/recent.json index ee3a39b778..64478e71f2 100644 --- a/public/language/nn-NO/recent.json +++ b/public/language/nn-NO/recent.json @@ -1,11 +1,11 @@ { - "title": "Nyleg", + "title": "Siste", "day": "Dag", "week": "Veke", "month": "Månad", "year": "År", "alltime": "Heile tida", - "no-recent-topics": "Det er ingen nylege emne.", + "no-recent-topics": "Ingen nye innlegg.", "no-popular-topics": "Det er ingen populære emne.", "load-new-posts": "Last nye innlegg", "uncategorized.title": "Alle kjente emner", diff --git a/public/language/nn-NO/search.json b/public/language/nn-NO/search.json index 8bb15e8cde..fe877e3e63 100644 --- a/public/language/nn-NO/search.json +++ b/public/language/nn-NO/search.json @@ -38,7 +38,7 @@ "relevance": "Relevans", "time": "Tid", "post-time": "Innleggstid", - "votes": "Stemmer", + "votes": "Anbefalingar", "newer-than": "Nyare enn", "older-than": "Eldre enn", "any-date": "Kva som helst dato", @@ -67,7 +67,7 @@ "sort": "Sorter", "last-reply-time": "Tid for siste svar", "topic-title": "Emnetittel", - "topic-votes": "Emnestemmer", + "topic-votes": "Anbefalingar av innlegg", "number-of-replies": "Antal svar", "number-of-views": "Antal visningar", "topic-start-date": "Startdato for emne", @@ -79,8 +79,8 @@ "sort-by-relevance-asc": "Sorter etter: Relevans i stigande rekkefølgje", "sort-by-timestamp-desc": "Sorter etter: Innleggstid i synkande rekkefølgje", "sort-by-timestamp-asc": "Sorter etter: Innleggstid i stigande rekkefølgje", - "sort-by-votes-desc": "Sorter etter: Stemmer i synkande rekkefølgje", - "sort-by-votes-asc": "Sorter etter: Stemmer i stigande rekkefølgje", + "sort-by-votes-desc": "Sorter etter: Anbefalingar i synkande rekkefølgje", + "sort-by-votes-asc": "Sorter etter: Anbefalingar i stigande rekkefølgje", "sort-by-topic.lastposttime-desc": "Sorter etter: Tid for siste svar i synkande rekkefølgje", "sort-by-topic.lastposttime-asc": "Sorter etter: Tid for siste svar i stigande rekkefølgje", "sort-by-topic.title-desc": "Sorter etter: Emnetittel i synkande rekkefølgje", @@ -89,8 +89,8 @@ "sort-by-topic.postcount-asc": "Sorter etter: Antal svar i stigande rekkefølgje", "sort-by-topic.viewcount-desc": "Sorter etter: Antal visningar i synkande rekkefølgje", "sort-by-topic.viewcount-asc": "Sorter etter: Antal visningar i stigande rekkefølgje", - "sort-by-topic.votes-desc": "Sorter etter: Emnestemmer i synkande rekkefølgje", - "sort-by-topic.votes-asc": "Sorter etter: Emnestemmer i stigande rekkefølgje", + "sort-by-topic.votes-desc": "Sorter etter: Anbefalingar i synkande rekkefølgje", + "sort-by-topic.votes-asc": "Sorter etter: Anbefalingar i stigande rekkefølgje", "sort-by-topic.timestamp-desc": "Sorter etter: Startdato for emne i synkande rekkefølgje", "sort-by-topic.timestamp-asc": "Sorter etter: Startdato for emne i stigande rekkefølgje", "sort-by-user.username-desc": "Sorter etter: Brukarnamn i synkande rekkefølgje", diff --git a/public/language/nn-NO/social.json b/public/language/nn-NO/social.json index 9f5af4b954..8afa1b2a1e 100644 --- a/public/language/nn-NO/social.json +++ b/public/language/nn-NO/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Logg inn med Facebook", "continue-with-facebook": "Hald fram med Facebook", "sign-in-with-linkedin": "Logg inn med LinkedIn", - "sign-up-with-linkedin": "Registrer deg med LinkedIn" + "sign-up-with-linkedin": "Registrer deg med LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/nn-NO/tags.json b/public/language/nn-NO/tags.json index fd1087d24c..fb28e5bf2d 100644 --- a/public/language/nn-NO/tags.json +++ b/public/language/nn-NO/tags.json @@ -8,10 +8,10 @@ "no-tags": "Det er ingen emneord enno.", "select-tags": "Vel emneord", "tag-whitelist": "Kviteliste for emneord", - "watching": "Følgjer", - "not-watching": "Følgjer ikkje", - "watching.description": "Varsle meg om nye emne.", - "not-watching.description": "Ikkje varsle meg om nye emne.", + "watching": "Følger", + "not-watching": "Følger ikkje", + "watching.description": "Varsle meg om nye innlegg", + "not-watching.description": "Ikkje varsle meg om nye innlegg", "following-tag.message": "Du vil no motta varsel når nokon postar eit emne med dette emneordet.", "not-following-tag.message": "Du vil ikkje motta varsel når nokon postar eit emne med dette emneordet." } \ No newline at end of file diff --git a/public/language/nn-NO/themes/harmony.json b/public/language/nn-NO/themes/harmony.json index deab342435..e53bc99701 100644 --- a/public/language/nn-NO/themes/harmony.json +++ b/public/language/nn-NO/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Tema-namn", "skins": "Utsjånad", + "light": "Light", + "dark": "Dark", "collapse": "Slå saman", "expand": "Utvid", "sidebar-toggle": "Vis/skjul sidefelt", diff --git a/public/language/nn-NO/topic.json b/public/language/nn-NO/topic.json index 48f429a550..a35cbf5541 100644 --- a/public/language/nn-NO/topic.json +++ b/public/language/nn-NO/topic.json @@ -9,7 +9,7 @@ "posted-by": "Posta av %1", "posted-by-guest": "Posta av gjest", "chat": "Chat", - "notify-me": "Varsle meg om nye svar i dette emnet", + "notify-me": "Varsle meg om nye svar på dette innlegget", "quote": "Sitat", "reply": "Svar", "replies-to-this-post": "%1 svar", @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 refererte til dette emnet den %3", "user-forked-topic-ago": "%1 kopierte dette emnet %3", "user-forked-topic-on": "%1 kopierte dette emnet den %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klikk her for å gå tilbake til siste lese innlegg i denne tråden.", "flag-post": "Rapporter dette innlegget", "flag-user": "Rapporter denne brukaren", @@ -80,22 +82,22 @@ "deleted-message": "Dette emnet er sletta. Berre brukarar med rettar til emneadministrasjon kan sjå det.", "following-topic.message": "Du vil no motta varsel når nokon legg inn innlegg i dette emnet.", "not-following-topic.message": "Du vil sjå dette emnet i lista over uleste emne, men du vil ikkje motta varsel når nokon legg inn innlegg.", - "ignoring-topic.message": "Du vil ikkje lenger sjå dette emnet i lista over uleste emne. Du vil verte varsla når du vert nemnd eller innlegget ditt får oppstemmer.", + "ignoring-topic.message": "Du vil ikkje lenger sjå dette emnet i lista over uleste emne. Du vil verte varsla når du vert nemnd eller innlegget ditt blir anbefalt.", "login-to-subscribe": "Ver venleg å registrer deg eller logg inn for å abonnere på dette emnet.", "markAsUnreadForAll.success": "Emne merka som ulest for alle.", "mark-unread": "Merk som ulest", "mark-unread.success": "Emne merka som ulest.", "watch": "Følg", "unwatch": "Ikkje følg", - "watch.title": "Varsle meg om nye svar i dette emnet", - "unwatch.title": "Slutt å følgje dette emnet", + "watch.title": "Varsle meg om nye svar på dette innlegget", + "unwatch.title": "Slutt å følge dette innlegget", "share-this-post": "Del dette innlegget", - "watching": "Følgjer", - "not-watching": "Følgjer ikkje", + "watching": "Følger", + "not-watching": "Følger ikkje", "ignoring": "Ignorerer", - "watching.description": "Varsle meg om nye svar.
Vis emne som ulest.", - "not-watching.description": "Ikkje varsle meg om nye svar.
Vis emne som ulest om kategorien ikkje er ignorert.", - "ignoring.description": "Ikkje varsle meg om nye svar.
Vis ikkje emne som ulest.", + "watching.description": "Varsle meg om nye svar.
Vis innlegg som ulest.", + "not-watching.description": "Ikkje varsle meg om nye svar.
Vis innlegg i ulest om kategorien ikkje er ignorert.", + "ignoring.description": "Ikkje varsle meg om nye svar.
Ikkje vis innlegg i ulest.", "thread-tools.title": "Emneverktøy", "thread-tools.markAsUnreadForAll": "Merk som ulest for alle", "thread-tools.pin": "Fest emne", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lås emne", "thread-tools.unlock": "Opne emne", "thread-tools.move": "Flytt emne", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Flytt innlegg", "thread-tools.move-all": "Flytt alle", "thread-tools.change-owner": "Endre eigar", @@ -132,6 +135,7 @@ "pin-modal-help": "Du kan eventuelt sette ein utløpsdato for det festa emna her. Alternativt kan du la feltet vere tomt slik at emna held seg festa til det blir manuelt avfesta.", "load-categories": "Lastar kategoriar", "confirm-move": "Flytt", + "confirm-crosspost": "Cross-post", "confirm-fork": "Kopier", "bookmark": "Bokmerke", "bookmarks": "Bokmerke", @@ -141,11 +145,12 @@ "loading-more-posts": "Lastar fleire innlegg", "move-topic": "Flytt emne", "move-topics": "Flytt emne", + "crosspost-topic": "Cross-post Topic", "move-post": "Flytt innlegg", "post-moved": "Innlegg flytta!", "fork-topic": "Kopier emne", - "enter-new-topic-title": "Skriv inn ny emnetittel", - "fork-topic-instruction": "Klikk på innlegga du vil kopiere, skriv inn ein tittel for det nye emnet, og klikk på kopier emne", + "enter-new-topic-title": "Skriv tittel på innlegg", + "fork-topic-instruction": "Klikk på innlegga du vil forgreine, skriv tittel for det nye emnet og velg forgrein emne", "fork-no-pids": "Ingen innlegg vald!", "no-posts-selected": "Ingen innlegg vald!", "x-posts-selected": "%1 innlegg vald", @@ -157,12 +162,15 @@ "merge-topic-list-title": "Liste over emne som skal slåast saman", "merge-options": "Samanslåingsalternativ", "merge-select-main-topic": "Vel hovudemnet", - "merge-new-title-for-topic": "Ny tittel for emne", + "merge-new-title-for-topic": "Ny tittel for innlegg", "topic-id": "Emne-ID", "move-posts-instruction": "Klikk på innlegga du vil flytte, og skriv inn ein emne-ID eller gå til målemnet", "move-topic-instruction": "Vel mål-kategorien, og klikk deretter på flytt", "change-owner-instruction": "Klikk på innlegga du vil tildele ein annan brukar", "manage-editors-instruction": "Administrer brukere som kan endre innlegget under", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Skriv emnetittelen her...", "composer.handle-placeholder": "Skriv namnet/aliaset ditt her", "composer.hide": "Skjul", @@ -172,8 +180,9 @@ "composer.post-later": "Post seinare", "composer.schedule": "Planlegg", "composer.replying-to": "Svarar til %1", - "composer.new-topic": "Nytt emne", + "composer.new-topic": "Nytt innlegg", "composer.editing-in": "Redigerer innlegg i %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "laster opp...", "composer.thumb-url-label": "Lim inn ein emne-miniatyr-URL", "composer.thumb-title": "Legg til ein miniatyr til dette emnet", @@ -190,12 +199,12 @@ "newest-to-oldest": "Nyaste til eldste", "recently-replied": "Sist svart", "recently-created": "Nyleg oppretta", - "most-votes": "Fleire stemmer", + "most-votes": "Flest anbefalingar", "most-posts": "Fleire innlegg", "most-views": "Fleire visningar", - "stale.title": "Opprett nytt emne i staden?", - "stale.warning": "Emnet du svarar på er gammalt. Ønskjer du å opprette eit nytt emne i staden, og referere til dette i svaret ditt?", - "stale.create": "Opprett nytt emne", + "stale.title": "Opprett nytt innlegg i staden?", + "stale.warning": "Innlegget du svarer på er gammalt. Ønskjer du å opprette eit nytt innlegg i staden, og referere til dette i svaret ditt?", + "stale.create": "Opprett nytt innlegg", "stale.reply-anyway": "Svar i dette emnet likevel", "link-back": "Sv: [%1](%2)", "diffs.title": "Innleggsendringshistorikk", @@ -215,14 +224,17 @@ "go-to-my-next-post": "Gå til mitt neste innlegg", "no-more-next-post": "Du har ikkje fleire innlegg i dette emnet", "open-composer": "Opne editor", - "post-quick-reply": "Raskt svar", + "post-quick-reply": "Svar", "navigator.index": "Innlegg %1 av %2", "navigator.unread": "%1 uleste", - "upvote-post": "Tilrå innlegg", + "upvote-post": "Anbefal innlegg", "downvote-post": "Stem ned innlegg", "post-tools": "Innleggsverktøy", "unread-posts-link": "Lenkje til uleste innlegg", "thumb-image": "Emne miniatyrbilete", "announcers": "Delingar", - "announcers-x": "Delingar (%1)" + "announcers-x": "Delingar (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/nn-NO/unread.json b/public/language/nn-NO/unread.json index 37583696e6..2a90cf157f 100644 --- a/public/language/nn-NO/unread.json +++ b/public/language/nn-NO/unread.json @@ -9,8 +9,8 @@ "all-categories": "Alle kategoriar", "topics-marked-as-read.success": "Emne merka som lest!", "all-topics": "Alle emne", - "new-topics": "Nye emne", - "watched-topics": "Emne du følgjer", + "new-topics": "Nye innlegg", + "watched-topics": "Emne du følger", "unreplied-topics": "Emne utan svar", "multiple-categories-selected": "Fleire vald" } \ No newline at end of file diff --git a/public/language/nn-NO/user.json b/public/language/nn-NO/user.json index 11eb7714c0..9de1b2b127 100644 --- a/public/language/nn-NO/user.json +++ b/public/language/nn-NO/user.json @@ -38,15 +38,15 @@ "profile-views": "Profilvisningar", "reputation": "Omdømme", "bookmarks": "Bokmerke", - "watched-categories": "Kategoriar du følgjer", - "watched-tags": "Stikkord du følgjer", + "watched-categories": "Kategoriar du følger", + "watched-tags": "Stikkord du følger", "change-all": "Endre alt", "watched": "Følgde", "ignored": "Ignorerte", "read": "Lese", - "default-category-watch-state": "Standard kategori følgjetilstand", - "followers": "Følgjarar", - "following": "Følgjer", + "default-category-watch-state": "Standard kategori følgetilstand", + "followers": "Følgarar", + "following": "Følger", "shares": "Delingar", "blocks": "Blokker", "blocked-users": "Blokkerte brukarar", @@ -59,12 +59,12 @@ "chat": "Chat", "chat-with": "Fortset chat med %1", "new-chat-with": "Start ny chat med %1", - "view-remote": "View Original", + "view-remote": "Vis opphavleg versjon", "flag-profile": "Rapporter profil", - "profile-flagged": "Allerede flagga", + "profile-flagged": "Allerede rapportert", "follow": "Følg", - "unfollow": "Slutt å følgje", - "cancel-follow": " Avbryt førespurnad om å følgje", + "unfollow": "Slutt å følge", + "cancel-follow": " Avbryt førespurnad om å følge", "more": "Meir", "profile-update-success": "Profilen er oppdatert!", "change-picture": "Endre bilete", @@ -104,25 +104,29 @@ "settings": "Innstillingar", "show-email": "Vis e-posten min", "show-fullname": "Vis fullt namn", - "restrict-chats": "Tillat berre chatmeldingar frå brukarar eg følgjer", + "restrict-chats": "Tillat berre chatmeldingar frå brukarar eg følger", + "disable-incoming-chats": "Slå av meldingar frå chat ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Abonner på oppsummering", - "digest-description": "Abonner på e-postoppdateringar for dette forumet (nye varsel og emne) etter ei fastsett tidsplan", + "digest-description": "Abonner på e-postoppdateringar for dette forumet (nye varsel og innlegg) etter ein fastsett tidsplan", "digest-off": "Av", "digest-daily": "Dagleg", "digest-weekly": "Kvar veke", "digest-biweekly": "Kvar andre veke", "digest-monthly": "Månadleg", - "has-no-follower": "Denne brukaren har ingen følgjarar :(", - "follows-no-one": "Denne brukaren følgjer ingen :(", + "has-no-follower": "Denne brukaren har ingen følgarar", + "follows-no-one": "Denne brukaren følger ingen ", "has-no-posts": "Denne brukaren har ikkje lagt inn noko enno.", - "has-no-best-posts": "Denne brukaren har ingen innlegg som er tilrådd enno.", + "has-no-best-posts": "Denne brukaren har ingen innlegg som er anbefalt enno.", "has-no-topics": "Denne brukaren har ikkje lagt ut nokre emne enno.", - "has-no-watched-topics": "Denne brukaren følgjer ingen emne enno.", + "has-no-watched-topics": "Denne brukaren følger ingen emne enno.", "has-no-ignored-topics": "Denne brukaren ignorerer ingen emne enno.", "has-no-read-topics": "Denne brukaren har ikkje lese nokre emne enno.", - "has-no-upvoted-posts": "Denne brukaren har ikkje tilrådd nokon innlegg enno.", + "has-no-upvoted-posts": "Denne brukaren har ikkje anbefalt nokon innlegg enno.", "has-no-downvoted-posts": "Denne brukaren har ikkje stemt ned nokre innlegg enno.", - "has-no-controversial-posts": "Denne brukaren har inga kontroversielle innlegg enno.", + "has-no-controversial-posts": "Denne brukaren har ingen nedstemte innlegg.", "has-no-blocks": "Du har ikkje blokkert nokon brukarar.", "has-no-shares": "Denne brukaren har ikkje delt nokre emner.", "email-hidden": "E-post er skjult", @@ -135,10 +139,10 @@ "max-items-per-page": "Maksimalt %1", "acp-language": "Språk for admin-side", "notifications": "Varsel", - "upvote-notif-freq": "Frekvens for oppstemmingsvarsel", - "upvote-notif-freq.all": "Alle tilrådingar", + "upvote-notif-freq": "Frekvens for anbefalingsvarsel", + "upvote-notif-freq.all": "Alle anbefalingar", "upvote-notif-freq.first": "Fyrste per innlegg", - "upvote-notif-freq.everyTen": "Kvar tiande tilråding", + "upvote-notif-freq.everyTen": "Kvar tiande anbefaling", "upvote-notif-freq.threshold": "På 1, 5, 10, 25, 50, 100, 150, 200...", "upvote-notif-freq.logarithmic": "På 10, 100, 1000...", "upvote-notif-freq.disabled": "Deaktivert", diff --git a/public/language/nn-NO/users.json b/public/language/nn-NO/users.json index 6d080dba84..1d2aea1465 100644 --- a/public/language/nn-NO/users.json +++ b/public/language/nn-NO/users.json @@ -1,13 +1,13 @@ { "all-users": "Alle brukarar", - "followed-users": "Brukarar du følgjer", + "followed-users": "Brukarar du følger", "latest-users": "Siste brukarar", "top-posters": "Mest aktive", "most-reputation": "Best omdømme", "most-flags": "Flest rapporteringar", "search": "Søk", "enter-username": "Skriv inn eit brukarnamn for å søke", - "search-user-for-chat": "Søk etter ein brukar for å starte chat", + "search-user-for-chat": "Søk etter ein brukar for å sende melding", "load-more": "Last meir", "users-found-search-took": "%1 brukar(ar) funne! Søket tok %2 sekund.", "filter-by": "Filtrer etter", @@ -17,7 +17,7 @@ "groups-to-join": "Grupper å bli med i når invitasjonen vert akseptert:", "invitation-email-sent": "Ein invitasjons-e-post har blitt sendt til %1", "user-list": "Brukarliste", - "recent-topics": "Nye emne", + "recent-topics": "Siste innlegg", "popular-topics": "Populære emne", "unread-topics": "Uleste emne", "categories": "Kategoriar", diff --git a/public/language/nn-NO/world.json b/public/language/nn-NO/world.json index c5c0c1ae1f..5d578a5b93 100644 --- a/public/language/nn-NO/world.json +++ b/public/language/nn-NO/world.json @@ -1,18 +1,25 @@ { "name": "Verda", - "popular": "Populære emne", - "recent": "Alle emne", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Hjelp", "help.title": "Kva er denne sida?", "help.intro": "Velkommen til ditt hjørne av fødiverset.", - "help.fediverse": "“Fødiverset” er eit nettverk av samanbundne applikasjonar og nettsider som kommuniserer med kvarandre, og der brukarane kan sjå kvarandre. Dette forumet er føderert og kan samhandle med det sosiale nettet (eller “fødiverset”). Denne sida er ditt hjørne av fødiverset. Ho består utelukkande av emne oppretta av – og delt frå – brukarar du følgjer.", - "help.build": "Det kan vere at det ikkje er mange emne her til å begynne med; det er heilt normalt. Du vil sjå meir innhald her etter kvart som du begynner å følgje andre brukarar.", - "help.federating": "På same måte, dersom brukarar frå utsida av dette forumet begynner å følgje deg, vil innlegga dine òg begynne å visast på desse appane og nettsidene.", + "help.fediverse": "“Fødiverset” er eit nettverk av samanbundne applikasjonar og nettsider som kommuniserer med kvarandre, og der brukarane kan sjå kvarandre. Dette forumet er føderert og kan samhandle med det sosiale nettet (eller “fødiverset”). Denne sida er ditt hjørne av fødiverset. Ho består utelukkande av emne oppretta av – og delt frå – brukarar du følger.", + "help.build": "Det kan vere at det ikkje er mange emne her til å begynne med; det er heilt normalt. Du vil sjå meir innhald her etter kvart som du begynner å følge andre brukarar.", + "help.federating": "På same måte, dersom brukarar frå utsida av dette forumet begynner å følge deg, vil innlegga dine òg begynne å visast på desse appane og nettsidene.", "help.next-generation": "Dette er den neste generasjonen av sosiale medium, begynn å bidra i dag!", "onboard.title": "Ditt vindauge til fødiverset...", - "onboard.what": "Dette er din personlege kategori som berre består av innhald funne utanfor dette forumet. Om noko blir vist på denne sida, avheng av om du følgjer dei, eller om innlegget blei delt av nokon du følgjer.", - "onboard.why": "Det skjer mykje utanfor dette forumet, og ikkje alt er relevant for interessene dine. Difor er det å følgje folk den beste måten å signalisere at du ønskjer å sjå meir frå nokon.", - "onboard.how": "I mellomtida kan du klikke på snarvegsknappane øvst for å sjå kva anna dette forumet kjenner til, og begynne å oppdage nytt innhald!" + "onboard.what": "Dette er din personlege kategori som berre består av innhald funne utanfor dette forumet. Om noko blir vist på denne sida, avheng av om du følger dei, eller om innlegget blei delt av nokon du følger.", + "onboard.why": "Det skjer mykje utanfor dette forumet, og ikkje alt er relevant for interessene dine. Difor er det å følge folk den beste måten å signalisere at du ønskjer å sjå meir frå nokon.", + "onboard.how": "I mellomtida kan du klikke på snarvegsknappane øvst for å sjå kva anna dette forumet inneheld, og begynne å oppdage nytt innhald!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/pl/admin/advanced/cache.json b/public/language/pl/admin/advanced/cache.json index 5fe5e88e76..d685fd2a5f 100644 --- a/public/language/pl/admin/advanced/cache.json +++ b/public/language/pl/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Pamięć podręczna", - "post-cache": "Pamięć podręczna postów", - "group-cache": "Pamięć podręczna grupy", - "local-cache": "Lokalna pamięć podręczna", - "object-cache": "Pamięć podręczna obiektów", "percent-full": "%1%", "post-cache-size": "Rozmiar pamięci podręcznej postów", "items-in-cache": "Elementów w pamięci podręcznej" diff --git a/public/language/pl/admin/dashboard.json b/public/language/pl/admin/dashboard.json index 5e7684f78a..48bc39c7bd 100644 --- a/public/language/pl/admin/dashboard.json +++ b/public/language/pl/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Wyświetlenia użytkowników", "graphs.page-views-guest": "Wyświetlenia gości", "graphs.page-views-bot": "Wyświetlenia botów", + "graphs.page-views-ap": "Wyświetlenia strony ActivityPub", "graphs.unique-visitors": "Unikalni użytkownicy", "graphs.registered-users": "Zarejestrowani użytkownicy", "graphs.guest-users": "Użytkownicy-goście", diff --git a/public/language/pl/admin/development/info.json b/public/language/pl/admin/development/info.json index a56206ad41..427c0d0c19 100644 --- a/public/language/pl/admin/development/info.json +++ b/public/language/pl/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "dostępny", "git": "git", - "process-memory": "pamięć procesowa", + "process-memory": "rss/heap used", "system-memory": "pamięć systemowa", "used-memory-process": "Używana pamięć według procesu", "used-memory-os": "Używana pamięć systemowa", diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index e7979f084c..7ff4422d86 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Zarządzaj kategoriami", "add-category": "Dodaj kategorię", + "add-local-category": "Dodaj lokalną kategorię", + "add-remote-category": "Dodaj zdalną kategorię", + "remove": "Usuń", + "rename": "Przemianuj", "jump-to": "Skocz do...", "settings": "Ustawienia kategorii", "edit-category": "Edytuj kategorię", "privileges": "Uprawnienia", "back-to-categories": "Wróć do kategorii", + "id": "ID kategorii", "name": "Nazwa kategorii", "handle": "Przydział kategorii", "handle.help": "Obsługa kategorii robi za znak rozpoznawczy w innych sieciach na wzór nazwy użytkownika. Z tej racji jej nazwa nie może się pokrywać z nazwą użytkownika lub grupą użytkowników.", "description": "Opis kategorii", - "federatedDescription": "Opis federacji", - "federatedDescription.help": "Ten tekst zostanie użyty dla opisu sekcji widocznej z poziomu innych stron/aplikacji.", - "federatedDescription.default": "Ta sekcja forum zawiera dyskusje tematyczne. Możesz rozpocząć nową dyskusję wzmiankując tę kategorię.", + "topic-template": "Szablon wątku", + "topic-template.help": "Stwórz szablon dla nowych wątków dodawanych w tej kategorii.", "bg-color": "Kolor tła", "text-color": "Kolor tekstu", "bg-image-size": "Wielkość obrazka tła", @@ -103,6 +107,11 @@ "alert.create-success": "Kategoria pomyślnie dodana!", "alert.none-active": "Nie masz aktywnych kategorii.", "alert.create": "Utwórz kategorię", + "alert.add": "Dodaj do kategorii", + "alert.add-help": "Zdalne kategorie mogą zostać przypisane do kategorii po ich wskazaniu.

Uwaga — Zdalna kategoria może nie być w pełni wypełniona wątkami do czasu gdy jeden z lokalnych użytkowników zacznie ją śledzić/obserwować.", + "alert.rename": "Nazwij kategorię zdalną", + "alert.rename-help": "Proszę wprowadź nazwę nowej kategorii. Pozostaw puste aby przywrócić pierwotną nazwę.", + "alert.confirm-remove": "Czy chcesz usunąć tę kategorię? Możesz dodać ją ponownie w dowolnym czasie.", "alert.confirm-purge": "

Czy na pewno chcesz wymazać tą kategorię \"%1\"?

Uwaga! Wszystkie tematy oraz posty z tej kategorii zostaną wymazane!

Wymazanie kategorii skasuje wszystkie tematy, posty oraz skasuję kategorię z bazy danych. Jeśli chcesz tymczasowousunąć kategorię, będziesz musiał \"wyłączyć\" kategorię.

", "alert.purge-success": "Kategoria wymazana!", "alert.copy-success": "Ustawienie skopiowane!", diff --git a/public/language/pl/admin/manage/custom-reasons.json b/public/language/pl/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/pl/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/pl/admin/manage/privileges.json b/public/language/pl/admin/manage/privileges.json index 558f77dffb..a54c11dd9e 100644 --- a/public/language/pl/admin/manage/privileges.json +++ b/public/language/pl/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Dostęp do tematów", "create-topics": "Tworzenie tematów", "reply-to-topics": "Odpowiadanie na tematy", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Zaplanuj tematy", "tag-topics": "Tagowanie tematów", "edit-posts": "Edycja postów", diff --git a/public/language/pl/admin/manage/users.json b/public/language/pl/admin/manage/users.json index 305ff20c78..704684b0b1 100644 --- a/public/language/pl/admin/manage/users.json +++ b/public/language/pl/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Usuń użytkownika(-ów) i treści", "download-csv": "Pobierz CSV", "custom-user-fields": "Dodatkowe pola profilu", + "custom-reasons": "Custom Reasons", "manage-groups": "Zarządzaj grupami", "set-reputation": "Ustaw reputację", "add-group": "Dodaj grupę", @@ -77,9 +78,11 @@ "temp-ban.length": "Długość", "temp-ban.reason": "Powód (Opcjonalnie)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Godziny", "temp-ban.days": "Dni", "temp-ban.explanation": "Podaj czas trwania bana. Okres równy 0 będzie traktowany jako ban permanentny.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Czy na pewno chcesz zbanować tego użytkownika permanentnie?", "alerts.confirm-ban-multi": "Czy na pewno chcesz zbanować tych użytkowników permanentnie?", diff --git a/public/language/pl/admin/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index 31aa6e7282..3d6712f199 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Czas oczekiwania (milisekundy)", "probe-timeout-help": "(Domyślnie: 2000) O ile zapytanie nie doczeka się odpowiedzi w tym czasie to użytkownik otrzyma odnośnik bezpośredni. Możesz wydłużyć czas oczekiwania o ile strony działają wolno a mimo to chcesz dać im szansę.", + "rules": "Przyszeregowanie", + "rules-intro": "Zawartość odkrywana via ActivePub może być automatycznie przyszeregowana w oparciu m.in o hashtag", + "rules.modal.title": "Jak to działa", + "rules.modal.instructions": "Wszelka zawartość z zewnątrz jest sprawdzana pod kątem zasad przyszeregowania a zatem z automatu umieszczana we własciwych kategoriach.

N.B. Zawartość już przyszeregowana (np. zdalna kategoria) nie podlega przetworzeniu przez reguły.", + "rules.add": "Dodaj nową regułę", + "rules.help-hashtag": "Wątki zawierające hashtag gdzie wielkość liter nie ma znaczenia będą dopasowane. Pomiń symbol # przy wprowadzaniu", + "rules.help-user": "Wątki utworzone przez wybranego użytkownika będą dopasowane. Wprowadź zawołanie lub pełne ID (np. bob@example.org lub https://example.org/users/bob.", + "rules.type": "Typ", + "rules.value": "Wartość", + "rules.cid": "Kategoria", + + "relays": "Przekaźniki", + "relays.intro": "Przekaźnik usprawnia odnajdowanie zawartości do i z Twojego NodeBB. Dodanie przekaźnika oznacza, że odebrana zawartość z jego udziałem jest przekierowywana w to miejsce i analogicznie z tym co ma być widoczne z zewnątrz.", + "relays.warning": "Uwaga: przekaźniki mogą tworzyć duży ruch a także zwiększyć wykorzystanie pamięci masowej a co za tym idzie kosztów.", + "relays.litepub": "NodeBB nawiązuje do LitePub jeśli idzie o przekaźniki. Wprowadzony URL powinien kończyć się znakiem /.", + "relays.add": "Dodaj nowy przekaźnik", + "relays.relay": "Przekaźnik", + "relays.state": "Stan", + "relays.state-0": "Oczekujący", + "relays.state-1": "Tylko odbiór", + "relays.state-2": "Aktywny", + "server-filtering": "Filtrowanie", "count": "NodeBB obecnie wykrywa 1% serwerów", "server.filter-help": "Określ serwery, z którymi nie chcesz spinać NodeBB w ramach fediverse. Alternatywnie możesz dobrać dozwolone serwery fediverse. Obie opcje są dostępne ale wybierz jedną z nich.", diff --git a/public/language/pl/admin/settings/chat.json b/public/language/pl/admin/settings/chat.json index 094bd043c8..9c4a768a01 100644 --- a/public/language/pl/admin/settings/chat.json +++ b/public/language/pl/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maksymalna długość nazw pokojów czatu", "max-room-size": "Maksymalna liczba użytkowników w pokojach czatu", "delay": "Czas pomiędzy wiadomościami czatu (ms)", - "notification-delay": "Opóźnienie powiadomień dla wiadomości czatu", - "notification-delay-help": "Kolejne wiadomości wysłane między tym czasem są szeregowane, a użytkownik jest powiadamiany raz na czas opóźnienia. Ustaw 0, aby wyłączyć opóźnienie.", "restrictions.seconds-edit-after": "Liczba sekund, przez którą wiadomość czatu pozostanie edytowalna.", "restrictions.seconds-delete-after": "Liczba sekund, przez którą wiadomość czatu pozostanie usuwalna." } \ No newline at end of file diff --git a/public/language/pl/admin/settings/email.json b/public/language/pl/admin/settings/email.json index e4f71bd921..06345d7326 100644 --- a/public/language/pl/admin/settings/email.json +++ b/public/language/pl/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling połączeń sprawia, że NodeBB nie będzie tworzył nowego połączenia dla każdego maila. Ta opcja ma zastosowanie tylko, jeśli transport SMTP jest włączony.", "smtp-transport.allow-self-signed": "Zezwól na własnoręcznie podpisane certyfikaty", "smtp-transport.allow-self-signed-help": "Kiedy włączone będzie przyjmować lokalne i błędne certyfikaty TLS.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edytuj szablon e-maila", "template.select": "Wybierz szablon e-maila", "template.revert": "Przywróć oryginalny szablon", + "test-smtp-settings": "Test SMTP Settings", "testing": "Testowanie e-maila", + "testing.success": "Test Email Sent.", "testing.select": "Wybierz szablon e-maila", "testing.send": "Wyślij testowy e-mail", - "testing.send-help": "Testowy e-mail zostanie wysłany na adres aktualnie zalogowanego użytkownika.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Podsumowania e-mail", "subscriptions.disable": "Wyłącz podsumowania e-maili", "subscriptions.hour": "Godzina podsumowania", diff --git a/public/language/pl/admin/settings/notifications.json b/public/language/pl/admin/settings/notifications.json index 2db02814ab..ace31e80db 100644 --- a/public/language/pl/admin/settings/notifications.json +++ b/public/language/pl/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Powiadomienie powitalne", "welcome-notification-link": "Odnośnik do komunikatu powitalnego", "welcome-notification-uid": "Powiadomienie powitalne użytkownika (UID)", - "post-queue-notification-uid": "Użytkownik kolejki pocztowej (UID)" + "post-queue-notification-uid": "Użytkownik kolejki pocztowej (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/pl/admin/settings/uploads.json b/public/language/pl/admin/settings/uploads.json index 10d0606239..724cb47659 100644 --- a/public/language/pl/admin/settings/uploads.json +++ b/public/language/pl/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Obrazy o szerokości przekraczającej tę wartość zostaną odrzucone.", "reject-image-height": "Maksymalna wysokość obrazu (w pikselach)", "reject-image-height-help": "Obrazy o wysokości przekraczającej tę wartość zostaną odrzucone.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Zezwalaj użytkownikom na ustawianie miniaturek tematów", + "show-post-uploads-as-thumbnails": "Pokaż załączniki do wpisów w formie miniaturek", "topic-thumb-size": "Rozmiar miniatury tematu", "allowed-file-extensions": "Dozwolone typy plików", "allowed-file-extensions-help": "Wprowadź rozdzielone przecinkami rozszerzenia plików (np. pdf,xls,doc). Pusta lista oznacza, że wszystkie rozszerzenia są dozwolone.", diff --git a/public/language/pl/admin/settings/user.json b/public/language/pl/admin/settings/user.json index 89ca1d1c44..df448c8368 100644 --- a/public/language/pl/admin/settings/user.json +++ b/public/language/pl/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Pokazuj adres e-mail", "show-fullname": "Pokazuj imię i nazwisko uzytkownika", "restrict-chat": "Przyjmuj wiadomości na czacie tylko od osób, które obserwuję", + "disable-incoming-chats": "Wyłącz wiadomości przychodzące czatu", "outgoing-new-tab": "Otwieraj odnośniki wychodzące na nowej karcie", "topic-search": "Włącz wyszukiwanie wewnątrz tematów", "update-url-with-post-index": "Aktualizuj adres w przeglądarce numerem postu, podczas przeglądania tematów", diff --git a/public/language/pl/admin/settings/web-crawler.json b/public/language/pl/admin/settings/web-crawler.json index 8952f2d888..e20868caa9 100644 --- a/public/language/pl/admin/settings/web-crawler.json +++ b/public/language/pl/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Wyłącz kanały RSS", "disable-sitemap-xml": "Wyłącz sitemap.xml", "sitemap-topics": "Liczba tematów do wyświetlenia w mapie strony", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Wyczyść pamięć podręczną mapy strony", "view-sitemap": "Wyświetl mapę strony" } \ No newline at end of file diff --git a/public/language/pl/aria.json b/public/language/pl/aria.json index 86ef9eab82..c61d9389e7 100644 --- a/public/language/pl/aria.json +++ b/public/language/pl/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profil użytkownika %1", "user-watched-tags": "Tagi obserwowane przez użytkownika", "delete-upload-button": "Przycisk kasowania załącznika", - "group-page-link-for": "Odsyłacz dla grupy %1" + "group-page-link-for": "Odsyłacz dla grupy %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/pl/category.json b/public/language/pl/category.json index 28b1f6b4e1..ebee64b271 100644 --- a/public/language/pl/category.json +++ b/public/language/pl/category.json @@ -1,12 +1,13 @@ { "category": "Kategoria", "subcategories": "Podkategorie", - "uncategorized": "Nieokreślone", - "uncategorized.description": "Wątki, które nie do końca pasują do istniejących kategorii", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "Tę kategorię można śledzić za pośrednictwem %1", "new-topic-button": "Nowy temat", "guest-login-post": "Zaloguj się, aby napisać post", "no-topics": "W tej kategorii nie ma jeszcze żadnych tematów.
Może pora na napisanie pierwszego?", + "no-followers": "Nikt na tej witrynie nie śledzi ani nie obserwuje tej kategorii. Zacznij śledzić lub obserwować tę kategorię aby zacząć odbierać aktualizacje.", "browsing": "przegląda", "no-replies": "Nikt jeszcze nie odpowiedział", "no-new-posts": "Brak nowych postów.", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 46879c09eb..adf1811b7d 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -3,6 +3,7 @@ "invalid-json": "Niewłaściwy JSON", "wrong-parameter-type": "Wartość typu %3 była oczekiwania dla właściwości `%1`, ale %2 został dostarczony", "required-parameters-missing": "Brakowało wymaganych parametrów w tym żądaniu API: %1", + "reserved-ip-address": "Wywołania sieciowe do zarezerwowanych zakresów IP nie są dozwolone.", "not-logged-in": "Nie jesteś zalogowany(-a).", "account-locked": "Twoje konto zostało tymczasowo zablokowane", "search-requires-login": "Wyszukiwanie wymaga konta - zaloguj się lub zarejestruj.", @@ -67,8 +68,8 @@ "no-chat-room": "Nie ma takiego pokoju", "no-privileges": "Nie masz przywileju wykonywania tej akcji", "category-disabled": "Kategoria wyłączona", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Skasowany wpis", + "topic-locked": "Zamknięty wątek", "post-edit-duration-expired": "Możesz edytować posty tylko przez %1 sekund(y) po ich napisaniu", "post-edit-duration-expired-minutes": "Możesz edytować posty tylko przez %1 minut(y) po ich napisaniu", "post-edit-duration-expired-minutes-seconds": "Możesz edytować posty tylko przez %1 minut(y) i %2 sekund(y) po ich napisaniu", @@ -146,6 +147,7 @@ "post-already-restored": "Ten post został już przywrócony", "topic-already-deleted": "Ten temat został już skasowany", "topic-already-restored": "Ten temat został już przywrócony", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Nie możesz wymazać głównego posta, zamiast tego usuń temat", "topic-thumbnails-are-disabled": "Miniatury tematów są wyłączone.", "invalid-file": "Błędny plik", @@ -154,6 +156,8 @@ "about-me-too-long": "Przepraszamy, Twój tekst „O mnie” nie może być dłuższy niż %1 znaków.", "cant-chat-with-yourself": "Nie możesz rozmawiać sam ze sobą!", "chat-restricted": "Ten użytkownik korzysta z czatu w ograniczonym zakresie. Mogą z nim rozmawiać tylko te osoby, które obserwuje.", + "chat-allow-list-user-already-added": "Ten użytkownik jest już na liście dozwolonych", + "chat-deny-list-user-already-added": "Ten użytkownik jest już na liście ignorowanych", "chat-user-blocked": "Jesteś zablokowany(a) przez tego użytkownika.", "chat-disabled": "System rozmów jest wyłączony", "too-many-messages": "Wysłałeś zbyt wiele wiadomości, prosimy chwilę poczekać.", @@ -225,6 +229,7 @@ "no-topics-selected": "Nie wybrano tematów.", "cant-move-to-same-topic": "Nie można przenieść wpisu do tego samego tematu!", "cant-move-topic-to-same-category": "Nie można przenieść tematu do tej samej kategorii!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Nie możesz zablokować samego siebie!", "cannot-block-privileged": "Nie możesz blokować administratorów ani globalnych moderatorów", "cannot-block-guest": "Goście nie mogą blokować innych użytkowników", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "W tej chwili nie można połączyć się z serwerem. Kliknij tutaj, aby spróbować ponownie, lub spróbuj ponownie później", "invalid-plugin-id": "Niepoprawny identyfikator wtyczki", "plugin-not-whitelisted": "Nie da się zainstalować tej wtyczki – tylko wtyczki z białej listy menadżera pakietów NodeBB mogą być instalowane przez ACP", + "cannot-toggle-system-plugin": "Nie można zmienić nastawu dla wtyczki systemowej", "plugin-installation-via-acp-disabled": "Instalacja wtyczek przez ACP jest wyłączona", "plugins-set-in-configuration": "Nie możesz zmienić stanu wtyczki, bo został on zdefiniowany przy uruchamianiu (config.json, zmienne środowiskowe lub argumenty z terminala). Zamiast tego zmień konfigurację.", "theme-not-set-in-configuration": "Pamiętaj o zależności między aktywnymi wtyczkami a wystrojem, który ma z nimi współpracować.", @@ -246,6 +252,7 @@ "api.401": "Poprawna sesja logowanie nie została znaleziona. Proszę zaloguj się i spróbuj ponownie.", "api.403": "Nie masz uprawnień do wykonania tego żądania", "api.404": "Niepoprawne żądanie API", + "api.413": "The request payload is too large", "api.426": "HTTPS jest wymagany dla żądań do API zapisu, wyślij ponownie żądanie przez HTTPS", "api.429": "Został przekroczony limit żądań, proszę spróbuj ponownie później", "api.500": "Wystąpił nieoczekiwany błąd podczas próby obsługi Twojego żądania.", diff --git a/public/language/pl/global.json b/public/language/pl/global.json index 44dec4b1c6..4d2d69b849 100644 --- a/public/language/pl/global.json +++ b/public/language/pl/global.json @@ -68,6 +68,7 @@ "users": "Użytkownicy", "topics": "Tematy", "posts": "Posty", + "crossposts": "Cross-posts", "x-posts": "%1 postów", "x-topics": "%1 tematów", "x-reputation": "%1 reputacja", @@ -82,6 +83,7 @@ "downvoted": "Oddane głosy przeciw", "views": "Wyświetlenia", "posters": "Uczestników", + "watching": "Obserwuje", "reputation": "Reputacja", "lastpost": "Ostatni post", "firstpost": "Pierwszy post", diff --git a/public/language/pl/groups.json b/public/language/pl/groups.json index a41738ab92..724b5ce472 100644 --- a/public/language/pl/groups.json +++ b/public/language/pl/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Wszystkie grupy", "groups": "Grupy", "members": "Użytkownicy", + "x-members": "%1 member(s)", "view-group": "Obejrzyj grupę", "owner": "Właściciel grupy", "new-group": "Stwórz nową grupę", diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index 65f5d143de..3022a1ca72 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Dodaj użytkownika", "chat.notification-settings": "Ustawienia powiadomień", "chat.default-notification-setting": "Domyślne ustawienia powiadomień", + "chat.join-leave-messages": "Dołącz/Odłącz wiadomości", "chat.notification-setting-room-default": "Domyślne dla pokoju", "chat.notification-setting-none": "Brak powiadomień", "chat.notification-setting-at-mention-only": "Tylko zawołania z użyciem @", @@ -81,7 +82,7 @@ "composer.hide-preview": "Ukryj podgląd", "composer.help": "Pomoc", "composer.user-said-in": "%1 napisał w %2:", - "composer.user-said": "%1 napisał:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Na pewno chcesz porzucić ten post?", "composer.submit-and-lock": "Prześlij i zablokuj", "composer.toggle-dropdown": "Przełącz listę rozwijaną", diff --git a/public/language/pl/notifications.json b/public/language/pl/notifications.json index 2c3cc3e068..8b3910634a 100644 --- a/public/language/pl/notifications.json +++ b/public/language/pl/notifications.json @@ -22,7 +22,7 @@ "upvote": "Głosy za", "awards": "Nagrody", "new-flags": "Nowe flagi", - "my-flags": "Flagi przypisane mnie", + "my-flags": "My Flags", "bans": "Bany", "new-message-from": "Nowa wiadomość od %1", "new-messages-from": "%1 nowych wiadomości od %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 i %2 napisali w %4", "user-posted-in-public-room-triple": "%1, %2 i %3 napisali w %5", "user-posted-in-public-room-multiple": "%1, %2 i %3 innych napisali w %5", - "upvoted-your-post-in": "%1 zagłosował na Twój post w %2", - "upvoted-your-post-in-dual": "%1 oraz %2 zagłosowali na Twój post w %3.", - "upvoted-your-post-in-triple": "%1, %2 i%3 zagłosowali na Twój post w %4.", - "upvoted-your-post-in-multiple": "%1, %2 i %3 innych zagłosowali na Twój post w %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 przeniósł Twój post do %2", "moved-your-topic": "%1 przeniósł %2", - "user-flagged-post-in": "%1 oflagował post w %2", - "user-flagged-post-in-dual": "%1 oraz %2 oflagowali post w %3", - "user-flagged-post-in-triple": "%1, %2 oraz %3 oflagowali post w %4", - "user-flagged-post-in-multiple": "%1, %2 i %3 innych oflagowali post w %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 oflagował profil użytkownika (%2)", "user-flagged-user-dual": "%1 oraz %2 oflagowali profil użytkownika (%3)", "user-flagged-user-triple": "%1, %2 i %3 oflagowali profil użytkownika (%4)", "user-flagged-user-multiple": "%1, %2 i %3 innych oflagowali profil użytkownika (%4)", - "user-posted-to": "%1 dodał odpowiedź do %2", - "user-posted-to-dual": "%1 oraz %2 dodali odpowiedzi do %3", - "user-posted-to-triple": "%1, %2 i %3 dodali odpowiedzi do %4", - "user-posted-to-multiple": "%1, %2 i %3 innych dodało odpowiedzi do: %4", - "user-posted-topic": "%1 stworzył nowy temat: %2", - "user-edited-post": "%1 edytował post w %2", - "user-posted-topic-with-tag": "%1 dodał(a) %2 (tag %3)", - "user-posted-topic-with-tag-dual": "%1 dodał(a) %2 (tagi %3 i %4)", - "user-posted-topic-with-tag-triple": "%1 dodał(a) %2 (tagi %3, %4 i %5)", - "user-posted-topic-with-tag-multiple": "%1 dodał(a) %2 (tag %3)", - "user-posted-topic-in-category": "%1 stworzył nowy temat w %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 zaczął Cię obserwować.", "user-started-following-you-dual": "%1 oraz %2 zaczęli Cię obserwować.", "user-started-following-you-triple": "%1, %2 i %3 zaczęli Cię obserwować.", @@ -71,11 +71,11 @@ "users-csv-exported": "Plik csv użytkowników wyeksportowany, kliknij aby pobrać", "post-queue-accepted": "Twój post oczekujący w kolejce został zaakceptowany. Kliknij tutaj, aby go zobaczyć.", "post-queue-rejected": "Twój post oczekujący w kolejce został odrzucony.", - "post-queue-notify": "Post oczekujący w kolejce otrzymał powiadomienie:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mail potwierdzony", "email-confirmed-message": "Dziękujemy za potwierdzenie maila. Twoje konto zostało aktywowane.", "email-confirm-error-message": "Wystąpił problem przy aktywacji - kod jest błędny lub przestarzały", - "email-confirm-error-message-already-validated": "Ten adres e-mail został już zweryfikowany.", "email-confirm-sent": "E-mail potwierdzający wysłany.", "none": "Żadna z opcji", "notification-only": "Tylko powiadomienie", diff --git a/public/language/pl/social.json b/public/language/pl/social.json index 25d33196d0..07559bcee7 100644 --- a/public/language/pl/social.json +++ b/public/language/pl/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Zaloguj się przez Facebook", "continue-with-facebook": "Kontynuuj z Facebook", "sign-in-with-linkedin": "Zaloguj się przez LinkedIn", - "sign-up-with-linkedin": "Zarejestruj się przez LinkedIn" + "sign-up-with-linkedin": "Zarejestruj się przez LinkedIn", + "sign-in-with-wordpress": "Zaloguj z WordPress", + "sign-up-with-wordpress": "Zarejestruj z WordPress" } \ No newline at end of file diff --git a/public/language/pl/themes/harmony.json b/public/language/pl/themes/harmony.json index 324acf6f5e..688bd07df6 100644 --- a/public/language/pl/themes/harmony.json +++ b/public/language/pl/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Styl Harmony", "skins": "Skórki", + "light": "Light", + "dark": "Dark", "collapse": "Zwiń", "expand": "Rozwiń", "sidebar-toggle": "Przełącz panele boczne", diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 4311bb4586..7a6b762b73 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 odniósł się do tego tematu dnia %3", "user-forked-topic-ago": "%1 rozdzielił ten temat %3", "user-forked-topic-on": "%1 rozdzielił ten temat dnia %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Kliknij tutaj, by powrócić do ostatniego przeczytanego postu w tym temacie.", "flag-post": "Zgłoś ten post", "flag-user": "Zgłoś tego użytkownika", @@ -103,6 +105,7 @@ "thread-tools.lock": "Zablokuj temat", "thread-tools.unlock": "Odblokuj temat", "thread-tools.move": "Przenieś temat", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Przenieś posty", "thread-tools.move-all": "Przenieś wszystko", "thread-tools.change-owner": "Zmień właściciela", @@ -132,6 +135,7 @@ "pin-modal-help": "Możesz tutaj opcjonalnie ustawić datę wygasania przypiętych tematów. Możesz też zostawić to pole puste, aby temat pozostawał przypięty, aż zostanie ręcznie odpięty.", "load-categories": "Ładowanie kategorii", "confirm-move": "Przenieś", + "confirm-crosspost": "Cross-post", "confirm-fork": "Rozdziel", "bookmark": "Dodaj do zakładek", "bookmarks": "Zakładki", @@ -141,6 +145,7 @@ "loading-more-posts": "Załaduj więcej postów", "move-topic": "Przenieś temat", "move-topics": "Przenieś tematy", + "crosspost-topic": "Cross-post Topic", "move-post": "Przenieś post", "post-moved": "Post został przeniesiony!", "fork-topic": "Rozdziel temat", @@ -163,6 +168,9 @@ "move-topic-instruction": "Wybierz kategorię docelową i kliknij przenieś", "change-owner-instruction": "Kliknij w posty, które chcesz przypisać do innego użytkownika", "manage-editors-instruction": "Zarządzaj użytkownikami, którzy mogą edytować ten post poniżej.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Tutaj wpisz tytuł tematu...", "composer.handle-placeholder": "Tutaj wpisz swoje imię/nazwę", "composer.hide": "Ukryj", @@ -174,6 +182,7 @@ "composer.replying-to": "Odpowiedź na %1", "composer.new-topic": "Nowy temat", "composer.editing-in": "Edytowanie posta w %1", + "composer.untitled-topic": "Nienazwany Temat", "composer.uploading": "wysyłanie...", "composer.thumb-url-label": "Wklej adres miniaturki tematu", "composer.thumb-title": "Dodaj miniaturkę do tego tematu", @@ -224,5 +233,8 @@ "unread-posts-link": "Link nieprzeczytanych postów", "thumb-image": "Obraz miniaturki tematu", "announcers": "Udostępnień", - "announcers-x": "Udostępnień (%1)" + "announcers-x": "Udostępnień (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/pl/user.json b/public/language/pl/user.json index ae651577a0..24602119f1 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -105,6 +105,10 @@ "show-email": "Wyświetlaj mój adres e-mail", "show-fullname": "Pokaż imię i nazwisko", "restrict-chats": "Przyjmuj wiadomości na czacie tylko od osób, które obserwuję", + "disable-incoming-chats": "Wyłącz wiadomości przychodzące czatu ", + "chat-allow-list": "Zezwól na wiadomości czatu od następujących użytkowników", + "chat-deny-list": "Ignoruj wiadomości czatu od następujących użytkowników", + "chat-list-add-user": "Dodaj użytkownika", "digest-label": "Przysyłaj okresowe podsumowanie wiadomości na forum", "digest-description": "Subskrybuj, aby otrzymywać maile dla tego forum (nowe powiadomienia i tematy) zgodnie z ustalonym harmonogramem", "digest-off": "Wyłączone", diff --git a/public/language/pl/world.json b/public/language/pl/world.json index 5e5a4d8247..2b7b467d6f 100644 --- a/public/language/pl/world.json +++ b/public/language/pl/world.json @@ -1,7 +1,12 @@ { "name": "Świat", - "popular": "Popularne tematy", - "recent": "Wszystkie tematy", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Pomoc", "help.title": "Co to za strona?", @@ -14,5 +19,7 @@ "onboard.title": "Twoje okno na fediverse...", "onboard.what": "Zawartość tej kategorii jest dostowana do Twoich poczynań i zawiera jedynie dane spoza tego forum. Aby się coś tu pojawiło trzeba zacząć śledzić zdalne źródła informacji.", "onboard.why": "Mnóstwo rzeczy dzieje się poza tym forum ale niekoniecznie zgodnych z Twoimi zainteresowaniami. Dlatego śledzenie konkretnych użytkowników jest dobrą metodą aby uzyskać więcej zawartości przez nich dodawanych.", - "onboard.how": "W międzyczasie możesz użyć przycisków skrótów na górze aby przekonać się jak forum jest powiązane a okaże się, że zapewnia wiele dodatkowych treści!" + "onboard.how": "W międzyczasie możesz użyć przycisków skrótów na górze aby przekonać się jak forum jest powiązane a okaże się, że zapewnia wiele dodatkowych treści!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/pt-BR/admin/advanced/cache.json b/public/language/pt-BR/admin/advanced/cache.json index 561c74c863..1c9e81c408 100644 --- a/public/language/pt-BR/admin/advanced/cache.json +++ b/public/language/pt-BR/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Cache de Posts", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Cheio", "post-cache-size": "Tamanho do Cache de Posts", "items-in-cache": "Itens no Cache" diff --git a/public/language/pt-BR/admin/advanced/database.json b/public/language/pt-BR/admin/advanced/database.json index 918fb8babd..ebf2a34e13 100644 --- a/public/language/pt-BR/admin/advanced/database.json +++ b/public/language/pt-BR/admin/advanced/database.json @@ -17,7 +17,7 @@ "mongo.file-size": "Tamanho do Arquivo", "mongo.resident-memory": "Resident Memory", "mongo.virtual-memory": "Memória Virtual", - "mongo.mapped-memory": "Mapped Memory", + "mongo.mapped-memory": "Memória Canalizada", "mongo.bytes-in": "Bytes recebidos", "mongo.bytes-out": "Bytes enviados", "mongo.num-requests": "Quantidade de Requisições", diff --git a/public/language/pt-BR/admin/dashboard.json b/public/language/pt-BR/admin/dashboard.json index 49dd02ba69..0e42f09063 100644 --- a/public/language/pt-BR/admin/dashboard.json +++ b/public/language/pt-BR/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Páginas Visualizadas por Registrados", "graphs.page-views-guest": "Páginas Visualizadas por Visitantes", "graphs.page-views-bot": "Páginas Visualizadas por Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Visitantes Únicos", "graphs.registered-users": "Usuários Registrados", "graphs.guest-users": "Guest Users", diff --git a/public/language/pt-BR/admin/development/info.json b/public/language/pt-BR/admin/development/info.json index e0fabb3ddb..36ba1651e3 100644 --- a/public/language/pt-BR/admin/development/info.json +++ b/public/language/pt-BR/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json index 28318bdae9..808455c32a 100644 --- a/public/language/pt-BR/admin/manage/categories.json +++ b/public/language/pt-BR/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Configurações de Categorias", "edit-category": "Edit Category", "privileges": "Privilégios", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Nome da Categoria", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Descrição da Categoria", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Cor de Fundo", "text-color": "Cor do Texto", "bg-image-size": "Tamanho da Imagem de Fundo", @@ -103,6 +107,11 @@ "alert.create-success": "Categoria criada com sucesso!", "alert.none-active": "Você não possui categorias ativas.", "alert.create": "Criar uma Categoria", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Você realmente quer purgar esta categoria \"%1\"?

Aviso! Todos os tópicos e posts desta categoria serão purgados!

Purgar uma categoria removerá todos os tópicos e posts, e deletará a categoria do banco de dados. Se você quiser remover uma categoria temporariamente, ao invés de fazer isso nós recomendados que você \"desabilite\" a categoria.

", "alert.purge-success": "Categoria purgada!", "alert.copy-success": "Configurações Copiadas!", diff --git a/public/language/pt-BR/admin/manage/custom-reasons.json b/public/language/pt-BR/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/pt-BR/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/pt-BR/admin/manage/privileges.json b/public/language/pt-BR/admin/manage/privileges.json index 1dd2a11d3c..0da254ea0c 100644 --- a/public/language/pt-BR/admin/manage/privileges.json +++ b/public/language/pt-BR/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Acessar Tópicos", "create-topics": "Criar Tópicos", "reply-to-topics": "Responder aos Tópicos", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Agendar Tópicos", "tag-topics": "Definir tag em tópicos", "edit-posts": "Editar Posts", diff --git a/public/language/pt-BR/admin/manage/users.json b/public/language/pt-BR/admin/manage/users.json index 093af74ffd..d93d1f7adb 100644 --- a/public/language/pt-BR/admin/manage/users.json +++ b/public/language/pt-BR/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Excluir Usuário(s) e Conteúdo", "download-csv": "Baixar CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Gerenciar Grupos", "set-reputation": "Set Reputation", "add-group": "Adicionar Grupo", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Motivo (Opcional)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Horas", "temp-ban.days": "Dias", "temp-ban.explanation": "Entre com o período de tempo para o banimento. Note que um tempo de 0 será considerado um banimento permanente.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Você realmente deseja banir este usuário permanentemente?", "alerts.confirm-ban-multi": "Você realmente quer banir estes usuários permanentemente?", diff --git a/public/language/pt-BR/admin/settings/activitypub.json b/public/language/pt-BR/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/pt-BR/admin/settings/activitypub.json +++ b/public/language/pt-BR/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/pt-BR/admin/settings/chat.json b/public/language/pt-BR/admin/settings/chat.json index 405541715e..8697c349e7 100644 --- a/public/language/pt-BR/admin/settings/chat.json +++ b/public/language/pt-BR/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Número máximo de usuários nas salas de chat", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/pt-BR/admin/settings/email.json b/public/language/pt-BR/admin/settings/email.json index a240a739d3..329b6b5442 100644 --- a/public/language/pt-BR/admin/settings/email.json +++ b/public/language/pt-BR/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "O pool de conexões evita que o NodeBB crie uma nova conexão para cada e-mail. Esta opção se aplica apenas se o Transporte SMTP estiver habilitado.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Editar Modelo do E-mail", "template.select": "Escolher Modelo do E-mail", "template.revert": "Reverter ao Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Teste de E-mail", + "testing.success": "Test Email Sent.", "testing.select": "Escolher Modelo do E-mail", "testing.send": "Enviar E-mail de Teste", - "testing.send-help": "O e-mail de teste será enviado para o seu endereço de e-mail.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Resumos por Email", "subscriptions.disable": "Desabilitar resumos por email", "subscriptions.hour": "Hora de Envio dos Resumos", diff --git a/public/language/pt-BR/admin/settings/notifications.json b/public/language/pt-BR/admin/settings/notifications.json index 9a78c28825..f09b8e4f75 100644 --- a/public/language/pt-BR/admin/settings/notifications.json +++ b/public/language/pt-BR/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Notificação de Boas-vindas", "welcome-notification-link": "Link da Notificação de Boas-vindas", "welcome-notification-uid": "Usuário de Notificação de Boas-vindas (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/pt-BR/admin/settings/uploads.json b/public/language/pt-BR/admin/settings/uploads.json index c203d2cd23..67fd3ab403 100644 --- a/public/language/pt-BR/admin/settings/uploads.json +++ b/public/language/pt-BR/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Imagens com uma largura maior que esta serão rejeitadas.", "reject-image-height": "Altura Máxima das Imagens (em pixels)", "reject-image-height-help": "Imagens com uma altura maior do que este valor serão rejeitadas.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Permitir usuários de enviar miniaturas de tópico", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tamanho da Miniatura de Tópico", "allowed-file-extensions": "Extensões de Arquivo Permitidas", "allowed-file-extensions-help": "Digite uma lista, separada por vírgulas, de extensões de arquivos aqui (por exemplo: pdf,xls,doc). Uma lista vazia significa que todas as extensões são permitidas.", diff --git a/public/language/pt-BR/admin/settings/user.json b/public/language/pt-BR/admin/settings/user.json index de5a286972..607ca7d2a2 100644 --- a/public/language/pt-BR/admin/settings/user.json +++ b/public/language/pt-BR/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Exibir e-mail", "show-fullname": "Exibir nome completo", "restrict-chat": "Permitir apenas mensagens de chat de usuários que eu sigo", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Abrir links externos em nova aba", "topic-search": "Permitir Busca dentro do Tópico", "update-url-with-post-index": "Atualizar url com índice de postagem enquanto navega pelos tópicos", diff --git a/public/language/pt-BR/admin/settings/web-crawler.json b/public/language/pt-BR/admin/settings/web-crawler.json index ed9a88aebf..65baccd2ec 100644 --- a/public/language/pt-BR/admin/settings/web-crawler.json +++ b/public/language/pt-BR/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Desabilitar Feeds RSS", "disable-sitemap-xml": "Desativar Sitemap.xml", "sitemap-topics": "Número de Tópicos para mostrar no Mapa do Site", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Limpar Cache de Mapa do Site", "view-sitemap": "Ver Mapa do Site" } \ No newline at end of file diff --git a/public/language/pt-BR/aria.json b/public/language/pt-BR/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/pt-BR/aria.json +++ b/public/language/pt-BR/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/pt-BR/category.json b/public/language/pt-BR/category.json index 6805f6bf3f..a69c80847d 100644 --- a/public/language/pt-BR/category.json +++ b/public/language/pt-BR/category.json @@ -1,12 +1,13 @@ { "category": "Categoria", "subcategories": "Subcategorias", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Novo Tópico", "guest-login-post": "Entre para postar", "no-topics": "Não há tópicos nesta categoria.
Por que você não tenta postar um?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "navegando", "no-replies": "Ninguém respondeu", "no-new-posts": "Não há nenhum post nesta categoria.", diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json index 69b57b194d..666b9c3e93 100644 --- a/public/language/pt-BR/error.json +++ b/public/language/pt-BR/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON Inválido", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Você não parece estar logado.", "account-locked": "Sua conta foi temporariamente bloqueada", "search-requires-login": "É necessário ter uma conta para pesquisar - por favor efetue o login ou cadastre-se.", @@ -146,6 +147,7 @@ "post-already-restored": "Este post já foi restaurado", "topic-already-deleted": "Esté tópico já foi deletado", "topic-already-restored": "Este tópico já foi restaurado", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Você não pode remover o post principal, ao invés disso, apague o tópico por favor.", "topic-thumbnails-are-disabled": "Thumbnails para tópico estão desativados.", "invalid-file": "Arquivo Inválido", @@ -154,6 +156,8 @@ "about-me-too-long": "Desculpe, o sobre não pode ser maior que %1 caractere(s).", "cant-chat-with-yourself": "Você não pode iniciar um chat consigo mesmo!", "chat-restricted": "Este usuário restringiu suas mensagens de chat. Eles devem seguir você antes que você possa conversar com eles", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "O sistema de chat foi desabilitado", "too-many-messages": "Você enviou muitas mensagens, por favor aguarde um momento.", @@ -225,6 +229,7 @@ "no-topics-selected": "Nenhum tópico selecionado!", "cant-move-to-same-topic": "Não é possível mover um post para o mesmo tópico!", "cant-move-topic-to-same-category": "Não é possível mover o tópico para a mesma categoria!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Você pode bloquear a si mesmo!", "cannot-block-privileged": "Você não pode bloquear administradores e moderadores globais", "cannot-block-guest": "Vistantes não podem bloquear outros usuários", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Não foi possível acessar o servidor neste momento. Clique aqui para tentar novamente ou tente novamente mais tarde", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Não foi possível instalar o plugin - apenas os plug-ins permitidos pelo NodeBB Package Manager podem ser instalados através do ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "Não foi encontrada uma sessão válida. Faça login e tente novamente.", "api.403": "Você não tem autorização para fazer esta chamada", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/pt-BR/global.json b/public/language/pt-BR/global.json index 49de073ba7..e5e72365bf 100644 --- a/public/language/pt-BR/global.json +++ b/public/language/pt-BR/global.json @@ -68,6 +68,7 @@ "users": "Usuários", "topics": "Tópicos", "posts": "Posts", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -79,9 +80,10 @@ "upvoters": "Votos positivos", "upvoted": "Votou positivamente", "downvoters": "Votos negativos", - "downvoted": "Votou negativamente", + "downvoted": "Rebaixou", "views": "Visualizações", "posters": "Posters", + "watching": "Watching", "reputation": "Reputação", "lastpost": "Última postagem", "firstpost": "Primeira postagem", @@ -135,7 +137,7 @@ "allowed-file-types": "Os tipos de arquivo permitidos são %1", "unsaved-changes": "Você tem alterações não salvas. Tem certeza de que você deseja sair da página?", "reconnecting-message": "Parece que a sua conexão com o %1 caiu. Por favor, aguarde enquanto tentamos reconectar.", - "play": "Executar", + "play": "Tocar", "cookies.message": "Este site usa cookies para garantir que você obtenha a melhor experiência em nosso site.", "cookies.accept": "Entendi!", "cookies.learn-more": "Saber Mais", diff --git a/public/language/pt-BR/groups.json b/public/language/pt-BR/groups.json index 5d9bb312cd..cf2236a6e0 100644 --- a/public/language/pt-BR/groups.json +++ b/public/language/pt-BR/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupos", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Ver Grupo", "owner": "Dono do Grupo", "new-group": "Criar Novo Grupo", diff --git a/public/language/pt-BR/modules.json b/public/language/pt-BR/modules.json index 64a27be0d1..6d58b3fe9c 100644 --- a/public/language/pt-BR/modules.json +++ b/public/language/pt-BR/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Esconder Pré-visualização", "composer.help": "Help", "composer.user-said-in": "%1 disse em %2:", - "composer.user-said": "%1 disse:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Tem certeza que deseja descartar essa postagem?", "composer.submit-and-lock": "Enviar e Trancar", "composer.toggle-dropdown": "Alternar Dropdown", diff --git a/public/language/pt-BR/notifications.json b/public/language/pt-BR/notifications.json index 912a4733b9..ddc68ff548 100644 --- a/public/language/pt-BR/notifications.json +++ b/public/language/pt-BR/notifications.json @@ -22,7 +22,7 @@ "upvote": "Votos positivos", "awards": "Awards", "new-flags": "Novas Sinalizações", - "my-flags": "Sinalizações designadas a mim", + "my-flags": "My Flags", "bans": "Banimentos", "new-message-from": "Nova mensagem de %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 deu voto positivo para seu post em %2.", - "upvoted-your-post-in-dual": "%1 e %2 deram voto positivo ao seu post em %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 moveu seu post para %2", "moved-your-topic": "%1 se mudou %2", - "user-flagged-post-in": "%1 sinalizou um post em %2", - "user-flagged-post-in-dual": "%1 e %2 sinalizaram um post em %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 sinalizou um perfil de usuário (%2)", "user-flagged-user-dual": "%1 e %2 sinalizaram um perfil de usuário (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 postou uma resposta para: %2", - "user-posted-to-dual": "%1 e %2 postaram respostas para: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 postou um novo tópico: %2", - "user-edited-post": "%1 editou um post em %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 começou a seguir você.", "user-started-following-you-dual": "%1 e %2 começaram a lhe acompanhar.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -65,17 +65,17 @@ "new-register-multiple": "Há %1 pedidos de registro aguardando revisão.", "flag-assigned-to-you": "A Sinalização %1
foi atribuída a você", "post-awaiting-review": "Post aguardando revisão", - "profile-exported": "%1 perfil exportado, clique para fazer download", + "profile-exported": "%1 dados do perfil exportado, clique para fazer download", "posts-exported": "%1 posts exportados, clique para fazer download", "uploads-exported": "%1 uploads exportados, clique para fazer download", "users-csv-exported": "Usuários csv exportados, clique para fazer o download", "post-queue-accepted": "Sua postagem na fila foi aceita. Clique aqui para ver sua postagem.", "post-queue-rejected": "Sua postagem na fila foi rejeitada.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confirmado", "email-confirmed-message": "Obrigado por validar o seu email. Agora sua conta está plenamente ativada.", "email-confirm-error-message": "Houve um problema ao validar o seu endereço de email. Talvez o código era invalido ou tenha expirado.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Email de confirmação enviado.", "none": "Nenhum", "notification-only": "Apenas Notificações", diff --git a/public/language/pt-BR/pages.json b/public/language/pt-BR/pages.json index ed80563df7..098b314683 100644 --- a/public/language/pt-BR/pages.json +++ b/public/language/pt-BR/pages.json @@ -56,7 +56,7 @@ "account/watched": "Tópicos assistidos por %1", "account/ignored": "Tópicos ignorados por %1", "account/read": "Topics read by %1", - "account/upvoted": "Posts votados positivamente por %1", + "account/upvoted": "Posts alavancados por %1", "account/downvoted": "Posts votados negativamente por %1", "account/best": "Melhores posts de %1", "account/controversial": "Controversial posts made by %1", diff --git a/public/language/pt-BR/social.json b/public/language/pt-BR/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/pt-BR/social.json +++ b/public/language/pt-BR/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/pt-BR/themes/harmony.json b/public/language/pt-BR/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/pt-BR/themes/harmony.json +++ b/public/language/pt-BR/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/pt-BR/topic.json b/public/language/pt-BR/topic.json index 42c79c8bd0..21244f2196 100644 --- a/public/language/pt-BR/topic.json +++ b/public/language/pt-BR/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenciou este tópico em %3", "user-forked-topic-ago": "%1 ramificou este tópico %3", "user-forked-topic-on": "%1 ramificou este tópico em %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Clique aqui para retornar ao último post lido neste tópico.", "flag-post": "Marque este post", "flag-user": "Marque este usuário", @@ -103,6 +105,7 @@ "thread-tools.lock": "Trancar Tópico", "thread-tools.unlock": "Destrancar Tópico", "thread-tools.move": "Mover Tópico", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Mover Posts", "thread-tools.move-all": "Mover Tudo", "thread-tools.change-owner": "Trocar proprietário", @@ -132,6 +135,7 @@ "pin-modal-help": "Você pode, opcionalmente, definir uma data de validade para o(s) tópico(s) fixado(s) aqui. Como alternativa, você pode deixar este campo em branco para que o tópico permaneça fixado até que seja liberado manualmente.", "load-categories": "Carregando Categorias", "confirm-move": "Mover", + "confirm-crosspost": "Cross-post", "confirm-fork": "Ramificar", "bookmark": "Favorito", "bookmarks": "Favoritos", @@ -141,6 +145,7 @@ "loading-more-posts": "Carregando Mais Posts", "move-topic": "Mover Tópico", "move-topics": "Mover Tópicos", + "crosspost-topic": "Cross-post Topic", "move-post": "Mover Post", "post-moved": "Post movido!", "fork-topic": "Ramificar Tópico", @@ -163,6 +168,9 @@ "move-topic-instruction": "Selecione a categoria destino e click em mover", "change-owner-instruction": "Clique na postagem que você quer associar a outro usuário", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Digite aqui o título para o seu tópico...", "composer.handle-placeholder": "Digite seu nome/usuário aqui", "composer.hide": "Esconder", @@ -174,6 +182,7 @@ "composer.replying-to": "Respondendo para %1", "composer.new-topic": "Novo Tópico", "composer.editing-in": "Editando post em %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "enviando...", "composer.thumb-url-label": "Cole o endereço de um thumbnail para o tópico", "composer.thumb-title": "Adicionar um thumbnail para este tópico", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/pt-BR/user.json b/public/language/pt-BR/user.json index bb46852fc3..3735d4537f 100644 --- a/public/language/pt-BR/user.json +++ b/public/language/pt-BR/user.json @@ -105,6 +105,10 @@ "show-email": "Mostrar Meu Email", "show-fullname": "Mostrar Meu Nome Completo", "restrict-chats": "Permitir mensagens de chat apenas para usuários que eu sigo", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Assinar ao Resumo", "digest-description": "Assinar para receber atualizações por email deste fórum (novas notificações e tópicos) de acordo com um calendário definido", "digest-off": "Desativado", diff --git a/public/language/pt-BR/world.json b/public/language/pt-BR/world.json index 3753335278..e6694bf507 100644 --- a/public/language/pt-BR/world.json +++ b/public/language/pt-BR/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/pt-PT/admin/advanced/cache.json b/public/language/pt-PT/admin/advanced/cache.json index a724929a46..2a0de9e93c 100644 --- a/public/language/pt-PT/admin/advanced/cache.json +++ b/public/language/pt-PT/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Cache de Publicações", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Cheio", "post-cache-size": "Tamanho da Cache de Publicações", "items-in-cache": "Itens em Cache" diff --git a/public/language/pt-PT/admin/dashboard.json b/public/language/pt-PT/admin/dashboard.json index 6f82ecb7e2..64fda774f5 100644 --- a/public/language/pt-PT/admin/dashboard.json +++ b/public/language/pt-PT/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Visualizações de páginas por utilizadores registados", "graphs.page-views-guest": "Visualizações de páginas por convidados", "graphs.page-views-bot": "Visualizações de páginas por bots", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Visitantes únicos", "graphs.registered-users": "Utilizadores Registados", "graphs.guest-users": "Guest Users", diff --git a/public/language/pt-PT/admin/development/info.json b/public/language/pt-PT/admin/development/info.json index 9dc6b6665e..75559e1924 100644 --- a/public/language/pt-PT/admin/development/info.json +++ b/public/language/pt-PT/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/pt-PT/admin/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json index 352db735c2..b309193c8f 100644 --- a/public/language/pt-PT/admin/manage/categories.json +++ b/public/language/pt-PT/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Definições da Categoria", "edit-category": "Edit Category", "privileges": "Privilégios", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Nome da Categoria", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Descrição da Categoria", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Cor de Fundo", "text-color": "Cor do Texto", "bg-image-size": "Tamanho da Imagem de Fundo", @@ -103,6 +107,11 @@ "alert.create-success": "Categoria criada com sucesso!", "alert.none-active": "Não tens categorias ativas.", "alert.create": "Criar uma Categoria", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Tens a certeza que pretendes eliminar definitivamente esta categoria \"%1\"?

\n
Atenção! Todos os tópicos e publicações feitas nesta categoria vão ser eliminados também!

Eliminar uma categoria irá remover todos os tópicos e publicações e eliminar a categoria da base de dados. Se pretendes remover temporariamente uma categoria, em vez disso podes apenas \"desativar\" essa categoria.

", "alert.purge-success": "Categoria eliminada!", "alert.copy-success": "Definições Copiadas!", diff --git a/public/language/pt-PT/admin/manage/custom-reasons.json b/public/language/pt-PT/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/pt-PT/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/pt-PT/admin/manage/privileges.json b/public/language/pt-PT/admin/manage/privileges.json index 47071155e4..e61399865d 100644 --- a/public/language/pt-PT/admin/manage/privileges.json +++ b/public/language/pt-PT/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Aceder aos Tópicos", "create-topics": "Criar Tópicos", "reply-to-topics": "Responder a Tópicos", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Marcar Tópicos", "edit-posts": "Editar Publicações", diff --git a/public/language/pt-PT/admin/manage/users.json b/public/language/pt-PT/admin/manage/users.json index 10b432f238..cbde56fc3c 100644 --- a/public/language/pt-PT/admin/manage/users.json +++ b/public/language/pt-PT/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Eliminar Utilizador(es) e os seus Conteúdos", "download-csv": "Transferir CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Gerir Grupos", "set-reputation": "Set Reputation", "add-group": "Adicionar Grupo", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Razão (Opcional)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Horas", "temp-ban.days": "Dias", "temp-ban.explanation": "Insere o tempo de duração para o banimento. Nota que um tempo de 0 irá ser considerado um banimento permanente.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Tens a certeza que queres banir este utilizador permanentemente?", "alerts.confirm-ban-multi": "Tens a certeza que queres banir estes utilizadores permanentemente?", diff --git a/public/language/pt-PT/admin/settings/activitypub.json b/public/language/pt-PT/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/pt-PT/admin/settings/activitypub.json +++ b/public/language/pt-PT/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/pt-PT/admin/settings/chat.json b/public/language/pt-PT/admin/settings/chat.json index 1a6c28be24..5ae8592502 100644 --- a/public/language/pt-PT/admin/settings/chat.json +++ b/public/language/pt-PT/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Número máximo de utilizadores nas salas de conversa", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/pt-PT/admin/settings/email.json b/public/language/pt-PT/admin/settings/email.json index 1d1e70cf12..d42c633b59 100644 --- a/public/language/pt-PT/admin/settings/email.json +++ b/public/language/pt-PT/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Editar Modelo de E-mail", "template.select": "Escolher Modelo de E-mail", "template.revert": "Reverter para o Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Teste de E-mail", + "testing.success": "Test Email Sent.", "testing.select": "Escolher Modelo de E-mail", "testing.send": "Enviar E-mail de Teste", - "testing.send-help": "The test email will be sent to the currently logged in user's email address.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Resumos por E-mail", "subscriptions.disable": "Desativar resumos por e-mail", "subscriptions.hour": "Hora do Resumo", diff --git a/public/language/pt-PT/admin/settings/notifications.json b/public/language/pt-PT/admin/settings/notifications.json index 0ce9737e1f..77169f6e1c 100644 --- a/public/language/pt-PT/admin/settings/notifications.json +++ b/public/language/pt-PT/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Notificação de Boas-vindas", "welcome-notification-link": "Link da Notificação de Boas-vindas", "welcome-notification-uid": "Notificação de boas-vindas ao utilizador (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/pt-PT/admin/settings/uploads.json b/public/language/pt-PT/admin/settings/uploads.json index 91c89d7a46..6ec31eedc8 100644 --- a/public/language/pt-PT/admin/settings/uploads.json +++ b/public/language/pt-PT/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Imagens mais largas que este valor vão ser rejeitadas.", "reject-image-height": "Altura Máxima da Imagem (em píxeis)", "reject-image-height-help": "Imagens mais altas que este valor vão ser rejeitadas.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Permitir aos utilizadores enviar miniaturas de tópicos", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tamanho da Miniatura do Tópico", "allowed-file-extensions": "Extensões de Ficheiro Permitidas", "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.", diff --git a/public/language/pt-PT/admin/settings/user.json b/public/language/pt-PT/admin/settings/user.json index fcd47b2d74..f83d7718df 100644 --- a/public/language/pt-PT/admin/settings/user.json +++ b/public/language/pt-PT/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Mostrar e-mail", "show-fullname": "Mostrar nome completo", "restrict-chat": "Apenas permitir mensagens de utilizadores que eu sigo", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Abrir links externos num novo separador", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/pt-PT/admin/settings/web-crawler.json b/public/language/pt-PT/admin/settings/web-crawler.json index b8d164ecdd..fd2c8c2b26 100644 --- a/public/language/pt-PT/admin/settings/web-crawler.json +++ b/public/language/pt-PT/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Desativar RSS Feeds", "disable-sitemap-xml": "Desativar Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Limpar Cache do Sitemap", "view-sitemap": "Ver Sitemap" } \ No newline at end of file diff --git a/public/language/pt-PT/aria.json b/public/language/pt-PT/aria.json index c10f98dd4f..ef87ccade7 100644 --- a/public/language/pt-PT/aria.json +++ b/public/language/pt-PT/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "Etiquetas subscritas pelo utilizador", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/pt-PT/category.json b/public/language/pt-PT/category.json index 7432b39471..ef43876aa1 100644 --- a/public/language/pt-PT/category.json +++ b/public/language/pt-PT/category.json @@ -1,12 +1,13 @@ { "category": "Categoria", "subcategories": "Subcategorias", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Novo Tópico", "guest-login-post": "Inicia sessão para publicar algo", "no-topics": "Não existe nenhum tópico nesta categoria.
Que tal seres o primeiro a publicar aqui?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "navegação", "no-replies": "Ainda sem respostas", "no-new-posts": "Não existem publicações novas.", diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json index dff0eb8ecb..150d801ae5 100644 --- a/public/language/pt-PT/error.json +++ b/public/language/pt-PT/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON inválido", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Não tens sessão iniciada.", "account-locked": "A sua conta foi bloqueada temporariamente", "search-requires-login": "A pesquisa requer uma conta de utilizador - por favor inicia sessão ou cria uma conta.", @@ -146,6 +147,7 @@ "post-already-restored": "Esta publicação já foi restaurada", "topic-already-deleted": "Este tópico já foi eliminado", "topic-already-restored": "Este tópico já foi restaurado", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Não podes eliminar a publicação principal, em vez disso, por favor apaga o tópico", "topic-thumbnails-are-disabled": "Miniaturas para os tópicos estão desativadas.", "invalid-file": "Ficheiro inválido", @@ -154,6 +156,8 @@ "about-me-too-long": "Desculpa, o teu \"sobre mim\" não pode ser superior a %1 caracter(es).", "cant-chat-with-yourself": "Não podes conversar contigo mesmo!", "chat-restricted": "Este utilizador colocou restrições sobre as suas mensagens de chat. Ele deve primeiro seguir-te antes que possas conversar com ele", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Sistema de conversas desativado", "too-many-messages": "Enviaste demasiadas mensagens, por favor espera um pouco.", @@ -225,6 +229,7 @@ "no-topics-selected": "Nenhum tópico selecionado!", "cant-move-to-same-topic": "Não podes mover publicações para o mesmo tópico!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Não podes bloquear-te a ti próprio!", "cannot-block-privileged": "Não podes bloquear administradores ou moderadores globais", "cannot-block-guest": "Convidados não podem bloquear outros utilizadores", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/pt-PT/global.json b/public/language/pt-PT/global.json index 7c2a5bb12a..af3059c4b4 100644 --- a/public/language/pt-PT/global.json +++ b/public/language/pt-PT/global.json @@ -68,6 +68,7 @@ "users": "Utilizadores", "topics": "Tópicos", "posts": "Publicações", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Votado negativamente", "views": "Visualizações", "posters": "Posters", + "watching": "Watching", "reputation": "Reputação", "lastpost": "Última publicação", "firstpost": "Primeira publicação", diff --git a/public/language/pt-PT/groups.json b/public/language/pt-PT/groups.json index 459fd41d67..74380f1b8a 100644 --- a/public/language/pt-PT/groups.json +++ b/public/language/pt-PT/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupos", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Ver o grupo", "owner": "Dono do grupo", "new-group": "Criar novo grupo", diff --git a/public/language/pt-PT/modules.json b/public/language/pt-PT/modules.json index 5b3018d4fa..169c71120a 100644 --- a/public/language/pt-PT/modules.json +++ b/public/language/pt-PT/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Ocultar pré-visualização", "composer.help": "Help", "composer.user-said-in": "%1 disse em %2:", - "composer.user-said": "%1 disse:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Tens a certeza que queres descartar esta publicação?", "composer.submit-and-lock": "Submeter e bloquear", "composer.toggle-dropdown": "Alternar entre caixas", diff --git a/public/language/pt-PT/notifications.json b/public/language/pt-PT/notifications.json index 7afa2dab8d..a23b9701a5 100644 --- a/public/language/pt-PT/notifications.json +++ b/public/language/pt-PT/notifications.json @@ -22,7 +22,7 @@ "upvote": "Votos positivos", "awards": "Awards", "new-flags": "Novas denúncias", - "my-flags": "Denúncias atribuídas a mim", + "my-flags": "My Flags", "bans": "Banimentos", "new-message-from": "Nova mensagem de %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 votou de forma favorável na tua publicação em %2.", - "upvoted-your-post-in-dual": "%1 e %2 votaram favoravelmente à tua publicação em %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 moveu a tua publicação para %2", "moved-your-topic": "%1 moveu %2", - "user-flagged-post-in": "%1 denunciou uma publicação em %2", - "user-flagged-post-in-dual": "%1 e %2 denunciaram uma publicação em %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 denunciou um perfil de um utilizador (%2)", "user-flagged-user-dual": "%1 e %2 denunciaram um perfil de um utilizador (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 publicou uma resposta a: %2", - "user-posted-to-dual": "%1 e %2 publicaram respostas a: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 publicou um novo tópico: %2", - "user-edited-post": "%1 editou uma publicação em %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 começou a seguir-te.", "user-started-following-you-dual": "%1 e %2 começaram a seguir-te.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mail confirmado", "email-confirmed-message": "Obrigado por validares o teu endereço de e-mail. A tua conta está agora totalmente ativa.", "email-confirm-error-message": "Ocorreu um problema a validar o teu endereço de e-mail. Talvez o código seja inválido ou já tenha expirado.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "E-mail de confirmação enviado.", "none": "Nada", "notification-only": "Apenas Notificação", diff --git a/public/language/pt-PT/social.json b/public/language/pt-PT/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/pt-PT/social.json +++ b/public/language/pt-PT/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/pt-PT/themes/harmony.json b/public/language/pt-PT/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/pt-PT/themes/harmony.json +++ b/public/language/pt-PT/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/pt-PT/topic.json b/public/language/pt-PT/topic.json index 535f260664..dc8eb35a40 100644 --- a/public/language/pt-PT/topic.json +++ b/public/language/pt-PT/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Carrega aqui para voltares à última publicação lide assunto.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Bloquear tópico", "thread-tools.unlock": "Desbloquear tópico", "thread-tools.move": "Mover tópico", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Mover publicações", "thread-tools.move-all": "Mover todos", "thread-tools.change-owner": "Alterar Proprietário", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Carregando Categorias", "confirm-move": "Mover", + "confirm-crosspost": "Cross-post", "confirm-fork": "Clonar", "bookmark": "Marcador", "bookmarks": "Marcadores", @@ -141,6 +145,7 @@ "loading-more-posts": "Carregando mais publicações", "move-topic": "Mover tópico", "move-topics": "Mover tópicos", + "crosspost-topic": "Cross-post Topic", "move-post": "Mover publicação", "post-moved": "Publicação movida!", "fork-topic": "Clonar tópico", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Insere aqui o título do tópico...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Respondendo a %1", "composer.new-topic": "Novo tópico", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "carregando...", "composer.thumb-url-label": "Cola um URL da miniatura do tópico", "composer.thumb-title": "Adiciona uma miniatura a este tópico", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/pt-PT/user.json b/public/language/pt-PT/user.json index 7dc13eeb42..9aaa619c7a 100644 --- a/public/language/pt-PT/user.json +++ b/public/language/pt-PT/user.json @@ -105,6 +105,10 @@ "show-email": "Mostrar o meu e-mail", "show-fullname": "Mostrar o meu nome completo", "restrict-chats": "Permitir apenas mensagens de utilizadores que eu sigo", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Subscrever o resumo", "digest-description": "Subscrever atualizações por e-mail para este fórum (novas notificações e tópicos) de acordo com um horário definido", "digest-off": "Desligado", diff --git a/public/language/pt-PT/world.json b/public/language/pt-PT/world.json index 3753335278..e6694bf507 100644 --- a/public/language/pt-PT/world.json +++ b/public/language/pt-PT/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/ro/admin/admin.json b/public/language/ro/admin/admin.json index 96c58b1733..7be102ffdc 100644 --- a/public/language/ro/admin/admin.json +++ b/public/language/ro/admin/admin.json @@ -1,18 +1,18 @@ { - "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": "Sigur dorești să reconstruiești și să repornești NodeBB?", + "alert.confirm-restart": "Sigur dorești să repornești NodeBB?", - "acp-title": "%1 | NodeBB Admin Control Panel", - "settings-header-contents": "Contents", - "changes-saved": "Changes Saved", - "changes-saved-message": "Your changes to the NodeBB configuration have been saved.", - "changes-not-saved": "Changes Not Saved", - "changes-not-saved-message": "NodeBB encountered a problem saving your changes. (%1)", - "save-changes": "Save changes", + "acp-title": "%1 | Panou de control NodeBB", + "settings-header-contents": "Conținut", + "changes-saved": "Modificări Salvate", + "changes-saved-message": "Modificările aduse configurației NodeBB au fost salvate.", + "changes-not-saved": "Modificări Nesalvate", + "changes-not-saved-message": "NodeBB a întâmpinat o problemă la salvarea modificărilor. (%1)", + "save-changes": "Salvați modificările", "min": "Min:", "max": "Max:", - "view": "View", - "edit": "Edit", - "add": "Add", - "select-icon": "Select Icon" + "view": "Vizualizare", + "edit": "Modifică", + "add": "Adaugă", + "select-icon": "Selectați Icon" } \ No newline at end of file diff --git a/public/language/ro/admin/advanced/cache.json b/public/language/ro/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/ro/admin/advanced/cache.json +++ b/public/language/ro/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/ro/admin/advanced/database.json b/public/language/ro/admin/advanced/database.json index 55eea6c023..f67e83859a 100644 --- a/public/language/ro/admin/advanced/database.json +++ b/public/language/ro/admin/advanced/database.json @@ -17,7 +17,7 @@ "mongo.file-size": "File Size", "mongo.resident-memory": "Resident Memory", "mongo.virtual-memory": "Virtual Memory", - "mongo.mapped-memory": "Mapped Memory", + "mongo.mapped-memory": "Memorie mapată", "mongo.bytes-in": "Bytes In", "mongo.bytes-out": "Bytes Out", "mongo.num-requests": "Number of Requests", diff --git a/public/language/ro/admin/dashboard.json b/public/language/ro/admin/dashboard.json index 6ad973f5f3..9e4b880154 100644 --- a/public/language/ro/admin/dashboard.json +++ b/public/language/ro/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "Vizualizări Pagină ActivityPub", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", @@ -95,7 +96,7 @@ "expand-analytics": "Expand analytics", "clear-search-history": "Clear Search History", "clear-search-history-confirm": "Are you sure you want to clear entire search history?", - "search-term": "Term", + "search-term": "Termen", "search-count": "Count", - "view-all": "View all" + "view-all": "Arată Tot" } diff --git a/public/language/ro/admin/development/info.json b/public/language/ro/admin/development/info.json index 9834719daf..c5993ca132 100644 --- a/public/language/ro/admin/development/info.json +++ b/public/language/ro/admin/development/info.json @@ -3,12 +3,12 @@ "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", - "primary": "primary / jobs", + "primary": "principal / job-uri", "pid": "pid", "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", @@ -19,7 +19,7 @@ "registered": "Registered", "sockets": "Sockets", - "connection-count": "Connection Count", + "connection-count": "Număr Conexiuni", "guests": "Guests", "info": "Info" diff --git a/public/language/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json index f51152f22d..a179d5eb4d 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Adăugați Categorie Locală", + "add-remote-category": "Adăugă Categorie de la Distanță", + "remove": "Elimină", + "rename": "Redenumește", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "ID Categorie", "name": "Category Name", - "handle": "Category Handle", - "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", + "handle": "Identificator Categorie", + "handle.help": "Identificatorul categoriei este folosit ca o reprezentare a acestei categorii în alte rețele, similar unui nume de utilizator. Un identificator de categorie nu trebuie să corespundă unui nume de utilizator sau unui grup de utilizatori existent.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Șablon Subiect", + "topic-template.help": "Definiți un șablon pentru subiectele noi create în această categorie.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -42,7 +46,7 @@ "disable": "Disable", "edit": "Edit", "analytics": "Analytics", - "federation": "Federation", + "federation": "Federație", "view-category": "View category", "set-order": "Set order", @@ -82,27 +86,32 @@ "analytics.topics-daily": "Figure 3 – Daily topics created in this category", "analytics.posts-daily": "Figure 4 – Daily posts made in this category", - "federation.title": "Federation settings for \"%1\" category", - "federation.disabled": "Federation is disabled site-wide, so category federation settings are currently unavailable.", - "federation.disabled-cta": "Federation Settings →", - "federation.syncing-header": "Synchronization", - "federation.syncing-intro": "A category can follow a \"Group Actor\" via the ActivityPub protocol. If content is received from one of the actors listed below, it will be automatically added to this category.", - "federation.syncing-caveat": "N.B. Setting up syncing here establishes a one-way synchronization. NodeBB attempts to subscribe/follow the actor, but the reverse cannot be assumed.", - "federation.syncing-none": "This category is not currently following anybody.", - "federation.syncing-add": "Synchronize with...", + "federation.title": "Setări Federație pentru categoria \"%1\"", + "federation.disabled": "Federația este dezactivată la nivel de site, așadar setările de federare a categoriilor nu sunt disponibile în prezent.", + "federation.disabled-cta": "Setări Federație →", + "federation.syncing-header": "Sincronizare", + "federation.syncing-intro": "O categorie poate urma un „Actor de grup” prin protocolul ActivityPub. Dacă se primește conținut de la unul dintre actorii enumerați mai jos, acesta va fi adăugat automat în această categorie.", + "federation.syncing-caveat": "Notă: Configurarea sincronizării aici stabilește o sincronizare unidirecțională. NodeBB încearcă să se aboneze/să urmărească actorul, dar nu se poate presupune inversul.", + "federation.syncing-none": "Această categorie nu urmărește pe nimeni în prezent.", + "federation.syncing-add": "Sincronizează cu...", "federation.syncing-actorUri": "Actor", - "federation.syncing-follow": "Follow", - "federation.syncing-unfollow": "Unfollow", - "federation.followers": "Remote users following this category", - "federation.followers-handle": "Handle", + "federation.syncing-follow": "Urmărește", + "federation.syncing-unfollow": "Nu urmări", + "federation.followers": "Utilizatori la distanță care urmăresc această categorie", + "federation.followers-handle": "Identificator", "federation.followers-id": "ID", - "federation.followers-none": "No followers.", - "federation.followers-autofill": "Autofill", + "federation.followers-none": "Niciun urmăritor.", + "federation.followers-autofill": "Completare automată", "alert.created": "Created", "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Adăugă Categorie", + "alert.add-help": "Categoriile la distanță pot fi adăugate la lista de categorii specificând identificatorul/identificatorul acestora.

Notă — Categoria la distanță poate să nu reflecte toate subiectele publicate, cu excepția cazului în care cel puțin un utilizator local o urmărește/o urmărește.", + "alert.rename": "Redenumiți o categorie de la distanță", + "alert.rename-help": "Vă rugăm să introduceți un nume nou pentru această categorie. Lăsați câmpul necompletat pentru a restaura numele original.", + "alert.confirm-remove": "Sigur doriți să eliminați această categorie? O puteți adăuga din nou oricând.", "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!", diff --git a/public/language/ro/admin/manage/custom-reasons.json b/public/language/ro/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ro/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ro/admin/manage/privileges.json b/public/language/ro/admin/manage/privileges.json index 240cff6aa5..5ffd0074cc 100644 --- a/public/language/ro/admin/manage/privileges.json +++ b/public/language/ro/admin/manage/privileges.json @@ -8,7 +8,7 @@ "edit-privileges": "Edit Privileges", "select-clear-all": "Select/Clear All", "chat": "Chat", - "chat-with-privileged": "Chat with Privileged", + "chat-with-privileged": "Conversează cu cineva cu drepturi", "upload-images": "Upload Images", "upload-files": "Upload Files", "signature": "Signature", @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/ro/admin/manage/user-custom-fields.json b/public/language/ro/admin/manage/user-custom-fields.json index dab10670d2..a2c2da240d 100644 --- a/public/language/ro/admin/manage/user-custom-fields.json +++ b/public/language/ro/admin/manage/user-custom-fields.json @@ -1,28 +1,28 @@ { - "title": "Manage Custom User Fields", - "create-field": "Create Field", - "edit-field": "Edit Field", - "manage-custom-fields": "Manage Custom Fields", - "type-of-input": "Type of input", - "key": "Key", - "name": "Name", - "icon": "Icon", - "type": "Type", - "min-rep": "Minimum Reputation", - "input-type-text": "Input (Text)", - "input-type-link": "Input (Link)", - "input-type-number": "Input (Number)", - "input-type-date": "Input (Date)", - "input-type-select": "Select", - "input-type-select-multi": "Select Multiple", - "select-options": "Options", - "select-options-help": "Add one option per line for the select element", - "minimum-reputation": "Minimum reputation", - "minimum-reputation-help": "If a user has less than this value they won't be able to use this field", - "delete-field-confirm-x": "Do you really want to delete custom field \"%1\"?", - "custom-fields-saved": "Custom fields saved", - "visibility": "Visibility", - "visibility-all": "Everyone can see the field", - "visibility-loggedin": "Only logged in users can see the field", - "visibility-privileged": "Only privileged users like admins & moderators can see the field" + "title": "Gestionarea câmpurilor personalizate ale utilizatorilor", + "create-field": "Creare Câmp", + "edit-field": "Modificare Câmp", + "manage-custom-fields": "Administrare Câmpuri Personalizate", + "type-of-input": "Tipul editorului", + "key": "Cheie", + "name": "Nume", + "icon": "Iconîță", + "type": "Tip", + "min-rep": "Reputație Minimă", + "input-type-text": "Editor (Text)", + "input-type-link": "Editor (Link)", + "input-type-number": "Editor (Număr)", + "input-type-date": "Editor (Dată)", + "input-type-select": "Selecție", + "input-type-select-multi": "Selecție Multiplă", + "select-options": "Opțiuni", + "select-options-help": "Adăugați pe linie câte o opțiune pentru elementul select", + "minimum-reputation": "Reputație Minimă", + "minimum-reputation-help": "Dacă un utilizator are o valoare mai mică decât această, nu va putea folosi acest câmp.", + "delete-field-confirm-x": "Sigur doriți să ștergeți câmpul personalizat „%1”?", + "custom-fields-saved": "Câmpuri personalizate salvate", + "visibility": "Vizibilitate", + "visibility-all": "Oricine poate vedea câmpul", + "visibility-loggedin": "Doar utilizatorii autentificați pot vedea câmpul", + "visibility-privileged": "Doar utilizatorii privilegiați ca administratori sau moderatori pot vedea câmpul" } \ No newline at end of file diff --git a/public/language/ro/admin/manage/users.json b/public/language/ro/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/ro/admin/manage/users.json +++ b/public/language/ro/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/ro/admin/menu.json b/public/language/ro/admin/menu.json index 913c74f475..8114756e80 100644 --- a/public/language/ro/admin/menu.json +++ b/public/language/ro/admin/menu.json @@ -38,7 +38,7 @@ "settings/tags": "Tags", "settings/notifications": "Notifications", "settings/api": "API Access", - "settings/activitypub": "Federation (ActivityPub)", + "settings/activitypub": "Federație (ActivityPub)", "settings/sounds": "Sounds", "settings/social": "Social", "settings/cookies": "Cookies", diff --git a/public/language/ro/admin/settings/activitypub.json b/public/language/ro/admin/settings/activitypub.json index 94f9ad7822..e94dfe6b6d 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -1,26 +1,48 @@ { - "intro-lead": "What is Federation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", + "intro-lead": "Ce este Federația?", + "intro-body": "NodeBB poate comunica cu alte instanțe NodeBB care îl suportă. Acest lucru se realizează printr-un protocol numit ActivityPub. Dacă este activat, NodeBB va putea comunica și cu alte aplicații și site-uri web care utilizează ActivityPub (de exemplu, Mastodon, Peertube etc.).", "general": "General", - "pruning": "Content Pruning", - "content-pruning": "Days to keep remote content", - "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", - "user-pruning": "Days to cache remote user accounts", - "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", - "enabled": "Enable Federation", - "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", - "allowLoopback": "Allow loopback processing", - "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", + "pruning": "Eliminarea Conținutului", + "content-pruning": "Zile pentru păstrarea conținutului de la distanță", + "content-pruning-help": "Rețineți că va fi păstrat conținutul de la distanță cu care s-a interacționat (un răspuns sau un vot pozitiv/negativ). (0 pentru dezactivat)", + "user-pruning": "Zile pentru a păstra în memoria cache conturile de utilizatori de la distanță", + "user-pruning-help": "Conturile de utilizatori de la distanță vor fi eliminate doar dacă nu au postări. În caz contrar, vor fi recuperate. (0 pentru dezactivat)", + "enabled": "Activează Federația", + "enabled-help": "Dacă este activat, acest lucru va permite ca NodeBB să poată comunica cu toți clienții compatibili cu Activitypub de pe fediverse.", + "allowLoopback": "Permite procesarea loopback", + "allowLoopback-help": "Util doar pentru depanare. Probabil ar trebui să lași această opțiune dezactivată.", - "probe": "Open in App", - "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", - "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", - "probe-timeout": "Lookup Timeout (milliseconds)", - "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "probe": "Deschide în Aplicație", + "probe-enabled": "Încercă să deschidă resurse compatibile cu ActivityPub în NodeBB", + "probe-enabled-help": "Dacă este activat, NodeBB va verifica fiecare link extern pentru un echivalent ActivityPub și îl va încărca în NodeBB.", + "probe-timeout": "Timp de așteptare (milisecunde)", + "probe-timeout-help": "(Implicit: 2000) Dacă interogarea de căutare nu primește un răspuns în intervalul de timp setat, utilizatorul va fi direcționat direct către link. Ajustați acest număr mai mare dacă site-urile răspund lent și doriți să acordați timp suplimentar.", - "server-filtering": "Filtering", - "count": "This NodeBB is currently aware of %1 server(s)", - "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", - "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.", - "server.filter-allow-list": "Use this as an Allow List instead" + "rules": "Clasificare", + "rules-intro": "Conținutul descoperit prin ActivityPub poate fi clasificat automat pe baza anumitor reguli (de exemplu, hashtag)", + "rules.modal.title": "Cum funcționează", + "rules.modal.instructions": "Orice conținut primit este verificat în funcție de aceste reguli de clasificare, iar conținutul corespunzător este mutat automat în categoria aleasă.

N.B. Conținutul care este deja clasificat (adică într-o categorie de la distanță) nu va trece prin aceste reguli.", + "rules.add": "Adăugă Regulă Nouă", + "rules.help-hashtag": "Subiectele care conțin acest hashtag fără a ține cont de majuscule/minuscule se vor potrivi. Nu introduceți simbolul #", + "rules.help-user": "Subiectele create de utilizatorul introdus se vor potrivi. Introduceți un nume de utilizator sau un ID complet (e.g. bob@example.org sau https://example.org/users/bob.", + "rules.type": "Tip", + "rules.value": "Valoare", + "rules.cid": "Categorie", + + "relays": "Retransmițători", + "relays.intro": "O funcție de retransmitere îmbunătățește descoperirea conținutului către și de la NodeBB-ul dvs. Abonarea la o funcție de retransmitere înseamnă că respectivul conținut primit de către retransmitere este redirecționat aici, iar conținutul postat aici este sindicalizat către exterior de către retransmitere.", + "relays.warning": "Notă: Releele pot trimite volume mari de trafic și pot crește costurile de stocare și procesare.", + "relays.litepub": "NodeBB respectă standardul de retransmisie în stil LitePub. URL-ul pe care îl introduceți aici ar trebui să se termine cu /actor.", + "relays.add": "Adaugă Retransmițător Nou", + "relays.relay": "Retransmițător", + "relays.state": "Stare", + "relays.state-0": "În Așteptare", + "relays.state-1": "Doar Primește", + "relays.state-2": "Activ", + + "server-filtering": "Filtrează", + "count": "NodeBB cunoaște acum %1
server(e)", + "server.filter-help": "Specificați serverele interzise a se conecta cu NodeBB-ul dvs. Alternativ, puteți opta să permiteți selectiv conectarea cu anumite servere. Ambele opțiuni sunt acceptate, deși se exclud reciproc.", + "server.filter-help-hostname": "Introduceți mai jos doar numele instanței (de exemplu, example.org), câte unul pe rând.", + "server.filter-allow-list": "Folosește ca Poziții Permise" } \ No newline at end of file diff --git a/public/language/ro/admin/settings/chat.json b/public/language/ro/admin/settings/chat.json index 6d6cad284b..0c9ca90de0 100644 --- a/public/language/ro/admin/settings/chat.json +++ b/public/language/ro/admin/settings/chat.json @@ -5,13 +5,11 @@ "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-length-remote": "Maximum length of remote chat messages", - "max-length-remote-help": "This value is usually set higher than the chat message maximum for local users as remote messages tend to be longer (with @ mentions, etc.)", + "max-length-remote": "Lungimea maximă a mesajelor de chat la distanță", + "max-length-remote-help": "Această valoare este de obicei setată la o valoare mai mare decât numărul maxim de mesaje de chat pentru utilizatorii locali, deoarece mesajele la distanță tind să fie mai lungi (cu mențiuni @ etc.).", "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/ro/admin/settings/email.json b/public/language/ro/admin/settings/email.json index 0310939cb3..400486bd76 100644 --- a/public/language/ro/admin/settings/email.json +++ b/public/language/ro/admin/settings/email.json @@ -28,16 +28,22 @@ "smtp-transport.password": "Password", "smtp-transport.pool": "Enable pooled connections", "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", - "smtp-transport.allow-self-signed": "Allow self-signed certificates", - "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.allow-self-signed": "Permite certificate self-signed", + "smtp-transport.allow-self-signed-help": "Activarea acestei setări vă va permite să utilizați certificate TLS self-signed sau invalide.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/ro/admin/settings/general.json b/public/language/ro/admin/settings/general.json index d56c819745..ebebb10fa5 100644 --- a/public/language/ro/admin/settings/general.json +++ b/public/language/ro/admin/settings/general.json @@ -15,7 +15,7 @@ "title-layout": "Title 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", + "description": "Descrierea site-ului", "keywords": "Site Keywords", "keywords-placeholder": "Keywords describing your community, comma-separated", "logo-and-icons": "Site Logo & Icons", @@ -51,7 +51,7 @@ "topic-tools": "Topic Tools", "home-page": "Home Page", "home-page-route": "Home Page Route", - "home-page-description": "Choose what page is shown when users navigate to the root URL of your forum.", + "home-page-description": "Alegeți ce pagină este afișată când utilizatorii navighează la adresa URL rădăcină a forumului dvs.", "custom-route": "Custom Route", "allow-user-home-pages": "Allow User Home Pages", "home-page-title": "Title of the home page (default \"Home\")", diff --git a/public/language/ro/admin/settings/navigation.json b/public/language/ro/admin/settings/navigation.json index 3a71061ecf..130a14c17a 100644 --- a/public/language/ro/admin/settings/navigation.json +++ b/public/language/ro/admin/settings/navigation.json @@ -10,7 +10,7 @@ "id": "ID: optional", "properties": "Properties:", - "show-to-groups": "Show to Groups:", + "show-to-groups": "Afișare în Grupurile:", "open-new-window": "Open in a new window", "dropdown": "Dropdown", "dropdown-placeholder": "Place your dropdown menu items below, ie:
<li><a class="dropdown-item" href="https://myforum.com">Link 1</a></li>", diff --git a/public/language/ro/admin/settings/notifications.json b/public/language/ro/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/ro/admin/settings/notifications.json +++ b/public/language/ro/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/ro/admin/settings/post.json b/public/language/ro/admin/settings/post.json index e000f6b10b..5c11545151 100644 --- a/public/language/ro/admin/settings/post.json +++ b/public/language/ro/admin/settings/post.json @@ -4,11 +4,11 @@ "sorting.post-default": "Default Post Sorting", "sorting.oldest-to-newest": "Oldest to Newest", "sorting.newest-to-oldest": "Newest to Oldest", - "sorting.recently-replied": "Recently Replied", - "sorting.recently-created": "Recently Created", + "sorting.recently-replied": "Răspunse Recent", + "sorting.recently-created": "Create Recent", "sorting.most-votes": "Most Votes", "sorting.most-posts": "Most Posts", - "sorting.most-views": "Most Views", + "sorting.most-views": "Cele Mai Văzute", "sorting.topic-default": "Default Topic Sorting", "length": "Post Length", "post-queue": "Post Queue", diff --git a/public/language/ro/admin/settings/uploads.json b/public/language/ro/admin/settings/uploads.json index 22046915d9..2d689a25eb 100644 --- a/public/language/ro/admin/settings/uploads.json +++ b/public/language/ro/admin/settings/uploads.json @@ -9,10 +9,10 @@ "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: 2000 pixels, set to 0 to disable)", + "resize-image-width-threshold-help": "(în pixeli, implicit: 2000 pixeli, setați la 0 pentru dezactivare)", "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-keep-original": "Keep original image after resize", + "resize-image-keep-original": "Păstrează imaginea originală după redimensionare", "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)", @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Afișează încărcările de postări ca miniaturi", "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.", diff --git a/public/language/ro/admin/settings/user.json b/public/language/ro/admin/settings/user.json index 4e43ab7be3..7f87942d2a 100644 --- a/public/language/ro/admin/settings/user.json +++ b/public/language/ro/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Dezactivați primirea de mesaje", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/ro/admin/settings/web-crawler.json b/public/language/ro/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/ro/admin/settings/web-crawler.json +++ b/public/language/ro/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/ro/aria.json b/public/language/ro/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/ro/aria.json +++ b/public/language/ro/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/ro/category.json b/public/language/ro/category.json index 2a139a40b8..9ad9d4c6a2 100644 --- a/public/language/ro/category.json +++ b/public/language/ro/category.json @@ -1,12 +1,13 @@ { "category": "Categorie", "subcategories": "Subcategorii", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Subiect Nou", "guest-login-post": "Conecteaza-te pentru a posta", "no-topics": "Nu există nici un subiect de discuție în această categorie.
De ce nu încerci să postezi tu unul?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "navighează", "no-replies": "Nu a răspuns nimeni", "no-new-posts": "Nici o postare nouă", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index ed1f838ade..1cf2ef5ef7 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Se pare ca nu ești logat.", "account-locked": "Contul tău a fost blocat temporar", "search-requires-login": "Pentru a cauta ai nevoie de un cont. Logheaza-te sau autentifica-te.", @@ -146,6 +147,7 @@ "post-already-restored": "This post has already been restored", "topic-already-deleted": "This topic has already been deleted", "topic-already-restored": "This topic has already been restored", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Pictogramele pentru subiect sunt interzise.", "invalid-file": "Fișier invalid", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "Nu poți conversa cu tine!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/ro/global.json b/public/language/ro/global.json index 1483caaeef..5a305b8ced 100644 --- a/public/language/ro/global.json +++ b/public/language/ro/global.json @@ -68,6 +68,7 @@ "users": "Utilizatori", "topics": "Subiecte", "posts": "Mesaje", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Vizualizări", "posters": "Posters", + "watching": "Watching", "reputation": "Reputație", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/ro/groups.json b/public/language/ro/groups.json index 03c8325f3b..323bd5dd30 100644 --- a/public/language/ro/groups.json +++ b/public/language/ro/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupuri", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Vezi Grup", "owner": "Propietar de group", "new-group": "Crează un grup nou", diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json index e45d834abc..80c8bd0408 100644 --- a/public/language/ro/modules.json +++ b/public/language/ro/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 a spus în %2:", - "composer.user-said": "%1 a spus:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Ești sigur că vrei să renunți la acest mesaj?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/ro/notifications.json b/public/language/ro/notifications.json index 90eaf58060..305d5b5414 100644 --- a/public/language/ro/notifications.json +++ b/public/language/ro/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Un mesaj nou de la %1", "new-messages-from": "%1 new messages from %2", @@ -32,13 +32,13 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 a votat pozitiv mesajul tău în %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", - "user-flagged-post-in": "%1 a semnalizat un mesaj în %2", + "user-flagged-post-in": "%1 flagged a post in %2", "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 a postat un răspuns la: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 a început să te urmărească.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email confirmat", "email-confirmed-message": "Îți mulțumim pentru validarea emailului. Contul tău este acuma activat.", "email-confirm-error-message": "A fost o problemă cu activarea adresei tale de email. Poate codul de activare a fost invalid sau expirat.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Un email de confirmare a fost trimis.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/ro/pages.json b/public/language/ro/pages.json index 87ab32ddb6..41f0901058 100644 --- a/public/language/ro/pages.json +++ b/public/language/ro/pages.json @@ -36,7 +36,7 @@ "chat": "Chatting with %1", "flags": "Flags", "flag-details": "Flag %1 Details", - "world": "World", + "world": "Lumea", "account/edit": "Editing \"%1\"", "account/edit/password": "Editing password of \"%1\"", "account/edit/username": "Editing username of \"%1\"", @@ -55,7 +55,7 @@ "account/settings-of": "Changing settings of %1", "account/watched": "Topics watched by %1", "account/ignored": "Topics ignored by %1", - "account/read": "Topics read by %1", + "account/read": "Subiecte citite de %1", "account/upvoted": "Posts upvoted by %1", "account/downvoted": "Posts downvoted by %1", "account/best": "Best posts made by %1", @@ -63,7 +63,7 @@ "account/blocks": "Blocked users for %1", "account/uploads": "Uploads by %1", "account/sessions": "Login Sessions", - "account/shares": "Topics shared by %1", + "account/shares": "Subiecte partajate de %1", "confirm": "Email Confirmed", "maintenance.text": "%1 is currently undergoing maintenance.
Please come back another time.", "maintenance.messageIntro": "Additionally, the administrator has left this message:", diff --git a/public/language/ro/post-queue.json b/public/language/ro/post-queue.json index 24b33da2e6..c9d806c61b 100644 --- a/public/language/ro/post-queue.json +++ b/public/language/ro/post-queue.json @@ -3,10 +3,10 @@ "post-queue": "Post Queue", "no-queued-posts": "There are no posts in the post queue.", "no-single-post": "The topic or post you are looking for is no longer in the queue. It has likely been approved or deleted already.", - "enabling-help": "The post queue is currently disabled. To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", + "enabling-help": "Coada de publicare este dezactivată . Pentru a o activa, mergeți la Setări → Post → Post Queue și activați Post Queue.", "back-to-list": "Back to Post Queue", - "public-intro": "If you have any queued posts, they will be shown here.", - "public-description": "This forum is configured to automatically queue posts from new accounts, pending moderator approval.
If you have queued posts awaiting approval, you will be able to see them here.", + "public-intro": "Dacă aveți postări în coadă, acestea vor fi afișate aici.", + "public-description": "Acest forum este configurat să adauge automat în coadă postările de la conturile noi, în așteptarea aprobării moderatorului.
Dacă aveți postări în coadă care așteaptă aprobarea, le veți putea vedea aici.", "user": "User", "when": "When", "category": "Category", @@ -39,5 +39,5 @@ "remove-selected-confirm": "Do you want to remove %1 selected posts?", "bulk-accept-success": "%1 posts accepted", "bulk-reject-success": "%1 posts rejected", - "links-in-this-post": "Links in this post" + "links-in-this-post": "Linkuri în această postare" } \ No newline at end of file diff --git a/public/language/ro/recent.json b/public/language/ro/recent.json index 825ef74692..9e878b96c7 100644 --- a/public/language/ro/recent.json +++ b/public/language/ro/recent.json @@ -8,6 +8,6 @@ "no-recent-topics": "Nu există subiecte recente.", "no-popular-topics": "Nu sunt subiecte populare.", "load-new-posts": "Load new posts", - "uncategorized.title": "All known topics", - "uncategorized.intro": "This page shows a chronological listing of every topic that this forum has received.
The views and opinions expressed in the topics below are not moderated and may not represent the views and opinions of this website." + "uncategorized.title": "Toate subiectele cunoscute", + "uncategorized.intro": "Această pagină prezintă o listă cronologică a fiecărui subiect primit de acest forum. Părerile și opiniile exprimate în subiectele de mai jos nu sunt moderate și este posibil să nu reprezinte opiniile și opiniile acestui site web." } \ No newline at end of file diff --git a/public/language/ro/search.json b/public/language/ro/search.json index f994b924cb..0d544d3545 100644 --- a/public/language/ro/search.json +++ b/public/language/ro/search.json @@ -7,7 +7,7 @@ "in-titles": "In titles", "in-titles-posts": "In titles and posts", "in-posts": "In posts", - "in-bookmarks": "In bookmarks", + "in-bookmarks": "În marcaje", "in-categories": "In categories", "in-users": "In users", "in-tags": "In tags", diff --git a/public/language/ro/social.json b/public/language/ro/social.json index 2ba690a187..40dc8ec5ec 100644 --- a/public/language/ro/social.json +++ b/public/language/ro/social.json @@ -7,6 +7,8 @@ "sign-up-with-google": "Sign up with Google", "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", - "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-in-with-linkedin": "Conectează-te cu LinkedIn", + "sign-up-with-linkedin": "Înregistrează-te cu LinkedIn", + "sign-in-with-wordpress": "Conectează-te cu WordPress", + "sign-up-with-wordpress": "Înregistrează-te cu WordPress" } \ No newline at end of file diff --git a/public/language/ro/tags.json b/public/language/ro/tags.json index b412a8c85d..cdf91bf1e0 100644 --- a/public/language/ro/tags.json +++ b/public/language/ro/tags.json @@ -3,7 +3,7 @@ "no-tag-topics": "Nu există nici un subiect cu acest tag.", "no-tags-found": "No tags found", "tags": "Taguri", - "enter-tags-here": "Enter tags, %1 - %2 characters.", + "enter-tags-here": "Introduceți etichete, %1 - %2 caractere.", "enter-tags-here-short": "Introdu taguri...", "no-tags": "În acest moment nu există nici un tag.", "select-tags": "Select Tags", diff --git a/public/language/ro/themes/harmony.json b/public/language/ro/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/ro/themes/harmony.json +++ b/public/language/ro/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index 5d44fe17d7..badbc2b499 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -15,7 +15,7 @@ "replies-to-this-post": "%1 Replies", "one-reply-to-this-post": "1 Reply", "last-reply-time": "Last reply", - "reply-options": "Reply options", + "reply-options": "Opțiuni răspuns", "reply-as-topic": "Răspunde ca subiect", "guest-login-reply": "Login pentru a răspunde", "login-to-view": "🔒 Log in to view", @@ -27,7 +27,7 @@ "restore": "Restaurează", "move": "Mută", "change-owner": "Change Owner", - "manage-editors": "Manage Editors", + "manage-editors": "Gestionați Editorii", "fork": "Bifurcă", "link": "Link", "share": "Distribuie", @@ -36,7 +36,7 @@ "pinned": "Pinned", "pinned-with-expiry": "Pinned until %1", "scheduled": "Scheduled", - "deleted": "Deleted", + "deleted": "Șters", "moved": "Moved", "moved-from": "Moved from %1", "copy-code": "Copy Code", @@ -61,14 +61,16 @@ "user-restored-topic-on": "%1 restored this topic on %2", "user-moved-topic-from-ago": "%1 moved this topic from %2 %3", "user-moved-topic-from-on": "%1 moved this topic from %2 on %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "user-shared-topic-ago": "%1 a distribuit acest subiect %2", + "user-shared-topic-on": "%1 a distribuit acest subiect pe %2", "user-queued-post-ago": "%1 queued post for approval %3", "user-queued-post-on": "%1 queued post for approval on %3", "user-referenced-topic-ago": "%1 referenced this topic %3", "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,10 +105,11 @@ "thread-tools.lock": "Închide Subiect", "thread-tools.unlock": "Deschide Subiect", "thread-tools.move": "Mută Subiect", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Mută-le pe toate", "thread-tools.change-owner": "Change Owner", - "thread-tools.manage-editors": "Manage Editors", + "thread-tools.manage-editors": "Gestionați Editorii", "thread-tools.select-category": "Select Category", "thread-tools.fork": "Bifurcă Subiect", "thread-tools.tag": "Tag Topic", @@ -132,15 +135,17 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Se Încarcă Categoriile", "confirm-move": "Mută", + "confirm-crosspost": "Cross-post", "confirm-fork": "Bifurcă", "bookmark": "Bookmark", "bookmarks": "Bookmarks", "bookmarks.has-no-bookmarks": "You haven't bookmarked any posts yet.", "copy-permalink": "Copy Permalink", - "go-to-original": "View Original Post", + "go-to-original": "Vizualizați Postarea Originală", "loading-more-posts": "Se încarcă mai multe mesaje", "move-topic": "Mută Subiect", "move-topics": "Mută Subiecte", + "crosspost-topic": "Cross-post Topic", "move-post": "Mută Mesaj", "post-moved": "Mesaj mutat!", "fork-topic": "Bifurcă Subiect", @@ -162,7 +167,10 @@ "move-posts-instruction": "Click the posts you want to move then enter a topic ID or go to the target topic", "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", - "manage-editors-instruction": "Manage the users who can edit this post below.", + "manage-editors-instruction": "Gestionați mai jos utilizatorii care pot edita această postare.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Introdu numele subiectului aici ...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Îi raspunde lui %1", "composer.new-topic": "Subiect Nou", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "se uploadează ...", "composer.thumb-url-label": "Lipește un link pentru pictogramă subiect", "composer.thumb-title": "Adaugă o pictogramă la acest subiect", @@ -188,8 +197,8 @@ "sort-by": "Sortează de la", "oldest-to-newest": "Vechi la Noi", "newest-to-oldest": "Noi la Vechi", - "recently-replied": "Recently Replied", - "recently-created": "Recently Created", + "recently-replied": "Răspunse Recent", + "recently-created": "Create Recent", "most-votes": "Most Votes", "most-posts": "Most Posts", "most-views": "Most Views", @@ -214,15 +223,18 @@ "last-post": "Last post", "go-to-my-next-post": "Go to my next post", "no-more-next-post": "You don't have more posts in this topic", - "open-composer": "Open composer", + "open-composer": "Deschide composer-ul", "post-quick-reply": "Quick reply", "navigator.index": "Post %1 of %2", "navigator.unread": "%1 unread", - "upvote-post": "Upvote post", - "downvote-post": "Downvote post", - "post-tools": "Post tools", - "unread-posts-link": "Unread posts link", - "thumb-image": "Topic thumbnail image", - "announcers": "Shares", - "announcers-x": "Shares (%1)" + "upvote-post": "Votează pentru postare", + "downvote-post": "Votează împotriva postării", + "post-tools": "Unelte de postare", + "unread-posts-link": "Link pentru postări necitite", + "thumb-image": "Imagine miniatură subiect", + "announcers": "Partajări", + "announcers-x": "Partajări (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/ro/unread.json b/public/language/ro/unread.json index bccada87c3..f80d56c432 100644 --- a/public/language/ro/unread.json +++ b/public/language/ro/unread.json @@ -3,7 +3,7 @@ "no-unread-topics": "Nu există nici un subiect necitit.", "load-more": "Încarcă mai multe", "mark-as-read": "Marchează ca citit", - "mark-as-unread": "Mark as Unread", + "mark-as-unread": "Marchează ca Necitit", "selected": "Selectate", "all": "Toate", "all-categories": "Toate categoriile", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index 8ad094b1ed..e20f824928 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -105,6 +105,10 @@ "show-email": "Arată adresa mea de email", "show-fullname": "Show My Full Name", "restrict-chats": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Abonează-te la digest", "digest-description": "Abonează-te la updateuri prin email de la acest forum (notificări noi si subiecte) în concordanță cu un program prestabilit", "digest-off": "Închis", diff --git a/public/language/ro/users.json b/public/language/ro/users.json index fd6c6a6c58..ba124c9d31 100644 --- a/public/language/ro/users.json +++ b/public/language/ro/users.json @@ -1,6 +1,6 @@ { "all-users": "All Users", - "followed-users": "Followed Users", + "followed-users": "Utilizatori Urmăriți", "latest-users": "Ultimii Utilizatori", "top-posters": "Top Utilizatori", "most-reputation": "Cei mai apreciați utilizatori", diff --git a/public/language/ro/world.json b/public/language/ro/world.json index 3753335278..1cf69ff755 100644 --- a/public/language/ro/world.json +++ b/public/language/ro/world.json @@ -1,18 +1,25 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Lumea", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "Ajutor", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", - "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.title": "Ce este în pagina curentă", + "help.intro": "Bine ai venit în colțul tău din universul fediverse", + "help.fediverse": "„Fediversul” este o rețea de aplicații și site-uri web interconectate care comunică între ele și ai căror utilizatori se pot vedea reciproc. Acest forum este federat și poate interacționa cu acea rețea socială (sau „fediversul”). Această pagină este colțul tău din fedivers. Constă exclusiv din subiecte create de — și partajate de — utilizatori pe care îi urmărești.", + "help.build": "S-ar putea să nu fie multe subiecte de început aici; este normal. Vei începe să vezi mai mult conținut aici în timp, când vei începe să urmărești alți utilizatori.", + "help.federating": "De asemenea, dacă utilizatori din afara acestui forum încep să te urmărească, atunci postările tale vor începe să apară și pe acele aplicații și site-uri web.", + "help.next-generation": "Aceasta este următoarea generație de social media, începe să contribui chiar azi!", - "onboard.title": "Your window to the fediverse...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.title": "Fereastra ta către fedivers...", + "onboard.what": "Aceasta este categoria ta personalizată, formată doar din conținut găsit în afara acestui forum. Afișarea unui element pe această pagină depinde de dacă îl urmărești sau dacă postarea respectivă a fost distribuită de cineva pe care îl urmărești.", + "onboard.why": "Se întâmplă multe lucruri în afara acestui forum și nu toate sunt relevante pentru interesele tale. De aceea, urmărirea oamenilor este cea mai bună modalitate de a semnala că vrei să vezi mai multe de la cineva.", + "onboard.how": "Între timp, puteți da clic pe butoanele de comandă rapidă din partea de sus pentru a vedea ce mai știe acest forum și pentru a începe să descoperiți conținut nou!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/ru/admin/advanced/cache.json b/public/language/ru/admin/advanced/cache.json index 7f3fadce28..d0335df601 100644 --- a/public/language/ru/admin/advanced/cache.json +++ b/public/language/ru/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Кэш сообщений", - "group-cache": "Кеш групп", - "local-cache": "Локальный кеш", - "object-cache": "Кеш объектов", "percent-full": "Заполнен на%1%", "post-cache-size": "Размер кэша сообщений", "items-in-cache": "Закешировано элементов" diff --git a/public/language/ru/admin/advanced/events.json b/public/language/ru/admin/advanced/events.json index f1d1c69dea..9ec80306ff 100644 --- a/public/language/ru/admin/advanced/events.json +++ b/public/language/ru/admin/advanced/events.json @@ -9,9 +9,9 @@ "filter-type": "Тип события", "filter-start": "Дата начала", "filter-end": "Дата окончания", - "filter-user": "Filter by User", - "filter-user.placeholder": "Type user name to filter...", - "filter-group": "Filter by Group", - "filter-group.placeholder": "Type group name to filter...", + "filter-user": "Фильтровать по пользователю", + "filter-user.placeholder": "Введите имя пользователя для фильтрации…", + "filter-group": "Фильтровать по группе", + "filter-group.placeholder": "Введите название группы для фильтрации…", "filter-per-page": "Записей на страницу" } \ No newline at end of file diff --git a/public/language/ru/admin/dashboard.json b/public/language/ru/admin/dashboard.json index 137241c469..846d395675 100644 --- a/public/language/ru/admin/dashboard.json +++ b/public/language/ru/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Просм. авторизованными", "graphs.page-views-guest": "Просмотров гостями", "graphs.page-views-bot": "Просмотров ботами", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Уникальных посетителей", "graphs.registered-users": "Авторизованных пользователей", "graphs.guest-users": "Неавторизированных посетителей", diff --git a/public/language/ru/admin/development/info.json b/public/language/ru/admin/development/info.json index a7f1481f88..2ec2eafd32 100644 --- a/public/language/ru/admin/development/info.json +++ b/public/language/ru/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "онлайн", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json index 456149cc90..9e7839f40b 100644 --- a/public/language/ru/admin/manage/categories.json +++ b/public/language/ru/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Настройки категории", "edit-category": "Edit Category", "privileges": "Права доступа", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Название категории", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Описание категории", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Цвет фона", "text-color": "Цвет текста", "bg-image-size": "Размер фонового изображения", @@ -103,6 +107,11 @@ "alert.create-success": "Категория успешно создана!", "alert.none-active": "У вас нет активных категорий.", "alert.create": "Создать категорию", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Вы точно хотите очистить категорию «%1»?

Предупреждение! Все темы и сообщения в этой категории будут удалены

Очистка категории удаляет все темы и сообщения, а также саму категорию из базы данных. Если вы хотите удалить категорию временно, вместо очистки вам нужно выбрать \"отключить\" .

", "alert.purge-success": "Категория очищена!", "alert.copy-success": "Настройки скопированы!", diff --git a/public/language/ru/admin/manage/custom-reasons.json b/public/language/ru/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ru/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ru/admin/manage/privileges.json b/public/language/ru/admin/manage/privileges.json index 03fc754264..67942773bc 100644 --- a/public/language/ru/admin/manage/privileges.json +++ b/public/language/ru/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Читать темы", "create-topics": "Создавать темы", "reply-to-topics": "Отвечать в темах", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Присваивать метки", "edit-posts": "Редактировать сообщения", diff --git a/public/language/ru/admin/manage/user-custom-fields.json b/public/language/ru/admin/manage/user-custom-fields.json index dab10670d2..96bb4c2546 100644 --- a/public/language/ru/admin/manage/user-custom-fields.json +++ b/public/language/ru/admin/manage/user-custom-fields.json @@ -3,26 +3,26 @@ "create-field": "Create Field", "edit-field": "Edit Field", "manage-custom-fields": "Manage Custom Fields", - "type-of-input": "Type of input", - "key": "Key", - "name": "Name", - "icon": "Icon", - "type": "Type", - "min-rep": "Minimum Reputation", - "input-type-text": "Input (Text)", - "input-type-link": "Input (Link)", - "input-type-number": "Input (Number)", - "input-type-date": "Input (Date)", - "input-type-select": "Select", - "input-type-select-multi": "Select Multiple", - "select-options": "Options", - "select-options-help": "Add one option per line for the select element", - "minimum-reputation": "Minimum reputation", - "minimum-reputation-help": "If a user has less than this value they won't be able to use this field", - "delete-field-confirm-x": "Do you really want to delete custom field \"%1\"?", - "custom-fields-saved": "Custom fields saved", - "visibility": "Visibility", - "visibility-all": "Everyone can see the field", - "visibility-loggedin": "Only logged in users can see the field", - "visibility-privileged": "Only privileged users like admins & moderators can see the field" + "type-of-input": "Тип ввода", + "key": "Ключ", + "name": "Имя", + "icon": "Иконка", + "type": "Тип", + "min-rep": "Минимальная репутация", + "input-type-text": "Ввод (текст)", + "input-type-link": "Ввод (ссылка)", + "input-type-number": "Ввод (число)", + "input-type-date": "Ввод (Дата)", + "input-type-select": "Выбрать", + "input-type-select-multi": "Выбрать несколько", + "select-options": "Параметры", + "select-options-help": "Добавьте по одному варианту на строку для выбранного элемента", + "minimum-reputation": "Минимальная репутация", + "minimum-reputation-help": "Если у пользователя значение меньше этого, он не сможет использовать это поле.", + "delete-field-confirm-x": "Вы действительно хотите удалить пользовательское поле \"%1\"?", + "custom-fields-saved": "Пользовательские поля сохранены", + "visibility": "Видимость", + "visibility-all": "Все могут видеть поле.", + "visibility-loggedin": "Только зарегистрированные пользователи могут видеть это поле.", + "visibility-privileged": "Только привилегированные пользователи, такие как администраторы и модераторы, могут видеть это поле." } \ No newline at end of file diff --git a/public/language/ru/admin/manage/users.json b/public/language/ru/admin/manage/users.json index 91509273ce..482a42b9e6 100644 --- a/public/language/ru/admin/manage/users.json +++ b/public/language/ru/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Удалить пользователя(-ей) и данные", "download-csv": "Скачать CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Изменить членство в группах", "set-reputation": "Set Reputation", "add-group": "Добавить группу", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Причина (Необязательно)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Часов", "temp-ban.days": "Дней", "temp-ban.explanation": "Укажите продолжительность блокировки. Имейте в виду, что «0» означает заблокировать навсегда.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Вы действительно хотите заблокировать пользователя навсегда?", "alerts.confirm-ban-multi": "Вы действительно хотите заблокировать этих пользователей навсегда?", diff --git a/public/language/ru/admin/settings/activitypub.json b/public/language/ru/admin/settings/activitypub.json index 229272d32e..201d4d7a65 100644 --- a/public/language/ru/admin/settings/activitypub.json +++ b/public/language/ru/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Время ожидания поиска (миллисекунды)", "probe-timeout-help": "(По умолчанию: 2000) Если поисковый запрос не получит ответа в установленные сроки, пользователь будет перенаправлен непосредственно по ссылке. Увеличьте это число, если сайты отвечают медленно и вы хотите предоставить дополнительное время.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Фильтрация", "count": "В настоящее время NodeBB знает о %1 сервере(ах)", "server.filter-help": "Укажите серверы, для которых вы хотели бы запретить объединение с вашим NodeBB. В качестве альтернативы вы можете выборочно разрешить объединение с определенными серверами. Поддерживаются оба варианта, хотя они и являются взаимоисключающими.", diff --git a/public/language/ru/admin/settings/chat.json b/public/language/ru/admin/settings/chat.json index 02ef7d04f7..73c8827b10 100644 --- a/public/language/ru/admin/settings/chat.json +++ b/public/language/ru/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Максимальное кол-во пользователей в чат-комнатах", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/ru/admin/settings/email.json b/public/language/ru/admin/settings/email.json index 6d282e9abc..600dc5cf7d 100644 --- a/public/language/ru/admin/settings/email.json +++ b/public/language/ru/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Объединение соединений не позволяет NodeBB создавать новое соединение для каждой электронной почты. Этот параметр применяется только в том случае, если включен транспортный протокол SMTP.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Шаблоны писем", "template.select": "Выберите шаблон письма", "template.revert": "Вернуть стандартный", + "test-smtp-settings": "Test SMTP Settings", "testing": "Проверка отправки", + "testing.success": "Test Email Sent.", "testing.select": "Выберите шаблон письма", "testing.send": "Отправить проверочное письмо", - "testing.send-help": "Проверочное письмо будет отправлено на электронную почту пользователя, который сейчас пользуется панелью администратора.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Новостные рассылки", "subscriptions.disable": "Отключить новостные рассылки", "subscriptions.hour": "Час отправки", diff --git a/public/language/ru/admin/settings/notifications.json b/public/language/ru/admin/settings/notifications.json index d6a0478bc5..1f0d8dfcaa 100644 --- a/public/language/ru/admin/settings/notifications.json +++ b/public/language/ru/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Приветственное уведомление", "welcome-notification-link": "Ссылка в уведомлении", "welcome-notification-uid": "UID отправителя", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/ru/admin/settings/uploads.json b/public/language/ru/admin/settings/uploads.json index 8725c70db8..d0f7380791 100644 --- a/public/language/ru/admin/settings/uploads.json +++ b/public/language/ru/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Загрузка изображений шире указанного значения будет отклонена.", "reject-image-height": "Макс. высота изображения (в пикселях)", "reject-image-height-help": "Загрузка изображений выше указанного значения будет отклонена.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Разрешить пользователям загружать миниатюры для тем", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Размер миниатюр", "allowed-file-extensions": "Допустимые расширения файлов", "allowed-file-extensions-help": "Укажите через запятую список расширений файлов, например pdf,xls,doc. Оставьте поле пустым, чтобы разрешить любые загрузки.", diff --git a/public/language/ru/admin/settings/user.json b/public/language/ru/admin/settings/user.json index 34b57d4f60..244128e652 100644 --- a/public/language/ru/admin/settings/user.json +++ b/public/language/ru/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Показывать адрес электронной почты", "show-fullname": "Показывать полное имя", "restrict-chat": "Разрешить чат только с теми, на кого подписаны", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Открывать внешние ссылки в новой вкладке", "topic-search": "Включить поиск по сообщениям внутри тем", "update-url-with-post-index": "Обновлять URL-адрес с индексом публикации при просмотре тем", diff --git a/public/language/ru/admin/settings/web-crawler.json b/public/language/ru/admin/settings/web-crawler.json index 2edbd1da0f..dd19329076 100644 --- a/public/language/ru/admin/settings/web-crawler.json +++ b/public/language/ru/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Отключить RSS", "disable-sitemap-xml": "Отключить Sitemap.xml", "sitemap-topics": "Сколько тем указывать в карте сайта", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Очистить кеш карты сайта", "view-sitemap": "Посмотреть карту сайта" } \ No newline at end of file diff --git a/public/language/ru/aria.json b/public/language/ru/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/ru/aria.json +++ b/public/language/ru/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/ru/category.json b/public/language/ru/category.json index b4f124d1ca..83d8687548 100644 --- a/public/language/ru/category.json +++ b/public/language/ru/category.json @@ -1,12 +1,13 @@ { "category": "Категория", "subcategories": "Подкатегории", - "uncategorized": "Без рубрики", - "uncategorized.description": "Темы, которые не вписываются строго ни в одну из существующих категорий", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "За этой категорией можно следить из открытой социальной сети, используя идентификатор %1", "new-topic-button": "Создать тему", "guest-login-post": "Авторизуйтесь, чтобы написать сообщение", "no-topics": "В этой категории еще нет тем.
Почему бы вам не создать первую?", + "no-followers": "Никто на этом сайте не отслеживает эту категорию. Начните отслеживать или наблюдать за этой категорией, чтобы получать обновления.", "browsing": "просматривают", "no-replies": "Нет ответов", "no-new-posts": "Нет новых сообщений", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index a2fc12354b..a0305375e5 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -3,6 +3,7 @@ "invalid-json": "Некорректный JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Обязательные параметры отсутствуют в API запросе: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Вы не вошли на сайт.", "account-locked": "Учётная запись временно заблокирована", "search-requires-login": "Поиск доступен только для зарегистрированных участников. Пожалуйста, войдите или зарегистрируйтесь.", @@ -38,7 +39,7 @@ "email-not-confirmed": "Вы не сможете отправлять сообщения, пока ваш адрес электронной почты не подтверждён. Пожалуйста, нажмите здесь, чтобы подтвердить его.", "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 may not be able to post in some categories or chat until your email is confirmed.", - "no-email-to-confirm": "Your account does not have an email set. An email is necessary for account recovery, and may be necessary for chatting and posting in some categories. Please click here to enter an email.", + "no-email-to-confirm": "В вашей учётной записи не указан адрес электронной почты. Он необходим для восстановления доступа, а также может потребоваться для общения и публикации в некоторых разделах. Пожалуйста, нажмите здесь, чтобы указать адрес электронной почты.", "user-doesnt-have-email": "У пользователя %1 не задана электронная почта.", "email-confirm-failed": "По техническим причинам мы не можем подтвердить ваш адрес электронной почты. Приносим вам наши извинения, пожалуйста, попробуйте позже.", "confirm-email-already-sent": "Сообщение для подтверждения регистрации уже выслано на ваш адрес электронной почты. Повторная отправка возможна через %1 мин.", @@ -146,6 +147,7 @@ "post-already-restored": "Это сообщение уже восстановлено", "topic-already-deleted": "Тема уже удалена", "topic-already-restored": "Тема уже восстановлена", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Вы не можете стереть первое сообщение в теме. Пожалуйста, удалите саму тему.", "topic-thumbnails-are-disabled": "Иконки тем отключены.", "invalid-file": "Некорректный файл", @@ -154,6 +156,8 @@ "about-me-too-long": "Пожалуйста, постарайтесь уложиться в поле \"О себе\" в %1 символов.", "cant-chat-with-yourself": "Вы не можете создать чат с самим собой!", "chat-restricted": "Пользователь ограничил приём сообщений. Чтобы написать ему личное сообщение, необходимо, чтобы он был подписан на вас.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Чат выключен", "too-many-messages": "Вы отправили слишком много сообщений, подождите немного.", @@ -225,6 +229,7 @@ "no-topics-selected": "Темы не выбраны!", "cant-move-to-same-topic": "Невозможно переместить сообщение в эту же тему!", "cant-move-topic-to-same-category": "Невозможно переместить тему в эту же категорию!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Вы не можете заблокировать себя!", "cannot-block-privileged": "Вы не можете заблокировать администраторов или глобальных модераторов", "cannot-block-guest": "Гости не могут блокировать пользователей", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "В настоящее время невозможно связаться с сервером. Нажмите здесь, чтобы повторить попытку, или сделайте это позднее", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Не удалось установить плагин – только плагины, внесенные в белый список диспетчером пакетов NodeBB, могут быть установлены через ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "У вас нет прав доступа, чтобы сделать этот запрос", "api.404": "Неверный API запрос", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "Вы сделали слишком много запросов, пожалуйста, повторите попытку позже.", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/ru/global.json b/public/language/ru/global.json index bccef6c5b4..da1bcc699c 100644 --- a/public/language/ru/global.json +++ b/public/language/ru/global.json @@ -24,14 +24,14 @@ "cancel": "Cancel", "close": "Закрыть", "pagination": "Разбивка на страницы", - "pagination.previouspage": "Previous Page", - "pagination.nextpage": "Next Page", - "pagination.firstpage": "First Page", - "pagination.lastpage": "Last Page", + "pagination.previouspage": "Предыдущая страница", + "pagination.nextpage": "Следующая страница", + "pagination.firstpage": "Первая страница", + "pagination.lastpage": "Последняя страница", "pagination.out-of": "%1 из %2", "pagination.enter-index": "Go to post index", "pagination.go-to-page": "Go to page", - "pagination.page-x": "Page %1", + "pagination.page-x": "Страница %1", "header.brand-logo": "Brand Logo", "header.admin": "Админка", "header.categories": "Категории", @@ -68,6 +68,7 @@ "users": "Пользователи", "topics": "Темы", "posts": "Сообщения", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Не понравилось", "views": "Просмотры", "posters": "Posters", + "watching": "Watching", "reputation": "Репутация", "lastpost": "Последнее сообщение", "firstpost": "Первое сообщение", diff --git a/public/language/ru/groups.json b/public/language/ru/groups.json index e0823ee2ea..cdd1df4485 100644 --- a/public/language/ru/groups.json +++ b/public/language/ru/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Группы", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Просмотр группы", "owner": "Администратор группы", "new-group": "Создать группу", diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index d59776b42d..01fe151d46 100644 --- a/public/language/ru/modules.json +++ b/public/language/ru/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Добавить пользователя", "chat.notification-settings": "Настройки уведомлений", "chat.default-notification-setting": "Настройка уведомлений по умолчанию", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Комната по умолчанию", "chat.notification-setting-none": "Нет уведомлений", "chat.notification-setting-at-mention-only": "только @упоминание", @@ -81,7 +82,7 @@ "composer.hide-preview": "Скрыть предпросмотр", "composer.help": "Помощь", "composer.user-said-in": "Пользователь %1 написал в %2:", - "composer.user-said": "Пользователь %1 написал:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Вы уверены, что передумали писать это сообщение?", "composer.submit-and-lock": "Отправить и закрыть", "composer.toggle-dropdown": "Показать выпадающий список", diff --git a/public/language/ru/notifications.json b/public/language/ru/notifications.json index 0cb9c2d9c8..a4e2db9c7d 100644 --- a/public/language/ru/notifications.json +++ b/public/language/ru/notifications.json @@ -22,7 +22,7 @@ "upvote": "Голоса", "awards": "Awards", "new-flags": "Новые жалобы", - "my-flags": "Назначенные мне жалобы", + "my-flags": "My Flags", "bans": "Блокировки", "new-message-from": "Новое сообщение от %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "Пользователь %1 проголосовал за ваше сообщение в %2.", - "upvoted-your-post-in-dual": "Пользователи %1 и %2 проголосовали за ваше сообщение в %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "Пользователь %1 пожаловался на профиль пользователя (%2)", "user-flagged-user-dual": "Пользователи %1 и %2 пожаловались на профиль пользователя (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "Пользователь %1 ответил на сообщение в %2", - "user-posted-to-dual": "Пользователи %1 и %2 ответили на сообщение в %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "Пользователь %1 создал новую тему: %2", - "user-edited-post": " %1 отредактировал сообщение в %2 ", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "Пользователь %1 подписался на вас.", "user-started-following-you-dual": "Пользователи %1 и %2 подписались на вас.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "CSV пользователей экспортирован, нажмите, чтобы загрузить", "post-queue-accepted": "Ваше сообщение из очереди было принято. Нажмите здесь, чтобы увидеть ваше сообщение.", "post-queue-rejected": "Ваше сообщение из очереди было отклонено.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Электронная почта подтверждена", "email-confirmed-message": "Спасибо за подтверждение адреса электронной почты. Ваша учётная запись активирована.", "email-confirm-error-message": "Ошибка проверки адреса электронной почты. Возможно, код подтверждения введён неправильно или у него истёк срок действия.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Письмо с проверочным кодом отправлено на ваш электронный адрес", "none": "Ничего", "notification-only": "Только уведомление", diff --git a/public/language/ru/social.json b/public/language/ru/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/ru/social.json +++ b/public/language/ru/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/ru/themes/harmony.json b/public/language/ru/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/ru/themes/harmony.json +++ b/public/language/ru/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index 86c8306ecf..f42021f6c4 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -61,14 +61,16 @@ "user-restored-topic-on": "%1 восстановил эту тему в %2", "user-moved-topic-from-ago": "%1 переместил эту тему из %2 %3", "user-moved-topic-from-on": "%1 переместил эту тему из %2 в %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "user-shared-topic-ago": "%1 поделился этой темой %2", + "user-shared-topic-on": "%1 поделился этой темой на %2", "user-queued-post-ago": "%1 добавил запись для одобрения %3", "user-queued-post-on": "%1 добавил запись для одобрения в %3", "user-referenced-topic-ago": "%1 сослался на эту тему %3", "user-referenced-topic-on": "%1 сослался на эту тему в %3", "user-forked-topic-ago": "%1 раздвоил эту тему %3", "user-forked-topic-on": "%1 раздвоил эту тему в %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Нажмите здесь, чтобы вернуться к последнему прочитанному сообщению в этой теме.", "flag-post": "Пожаловаться на это сообщение", "flag-user": "Пожаловаться на этого пользователя", @@ -103,6 +105,7 @@ "thread-tools.lock": "Закрыть тему", "thread-tools.unlock": "Открыть тему", "thread-tools.move": "Перенести тему", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Перенести сообщения", "thread-tools.move-all": "Перенести всё", "thread-tools.change-owner": "Сменить автора", @@ -132,6 +135,7 @@ "pin-modal-help": "При желании вы можете установить дату истечения срока для закрепленных тем здесь. Кроме того, вы можете оставить это поле пустым, чтобы тема оставалась закрепленной до тех пор, пока она не будет откреплена вручную.", "load-categories": "Загружаем категории", "confirm-move": "Перенести", + "confirm-crosspost": "Cross-post", "confirm-fork": "Разделить", "bookmark": "Добавить в закладки", "bookmarks": "Закладки", @@ -141,6 +145,7 @@ "loading-more-posts": "Загружаем больше сообщений", "move-topic": "Перенести тему", "move-topics": "Перенести темы", + "crosspost-topic": "Cross-post Topic", "move-post": "Перенести сообщение", "post-moved": "Сообщение перенесено!", "fork-topic": "Создать дополнительную ветвь дискуссии", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Нажмите на сообщения, которые вы хотите присвоить другому пользователю", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Введите название темы...", "composer.handle-placeholder": "Введите ваше имя здесь", "composer.hide": "Скрыть", @@ -174,6 +182,7 @@ "composer.replying-to": "Ответ %1", "composer.new-topic": "Создать тему", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "загрузка...", "composer.thumb-url-label": "Вставьте ссылку на картинку с иконкой темы.", "composer.thumb-title": "Добавить иконку к этой теме", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 9fb1d0fc28..2942cb492a 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -105,6 +105,10 @@ "show-email": "Показывать мою электронную почту", "show-fullname": "Показывать моё полное имя", "restrict-chats": "Разрешить чат только с теми, на кого я подписан", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Добавить участника", "digest-label": "Подписка на дайджест", "digest-description": "Подписаться на рассылку уведомлений о событиях и новых темах на форуме с указанной периодичностью", "digest-off": "Отключена", diff --git a/public/language/ru/world.json b/public/language/ru/world.json index 3753335278..6a99c712a2 100644 --- a/public/language/ru/world.json +++ b/public/language/ru/world.json @@ -1,18 +1,25 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Мир", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "Помощь", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", - "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.title": "Что это за страница?", + "help.intro": "Добро пожаловать в ваш уголок федиверса.", + "help.fediverse": "«Fediverse» — это сеть взаимосвязанных приложений и веб-сайтов, которые общаются друг с другом, и пользователи которых могут видеть друг друга. Этот форум является федеративным и может взаимодействовать с этой социальной сетью (или «Fediverse»). Эта страница — ваш уголок Fediverse. Она состоит исключительно из тем, созданных и опубликованных пользователями, на которых вы подписаны.", + "help.build": "Здесь может быть не так много тем для начала; это нормально. Со временем вы начнете видеть здесь больше контента, когда начнете подписываться на других пользователей.", + "help.federating": "Аналогично, если пользователи за пределами этого форума начнут подписываться на вас, то ваши сообщения также начнут появляться в этих приложениях и на этих сайтах.", + "help.next-generation": "Это новое поколение социальных сетей, начните вносить свой вклад уже сегодня!", - "onboard.title": "Your window to the fediverse...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.title": "Ваше окно в мир fediverse...", + "onboard.what": "Это ваша персонализированная категория, состоящая только из контента, найденного за пределами этого форума. Появится ли что-то на этой странице, зависит от того, подписаны ли вы на них, или же этот пост был опубликован кем-то, на кого вы подписаны.", + "onboard.why": "За пределами этого форума происходит много всего, и не все из этого соответствует вашим интересам. Вот почему подписка на людей — лучший способ подать сигнал о том, что вы хотите видеть больше от кого-то.", + "onboard.how": "А пока вы можете нажать на кнопки быстрого доступа вверху, чтобы узнать, что еще известно на этом форуме, и начать открывать для себя новый контент!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/rw/admin/advanced/cache.json b/public/language/rw/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/rw/admin/advanced/cache.json +++ b/public/language/rw/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/rw/admin/dashboard.json b/public/language/rw/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/rw/admin/dashboard.json +++ b/public/language/rw/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/rw/admin/development/info.json b/public/language/rw/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/rw/admin/development/info.json +++ b/public/language/rw/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/rw/admin/manage/categories.json b/public/language/rw/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/rw/admin/manage/categories.json +++ b/public/language/rw/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/rw/admin/manage/custom-reasons.json b/public/language/rw/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/rw/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/rw/admin/manage/privileges.json b/public/language/rw/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/rw/admin/manage/privileges.json +++ b/public/language/rw/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/rw/admin/manage/users.json b/public/language/rw/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/rw/admin/manage/users.json +++ b/public/language/rw/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/rw/admin/settings/activitypub.json b/public/language/rw/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/rw/admin/settings/activitypub.json +++ b/public/language/rw/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/rw/admin/settings/chat.json b/public/language/rw/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/rw/admin/settings/chat.json +++ b/public/language/rw/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/rw/admin/settings/email.json b/public/language/rw/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/rw/admin/settings/email.json +++ b/public/language/rw/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/rw/admin/settings/notifications.json b/public/language/rw/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/rw/admin/settings/notifications.json +++ b/public/language/rw/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/rw/admin/settings/uploads.json b/public/language/rw/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/rw/admin/settings/uploads.json +++ b/public/language/rw/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/rw/admin/settings/user.json b/public/language/rw/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/rw/admin/settings/user.json +++ b/public/language/rw/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/rw/admin/settings/web-crawler.json b/public/language/rw/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/rw/admin/settings/web-crawler.json +++ b/public/language/rw/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/rw/aria.json b/public/language/rw/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/rw/aria.json +++ b/public/language/rw/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/rw/category.json b/public/language/rw/category.json index d2a6578395..6114d829cd 100644 --- a/public/language/rw/category.json +++ b/public/language/rw/category.json @@ -1,12 +1,13 @@ { "category": "Icyiciro", "subcategories": "Icyiciro gito", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Ikiganiro Gishya", "guest-login-post": "Injiramo wandike", "no-topics": "Nta biganiro byo muri iki cyiciro bihari
Watangije kimwe hano se?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "abari kureba", "no-replies": "Nta muntu urasubiza", "no-new-posts": "Nta bishya.", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 82c8d12330..da3d340c51 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Biragaragara ko utinjiyemo.", "account-locked": "Konte yawe yabaye ifunze", "search-requires-login": "Gushaka ikintu bisaba kuba ufite konte - Injiramo cyangwa wiyandike.", @@ -146,6 +147,7 @@ "post-already-restored": "Ibi byari byaragaruwe", "topic-already-deleted": "Iki kiganiro cyari cyarakuweho", "topic-already-restored": "Iki kiganiro cyari cyaragaruwe", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Ntabwo ushobora gusibanganya icyashyizweho kandi ibindi bigishamikiyeho. Ahubwo wakuraho ikiganiro cyose", "topic-thumbnails-are-disabled": "Ishushondanga ntiyemerewe.", "invalid-file": "Ifayilo Ntiyemewe", @@ -154,6 +156,8 @@ "about-me-too-long": "Inshamake y'Ubuzima yawe ntiyemerewe kurenza inyuguti (cyangwa ibimenyetso) %1.", "cant-chat-with-yourself": "Ntabwo wakwiganiriza!", "chat-restricted": "Uyu muntu yemerera kuganirira mu gikari n'abantu bamwe na bamwe. Agomba kuba yarahisemo kugukurikira kugirango ube wabasha kumuganiriza uciye mu gikari.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "Wohereje ubutumwa bwinshi cyane. Ba utegerejeho gato.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/rw/global.json b/public/language/rw/global.json index 64c0f6a3c0..984a827520 100644 --- a/public/language/rw/global.json +++ b/public/language/rw/global.json @@ -68,6 +68,7 @@ "users": "Abantu", "topics": "Ibiganiro", "posts": "Ibyashyizweho", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Byagawe", "views": "Byarebwe", "posters": "Posters", + "watching": "Watching", "reputation": "Amanota", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/rw/groups.json b/public/language/rw/groups.json index 94dfbb195e..b700bb6a8a 100644 --- a/public/language/rw/groups.json +++ b/public/language/rw/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Amatsinda", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Reba Itsinda", "owner": "Nyir'Itsinda", "new-group": "Tangiza Itsinda Rishya", diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json index 340d16cac4..a556e5a92b 100644 --- a/public/language/rw/modules.json +++ b/public/language/rw/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hisha Uko Biza Gusa", "composer.help": "Help", "composer.user-said-in": "%1 yavuze muri %2:", - "composer.user-said": "%1 yavuze:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Wiringiye neza ko ushaka kureka kubishyiraho?", "composer.submit-and-lock": "Shyiraho kandi Unafungirane", "composer.toggle-dropdown": "Hindura Icyerekezo", diff --git a/public/language/rw/notifications.json b/public/language/rw/notifications.json index 0afc5da049..5625d74e5a 100644 --- a/public/language/rw/notifications.json +++ b/public/language/rw/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "%1 yakwandikiye", "new-messages-from": "%1 new messages from %2", @@ -32,13 +32,13 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 yagushimye aguha inota kuri %2 washyizeho.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", - "user-flagged-post-in": "%1 yatambikanye ikintu muri %2", + "user-flagged-post-in": "%1 flagged a post in %2", "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 yanditse kuri: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 yatangije ikiganiro gishya: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 yatangiye kugukurikira.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Yemejwe", "email-confirmed-message": "Urakoze kugaragaza ko email yawe ikora. Ubu ngubu konte yawe irakora nta kabuza.", "email-confirm-error-message": "Havutse ikibazo mu gushaka kumenya niba email yawe ikora. Ushobora kuba wakoresheje kode itari yo cyangwa se yarengeje igihe.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Hoherejwe email yo kubyemeza.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/rw/social.json b/public/language/rw/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/rw/social.json +++ b/public/language/rw/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/rw/themes/harmony.json b/public/language/rw/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/rw/themes/harmony.json +++ b/public/language/rw/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json index ffc4c4084f..b640897aca 100644 --- a/public/language/rw/topic.json +++ b/public/language/rw/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Fungirana Ikiganiro", "thread-tools.unlock": "Fungurira Ikiganiro", "thread-tools.move": "Imura Ikiganiro", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Byimure Byose", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Ibyiciro Biraje", "confirm-move": "Imura", + "confirm-crosspost": "Cross-post", "confirm-fork": "Gabanyaho", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Ibindi Biraje", "move-topic": "Imura Ikiganiro", "move-topics": "Imura Ibiganiro", + "crosspost-topic": "Cross-post Topic", "move-post": "Imura Icyashyizweho", "post-moved": "Icyashizweho kirimuwe!", "fork-topic": "Gabanyaho ku Kiganiro", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Shyira umutwe w'ikiganiro cyawe aha...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Gusubiza %1", "composer.new-topic": "Ikiganiro Gishya", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "gupakira...", "composer.thumb-url-label": "Omekaho thumbnail URL y'ikiganiro", "composer.thumb-title": "Ongera agafotondanga kuri iki kiganiro", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/rw/user.json b/public/language/rw/user.json index 336c3ab8e9..7cb67393e2 100644 --- a/public/language/rw/user.json +++ b/public/language/rw/user.json @@ -105,6 +105,10 @@ "show-email": "Hagaragazwe Email Yanjye", "show-fullname": "Hagaragazwe Izina Ryuzuye Ryanjye", "restrict-chats": "Emerera ubutumwa buciye mu gikari abantu ukurikira gusa", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Iyandikishe ku Ngingo z'Ingenzi", "digest-description": "Iyandikishe ku makuru aciye kuri email ajyanye n'ibivugirwa aha (amatangazo mashya n'ibiganiro) biciye muri gahunda yagenwe", "digest-off": "Birafunze", diff --git a/public/language/rw/world.json b/public/language/rw/world.json index 3753335278..e6694bf507 100644 --- a/public/language/rw/world.json +++ b/public/language/rw/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/sc/admin/advanced/cache.json b/public/language/sc/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/sc/admin/advanced/cache.json +++ b/public/language/sc/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/sc/admin/dashboard.json b/public/language/sc/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/sc/admin/dashboard.json +++ b/public/language/sc/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/sc/admin/development/info.json b/public/language/sc/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/sc/admin/development/info.json +++ b/public/language/sc/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/sc/admin/manage/categories.json b/public/language/sc/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/sc/admin/manage/categories.json +++ b/public/language/sc/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/sc/admin/manage/custom-reasons.json b/public/language/sc/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/sc/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/sc/admin/manage/privileges.json b/public/language/sc/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/sc/admin/manage/privileges.json +++ b/public/language/sc/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/sc/admin/manage/users.json b/public/language/sc/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/sc/admin/manage/users.json +++ b/public/language/sc/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/sc/admin/settings/activitypub.json b/public/language/sc/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/sc/admin/settings/activitypub.json +++ b/public/language/sc/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/sc/admin/settings/chat.json b/public/language/sc/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/sc/admin/settings/chat.json +++ b/public/language/sc/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/sc/admin/settings/email.json b/public/language/sc/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/sc/admin/settings/email.json +++ b/public/language/sc/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/sc/admin/settings/notifications.json b/public/language/sc/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/sc/admin/settings/notifications.json +++ b/public/language/sc/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/sc/admin/settings/uploads.json b/public/language/sc/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/sc/admin/settings/uploads.json +++ b/public/language/sc/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/sc/admin/settings/user.json b/public/language/sc/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/sc/admin/settings/user.json +++ b/public/language/sc/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/sc/admin/settings/web-crawler.json b/public/language/sc/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/sc/admin/settings/web-crawler.json +++ b/public/language/sc/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/sc/aria.json b/public/language/sc/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/sc/aria.json +++ b/public/language/sc/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/sc/category.json b/public/language/sc/category.json index aa2e9bfb60..feb35e197e 100644 --- a/public/language/sc/category.json +++ b/public/language/sc/category.json @@ -1,12 +1,13 @@ { "category": "Category", "subcategories": "Subcategories", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Arresonada Noa", "guest-login-post": "Log in to post", "no-topics": "Non bi sunt arresonadas in custa creze.
Pro ite non nde pones una?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "navighende", "no-replies": "Perunu at rispostu", "no-new-posts": "No new posts.", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 7f924fc7ac..0ec4d83c98 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "You don't seem to be logged in.", "account-locked": "Your account has been locked temporarily", "search-requires-login": "Searching requires an account - please login or register.", @@ -146,6 +147,7 @@ "post-already-restored": "This post has already been restored", "topic-already-deleted": "This topic has already been deleted", "topic-already-restored": "This topic has already been restored", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", "topic-thumbnails-are-disabled": "Topic thumbnails are disabled.", "invalid-file": "Invalid File", @@ -154,6 +156,8 @@ "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", "cant-chat-with-yourself": "You can't chat with yourself!", "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/sc/global.json b/public/language/sc/global.json index d2475a0675..3e3b784808 100644 --- a/public/language/sc/global.json +++ b/public/language/sc/global.json @@ -68,6 +68,7 @@ "users": "Users", "topics": "Topics", "posts": "Arresonos", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Downvoted", "views": "Bìsitas", "posters": "Posters", + "watching": "Watching", "reputation": "Reputation", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/sc/groups.json b/public/language/sc/groups.json index 25fe9c75e6..4bf47bdd80 100644 --- a/public/language/sc/groups.json +++ b/public/language/sc/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Groups", "members": "Members", + "x-members": "%1 member(s)", "view-group": "View Group", "owner": "Group Owner", "new-group": "Create New Group", diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json index 7145e11029..3b9b012d16 100644 --- a/public/language/sc/modules.json +++ b/public/language/sc/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Hide Preview", "composer.help": "Help", "composer.user-said-in": "%1 said in %2:", - "composer.user-said": "%1 said:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Are you sure you wish to discard this post?", "composer.submit-and-lock": "Submit and Lock", "composer.toggle-dropdown": "Toggle Dropdown", diff --git a/public/language/sc/notifications.json b/public/language/sc/notifications.json index 90f6e9f3d9..84c1370919 100644 --- a/public/language/sc/notifications.json +++ b/public/language/sc/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "New message from %1", "new-messages-from": "%1 new messages from %2", @@ -32,10 +32,10 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 has upvoted your post in %2.", - "upvoted-your-post-in-dual": "%1 and %2 have upvoted your post in %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 has moved your post to %2", "moved-your-topic": "%1 has moved %2", "user-flagged-post-in": "%1 flagged a post in %2", @@ -46,17 +46,17 @@ "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 has posted a reply to: %2", - "user-posted-to-dual": "%1 and %2 have posted replies to: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 has posted a new topic: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 started following you.", "user-started-following-you-dual": "%1 and %2 started following you.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Confirmation email sent.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/sc/social.json b/public/language/sc/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/sc/social.json +++ b/public/language/sc/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/sc/themes/harmony.json b/public/language/sc/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/sc/themes/harmony.json +++ b/public/language/sc/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json index 36b50ac704..28223106ff 100644 --- a/public/language/sc/topic.json +++ b/public/language/sc/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Click here to return to the last read post in this thread.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Bloca Arresonada", "thread-tools.unlock": "Isbloca Arresonada", "thread-tools.move": "Move Arresonada", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Move All", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Carrighende Crezes", "confirm-move": "Move", + "confirm-crosspost": "Cross-post", "confirm-fork": "Partzi", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +145,7 @@ "loading-more-posts": "Càrriga Prus Arresonos", "move-topic": "Move Arresonada", "move-topics": "Move Topics", + "crosspost-topic": "Cross-post Topic", "move-post": "Move Arresonu", "post-moved": "Post moved!", "fork-topic": "Partzi Arresonada", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Pone su tìtulu de s'arresonada inoghe...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Replying to %1", "composer.new-topic": "Arresonada Noa", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "carrighende...", "composer.thumb-url-label": "Apodda unu URL cun un'immàgine pro s'arresonada", "composer.thumb-title": "Annanghe un'immàgine pitica a custa arresonada", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/sc/user.json b/public/language/sc/user.json index b750d4dcb9..d10f0ae737 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -105,6 +105,10 @@ "show-email": "Ammustra s'Email Mia", "show-fullname": "Show My Full Name", "restrict-chats": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Subscribe to Digest", "digest-description": "Subscribe to email updates for this forum (new notifications and topics) according to a set schedule", "digest-off": "Off", diff --git a/public/language/sc/world.json b/public/language/sc/world.json index 3753335278..e6694bf507 100644 --- a/public/language/sc/world.json +++ b/public/language/sc/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/sk/admin/advanced/cache.json b/public/language/sk/admin/advanced/cache.json index 9f34b81dec..8de9f47724 100644 --- a/public/language/sk/admin/advanced/cache.json +++ b/public/language/sk/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Vyrovnávacia pamäť príspevku", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% plné", "post-cache-size": "Veľkosť vyrovnávacej pamäti príspevku", "items-in-cache": "Položky vo vyrovnávacej pamäti" diff --git a/public/language/sk/admin/dashboard.json b/public/language/sk/admin/dashboard.json index e979c81301..55c1d314cc 100644 --- a/public/language/sk/admin/dashboard.json +++ b/public/language/sk/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unikátny navštevníci", "graphs.registered-users": "Zarestrovaný užívatelia", "graphs.guest-users": "Guest Users", diff --git a/public/language/sk/admin/development/info.json b/public/language/sk/admin/development/info.json index c6dbfd349e..93705d30c8 100644 --- a/public/language/sk/admin/development/info.json +++ b/public/language/sk/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "pripojený", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/sk/admin/manage/categories.json b/public/language/sk/admin/manage/categories.json index f6768fe299..e3ecddda6c 100644 --- a/public/language/sk/admin/manage/categories.json +++ b/public/language/sk/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Nastavenia kategórie", "edit-category": "Edit Category", "privileges": "Oprávnenia", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Názov kategórie", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Popis kategórie", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Farba pozadia", "text-color": "Farba textu", "bg-image-size": "Veľkosť obrázku na pozadí", @@ -103,6 +107,11 @@ "alert.create-success": "Kategória bola úspešne vytvorená.", "alert.none-active": "Nemáte žiadne aktívne kategórie.", "alert.create": "Vytvoriť kategóriu", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Naozaj chcete vyčistiť túto kategóriu „%1“?

Upozornenie! Všetky témy a príspevky v tejto kategórií budu odstránené!

Vyčistenie kategórií odstráni všetky témy a príspevky a odstráni kategórie z databázy. Pokiaľ chcete vyčistiť kategórie dočasne. radšej namiesto toho kategóriu „zakážte“.

", "alert.purge-success": "Kategória bola vyčistená!", "alert.copy-success": "Nastavenia boli skopírované!", diff --git a/public/language/sk/admin/manage/custom-reasons.json b/public/language/sk/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/sk/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/sk/admin/manage/privileges.json b/public/language/sk/admin/manage/privileges.json index 59f3265420..7854154960 100644 --- a/public/language/sk/admin/manage/privileges.json +++ b/public/language/sk/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Prístup k témam", "create-topics": "Vytvoriť témy", "reply-to-topics": "Odpovedať na témy", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Značka tém", "edit-posts": "Upraviť príspevky", diff --git a/public/language/sk/admin/manage/users.json b/public/language/sk/admin/manage/users.json index 227f71e1f6..a204a6f1d5 100644 --- a/public/language/sk/admin/manage/users.json +++ b/public/language/sk/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Stiahnuť ako CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Dôvod (voliteľné)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Hodiny", "temp-ban.days": "Dni", "temp-ban.explanation": "Zadajte dĺžku trvania pre zákaz. Nezabudnite, že 0 je považovaná ako trvalý zákaz.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Naozaj chcete trvalo zablokovať tohto používateľa?", "alerts.confirm-ban-multi": "Naozaj chcete trvalo zablokovať týchto používateľov? ", diff --git a/public/language/sk/admin/settings/activitypub.json b/public/language/sk/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/sk/admin/settings/activitypub.json +++ b/public/language/sk/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/sk/admin/settings/chat.json b/public/language/sk/admin/settings/chat.json index 022fe1e7b1..5da12c340a 100644 --- a/public/language/sk/admin/settings/chat.json +++ b/public/language/sk/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximálny počet používateľov v konverzačnej miestnosti", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/sk/admin/settings/email.json b/public/language/sk/admin/settings/email.json index e53bfefaf9..69ba7d358a 100644 --- a/public/language/sk/admin/settings/email.json +++ b/public/language/sk/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Upraviť šablónu e-mailu", "template.select": "Vybrať šablónu e-mailu", "template.revert": "Späť k pôvodnému", + "test-smtp-settings": "Test SMTP Settings", "testing": "Skúška e-mailu", + "testing.success": "Test Email Sent.", "testing.select": "Vyberte šablónu e-mailu", "testing.send": "Odoslať skúšobný e-mail", - "testing.send-help": "Skúšobný e-mail bude odoslaný aktuálne prihlásenému používateľovi na jeho e-mailovú adresu z registrácie.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/sk/admin/settings/notifications.json b/public/language/sk/admin/settings/notifications.json index a873f10b02..caf138fb58 100644 --- a/public/language/sk/admin/settings/notifications.json +++ b/public/language/sk/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Uvítacie oznámenie", "welcome-notification-link": "Odkaz na uvítanie", "welcome-notification-uid": "Uvítanie používateľa (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/sk/admin/settings/uploads.json b/public/language/sk/admin/settings/uploads.json index f9a18dcb4c..155b91d72d 100644 --- a/public/language/sk/admin/settings/uploads.json +++ b/public/language/sk/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Povoliť používateľom nahrať miniatúry tém", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Veľkosť miniatúry témy", "allowed-file-extensions": "Predvolené prípony súborov", "allowed-file-extensions-help": "Zadajte zoznam prípon súborov oddelených čiarkou (napr.: pdf, xls, doc). Prázdny zoznam znamená, že všetky prípony sú povolené.", diff --git a/public/language/sk/admin/settings/user.json b/public/language/sk/admin/settings/user.json index 550a32f101..8a312eb89d 100644 --- a/public/language/sk/admin/settings/user.json +++ b/public/language/sk/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Zobraziť e-mail", "show-fullname": "Zobraziť celé meno", "restrict-chat": "Povoliť správy konverzácie iba od používateľov, ktorých sledujem", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Otvoriť odchádzajúce odkazy v novom liste", "topic-search": "Povoliť vyhľadávanie v témach", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/sk/admin/settings/web-crawler.json b/public/language/sk/admin/settings/web-crawler.json index 3308016107..74a0df8fa4 100644 --- a/public/language/sk/admin/settings/web-crawler.json +++ b/public/language/sk/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Zakázať zdroje RSS", "disable-sitemap-xml": "Zakázať Sitemap.xml", "sitemap-topics": "Počet tém zobrazených na mape stránky", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Zmazať vyrovnávaciu pamäť mapy stránky", "view-sitemap": "Zobraziť mapu stránky" } \ No newline at end of file diff --git a/public/language/sk/aria.json b/public/language/sk/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/sk/aria.json +++ b/public/language/sk/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/sk/category.json b/public/language/sk/category.json index fe29105bbe..7eb7ffa0ee 100644 --- a/public/language/sk/category.json +++ b/public/language/sk/category.json @@ -1,12 +1,13 @@ { "category": "Kategória", "subcategories": "Podkategórie", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nová téma", "guest-login-post": "Prihlásiť sa k pridávaniu príspevkov", "no-topics": "V tejto kategórií zatiaľ nie sú žiadne témy.
Môžete byť prvý!", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "prehliada", "no-replies": "Nikto ešte neodpovedal", "no-new-posts": "Žiadne nové príspevky.", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 813583383e..7bfc298607 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -3,6 +3,7 @@ "invalid-json": "Neplatné JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Zdá sa že nie ste prihlásený/á.", "account-locked": "Váš účet bol dočasne uzamknutý", "search-requires-login": "K vyhľadávaniu je vyžadovaný účet - prosím prihláste sa alebo zaregistrujte.", @@ -146,6 +147,7 @@ "post-already-restored": "Tento príspevok bol obnovený", "topic-already-deleted": "Táto téma bola odstránená", "topic-already-restored": "Táto téma bola obnovená", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Nemôžete očistiť hlavný príspevok, namiesto toho prosíme odstráňte tému", "topic-thumbnails-are-disabled": "Náhľady tém sú zablokované.", "invalid-file": "Neplatný súbor", @@ -154,6 +156,8 @@ "about-me-too-long": "Prepáčte, ale Vaše 'O mne' nemôže byť dlhšie ako %1 znaky(ov).", "cant-chat-with-yourself": "Nemôžete sa rozprávať so samým sebou!", "chat-restricted": "Tento používateľ obmedzil svoje správy v konverzácií. Musí Vás sledovať, aby ste sa s ním mohli rozprávať", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Systém konverzácií je zablokovaný", "too-many-messages": "Odoslali ste príliš veľa správ, počkajte chvíľu prosím.", @@ -225,6 +229,7 @@ "no-topics-selected": "Žiadne vybrané témy.", "cant-move-to-same-topic": "Nie je možné presunúť príspevok do rovnakej témy!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Nemôžete zablokovať seba samého!", "cannot-block-privileged": "Nemôžete zablokovať správcov alebo hlavných moderátorov", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/sk/global.json b/public/language/sk/global.json index 8c7b6b28b7..094838bf0b 100644 --- a/public/language/sk/global.json +++ b/public/language/sk/global.json @@ -68,6 +68,7 @@ "users": "Užívatelia", "topics": "Témy", "posts": "Príspevky", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Odobratý hlas", "views": "Zhliadnutí", "posters": "Posters", + "watching": "Watching", "reputation": "Reputácia", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/sk/groups.json b/public/language/sk/groups.json index 5e4b17fd8f..2e05b22709 100644 --- a/public/language/sk/groups.json +++ b/public/language/sk/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Skupiny", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Zobraziť skupinu", "owner": "Vlastník skupiny", "new-group": "Vytvoriť novú skupinu", diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json index 51c3df2339..3fc6e77367 100644 --- a/public/language/sk/modules.json +++ b/public/language/sk/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Skryť náhľad", "composer.help": "Help", "composer.user-said-in": "%1 povedal v %2:", - "composer.user-said": "%1 povedal:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Ste si istý, že chcete zahodiť tento príspevok?", "composer.submit-and-lock": "Vložiť a uzamknúť", "composer.toggle-dropdown": "Prepnúť rozbalovací zoznam", diff --git a/public/language/sk/notifications.json b/public/language/sk/notifications.json index 31587c4102..13d5f0adbf 100644 --- a/public/language/sk/notifications.json +++ b/public/language/sk/notifications.json @@ -22,7 +22,7 @@ "upvote": "Súhlasy", "awards": "Awards", "new-flags": "Nové označenia", - "my-flags": "Označenia priradené mne", + "my-flags": "My Flags", "bans": "Zablokované", "new-message-from": "Nova spáva od %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 dal hlas Vášmu príspevku v %2.", - "upvoted-your-post-in-dual": "%1 a %2 dali hlas Vášmu príspevku v %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 presunul Váš príspevok do %2", "moved-your-topic": "%1 presunul %2", - "user-flagged-post-in": "%1 pridal značku na príspevok %2", - "user-flagged-post-in-dual": "%1 a %2 pridali značky na príspevok %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 označil profil používateľa (%2)", "user-flagged-user-dual": "%1 a %2 označil profil používateľa (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 odpovedal: %2", - "user-posted-to-dual": "%1 a %2 uverejnili odpoveď na:%3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 pridal novú tému: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 Vás začal sledovať.", "user-started-following-you-dual": "%1 a %2 Vás začali sledovať.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-mail bol potvrdený", "email-confirmed-message": "Ďakujeme za potvrdenie Vášho e-mailu. Váš účet je teraz aktivovaný.", "email-confirm-error-message": "Vyskytla sa chyba pri overení Vašej e-mailovej adresy.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Potvrdzovací e-mail bol odoslaný.", "none": "Nič", "notification-only": "Iba oznámenia", diff --git a/public/language/sk/social.json b/public/language/sk/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/sk/social.json +++ b/public/language/sk/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/sk/themes/harmony.json b/public/language/sk/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/sk/themes/harmony.json +++ b/public/language/sk/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json index 1f20e2e431..da85522a3b 100644 --- a/public/language/sk/topic.json +++ b/public/language/sk/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Kliknite sem pre návrat k poslednému prečítanému príspevku vo vlákne.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Uzamknúť tému", "thread-tools.unlock": "Odomknúť tému", "thread-tools.move": "Presunúť tému", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Presunúť príspevky", "thread-tools.move-all": "Presunúť všetko", "thread-tools.change-owner": "Change Owner", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Načítanie kategórií", "confirm-move": "Presunúť", + "confirm-crosspost": "Cross-post", "confirm-fork": "Rozdeliť", "bookmark": "Záložka", "bookmarks": "Záložky", @@ -141,6 +145,7 @@ "loading-more-posts": "Načítavanie ďalších príspevkov", "move-topic": "Presunúť tému", "move-topics": "Presunúť témy", + "crosspost-topic": "Cross-post Topic", "move-post": "Presunúť príspevok", "post-moved": "Príspevok presunutý!", "fork-topic": "Rozdeliť príspevok", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Sem zadajte názov témy...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Odpovedať na %1", "composer.new-topic": "Nová téma", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "nahrávanie...", "composer.thumb-url-label": "Prilep URL náhľadu témy", "composer.thumb-title": "Pridaj náhľad tejto Témy", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/sk/user.json b/public/language/sk/user.json index 4dedd4812c..55365a0639 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -105,6 +105,10 @@ "show-email": "Zobrazovať môj e-mail", "show-fullname": "Zobrazovať moje skutočné meno", "restrict-chats": "Prijímať správy s konverzácií iba od užívateľov ktorých sledujete", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Prihláste sa na odber", "digest-description": "Prihláste sa k e-mailovým novinkám tohto fóra (nové oznámenia a témy) podľa nastavení v rozvrhu.", "digest-off": "Vypnuté", diff --git a/public/language/sk/world.json b/public/language/sk/world.json index 3753335278..e6694bf507 100644 --- a/public/language/sk/world.json +++ b/public/language/sk/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/sl/admin/advanced/cache.json b/public/language/sl/admin/advanced/cache.json index 91c42ab7c1..3a601e2817 100644 --- a/public/language/sl/admin/advanced/cache.json +++ b/public/language/sl/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Predpomnilnik objav", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1%Zasedeno", "post-cache-size": "Velikost predpomnilnika objav", "items-in-cache": "Elementi v predpomnilniku" diff --git a/public/language/sl/admin/dashboard.json b/public/language/sl/admin/dashboard.json index 7d36506d8f..5504608a86 100644 --- a/public/language/sl/admin/dashboard.json +++ b/public/language/sl/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Ogledov strani-registrirani", "graphs.page-views-guest": "Ogledov strani-gosti", "graphs.page-views-bot": "Ogledov strani-robot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Edinstveni obiskovalci", "graphs.registered-users": "Registrirani uporabniki", "graphs.guest-users": "Gostujoči uporabniki", diff --git a/public/language/sl/admin/development/info.json b/public/language/sl/admin/development/info.json index f53089170f..2fe1f6dde3 100644 --- a/public/language/sl/admin/development/info.json +++ b/public/language/sl/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "na spletu", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/sl/admin/manage/categories.json b/public/language/sl/admin/manage/categories.json index acd1f9a506..e74837dc71 100644 --- a/public/language/sl/admin/manage/categories.json +++ b/public/language/sl/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Nastavitve kategorije", "edit-category": "Edit Category", "privileges": "Privilegiji", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Ime kategorije", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Opis kategorije", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Barva ozadja", "text-color": "Barva besedila", "bg-image-size": "Velikost slike ozadja", @@ -103,6 +107,11 @@ "alert.create-success": "Kategorija je uspešno ustvarjena!", "alert.none-active": "Nimate aktivnih kategorij.", "alert.create": "Ustvari kategorijo", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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": "Kategorija je počiščena!", "alert.copy-success": "Nastavitve so kopirane!", diff --git a/public/language/sl/admin/manage/custom-reasons.json b/public/language/sl/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/sl/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/sl/admin/manage/privileges.json b/public/language/sl/admin/manage/privileges.json index 23edbca232..8278dd3581 100644 --- a/public/language/sl/admin/manage/privileges.json +++ b/public/language/sl/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Ustvari teme", "reply-to-topics": "Odgovori na teme", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Označi teme", "edit-posts": "Uredi objave", diff --git a/public/language/sl/admin/manage/users.json b/public/language/sl/admin/manage/users.json index 2ba6fe3a08..12f62419ef 100644 --- a/public/language/sl/admin/manage/users.json +++ b/public/language/sl/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Izbrišiteuporabnika(e) in vsebino", "download-csv": "Prenesite CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Upravljaj skupine", "set-reputation": "Set Reputation", "add-group": "Dodaj skupino", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Ur", "temp-ban.days": "Dni", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/sl/admin/settings/activitypub.json b/public/language/sl/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/sl/admin/settings/activitypub.json +++ b/public/language/sl/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/sl/admin/settings/chat.json b/public/language/sl/admin/settings/chat.json index 92503563ad..21e21b2022 100644 --- a/public/language/sl/admin/settings/chat.json +++ b/public/language/sl/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/sl/admin/settings/email.json b/public/language/sl/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/sl/admin/settings/email.json +++ b/public/language/sl/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/sl/admin/settings/notifications.json b/public/language/sl/admin/settings/notifications.json index 15cc81a25c..d153294a6b 100644 --- a/public/language/sl/admin/settings/notifications.json +++ b/public/language/sl/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Obvestilo o dobrodošlici", "welcome-notification-link": "Povezava do obvestila o dobrodošlici", "welcome-notification-uid": "Obvestilo o dobrodošlici za uporabnika (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/sl/admin/settings/uploads.json b/public/language/sl/admin/settings/uploads.json index fc43ca3793..8019ba6ddc 100644 --- a/public/language/sl/admin/settings/uploads.json +++ b/public/language/sl/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Dovoljene pripone datoteke", "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.", diff --git a/public/language/sl/admin/settings/user.json b/public/language/sl/admin/settings/user.json index ef5217d556..10dd21fa8f 100644 --- a/public/language/sl/admin/settings/user.json +++ b/public/language/sl/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Pokaži e-poštni naslov", "show-fullname": "Pokaži polno ime", "restrict-chat": "V klepetu dovoli samo sporočila uporabnikov, ki jih spremljam", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Zunanje povezave odpri na novem zavihku", "topic-search": "Omogoči iskanje v temi", "update-url-with-post-index": "Med brskanjem po temah posodobite URL z indeksom objav", diff --git a/public/language/sl/admin/settings/web-crawler.json b/public/language/sl/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/sl/admin/settings/web-crawler.json +++ b/public/language/sl/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/sl/aria.json b/public/language/sl/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/sl/aria.json +++ b/public/language/sl/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/sl/category.json b/public/language/sl/category.json index 392afdfff4..4bef4b3a25 100644 --- a/public/language/sl/category.json +++ b/public/language/sl/category.json @@ -1,12 +1,13 @@ { "category": "Od sedaj naprej spremljate posodobitve te kategorije in njenih podkategorij.\nOd sedaj naprej ne spremljate posodobitev te kategorije in njenih podkategorij.", "subcategories": "Podkategorije", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nova tema", "guest-login-post": "Prijava", "no-topics": "V tej kategoriji ni tem.", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "Brskanje", "no-replies": "Nihče ni odgovoril.", "no-new-posts": "Ni novih objav.", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index 3dd9adfefa..bf38e1ed81 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -3,6 +3,7 @@ "invalid-json": "Invalid JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Niste prijavljeni.", "account-locked": "Vaš račun je bil začasno zaklenjen.", "search-requires-login": "Iskanje zahteva uporabniški račun - prosimo, da se prijavite ali registrirate.", @@ -146,6 +147,7 @@ "post-already-restored": "Ta objava je že bila obnovljena.", "topic-already-deleted": "Ta tema je že bila izbrisana.", "topic-already-restored": "Ta tema je že bila obnovljena.", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Ne morete odstraniti prve objave, prosimo, izbrišite temo.", "topic-thumbnails-are-disabled": "Sličice teme so onemogočene.", "invalid-file": "Nedovoljena datoteka", @@ -154,6 +156,8 @@ "about-me-too-long": "Rubrika O meni je predolga. Največje število znakov: %1.", "cant-chat-with-yourself": "Ne morete klepetati s seboj!", "chat-restricted": "Uporabnik je omejil klepetanje. Za možnost klepeta vas mora uporabnik spremljati.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Klepet je onemogočen.", "too-many-messages": "Poslali ste preveč sporočil, prosimo, počakajte nekaj časa.", @@ -225,6 +229,7 @@ "no-topics-selected": "No topics selected!", "cant-move-to-same-topic": "Can't move post to same topic!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "You cannot block yourself!", "cannot-block-privileged": "You cannot block administrators or global moderators", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/sl/global.json b/public/language/sl/global.json index 218584b5d8..9f826221dc 100644 --- a/public/language/sl/global.json +++ b/public/language/sl/global.json @@ -68,6 +68,7 @@ "users": "Uporabniki", "topics": "Teme", "posts": "Objave", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Glasov proti", "views": "Ogledov", "posters": "Posters", + "watching": "Watching", "reputation": "Ugled", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/language/sl/groups.json b/public/language/sl/groups.json index 8f0f95c024..e088b32300 100644 --- a/public/language/sl/groups.json +++ b/public/language/sl/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Skupine", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Poglej skupino", "owner": "Lastnik skupine", "new-group": "Ustvari novo skupino", diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json index 5571ad4c35..90a194a598 100644 --- a/public/language/sl/modules.json +++ b/public/language/sl/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Skrij predogled", "composer.help": "Help", "composer.user-said-in": "%1 je napisal/-a v %2:", - "composer.user-said": "%1 je napisal/-a:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Ste prepričani, da želite zavreči to objavo?", "composer.submit-and-lock": "Pošlji in zakleni", "composer.toggle-dropdown": "Preklopi spustni meni", diff --git a/public/language/sl/notifications.json b/public/language/sl/notifications.json index a6c33aa49e..1d850a4498 100644 --- a/public/language/sl/notifications.json +++ b/public/language/sl/notifications.json @@ -22,7 +22,7 @@ "upvote": "Upvotes", "awards": "Awards", "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "my-flags": "My Flags", "bans": "Bans", "new-message-from": "Novo obvestilo od %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 je glasoval/-a za vašo objavo v %2.", - "upvoted-your-post-in-dual": "%1 in %2 sta glasovala/-i za vašo objavo v %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 je premaknil/-a vašo objavo v %2.", "moved-your-topic": "%1 je premaknil/-a %2.", - "user-flagged-post-in": "%1je označil/-a vašo objavo v %2.", - "user-flagged-post-in-dual": "%1 in %2 sta označila/-a vašo objavo v %3.", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flagged a user profile (%2)", "user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 je objavil/-a odgovor na: %2.", - "user-posted-to-dual": "%1 in %2 sta objavila/-i odgovor na: %3.", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 je odprl/-a novo temo: %2.", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 te je začel/-a spremljati.", "user-started-following-you-dual": "%1 in %2 sta te začela/-i spremljati.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-poštni naslov potrjen", "email-confirmed-message": "Hvala, da ste potrdili svoj e-naslov. Račun je sedaj aktiviran.", "email-confirm-error-message": "Prišlo je do napake pri preverjanju vašega e-poštnega naslova. Morda je bila koda napačna ali pa je potekla.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Potrditveno e-sporočilo je poslano.", "none": "None", "notification-only": "Notification Only", diff --git a/public/language/sl/social.json b/public/language/sl/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/sl/social.json +++ b/public/language/sl/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/sl/themes/harmony.json b/public/language/sl/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/sl/themes/harmony.json +++ b/public/language/sl/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/sl/topic.json b/public/language/sl/topic.json index 18861a85ce..4f6795b5b1 100644 --- a/public/language/sl/topic.json +++ b/public/language/sl/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klikni tukaj za vrnitev na zadnje prebrano objavo v tej niti", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Zakleni temo", "thread-tools.unlock": "Odkleni temo", "thread-tools.move": "Premakni temo", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Premakni objave", "thread-tools.move-all": "Premakni vse", "thread-tools.change-owner": "Spremeni lastnika", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Nalagam kategorije", "confirm-move": "Premakni", + "confirm-crosspost": "Cross-post", "confirm-fork": "Razcepi", "bookmark": "Zaznamek", "bookmarks": "Zaznamki", @@ -141,6 +145,7 @@ "loading-more-posts": "Nalagam več objav", "move-topic": "Premakni temo", "move-topics": "Premakni teme", + "crosspost-topic": "Cross-post Topic", "move-post": "Premakni objavo", "post-moved": "Objava premaknjena!", "fork-topic": "Razcepi temo", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Kliknite objave, ki jih želite dodeliti drugemu uporabniku", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Vpiši naslov teme...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Odgovor na %1", "composer.new-topic": "Nova tema", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "nalagam...", "composer.thumb-url-label": "Prilepite URL sličice teme", "composer.thumb-title": "Dodajte sličico tej temi", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/sl/user.json b/public/language/sl/user.json index 96000a2d85..bf6a1884be 100644 --- a/public/language/sl/user.json +++ b/public/language/sl/user.json @@ -105,6 +105,10 @@ "show-email": "Pokaži moj e-poštni naslov.", "show-fullname": "Pokaži moj ime in priimek.", "restrict-chats": "Dovoli klepet samo z osebami, ki jim sledim.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Prijavi se na izvleček", "digest-description": "Prijavi se na obveščanje preko e-pošte (nova obvestila ali teme) na podlagi naslednjega urnika", "digest-off": "Izključi", diff --git a/public/language/sl/world.json b/public/language/sl/world.json index 3753335278..e6694bf507 100644 --- a/public/language/sl/world.json +++ b/public/language/sl/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/sq-AL/admin/advanced/cache.json b/public/language/sq-AL/admin/advanced/cache.json index a7dfe8704c..b0df19e7f5 100644 --- a/public/language/sq-AL/admin/advanced/cache.json +++ b/public/language/sq-AL/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Plot ", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/sq-AL/admin/dashboard.json b/public/language/sq-AL/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/sq-AL/admin/dashboard.json +++ b/public/language/sq-AL/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/sq-AL/admin/development/info.json b/public/language/sq-AL/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/sq-AL/admin/development/info.json +++ b/public/language/sq-AL/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/sq-AL/admin/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/sq-AL/admin/manage/categories.json +++ b/public/language/sq-AL/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/sq-AL/admin/manage/custom-reasons.json b/public/language/sq-AL/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/sq-AL/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/sq-AL/admin/manage/privileges.json b/public/language/sq-AL/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/sq-AL/admin/manage/privileges.json +++ b/public/language/sq-AL/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/sq-AL/admin/manage/users.json b/public/language/sq-AL/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/sq-AL/admin/manage/users.json +++ b/public/language/sq-AL/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/sq-AL/admin/settings/activitypub.json b/public/language/sq-AL/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/sq-AL/admin/settings/activitypub.json +++ b/public/language/sq-AL/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/sq-AL/admin/settings/chat.json b/public/language/sq-AL/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/sq-AL/admin/settings/chat.json +++ b/public/language/sq-AL/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/sq-AL/admin/settings/email.json b/public/language/sq-AL/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/sq-AL/admin/settings/email.json +++ b/public/language/sq-AL/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/sq-AL/admin/settings/notifications.json b/public/language/sq-AL/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/sq-AL/admin/settings/notifications.json +++ b/public/language/sq-AL/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/sq-AL/admin/settings/uploads.json b/public/language/sq-AL/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/sq-AL/admin/settings/uploads.json +++ b/public/language/sq-AL/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/sq-AL/admin/settings/user.json b/public/language/sq-AL/admin/settings/user.json index 4e43ab7be3..c8cc3c9c34 100644 --- a/public/language/sq-AL/admin/settings/user.json +++ b/public/language/sq-AL/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/sq-AL/admin/settings/web-crawler.json b/public/language/sq-AL/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/sq-AL/admin/settings/web-crawler.json +++ b/public/language/sq-AL/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/sq-AL/aria.json b/public/language/sq-AL/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/sq-AL/aria.json +++ b/public/language/sq-AL/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/sq-AL/category.json b/public/language/sq-AL/category.json index 7bf412b891..3121688acd 100644 --- a/public/language/sq-AL/category.json +++ b/public/language/sq-AL/category.json @@ -1,12 +1,13 @@ { "category": "Kategoria", "subcategories": "Nënkategoritë", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Temë e re", "guest-login-post": "Hyr për të postuar", "no-topics": "Nuk ka tema në këtë kategori.
Pse nuk provon të postosh diçka?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "Duke Shfletuar", "no-replies": "Askush nuk ka kthyer përgjigje", "no-new-posts": "Nuk ka postime të reja", diff --git a/public/language/sq-AL/error.json b/public/language/sq-AL/error.json index 235b534851..b24d7ebb31 100644 --- a/public/language/sq-AL/error.json +++ b/public/language/sq-AL/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON i pavlefshëm", "wrong-parameter-type": "Pritej një vlerë e tipit %3 për vetinë '%1', por në vend të saj u mor %2", "required-parameters-missing": "Parametrat e kërkuar mungonin në këtë API: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Mesa duket nuk jeni identifikuar.", "account-locked": "Llogaria juaj është bllokuar përkohësisht", "search-requires-login": "Për të kërkuar ju duhet të keni një llogari - ju lutemi identifikohuni ose regjistrohuni.", @@ -146,6 +147,7 @@ "post-already-restored": "Ky postim tashmë është rikthyer", "topic-already-deleted": "Kjo temë tashmë është fshirë", "topic-already-restored": "Kjo temë tashmë është rikthyer", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Ju nuk mund të fshini postimin kryesor, ju lutemi fshini temën në vend të saj", "topic-thumbnails-are-disabled": "Miniaturat e temës janë çaktivizuar.", "invalid-file": "Dokument i pavlefshëm", @@ -154,6 +156,8 @@ "about-me-too-long": "Na vjen keq, por përshkrimi nuk mund të jetë më i gjatë se %1 karakter(e).", "cant-chat-with-yourself": "Nuk mund të bësh bashkëbisedim me veten!", "chat-restricted": "Ky përdorues ka kufizuar mesazhet e tij. Duhet t'ju ndjekin përpara se të bisedoni të", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Sistemi i bisedës është çaktivizuar", "too-many-messages": "Ju keni dërguar shumë mesazhe, ju lutemi prisni pak.", @@ -225,6 +229,7 @@ "no-topics-selected": "Asnjë temë e zgjedhur!", "cant-move-to-same-topic": "Postimi nuk mund të zhvendoset në të njëjtën temë!", "cant-move-topic-to-same-category": "Tema nuk mund të zhvendoset në të njëjtën kategori!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Ju nuk mund të bllokoni veten!", "cannot-block-privileged": "Ju nuk mund të bllokoni administratorët ose moderatorët", "cannot-block-guest": "Vizitorët nuk mund të bllokojnë përdoruesit e tjerë", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Nuk mund të arrihet serveri në këtë moment. Kliko këtu për të provuar përsëri, ose provo më vonë", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Nuk mund të instalohet plugin – vetëm shtojcat e listuara në listën e bardhë nga Menaxheri i Paketave të NodeBB mund të instalohen nëpërmjet ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "Nuk u gjet një sesion i vlefshëm identifikimi. Ju lutemi identifikohuni dhe provoni përsëri.", "api.403": "Ju nuk jeni i autorizuar për ta bërë këtë thirrje", "api.404": "Thirrje e pasakte e API", + "api.413": "The request payload is too large", "api.426": "Kërkohet HTTPS për kërkesat në api, ju lutemi ridërgojeni kërkesën tuaj nëpërmjet HTTPS", "api.429": "Ju keni bërë shumë kërkesa, ju lutemi provoni përsëri më vonë", "api.500": "Një gabim i papritur u ndesh gjatë përpjekjes për të kryer kërkesën tuaj.", diff --git a/public/language/sq-AL/global.json b/public/language/sq-AL/global.json index 60564bbd01..1459ec9f23 100644 --- a/public/language/sq-AL/global.json +++ b/public/language/sq-AL/global.json @@ -68,6 +68,7 @@ "users": "Përdoruesit", "topics": "Temat", "posts": "Postimet", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Votoi kundër", "views": "Shikimet", "posters": "Banera", + "watching": "Watching", "reputation": "Reputacioni", "lastpost": "Postimi i fundit", "firstpost": "Postimi i parë", diff --git a/public/language/sq-AL/groups.json b/public/language/sq-AL/groups.json index f6f0c40209..fc5f1488cf 100644 --- a/public/language/sq-AL/groups.json +++ b/public/language/sq-AL/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupe", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Shiko grupin", "owner": "Zotuesi i grupit", "new-group": "Krijo një grup të ri", diff --git a/public/language/sq-AL/modules.json b/public/language/sq-AL/modules.json index e715ed133c..93072fc01a 100644 --- a/public/language/sq-AL/modules.json +++ b/public/language/sq-AL/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Mbulo rezultatin", "composer.help": "Help", "composer.user-said-in": "%1 tha në %2:", - "composer.user-said": "%1 tha:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Jeni i sigurt që dëshironi ta hiqni këtë postim?", "composer.submit-and-lock": "Dorëzo dhe izolo", "composer.toggle-dropdown": "Aktivizo Dropdown", diff --git a/public/language/sq-AL/notifications.json b/public/language/sq-AL/notifications.json index 43ca29844d..9a13c89ed2 100644 --- a/public/language/sq-AL/notifications.json +++ b/public/language/sq-AL/notifications.json @@ -22,7 +22,7 @@ "upvote": "Votat pro", "awards": "Awards", "new-flags": "Raportim i ri", - "my-flags": "Raportimet u kaluan tek unë", + "my-flags": "My Flags", "bans": "Të bllokuar", "new-message-from": "Mesazh i ri nga%1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1ka votuar në postin tënd në %2.", - "upvoted-your-post-in-dual": "%1 dhe % 2 kanë votuar për postimin tuaj në %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 e ka zhvendosur postimin tuaj në %2", "moved-your-topic": "%1 1 ka lëvizur %2", - "user-flagged-post-in": "%1 ka raportuar një postim në %2", - "user-flagged-post-in-dual": "%1 dhe %2 raportuam një postim në %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 ka raportuar një profil përdoruesi (%2)", "user-flagged-user-dual": "%1 dhe %2 kane raportuar një profil përdoruesi (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 ka postuar një përgjigje në: %2", - "user-posted-to-dual": "%1 dhe %2 kanë postuar përgjigje në: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 ka postuar një temë të re: %2", - "user-edited-post": "%1 ka redaktuar një postim në %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 filloi t'ju ndjekë.", "user-started-following-you-dual": "% 1 dhe %2 filluan t'ju ndjekin.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Csv e përdoruesve u eksportua, klikoni për ta shkarkuar", "post-queue-accepted": "Postimi juaj në radhë është pranuar. Klikoni këtu për të parë postimin tuaj.", "post-queue-rejected": "Postimi juaj në pritje nuk është pranuar.", - "post-queue-notify": "Ka nje njoftim te ri per postimin ne pritje:
''%1''", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Email-i u konfirmua", "email-confirmed-message": "Faleminderit për vërtetimin e emailit tuaj. Llogaria juaj tani është plotësisht e aktivizuar.", "email-confirm-error-message": "Pati një problem me vërtetimin e adresës tuaj të emailit. Ndoshta kodi ishte i pavlefshëm ose ka skaduar.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Email i konfirmimit u dërgua.", "none": "Asnjë", "notification-only": "Vetëm njoftime", diff --git a/public/language/sq-AL/social.json b/public/language/sq-AL/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/sq-AL/social.json +++ b/public/language/sq-AL/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/sq-AL/themes/harmony.json b/public/language/sq-AL/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/sq-AL/themes/harmony.json +++ b/public/language/sq-AL/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/sq-AL/topic.json b/public/language/sq-AL/topic.json index d3313d7967..60cadd85f7 100644 --- a/public/language/sq-AL/topic.json +++ b/public/language/sq-AL/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klikoni këtu për tu kthyer në postimin e fundit të lexuar në këtë temë.", "flag-post": "Raporto këtë postim", "flag-user": "Raporto këtë përdorues", @@ -103,6 +105,7 @@ "thread-tools.lock": "Blloko temën", "thread-tools.unlock": "Zhblloko temën", "thread-tools.move": "Zhvendos temën", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Zhvendos postimin", "thread-tools.move-all": "Zhvendos të gjitha", "thread-tools.change-owner": "Ndrysho pronarin", @@ -132,6 +135,7 @@ "pin-modal-help": "Mund të caktoni opsionalisht një datë skadimi për temat() e ngjitura këtu. Përndryshe, mund ta lini këtë fushë bosh që tema të qëndrojë e renditur e para derisa të hiqet manualisht.", "load-categories": "Duke ngarkuar kategoritë", "confirm-move": "Lëvizni", + "confirm-crosspost": "Cross-post", "confirm-fork": "Ndrysho", "bookmark": "Ruaj", "bookmarks": "Të ruajtura", @@ -141,6 +145,7 @@ "loading-more-posts": "Duke ngarkuar më shumë postime", "move-topic": "Zhvendos Temën", "move-topics": "Zhvendos Temat", + "crosspost-topic": "Cross-post Topic", "move-post": "Zhvendos Postimin", "post-moved": "Postimi u zhvendos!", "fork-topic": "Ndrysho temën", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Klikoni postimet që dëshironi t'i caktoni një përdoruesi tjetër", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Shkruani titullin e temës suaj këtu...", "composer.handle-placeholder": "Shkruani emrin tuaj këtu", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Duke komentuar \"%1\"", "composer.new-topic": "Temë e re", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "duke u ngarkuar...", "composer.thumb-url-label": "Ngjit një URL të fotos së coverit të temës", "composer.thumb-title": "Shtoni një foto coveri në këtë temë", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/sq-AL/user.json b/public/language/sq-AL/user.json index 15010c35c7..f8ebf13c43 100644 --- a/public/language/sq-AL/user.json +++ b/public/language/sq-AL/user.json @@ -105,6 +105,10 @@ "show-email": "Shfaq emailin tim", "show-fullname": "Shfaq emrin tim të plotë", "restrict-chats": "Lejo vetëm mesazhet nga përdoruesit që ndjek.", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Abonohu të informohesh", "digest-description": "Abonohu ​​për përditësime me email në këtë forum (njoftime dhe tema të reja) në orare të caktuara", "digest-off": "Fikur", diff --git a/public/language/sq-AL/world.json b/public/language/sq-AL/world.json index 3753335278..e6694bf507 100644 --- a/public/language/sq-AL/world.json +++ b/public/language/sq-AL/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/sr/admin/advanced/cache.json b/public/language/sr/admin/advanced/cache.json index 6d290e9112..7c9a89d14f 100644 --- a/public/language/sr/admin/advanced/cache.json +++ b/public/language/sr/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Post Cache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache" diff --git a/public/language/sr/admin/dashboard.json b/public/language/sr/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/sr/admin/dashboard.json +++ b/public/language/sr/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/sr/admin/development/info.json b/public/language/sr/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/sr/admin/development/info.json +++ b/public/language/sr/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/sr/admin/manage/categories.json b/public/language/sr/admin/manage/categories.json index f51152f22d..cdb3e1f356 100644 --- a/public/language/sr/admin/manage/categories.json +++ b/public/language/sr/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/sr/admin/manage/custom-reasons.json b/public/language/sr/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/sr/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/sr/admin/manage/privileges.json b/public/language/sr/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/sr/admin/manage/privileges.json +++ b/public/language/sr/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/sr/admin/manage/users.json b/public/language/sr/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/sr/admin/manage/users.json +++ b/public/language/sr/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/sr/admin/settings/activitypub.json b/public/language/sr/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/sr/admin/settings/activitypub.json +++ b/public/language/sr/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/sr/admin/settings/chat.json b/public/language/sr/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/sr/admin/settings/chat.json +++ b/public/language/sr/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/sr/admin/settings/email.json b/public/language/sr/admin/settings/email.json index 7b15d0c7b9..01099cf49c 100644 --- a/public/language/sr/admin/settings/email.json +++ b/public/language/sr/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Promeni šablon Email-a", "template.select": "Izaberi šablon Email-a", "template.revert": "Vrati na Originalno podešavanje.", + "test-smtp-settings": "Test SMTP Settings", "testing": "Testiranje Email-a", + "testing.success": "Test Email Sent.", "testing.select": "Izaberi šablon Email-a", "testing.send": "Pošalji probni Email", - "testing.send-help": "Probni email će biti poslat na adresu trenutno ulogovanog korisnika", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/sr/admin/settings/notifications.json b/public/language/sr/admin/settings/notifications.json index c6d8b928ce..a2f82b82fb 100644 --- a/public/language/sr/admin/settings/notifications.json +++ b/public/language/sr/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/sr/admin/settings/uploads.json b/public/language/sr/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/sr/admin/settings/uploads.json +++ b/public/language/sr/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/sr/admin/settings/user.json b/public/language/sr/admin/settings/user.json index c938bbf354..a0c8731356 100644 --- a/public/language/sr/admin/settings/user.json +++ b/public/language/sr/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Prikaži email", "show-fullname": "Prikaži puno ime", "restrict-chat": "Samo dozvoli chat poruke korisnika koje ja pratim", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Otvori odlazeće linove u novom tabu", "topic-search": "Omogući pretraživanje u temi", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/sr/admin/settings/web-crawler.json b/public/language/sr/admin/settings/web-crawler.json index 82e852567c..aeb8bac3c3 100644 --- a/public/language/sr/admin/settings/web-crawler.json +++ b/public/language/sr/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Onemogući RSS Feed", "disable-sitemap-xml": "Onemogući Sitemap.xml", "sitemap-topics": "Broj Tema za prikaz u Mapi sajta", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Obriši cache Mape sajta", "view-sitemap": "Pogledaj Mapu sajta" } \ No newline at end of file diff --git a/public/language/sr/aria.json b/public/language/sr/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/sr/aria.json +++ b/public/language/sr/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/sr/category.json b/public/language/sr/category.json index 21e820233d..eb2a2a4806 100644 --- a/public/language/sr/category.json +++ b/public/language/sr/category.json @@ -1,12 +1,13 @@ { "category": "Категорија", "subcategories": "Поткатегорије", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Нова тема", "guest-login-post": "Пријавите се да бисте послали поруку", "no-topics": "Нема тема у овој категорији.
Зашто не бисте поставили једну?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "гледа", "no-replies": "Још увек нема одговора", "no-new-posts": "Нема нових порука", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index fa0053ce3f..b9dc793b07 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -3,6 +3,7 @@ "invalid-json": "Неважећи JSON", "wrong-parameter-type": "Очекивана је вредност типа %3 за својство %1, али је уместо тога примљен %2", "required-parameters-missing": "Недостајали су обавезни параметри у овом API позиву: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Изгледа да нисте пријављени.", "account-locked": "Ваш налог је привремено закључан", "search-requires-login": "Претраживање захтева налог — пријавите се или се региструјте.", @@ -146,6 +147,7 @@ "post-already-restored": "Ова порука је већ обновљена", "topic-already-deleted": "Ова тема је већ избрисана", "topic-already-restored": "Ова тема је већ обновљена", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Не можете очистити насловну поруку, избришите тему уместо тога", "topic-thumbnails-are-disabled": "Сличице тема су онемогућене.", "invalid-file": "Неисправна датотека", @@ -154,6 +156,8 @@ "about-me-too-long": "Жао нам је, информације о вама не смеју бити дуже од %1 знак(ов)а.", "cant-chat-with-yourself": "Не можете ћаскати са самим собом!", "chat-restricted": "Овај корисник је ограничио њихова ћаскања. Морају вас пратити пре него што можете ћаскати са њима.", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Ћаскања су онемогућена", "too-many-messages": "Послали сте превише порука, сачекајте мало.", @@ -225,6 +229,7 @@ "no-topics-selected": "Нема одабраних тема!", "cant-move-to-same-topic": "Није могуће преместити поруку у исту тему!", "cant-move-topic-to-same-category": "Није могуће преместити тему у исту категорију!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Не можете блокирати себе!", "cannot-block-privileged": "Не можете блокирати администраторе или глобалне модераторе", "cannot-block-guest": "Гости нису у могућности да блокирају друге кориснике", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Тренутно није могуће приступити серверу. Кликните овде да бисте покушали поново или покушајте поново касније", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Инсталација додатне компоненте &ndash није могућа; преко ACP-а могу се инсталирати само додатне компоненте које је на белој листи ставио NodeBB Package Manager", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "Није вам дозвољено да мењате стање додатне компоненте онако како је дефинисано у време извршавања (config.json, променљиве окружења или аргументи терминала), уместо тога измените конфигурацију.", "theme-not-set-in-configuration": "Приликом дефинисања активних додатних компоненти у конфигурацији, промена тема захтева додавање нове теме на листу активних додатних компоненти пре ажурирања у ACP", @@ -246,6 +252,7 @@ "api.401": "Није пронађена важећа сесија за пријављивање. Пријавите се и покушајте поново.", "api.403": "Нисте овлашћени да обавите овај позив", "api.404": "Неважећи API позив", + "api.413": "The request payload is too large", "api.426": "HTTPS је неопходан за захтеве за записан api, молимо вас да поново пошаљете ваш захтев путем HTTPS-а", "api.429": "Поднели сте превише захтева, покушајте поново касније", "api.500": "Дошло је до неочекиване грешке приликом покушаја сервисирања вашег захтева.", diff --git a/public/language/sr/global.json b/public/language/sr/global.json index 590aefc39d..0a66aac2a2 100644 --- a/public/language/sr/global.json +++ b/public/language/sr/global.json @@ -68,6 +68,7 @@ "users": "Корисници", "topics": "Теме", "posts": "Поруке", + "crossposts": "Cross-posts", "x-posts": "%1 поруке", "x-topics": "%1 теме", "x-reputation": "%1 угледа", @@ -82,6 +83,7 @@ "downvoted": "Негативно гласано", "views": "Прегледи", "posters": "Аутори порука", + "watching": "Watching", "reputation": "Углед", "lastpost": "Последња порука", "firstpost": "Прва порука", diff --git a/public/language/sr/groups.json b/public/language/sr/groups.json index dc504c9319..47b145b9fc 100644 --- a/public/language/sr/groups.json +++ b/public/language/sr/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Све групе", "groups": "Групе", "members": "Чланови", + "x-members": "%1 member(s)", "view-group": "Преглед групе", "owner": "Власник групе", "new-group": "Направи нову групу", diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index f10f3e5d83..333ced35ae 100644 --- a/public/language/sr/modules.json +++ b/public/language/sr/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Додај корисника", "chat.notification-settings": "Подешавања обавештења", "chat.default-notification-setting": "Подразумевано подешавање обавештења", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Подразумевана соба", "chat.notification-setting-none": "Без обавештења", "chat.notification-setting-at-mention-only": "@помињање само", @@ -81,7 +82,7 @@ "composer.hide-preview": "Сакриј преглед", "composer.help": "Помоћ", "composer.user-said-in": "%1 је рекао у %2", - "composer.user-said": "%1 је рекао:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Желите ли да одбаците ову поруку?", "composer.submit-and-lock": "Проследи и закључај", "composer.toggle-dropdown": "Подесите \"Dropdown\"", diff --git a/public/language/sr/notifications.json b/public/language/sr/notifications.json index 88d08b3a85..3f844ba53e 100644 --- a/public/language/sr/notifications.json +++ b/public/language/sr/notifications.json @@ -22,7 +22,7 @@ "upvote": "Гласови", "awards": "Награде", "new-flags": "Нове заставице", - "my-flags": "Заставице додељене мени", + "my-flags": "My Flags", "bans": "Забране", "new-message-from": "Нова порука од %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 и %2 су написали у %4", "user-posted-in-public-room-triple": "%1, %2 и %3 су написали у %5", "user-posted-in-public-room-multiple": "%1, %2 и осталих %3 су написали у %5", - "upvoted-your-post-in": "%1 је гласао за вашу поруку у %2", - "upvoted-your-post-in-dual": "%1 и %2 осталих су гласали за вашу поруку у %3.", - "upvoted-your-post-in-triple": "%1, %2 и %3 су гласали за вашу објаву у %4.", - "upvoted-your-post-in-multiple": "%1, %2 и осталих %3 су гласали за вашу објаву у %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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-triple": "%1, %2 и %3 су означили заставицом објаву у %4", - "user-flagged-post-in-multiple": "%1, %2 и осталих %3 су означили заставицом објаву у %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 је означио заставицом кориснички профил (%2)", "user-flagged-user-dual": "%1 и %2 су означили заставицом кориснички профил (%3)", "user-flagged-user-triple": "%1, %2 и %3 су означили заставицом кориснички профил (%4)", "user-flagged-user-multiple": "%1, %2 и осталих %3 су означили заставицом кориснички профил (%4)", - "user-posted-to": "%1 је послао нови одговор на: %2", - "user-posted-to-dual": "%1 и %2 су одговорили на: %3", - "user-posted-to-triple": "%1, %2 и %3 су објавили одговор: %4", - "user-posted-to-multiple": "%1, %2 и осталих %3 су објавили одговор: %4", - "user-posted-topic": "%1 је поставио нову тему: %2", - "user-edited-post": "%1 је уредио поруку у %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 је почео да вас прати.", "user-started-following-you-dual": "%1 и %2 су почели да вас прате.", "user-started-following-you-triple": "%1, %2 и %3 су вас запратили.", @@ -71,11 +71,11 @@ "users-csv-exported": "Кориснички csv извезен, кликните за преузимање", "post-queue-accepted": "Ваша порука на чекању је прихваћена. Кликните овде да бисте видели своју поруку.", "post-queue-rejected": "Ваша порука на чекању је одбијена.", - "post-queue-notify": "Порука на чекању је примила обавештење:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Е-пошта је потврђена.", "email-confirmed-message": "Хвала на овери ваше е-поште. Ваш налог је сада у потпуности активан.", "email-confirm-error-message": "Дошло је до проблема са овером ваше е-поште. Можда је код неисправан или је истекао.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Е-пошта за потврду је послата.", "none": "Ниједно", "notification-only": "Само обавештење", diff --git a/public/language/sr/social.json b/public/language/sr/social.json index 5ceb7d44d5..2e6ee44612 100644 --- a/public/language/sr/social.json +++ b/public/language/sr/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Пријавите се преко Facebook-а", "continue-with-facebook": "Наставите се преко Facebook-а", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/sr/themes/harmony.json b/public/language/sr/themes/harmony.json index 439fd93b97..a879e81f8c 100644 --- a/public/language/sr/themes/harmony.json +++ b/public/language/sr/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony тема", "skins": "Маске", + "light": "Light", + "dark": "Dark", "collapse": "Скупи", "expand": "Рашири", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json index 27775992d9..9d511aa929 100644 --- a/public/language/sr/topic.json +++ b/public/language/sr/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 се осврнуо на ову тему %3", "user-forked-topic-ago": "%1 је раздвојио ову тему %3", "user-forked-topic-on": "%1 је раздвојио ову тему %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Кликните овде за повратак на последњу прочитану поруку у овој теми.", "flag-post": "Означи поруку заставицом", "flag-user": "Означи корисника заставицом", @@ -103,6 +105,7 @@ "thread-tools.lock": "Закључај тему", "thread-tools.unlock": "Откључај тему", "thread-tools.move": "Премести тему", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Премести поруке", "thread-tools.move-all": "Премести све", "thread-tools.change-owner": "Промени власника", @@ -132,6 +135,7 @@ "pin-modal-help": "Овде можете по жељи да одредите датум истека закачених тема. Можете и да ово поље оставите празно да би тема остала закачена док се ручно не откачи.", "load-categories": "Учитавање категорија", "confirm-move": "Премести", + "confirm-crosspost": "Cross-post", "confirm-fork": "Раздвоји", "bookmark": "Обележивач", "bookmarks": "Обележивачи", @@ -141,6 +145,7 @@ "loading-more-posts": "Учитавање још порука", "move-topic": "Премести тему", "move-topics": "Премести теме", + "crosspost-topic": "Cross-post Topic", "move-post": "Премести поруку", "post-moved": "Порука је премештена!", "fork-topic": "Раздвоји тему", @@ -163,6 +168,9 @@ "move-topic-instruction": "Изаберите циљну категорију, а затим кликните на премести", "change-owner-instruction": "Кликните на поруке које желите да доделите другом кориснику", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Овде унесите наслов теме...", "composer.handle-placeholder": "Унесите ваше име/идентитет овде", "composer.hide": "Сакриј", @@ -174,6 +182,7 @@ "composer.replying-to": "Писање одговора на %1", "composer.new-topic": "Нова тема", "composer.editing-in": "Уређивање објаве у %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "отпремање...", "composer.thumb-url-label": "Налепи адресу сличице теме", "composer.thumb-title": "Додај сличицу овој теми", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/sr/user.json b/public/language/sr/user.json index 78c7496662..0dead2c321 100644 --- a/public/language/sr/user.json +++ b/public/language/sr/user.json @@ -105,6 +105,10 @@ "show-email": "Прикажи моју лозинку", "show-fullname": "Прикажи моје пуно име", "restrict-chats": "Дозволи поруке ћаскања само од корисника које пратим", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Пријава за сажетак", "digest-description": "Пријавите се за праћење ажурирања форума (нова обавештења и теме) путем е-поште према одређеном распореду", "digest-off": "Искључено", diff --git a/public/language/sr/world.json b/public/language/sr/world.json index 3753335278..e6694bf507 100644 --- a/public/language/sr/world.json +++ b/public/language/sr/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ 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 81e5a41473..e2aaf02840 100644 --- a/public/language/sv/admin/advanced/cache.json +++ b/public/language/sv/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Inläggscache", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "%1% Full", "post-cache-size": "Storlek på inläggscache", "items-in-cache": "Föremål i cache" diff --git a/public/language/sv/admin/dashboard.json b/public/language/sv/admin/dashboard.json index 6ad973f5f3..0be6d5866c 100644 --- a/public/language/sv/admin/dashboard.json +++ b/public/language/sv/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", diff --git a/public/language/sv/admin/development/info.json b/public/language/sv/admin/development/info.json index 9834719daf..f7c69a1149 100644 --- a/public/language/sv/admin/development/info.json +++ b/public/language/sv/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/sv/admin/manage/categories.json b/public/language/sv/admin/manage/categories.json index 3e10ad60d8..b48ce5b455 100644 --- a/public/language/sv/admin/manage/categories.json +++ b/public/language/sv/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Category Name", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/sv/admin/manage/custom-reasons.json b/public/language/sv/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/sv/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/sv/admin/manage/privileges.json b/public/language/sv/admin/manage/privileges.json index b4b76178f7..f1887073ec 100644 --- a/public/language/sv/admin/manage/privileges.json +++ b/public/language/sv/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/sv/admin/manage/users.json b/public/language/sv/admin/manage/users.json index 6cd6a14aef..fc36120840 100644 --- a/public/language/sv/admin/manage/users.json +++ b/public/language/sv/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Download CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Reason (Optional)", + "temp-ban.select-reason": "Select a reason", "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-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "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?", diff --git a/public/language/sv/admin/settings/activitypub.json b/public/language/sv/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/sv/admin/settings/activitypub.json +++ b/public/language/sv/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/sv/admin/settings/chat.json b/public/language/sv/admin/settings/chat.json index 6d6cad284b..b491a3104b 100644 --- a/public/language/sv/admin/settings/chat.json +++ b/public/language/sv/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/sv/admin/settings/email.json b/public/language/sv/admin/settings/email.json index ab1e2bcf4a..b644e4f18b 100644 --- a/public/language/sv/admin/settings/email.json +++ b/public/language/sv/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Epostsammandrag", "subscriptions.disable": "Avaktivera epostsammandrag", "subscriptions.hour": "Digest Hour", diff --git a/public/language/sv/admin/settings/notifications.json b/public/language/sv/admin/settings/notifications.json index e00e779176..58aba6ecd5 100644 --- a/public/language/sv/admin/settings/notifications.json +++ b/public/language/sv/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Welcome Notification", "welcome-notification-link": "Welcome Notification Link", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/sv/admin/settings/uploads.json b/public/language/sv/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/sv/admin/settings/uploads.json +++ b/public/language/sv/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/sv/admin/settings/user.json b/public/language/sv/admin/settings/user.json index bb9d83586f..631bde181c 100644 --- a/public/language/sv/admin/settings/user.json +++ b/public/language/sv/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/sv/admin/settings/web-crawler.json b/public/language/sv/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/sv/admin/settings/web-crawler.json +++ b/public/language/sv/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/sv/aria.json b/public/language/sv/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/sv/aria.json +++ b/public/language/sv/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/sv/category.json b/public/language/sv/category.json index 601480b5ac..ee57b58552 100644 --- a/public/language/sv/category.json +++ b/public/language/sv/category.json @@ -1,12 +1,13 @@ { "category": "Kategori", "subcategories": "Underkategori", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nytt ämne", "guest-login-post": "Logga in för att posta", "no-topics": "Det finns inga ämnen i denna kategori.
Varför skapar inte du ett ämne?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "läser", "no-replies": "Ingen har svarat", "no-new-posts": "Inga nya inlägg.", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index c6887f1733..48d692e3d9 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -3,6 +3,7 @@ "invalid-json": "Ogiltig JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Du verkar inte vara inloggad.", "account-locked": "Ditt konto har tillfälligt blivit låst", "search-requires-login": "Sökning kräver ett konto, var god logga in eller registrera dig.", @@ -146,6 +147,7 @@ "post-already-restored": "Inlägget är redan återställt", "topic-already-deleted": "Ämnet är redan raderat", "topic-already-restored": "Ämnet är redan återställt", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Huvudinlägg kan ej rensas bort, ta bort ämnet istället", "topic-thumbnails-are-disabled": "Miniatyrbilder för ämnen är inaktiverat", "invalid-file": "Ogiltig fil", @@ -154,6 +156,8 @@ "about-me-too-long": "Din text om dig själv kan inte vara längre än %1 tecken.", "cant-chat-with-yourself": "Du kan inte chatta med dig själv!", "chat-restricted": "Denna användaren har begränsat sina meddelanden. Användaren måste följa dig innan ni kan chatta med varandra", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Chatten är inaktiverad", "too-many-messages": "Du har skickat för många meddelanden, var god vänta", @@ -225,6 +229,7 @@ "no-topics-selected": "Inga ämnen valda!", "cant-move-to-same-topic": "Kan inte flytta inlägg till samma ämne!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Du kan inte blockera dig själv!", "cannot-block-privileged": "Du kan inte blockera administratörer eller globala moderatorer", "cannot-block-guest": "Guest are not able to block other users", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/sv/global.json b/public/language/sv/global.json index 4ffb3b0b7a..12034b4e1a 100644 --- a/public/language/sv/global.json +++ b/public/language/sv/global.json @@ -68,6 +68,7 @@ "users": "Användare", "topics": "Ämnen", "posts": "Inlägg", + "crossposts": "Cross-posts", "x-posts": "%1 inlägg", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Nedröstad", "views": "Visningar", "posters": "Användare", + "watching": "Watching", "reputation": "Rykte", "lastpost": "Senaste inlägget", "firstpost": "Först inlägget", diff --git a/public/language/sv/groups.json b/public/language/sv/groups.json index e5ec1a840c..a9700c3d8a 100644 --- a/public/language/sv/groups.json +++ b/public/language/sv/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Grupper", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Visa grupp", "owner": "Gruppägare", "new-group": "Skapa ny grupp", diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json index 14b07ee511..3685692133 100644 --- a/public/language/sv/modules.json +++ b/public/language/sv/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Dölj förhandsgranskning", "composer.help": "Hjälp", "composer.user-said-in": "%1 sa i %2:", - "composer.user-said": "%1 sa:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Är du säker på att du vill ta bort det här inlägget?", "composer.submit-and-lock": "Skicka och lås", "composer.toggle-dropdown": "Visa/Dölj dropdown", diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json index e61853dd41..f748786e67 100644 --- a/public/language/sv/notifications.json +++ b/public/language/sv/notifications.json @@ -22,7 +22,7 @@ "upvote": "Uppröster", "awards": "Awards", "new-flags": "Nya flaggor", - "my-flags": "Mina tilldelade flaggor", + "my-flags": "My Flags", "bans": "Bannlysningar", "new-message-from": "Nytt medelande från %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 och %2 skrev i %4", "user-posted-in-public-room-triple": "%1, %2 och %3 skrev i %5", "user-posted-in-public-room-multiple": "%1, %2 och %3 andra skrev i %5", - "upvoted-your-post-in": "%1 har röstat upp ditt inlägg i %2", - "upvoted-your-post-in-dual": "%1 och %2 har röstat upp ditt inlägg i %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 har flyttat ditt inlägg till %2", "moved-your-topic": "%1 har flyttat %2", - "user-flagged-post-in": "%1 flaggade ett inlägg i %2", - "user-flagged-post-in-dual": "%1 och %2 rapporterade ett inlägg i %3", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 flaggade en användarprofil (%2)", "user-flagged-user-dual": "%1 och %2 flaggade en användarprofil (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 har skrivit ett svar på: %2", - "user-posted-to-dual": "%1 och %2 har svarat på: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 har skapat ett nytt ämne: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 började följa dig.", "user-started-following-you-dual": "%1 och %2 började följa dig.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-post bekräftad", "email-confirmed-message": "Tack för att du bekräftat din e-postadress. Ditt konto är nu fullt ut aktiverat.", "email-confirm-error-message": "Det uppstod ett problem med bekräftelsen av din e-postadress. Kanske var koden felaktig eller ogiltig.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Bekräftelsemeddelande skickat.", "none": "Inga", "notification-only": "Endast notis", diff --git a/public/language/sv/social.json b/public/language/sv/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/sv/social.json +++ b/public/language/sv/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/sv/themes/harmony.json b/public/language/sv/themes/harmony.json index 70ea942824..dd503d7ce4 100644 --- a/public/language/sv/themes/harmony.json +++ b/public/language/sv/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Dölj", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json index 483b26e254..d2361ead1e 100644 --- a/public/language/sv/topic.json +++ b/public/language/sv/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Klicka här för att återgå till senast lästa inlägg i detta ämne.", "flag-post": "Flagga inlägg", "flag-user": "Flagga användare", @@ -103,6 +105,7 @@ "thread-tools.lock": "Lås ämne", "thread-tools.unlock": "Lås upp ämne", "thread-tools.move": "Flytta ämne", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Flytta inlägg", "thread-tools.move-all": "Flytta alla", "thread-tools.change-owner": "Ändra ägare", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Laddar kategorier", "confirm-move": "Flytta", + "confirm-crosspost": "Cross-post", "confirm-fork": "Grena", "bookmark": "Bokmärke", "bookmarks": "Bokmärken", @@ -141,6 +145,7 @@ "loading-more-posts": "Laddar fler inlägg", "move-topic": "Flytta ämne", "move-topics": "Flytta ämnen", + "crosspost-topic": "Cross-post Topic", "move-post": "Flytta inlägg", "post-moved": "Inlägget flyttades.", "fork-topic": "Grena ämne", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Klicka på de inlägg du vill tilldela en annan användare", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Skriv in ämnets titel här...", "composer.handle-placeholder": "Skriv ditt namn/användarnamn här", "composer.hide": "Dölj", @@ -174,6 +182,7 @@ "composer.replying-to": "Svarar till %1", "composer.new-topic": "Nytt ämne", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "laddar upp...", "composer.thumb-url-label": "Klistra in URL till tumnagel för ämnet", "composer.thumb-title": "Lägg till tumnagel för detta ämne", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/sv/user.json b/public/language/sv/user.json index c2e3469ab9..7026836ea1 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -105,6 +105,10 @@ "show-email": "Visa min e-postadress", "show-fullname": "Visa fullständigt namn", "restrict-chats": "Tillåt endast chatt-meddelanden från användare som jag följer", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Prenumerera på sammanställt flöde", "digest-description": "Prenumerera på e-postuppdateringar för det här forumet (notiser och ämnen) med en viss regelbundenhet", "digest-off": "Avslagen", diff --git a/public/language/sv/world.json b/public/language/sv/world.json index 3753335278..e6694bf507 100644 --- a/public/language/sv/world.json +++ b/public/language/sv/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/th/admin/advanced/cache.json b/public/language/th/admin/advanced/cache.json index a82ddf0724..4820c178d2 100644 --- a/public/language/th/admin/advanced/cache.json +++ b/public/language/th/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "แคชข้อความ", - "group-cache": "แคชกลุ่ม", - "local-cache": "แคชโลคอล", - "object-cache": "แคชออพเจ็กท์", "percent-full": "เต็ม %1%", "post-cache-size": "ขนาดแคชของข้อความ", "items-in-cache": "รายการที่ถูกแคช" diff --git a/public/language/th/admin/dashboard.json b/public/language/th/admin/dashboard.json index 5049bb87e1..fb36057e85 100644 --- a/public/language/th/admin/dashboard.json +++ b/public/language/th/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "ยอดวิวจากผู้ลงทะเบียนแล้ว", "graphs.page-views-guest": "ยอดวิวจากผู้มาเยือน", "graphs.page-views-bot": "ยอดวิวจากบอต", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "จำนวนผู้ใช้ที่ไม่ซ้ำกัน", "graphs.registered-users": "ผู้ใช้ที่ลงทะเบียนแล้ว", "graphs.guest-users": "ผู้ใช้ที่เป็นผู้มาเยือน", diff --git a/public/language/th/admin/development/info.json b/public/language/th/admin/development/info.json index b78747dc04..6dc488f147 100644 --- a/public/language/th/admin/development/info.json +++ b/public/language/th/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "หน่วยความจำโปรเซส", + "process-memory": "rss/heap used", "system-memory": "หน่วยความจำระบบ", "used-memory-process": "หน่วยความจำที่ใช้โดยโปรเซส", "used-memory-os": "หน่วยความจำระบบที่ใช้", diff --git a/public/language/th/admin/manage/categories.json b/public/language/th/admin/manage/categories.json index f64e658a6b..45f2da252d 100644 --- a/public/language/th/admin/manage/categories.json +++ b/public/language/th/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "จัดการหมวดหมู่", "add-category": "เพิ่มหมวดหมู่", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "ไปที่...", "settings": "การตั้งค่าหมวดหมู่", "edit-category": "แก้ไขหมวดหมู่", "privileges": "สิทธิ์", "back-to-categories": "กลับไปที่หมวดหมู่ทั้งหมด", + "id": "Category ID", "name": "ชื่อหมวดหมู่", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "คำอธิบายหมวดหมู่", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "สีพื้น", "text-color": "สีข้อความ", "bg-image-size": "ขนาดภาพพื้นหลัง", @@ -103,6 +107,11 @@ "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "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!", diff --git a/public/language/th/admin/manage/custom-reasons.json b/public/language/th/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/th/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/th/admin/manage/privileges.json b/public/language/th/admin/manage/privileges.json index 240cff6aa5..bb4b33494f 100644 --- a/public/language/th/admin/manage/privileges.json +++ b/public/language/th/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Access Topics", "create-topics": "Create Topics", "reply-to-topics": "Reply to Topics", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Tag Topics", "edit-posts": "Edit Posts", diff --git a/public/language/th/admin/manage/users.json b/public/language/th/admin/manage/users.json index 99ce57d808..4234744a44 100644 --- a/public/language/th/admin/manage/users.json +++ b/public/language/th/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "ลบผู้ใช้งานและเนื้อหา", "download-csv": "ดาวน์โหลด CSV", "custom-user-fields": "ฟิลด์ข้อมูลผู้ใช้ที่ปรับแต่งได้", + "custom-reasons": "Custom Reasons", "manage-groups": "จัดการกลุ่ม", "set-reputation": "ตั้งค่าชื่อเสียง", "add-group": "เพิ่มกลุ่ม", @@ -77,9 +78,11 @@ "temp-ban.length": "ความยาว", "temp-ban.reason": "เหตุผล (ตัวเลือก)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "ชั่วโมง", "temp-ban.days": "วัน", "temp-ban.explanation": "ระบุระยะเวลาของการแบน ถ้าระยะเวลาเป็น \"0\" คือการแบนถาวร", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "คุณต้องการที่จะแบนผู้ใช้คนนี้ ถาวร ?", "alerts.confirm-ban-multi": "คุณต้องการที่จะแบนผู้ใช้กลุ่มนี้ ถาวร ?", diff --git a/public/language/th/admin/settings/activitypub.json b/public/language/th/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/th/admin/settings/activitypub.json +++ b/public/language/th/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/th/admin/settings/chat.json b/public/language/th/admin/settings/chat.json index 1261a49b1c..4152f54e35 100644 --- a/public/language/th/admin/settings/chat.json +++ b/public/language/th/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "จำนวนผู้ใช้ในห้องแชทมากที่สุด", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/th/admin/settings/email.json b/public/language/th/admin/settings/email.json index 0310939cb3..c7a3628a7f 100644 --- a/public/language/th/admin/settings/email.json +++ b/public/language/th/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Edit Email Template", "template.select": "Select Email Template", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Digest Hour", diff --git a/public/language/th/admin/settings/notifications.json b/public/language/th/admin/settings/notifications.json index 0d8e4ec3c1..382fb9e080 100644 --- a/public/language/th/admin/settings/notifications.json +++ b/public/language/th/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "การยินดีต้อนรับแจ้งเตือน", "welcome-notification-link": "ลิงค์การยินดีต้อนรับแจ้งเตือน", "welcome-notification-uid": "Welcome Notification User (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/th/admin/settings/uploads.json b/public/language/th/admin/settings/uploads.json index 22046915d9..b08d56a5f8 100644 --- a/public/language/th/admin/settings/uploads.json +++ b/public/language/th/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as 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.", diff --git a/public/language/th/admin/settings/user.json b/public/language/th/admin/settings/user.json index 6c72bc9d79..1e5cdf9d30 100644 --- a/public/language/th/admin/settings/user.json +++ b/public/language/th/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/th/admin/settings/web-crawler.json b/public/language/th/admin/settings/web-crawler.json index 2e0d31d12b..b398d764ba 100644 --- a/public/language/th/admin/settings/web-crawler.json +++ b/public/language/th/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Disable RSS Feeds", "disable-sitemap-xml": "Disable Sitemap.xml", "sitemap-topics": "Number of Topics to display in the Sitemap", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Clear Sitemap Cache", "view-sitemap": "View Sitemap" } \ No newline at end of file diff --git a/public/language/th/aria.json b/public/language/th/aria.json index 5e3f21e45b..044cf1fae6 100644 --- a/public/language/th/aria.json +++ b/public/language/th/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "แท็กที่ผู้ใช้เฝ้าดู", "delete-upload-button": "ลบปุ่มอัพโหลด", - "group-page-link-for": "ลิงก์ไปหน้ากลุ่มสำหรับ %1" + "group-page-link-for": "ลิงก์ไปหน้ากลุ่มสำหรับ %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/th/category.json b/public/language/th/category.json index e21e8c6c3e..768184dc22 100644 --- a/public/language/th/category.json +++ b/public/language/th/category.json @@ -1,12 +1,13 @@ { "category": "หมวดหมู่", "subcategories": "หมวดหมู่ย่อย", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "ตั้งกระทู้", "guest-login-post": "เข้าสู่ระบบเพื่อโพสต์", "no-topics": "ยังไม่มีกระทู้ในหมวดนี้
ลองโพสต์กระทู้แรกดูมั้ย?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "เรียกดู", "no-replies": "ยังไม่มีใครตอบ", "no-new-posts": "ไม่มีกระทู้ใหม่", diff --git a/public/language/th/error.json b/public/language/th/error.json index ac8a316bb2..f0fd571f3b 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -3,6 +3,7 @@ "invalid-json": "รูปแบบ JSON ไม่ถูกต้อง", "wrong-parameter-type": "ต้องการข้อมูลประเภท %3 สำหรับค่า `%1` แต่ได้รับค่า %2 แทน", "required-parameters-missing": "ขาดพารามิเตอร์ที่จำเป็นต่อการเรียก API นี้: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "คุณยังไม่ได้เข้าสู่ระบบ", "account-locked": "บัญชีของคุณถูกระงับการใช้งานชั่วคราว", "search-requires-login": "\"ฟังก์ชั่นการค้นหา\" ต้องการบัญชีผู้ใช้ กรุณาเข้าสู่ระบบหรือสมัครสมาชิก", @@ -146,6 +147,7 @@ "post-already-restored": "โพสต์นี้ถูกกู้คืนเรียบร้อยแล้ว", "topic-already-deleted": "กระทู้นี้ถูกลบไปแล้ว", "topic-already-restored": "กระทู้นี้ถูกกู้คืนเรียบร้อยแล้ว", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "คุณไม่สามารถลบล้างโพสต์หลักได้ กรุณาลบกระทู้แทน", "topic-thumbnails-are-disabled": "ภาพตัวอย่างของกระทู้ถูกปิดใช้งาน", "invalid-file": "ไฟล์ไม่ถูกต้อง", @@ -154,6 +156,8 @@ "about-me-too-long": "ขออภัย \"เกี่ยวกับฉัน\" ของคุณไม่สามารถยาวเกิน %1 ตัวอักษร(s) ได้", "cant-chat-with-yourself": "คุณไม่สามารถแชทกับตัวเองได้นะ!", "chat-restricted": "ผู้ใช้นี้ถูกจำกัดข้อความแชท เขาต้องติดตามคุณก่อน คุณจึงจะสามารถแชทกับเขาได้", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "คุณถูกบล็อกโดยผู้ใช้คนนี้", "chat-disabled": "ระบบแชทถูกปิดใช้งาน", "too-many-messages": "คุณได้ส่งข้อความมากเกินไป กรุณารอสักครู่", @@ -225,6 +229,7 @@ "no-topics-selected": "ไม่มีกระทู้ที่เลือก!", "cant-move-to-same-topic": "ไม่สามารถย้ายไปกระทู้เดิม!", "cant-move-topic-to-same-category": "ไม่สามารถย้ายกระทู้ไปหมวดหมู่เดิม!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "คุณไม่สามารถบล็อกตัวเองได้!", "cannot-block-privileged": "คุณไม่สามารถบล็อกผู้ดูแลระบบหรือ moderator ส่วนกลาง", "cannot-block-guest": "ผู้มาเยือนไม่สามารถบล็อกผู้ใช้งานอื่น", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "ไม่สามารถติดต่อกับเซิร์ฟเวอร์ในขณะนี้ คลิกที่นี่เพื่อลองใหม่ หรือลองอีกครั้งภายหลัง", "invalid-plugin-id": "รหัสปลั๊กอินไม่ถูกต้อง", "plugin-not-whitelisted": "ไม่สามารถติดตั้งปลั๊กอิน – เฉพาะปลั๊กอินที่ได้รับอนุญาตจาก NodeBB Package Manager ถึงจะติดตั้งผ่านแผงควบคุมผู้ดูแลระบบได้", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "คุณไม่สามารถเปลี่ยนสถานะของปลั๊กอินเนื่องจากถูกกำหนดตอนรัน (ไฟล์ config.json, ตัวแปร environmental หรือระบุตอนสั่งในบรรทัดคำสั่ง) โปรดปรับที่การตั้งค่าแทน", "theme-not-set-in-configuration": "เมื่อกำหนดปลั๊กอันที่กำลังทำงานในส่วนตั้งค่า การเปลี่ยนธีมต้องเพิ่มทีมในรายการปลั๊กอินที่กำลังใช้งานก่อนที่จะเปลี่ยนในแผงควบคุมผู้ดูแล", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/th/global.json b/public/language/th/global.json index 67ce8f1ffc..8a52feea35 100644 --- a/public/language/th/global.json +++ b/public/language/th/global.json @@ -68,6 +68,7 @@ "users": "ผู้ใช้", "topics": "กระทู้", "posts": "โพสต์", + "crossposts": "Cross-posts", "x-posts": "%1 โพสต์", "x-topics": "%1 กระทู้", "x-reputation": "ชื่อเสียง %1", @@ -82,6 +83,7 @@ "downvoted": "โหวตลง", "views": "ยอดดู", "posters": "ผู้โพสต์", + "watching": "Watching", "reputation": "ชื่อเสียง", "lastpost": "โพสต์สุดท้าย", "firstpost": "โพสต์แรก", diff --git a/public/language/th/groups.json b/public/language/th/groups.json index 4b32fe99cd..dcf513f450 100644 --- a/public/language/th/groups.json +++ b/public/language/th/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "กลุ่มทั้งหมด", "groups": "กลุ่ม", "members": "สมาชิก", + "x-members": "%1 member(s)", "view-group": "ดูกลุ่ม", "owner": "เจ้าของกลุ่ม", "new-group": "สร้างกลุ่มใหม่", diff --git a/public/language/th/modules.json b/public/language/th/modules.json index c8198f1d99..e723fcdb89 100644 --- a/public/language/th/modules.json +++ b/public/language/th/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "เพิ่มผู้ใช้งาน", "chat.notification-settings": "การตั้งค่าการแจ้งเตือน", "chat.default-notification-setting": "ค่าเริ่มต้นการแจ้งเตือน", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "ค่าเริ่มต้นของห้อง", "chat.notification-setting-none": "ไม่มีการแจ้งเตือน", "chat.notification-setting-at-mention-only": "เฉพาะเมื่อ @ถูกพูดถึง", @@ -81,7 +82,7 @@ "composer.hide-preview": "ซ่อนพรีวิว", "composer.help": "ความช่วยเหลือ", "composer.user-said-in": "%1 พูดใน %2:", - "composer.user-said": "%1 พูด:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "คุณแน่ใจแล้วใช่ไหมว่าจะทิ้งโพสต์นี้?", "composer.submit-and-lock": "ยืนยันและล็อก", "composer.toggle-dropdown": "ท็อกเกิลดร็อปดาวน์", diff --git a/public/language/th/notifications.json b/public/language/th/notifications.json index 6596b2bf9a..b3c1eaade7 100644 --- a/public/language/th/notifications.json +++ b/public/language/th/notifications.json @@ -22,7 +22,7 @@ "upvote": "โหวตขึ้น", "awards": "รางวัล", "new-flags": "รายงานใหม่", - "my-flags": "รายงานที่ถูกมอบหมายให้ฉัน", + "my-flags": "My Flags", "bans": "แบน", "new-message-from": "ข้อความใหม่จาก %1", "new-messages-from": "%1 ข้อความใหม่จาก %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 และ %2 ได้เขียนลงใน %4", "user-posted-in-public-room-triple": "%1, %2 และ %3 ได้เขียนลงใน %5", "user-posted-in-public-room-multiple": "%1, %2 และอีก %3 คน ได้เขียนลงใน %5", - "upvoted-your-post-in": "%1 ได้โหวตโพสต์ของคุณขึ้นใน %2", - "upvoted-your-post-in-dual": "%1 และ %2ได้โหวตโพสต์ของคุณขึ้นใน %3", - "upvoted-your-post-in-triple": "%1, %2 และ %3 ได้โหวตโพสต์ของคุณขึ้นใน %4.", - "upvoted-your-post-in-multiple": "%1, %2 และอีก %3 คน ได้โหวตโพสต์ของคุณขึ้นใน %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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-triple": "%1, %2 และ %3 ได้รายงานโพสต์ใน %4", - "user-flagged-post-in-multiple": "%1, %2 และอีก %3 คนได้รายงานโพสต์ใน %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 ได้รายงานโปรไฟล์ผู้ใช้ (%2)", "user-flagged-user-dual": "%1และ%2ได้รายงานโปรไฟล์ผู้ใช้ (%3)", "user-flagged-user-triple": "%1, %2 และ %3 ได้รายงานโปรไฟล์ผู้ใช้ (%4)", "user-flagged-user-multiple": "%1, %2 และอีก %3 คนได้รายงานโปรไฟล์ผู้ใช้ (%4)", - "user-posted-to": "%1 ได้โพสต์คำตอบไปยัง %2", - "user-posted-to-dual": "%1และ %2ได้โพสต์คำตอบไปยัง %3", - "user-posted-to-triple": "%1, %2 และ %3 ได้โพสต์คำตอบไปยัง %4", - "user-posted-to-multiple": "%1, %2 และอีก %3 คน ได้โพสต์คำตอบไปยัง %4", - "user-posted-topic": "%1ได้โพสต์กระทู้ใหม่ : %2", - "user-edited-post": "%1 ได้แก้ไขโพสต์ใน %2", - "user-posted-topic-with-tag": "%1 ได้โพสต์ %2 (ติดแท็ก %3)", - "user-posted-topic-with-tag-dual": "%1 ได้โพสต์ %2 (ติดแท็ก %3 และ %4)", - "user-posted-topic-with-tag-triple": "%1 ได้โพสต์ %2 (ติดแท็ก %3, %4 และ %5)", - "user-posted-topic-with-tag-multiple": "%1 ได้โพสต์ %2 (ติดแท็ก %3)", - "user-posted-topic-in-category": "%1 ได้โพสต์กระทู้ใหม่ใน %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 ได้เริ่มติดตามคุณ", "user-started-following-you-dual": "%1และ%2ได้เริ่มติดตามคุณ", "user-started-following-you-triple": "%1, %2 และ %3 ได้เริ่มติดตามคุณ", @@ -71,11 +71,11 @@ "users-csv-exported": "ส่งออกข้อมูล csv ของผู้ใช้งานเสร็จแล้ว คลิกเพื่อดาวน์โหลด", "post-queue-accepted": "โพสต์ที่รอคิวของคุณได้รับการอนุมัติแล้ว คลิกที่นี่เพื่อดูโพสต์ของคุณ", "post-queue-rejected": "โพสต์ที่รอคิวของคุณได้รับการปฏิเสธ", - "post-queue-notify": "ได้รับแจ้งเตือนสำหรับโพสต์ที่รอคิว:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "อีเมลได้รับการยืนยันแล้ว", "email-confirmed-message": "ขอบคุณที่ยืนยันอีเมลของคุณ บัญชีของคุณสามารถใช้งานได้แล้ว", "email-confirm-error-message": "มีปัญหาในการยืนยันอีเมลของคุณ รหัสอาจไม่ถูกต้องหรือหมดอายุแล้ว", - "email-confirm-error-message-already-validated": "อีเมลของคุณได้รับการยืนยันไปแล้ว", "email-confirm-sent": "ส่งอีเมลยืนยันแล้ว", "none": "ไม่มี", "notification-only": "แจ้งเตือนอย่างเดียว", diff --git a/public/language/th/social.json b/public/language/th/social.json index 7930476093..062af524bc 100644 --- a/public/language/th/social.json +++ b/public/language/th/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "เข้าสู่ระบบด้วยบัญชี Facebook", "continue-with-facebook": "ไปต่อโดยใช้บัญชี Facebook", "sign-in-with-linkedin": "เข้าสู่ระบบด้วยบัญชี LinkedIn", - "sign-up-with-linkedin": "สร้างบัญชีใหม่ด้วยบัญชี LinkedIn" + "sign-up-with-linkedin": "สร้างบัญชีใหม่ด้วยบัญชี LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/th/themes/harmony.json b/public/language/th/themes/harmony.json index 1f02284aa2..091b21471f 100644 --- a/public/language/th/themes/harmony.json +++ b/public/language/th/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "ธีม Harmony", "skins": "สกิน", + "light": "Light", + "dark": "Dark", "collapse": "ย่อ", "expand": "ขยาย", "sidebar-toggle": "สลับตำแหน่งแถบข้าง", diff --git a/public/language/th/topic.json b/public/language/th/topic.json index 6d4e92b082..818384ecf6 100644 --- a/public/language/th/topic.json +++ b/public/language/th/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 อ้างอิง กระทู้นี้เมื่อ %3", "user-forked-topic-ago": "%1 แยก กระทู้นี้เมื่อ %3", "user-forked-topic-on": "%1 แยก กระทู้นี้เมื่อ %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "คลิกที่นี่เพื่อกลับไปยังโพสต์ที่อ่านล่าสุดในหัวข้อนี้", "flag-post": "รายงานโพสต์นี้", "flag-user": "รายงานผู้ใช้นี้", @@ -103,6 +105,7 @@ "thread-tools.lock": "ล็อคกระทู้", "thread-tools.unlock": "ปลดล็อคกระทู้", "thread-tools.move": "ย้ายกระทู้", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "ย้ายโพสต์", "thread-tools.move-all": "ย้ายทั้งหมด", "thread-tools.change-owner": "เปลี่ยนเจ้าของ", @@ -132,6 +135,7 @@ "pin-modal-help": "คุณสามารถเลือกจะตั้งค่าวันหมดอายุสำหรับกระทู้ปักหมุดที่นี่ คูณยังสามารถปล่อยให้ฟิลด์นี้ว่างเพื่อให้กระทู้ยังคงถูกปักหมดจนกว่าจะยกเลิกด้วยมือ", "load-categories": "กำลังโหลดหมวดหมู่", "confirm-move": "ย้าย", + "confirm-crosspost": "Cross-post", "confirm-fork": "แยก", "bookmark": "บุ๊กมาร์ก", "bookmarks": "บุ๊กมาร์ก", @@ -141,6 +145,7 @@ "loading-more-posts": "โหลดโพสเพิ่มเติม", "move-topic": "ย้ายกระทู้", "move-topics": "ย้ายกระทู้", + "crosspost-topic": "Cross-post Topic", "move-post": "ย้ายโพส", "post-moved": "โพสต์ถูกย้ายแล้ว!", "fork-topic": "แยกกระทู้", @@ -163,6 +168,9 @@ "move-topic-instruction": "เลือกหมวดหมู่ปลายทางและคลิกย้าย", "change-owner-instruction": "คลิกที่โพสต์ที่คุณต้องการมอบหมายให้ผู้ใช้งานอีกคน", "manage-editors-instruction": "จัดการผู้ใช้ที่สามารถแก้ไขโพสต์นี้ด้านล่าง", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "ป้อนชื่อกระทู้ของคุณที่นี่ ...", "composer.handle-placeholder": "ป้อนชื่อหรือชื่อเล่นของคุณที่นี่", "composer.hide": "ซ่อน", @@ -174,6 +182,7 @@ "composer.replying-to": "ตอบไปยัง %1", "composer.new-topic": "กระทู้ใหม่", "composer.editing-in": "แก้ไขโพสต์ใน %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "กำลังอัพโหลด ...", "composer.thumb-url-label": "วาง URL ของภาพของกระทู้นี้", "composer.thumb-title": "เพิ่มภาพให้กับกระทู้นี้", @@ -224,5 +233,8 @@ "unread-posts-link": "ลิงก์ไปโพสต์ที่ยังไม่ได้อ่าน", "thumb-image": "รูปย่อของกระทู้", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/th/user.json b/public/language/th/user.json index 0a4657569a..ab47a2c631 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -105,6 +105,10 @@ "show-email": "แสดงอีเมลของฉัน", "show-fullname": "แสดงชื่อจริงของฉัน", "restrict-chats": "รับข้อความสนทนาจากคนที่ฉันติดตามเท่านั้น", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "สมัครรับสรุปเนื้อหา", "digest-description": "สมัครรับอีเมลอัพเดทข้อมูลของบอร์ดสนทนา (ข้อความแจ้งเตือนและกระทู้ใหม่ๆ) ตามเวลาที่ตั้งไว้", "digest-off": "ปิด", diff --git a/public/language/th/world.json b/public/language/th/world.json index 3753335278..e6694bf507 100644 --- a/public/language/th/world.json +++ b/public/language/th/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/tr/admin/advanced/cache.json b/public/language/tr/admin/advanced/cache.json index c9537f7f7d..902c492af8 100644 --- a/public/language/tr/admin/advanced/cache.json +++ b/public/language/tr/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "İleti Önbelleği", - "group-cache": "Grup Önbelleği", - "local-cache": "Yerel Önbellek", - "object-cache": "Öğe Önbelleği", "percent-full": "%1% Tam", "post-cache-size": "İleti Önbellek Boyutu", "items-in-cache": "Önbellekteki Öğeler" diff --git a/public/language/tr/admin/dashboard.json b/public/language/tr/admin/dashboard.json index a6ef8394c2..1c40d281c9 100644 --- a/public/language/tr/admin/dashboard.json +++ b/public/language/tr/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Kayıtlı Kullanıcıların Sayfa Gösterimi", "graphs.page-views-guest": "Ziyaretçilerin Sayfa Gösterimi", "graphs.page-views-bot": "Bot Sayfa Gösterimi", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Benzersiz Ziyaretçiler", "graphs.registered-users": "Kayıtlı Kullanıcılar", "graphs.guest-users": "Misafir Kullanıcılar", diff --git a/public/language/tr/admin/development/info.json b/public/language/tr/admin/development/info.json index d1bccbc674..b8ee9c0881 100644 --- a/public/language/tr/admin/development/info.json +++ b/public/language/tr/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "çevrimiçi", "git": "git", - "process-memory": "işlem belleği", + "process-memory": "rss/heap used", "system-memory": "sistem hafızası", "used-memory-process": "İşleme göre kullanılan bellek", "used-memory-os": "Kullanılan sistem belleği", diff --git a/public/language/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json index 1cad49d52c..9af38c0945 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Kategori Ayarları", "edit-category": "Edit Category", "privileges": "İzinler", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Kategori Adı", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategori Açıklaması", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Arkaplan Rengi", "text-color": "Yazı Rengi", "bg-image-size": "Arkaplan Görseli Boyutu", @@ -103,6 +107,11 @@ "alert.create-success": "Kategori başarıyla yaratıldı!", "alert.none-active": "Aktif kategoriniz mevcut değil.", "alert.create": "Bir Kategori Yarat", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

\"% 1\" kategorisini gerçekten temizlemek istiyor musunuz?

Uyarı! Bu kategorideki tüm başlıklar ve iletiler temizlenir!

Bir kategoriyi temizlemek, tüm başlıkları ve iletileri kaldıracak ve kategoriyi veritabanından silecektir. Bir kategoriyi geçici olarak kaldırmak isterseniz, kategoriyi \"devre dışı\" bırakmanız yeterlidir.

", "alert.purge-success": "Kategori temizlendi!", "alert.copy-success": "Ayarlar Kopyalandı!", diff --git a/public/language/tr/admin/manage/custom-reasons.json b/public/language/tr/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/tr/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/tr/admin/manage/privileges.json b/public/language/tr/admin/manage/privileges.json index b4193c4116..9b8a651747 100644 --- a/public/language/tr/admin/manage/privileges.json +++ b/public/language/tr/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Başlıklara Eriş", "create-topics": "Başlık Oluştur", "reply-to-topics": "Başlığı Cevapla", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Konuları Planla", "tag-topics": "Başlığı etiketle", "edit-posts": "İletiyi düzenle", diff --git a/public/language/tr/admin/manage/users.json b/public/language/tr/admin/manage/users.json index a3ce756c68..cdce9d0e88 100644 --- a/public/language/tr/admin/manage/users.json +++ b/public/language/tr/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Kullanıcıyı/ları ve İçeriği Sil", "download-csv": "CSV İndir", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Grupları Düzenle", "set-reputation": "Set Reputation", "add-group": "Grup ekle", @@ -77,9 +78,11 @@ "temp-ban.length": "Uzunluk", "temp-ban.reason": "Sebep(İsteğe Bağlı)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Saat", "temp-ban.days": "Gün", "temp-ban.explanation": "Yasağın süresini girin. 0'lık bir zamanın kalıcı bir yasak olarak sayılacağını unutmayın.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Bu kullanıcıyı kalıcı olarak yasaklamak istiyor musunuz?", "alerts.confirm-ban-multi": "Bu kullanıcıları kalıcı olarak yasaklamak istiyor musunuz?", diff --git a/public/language/tr/admin/settings/activitypub.json b/public/language/tr/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/tr/admin/settings/activitypub.json +++ b/public/language/tr/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/tr/admin/settings/chat.json b/public/language/tr/admin/settings/chat.json index 0b7d49a69d..61566bae6d 100644 --- a/public/language/tr/admin/settings/chat.json +++ b/public/language/tr/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maksimum sohbet oda adı uzunluğu", "max-room-size": "Sohbet odalarındaki maksimum kullanıcı sayısı", "delay": "Time between chat messages (ms)", - "notification-delay": "Geciken sohbet mesajları bildirimi", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/tr/admin/settings/email.json b/public/language/tr/admin/settings/email.json index ebde9b25ee..25a4561e89 100644 --- a/public/language/tr/admin/settings/email.json +++ b/public/language/tr/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Toplu bağlantılar NodeBB'nin her e-posta için yeni bir bağlantı oluşturmasını engeller. Bu seçenek sadece SMTP Transport aktif ise geçerlidir.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "E-posta Kalıbını Düzenle", "template.select": "E-posta Kalıbını Seç", "template.revert": "Revert to Original", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Testing", + "testing.success": "Test Email Sent.", "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.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Özet E-postaları", "subscriptions.disable": "Özet e-postalarını kapat", "subscriptions.hour": "Digest Hour", diff --git a/public/language/tr/admin/settings/notifications.json b/public/language/tr/admin/settings/notifications.json index bf9502ff3d..6d0b71a510 100644 --- a/public/language/tr/admin/settings/notifications.json +++ b/public/language/tr/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Hoş Geldin Bildirimi", "welcome-notification-link": "Hoş Geldin Bildirimi Bağlantısı", "welcome-notification-uid": "Kullanıcı Hoş Geldiniz Bildirimi (UID)", - "post-queue-notification-uid": "İletisi kuyruğa alınan kullanıcı (UID)" + "post-queue-notification-uid": "İletisi kuyruğa alınan kullanıcı (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/tr/admin/settings/uploads.json b/public/language/tr/admin/settings/uploads.json index f9b369f66a..f84124fb4b 100644 --- a/public/language/tr/admin/settings/uploads.json +++ b/public/language/tr/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Bu değerden daha geniş olan görseller reddedilecektir.", "reject-image-height": "Maksimum Görsel Yüksekliği (piksel)", "reject-image-height-help": "Bu değerden daha uzun olan görseller reddedilecektir.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Kullanıcıların konulara küçük resim yüklemesine izin ver", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Konu Küçük Resim Boyutu", "allowed-file-extensions": "İzin Verilen Dosya Uzantıları", "allowed-file-extensions-help": "Virgül ile ayrılmış dosya uzantıları listesini buraya girin (ör. pdf, xls, doc). Boş bir liste, tüm uzantılara izin verildiği anlamına gelir.", diff --git a/public/language/tr/admin/settings/user.json b/public/language/tr/admin/settings/user.json index 597f2876f7..dda0718351 100644 --- a/public/language/tr/admin/settings/user.json +++ b/public/language/tr/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "E-posta Göster", "show-fullname": "Tam adı göster", "restrict-chat": "Sadece takip ettiğim kişilerden sohbetleri kabul et", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Dışarı giden bağlantıları yeni sekmede aç", "topic-search": "Konu içi aramayı etkinleştir", "update-url-with-post-index": "Sayfayı okurken URL bağlantısındaki ileti numarasını güncelle", diff --git a/public/language/tr/admin/settings/web-crawler.json b/public/language/tr/admin/settings/web-crawler.json index a3e94189ca..caefb4f0e0 100644 --- a/public/language/tr/admin/settings/web-crawler.json +++ b/public/language/tr/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "RSS Besleyicilerini Devre dışı bırak", "disable-sitemap-xml": "sitemap.xml devre dışı bırak", "sitemap-topics": "Site Haritası'nda görüntülenecek başlıkların sayısı", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Site haritası çerezlerini temizle", "view-sitemap": "Site haritasını gör" } \ No newline at end of file diff --git a/public/language/tr/aria.json b/public/language/tr/aria.json index 7b42ff4210..40aa1891e0 100644 --- a/public/language/tr/aria.json +++ b/public/language/tr/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "Üyenin takip ettiği etiketler", "delete-upload-button": "Yükleme butonunu sil", - "group-page-link-for": "%1 için grup sayfa bağlantısı" + "group-page-link-for": "%1 için grup sayfa bağlantısı", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/tr/category.json b/public/language/tr/category.json index a794e6433d..9893fdac5e 100644 --- a/public/language/tr/category.json +++ b/public/language/tr/category.json @@ -1,12 +1,13 @@ { "category": "Kategori", "subcategories": "Alt kategoriler", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Yeni Başlık", "guest-login-post": "Giriş Yap", "no-topics": " Bu kategoride hiç konu yok.
Yeni bir konu oluşturmak istemez misiniz?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "göz gezdiriyor", "no-replies": "Kimse yanıtlamadı", "no-new-posts": "Yeni ileti yok", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index c6d63663e5..f6cfaaf0dc 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -3,6 +3,7 @@ "invalid-json": "Geçersiz JSON", "wrong-parameter-type": "\"%1\" özelliği için %3 türünde bir değer bekleniyordu, ancak bunun yerine %2 alındı", "required-parameters-missing": "Bu API çağrısında gerekli parametreler eksikti: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Giriş yapmamış görünüyorsunuz.", "account-locked": "Hesabınız geçici olarak kilitlendi", "search-requires-login": "Arama yapmak için üyelik hesabı gerekiyor. Lütfen giriş yapın ya da kaydolun.", @@ -146,6 +147,7 @@ "post-already-restored": "İleti zaten geri getirilmiş", "topic-already-deleted": "Başlık zaten silinmiş", "topic-already-restored": "Başlık zaten geri getirilmiş", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "İlk iletiyi silemezsiniz, bunun yerine konuyu silin", "topic-thumbnails-are-disabled": "Başlık resimleri kapalı.", "invalid-file": "Geçersiz Dosya", @@ -154,6 +156,8 @@ "about-me-too-long": "Hakkınızda yazdıklarınız en fazla %1 karakter olabilir.", "cant-chat-with-yourself": "Kendinizle sohbet edemezsiniz!", "chat-restricted": "Bu kullanıcı sohbet ayarlarını kısıtlamış. Bu kişiye mesaj gönderebilmeniz için sizi takip etmeleri gerekiyor", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Sohbet özelliği kapalı", "too-many-messages": "Ardı ardına çok fazla mesaj yolladınız, lütfen biraz bekleyiniz.", @@ -225,6 +229,7 @@ "no-topics-selected": "Hiçbir başlık seçilmedi!", "cant-move-to-same-topic": "İletiyi aynı başlığa taşıyamazsın!", "cant-move-topic-to-same-category": "Başlığı bulunduğu kategoriye taşıyamazsınız!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Kendi kendinizi engelleyemezsiniz!", "cannot-block-privileged": "Yöneticileri veya genel moderatörleri engelleyemezsiniz", "cannot-block-guest": "Misafir diğer kullanıcıları engelleyemez", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Şu anda sunucuya ulaşılamıyor. Tekrar denemek için buraya tıklayın, veya daha sonra tekrar deneyin.", "invalid-plugin-id": "Geçersiz Eklenti ID", "plugin-not-whitelisted": "– eklentisi yüklenemedi, sadece NodeBB Paket Yöneticisi tarafından onaylanan eklentiler kontrol panelinden kurulabilir", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "Geçerli bir giriş oturumu bulunamadı. Lütfen yeniden giriş yapıp tekrar deneyin.", "api.403": "Bu aramayı yapmak için yetkiniz yok", "api.404": "Geçersiz API çağrısı", + "api.413": "The request payload is too large", "api.426": "Yazma API'sine yapılan istekler için HTTPS gereklidir, lütfen isteğinizi HTTPS aracılığıyla yeniden gönderin", "api.429": "Fazla sayıda istekte bulundunuz, lütfen daha sonra tekrar deneyiniz.", "api.500": "İsteğinizi gerçekleştirmeye çalışırken beklenmeyen bir hata ile karşılaşıldı.", diff --git a/public/language/tr/global.json b/public/language/tr/global.json index 2a26960c3d..57e50efe94 100644 --- a/public/language/tr/global.json +++ b/public/language/tr/global.json @@ -68,6 +68,7 @@ "users": "Kullanıcı", "topics": "Konu", "posts": "İleti", + "crossposts": "Cross-posts", "x-posts": "%1 ileti", "x-topics": "%1 başlık", "x-reputation": "%1 saygınlık", @@ -82,6 +83,7 @@ "downvoted": "Eksi Oylar", "views": "Bakış", "posters": "Yayımlayıcılar", + "watching": "Watching", "reputation": "İtibar", "lastpost": "Son ileti", "firstpost": "İlk ileti", diff --git a/public/language/tr/groups.json b/public/language/tr/groups.json index b84d9a26fd..1018dad5d3 100644 --- a/public/language/tr/groups.json +++ b/public/language/tr/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "Tüm gruplar", "groups": "Gruplar", "members": "Üyeler", + "x-members": "%1 member(s)", "view-group": "Grubu Gör", "owner": "Grup Kurucusu", "new-group": "Yeni Grup Oluştur", diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index d85dbd2821..daf5de0fd6 100644 --- a/public/language/tr/modules.json +++ b/public/language/tr/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Kullanıcı Ekle", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Önizleme Sakla", "composer.help": "Yardım", "composer.user-said-in": "%1, içinde söyledi: %2", - "composer.user-said": "%1 söyledi:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Bu iletiyi iptal etmek istediğinizden emin misiniz?", "composer.submit-and-lock": "Gönder ve Kilitle", "composer.toggle-dropdown": "Menü aç", diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json index b44ab2717f..b88efbd89c 100644 --- a/public/language/tr/notifications.json +++ b/public/language/tr/notifications.json @@ -22,7 +22,7 @@ "upvote": "Artı Oylananlar", "awards": "Ödüller", "new-flags": "Yeni Şikayetler", - "my-flags": "Vekil olarak atandığım şikayetler", + "my-flags": "My Flags", "bans": "Yasaklamalar", "new-message-from": "%1 size bir mesaj gönderdi", "new-messages-from": "%2 kullanıcısından %1 yeni mesaj var", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 ve %2 şu odaya yazdı: %4", "user-posted-in-public-room-triple": "%1, %2 ve %3 şu odaya yazdılar: %5", "user-posted-in-public-room-multiple": "%1, %2 ve %3 diğer kullanıcı şu odaya yazdılar: %5", - "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-triple": "%1, %2 ve %3 şu konudaki iletinizi beğendi: %4", - "upvoted-your-post-in-multiple": "%1, %2 ve %3 diğer kullanıcı şu konudaki iletinizi beğendi: %4", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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 şikayet etti: %2", - "user-flagged-post-in-dual": "%1 ve %2 şu konudaki bir iletiyi şikayet etti: %3", - "user-flagged-post-in-triple": "%1, %2 ve %3 şu konudaki bir iletiyi şikayet etti: %4", - "user-flagged-post-in-multiple": "%1, %2 ve %3 diğer kullanıcı şu konudaki bir iletiyi şikayet etti: %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 şu kullanıcıyı şikayet etti: (%2)", "user-flagged-user-dual": "%1 ve %2 şu kullanıcıyı şikayet etti: (%3)", "user-flagged-user-triple": "%1, %2 ve %3 şu kullanıcıyı şikayet etti: (%4)", "user-flagged-user-multiple": "%1, %2 ve %3 diğer üye şu kullanıcıyı şikayet etti: (%4)", - "user-posted-to": "%1 şu konuya bir ileti yazdı: %2", - "user-posted-to-dual": "%1 ve %2 şu konuya ileti yazdılar: %3", - "user-posted-to-triple": "%1, %2 ve %3 şu konuya ileti yazdılar: %4", - "user-posted-to-multiple": "%1, %2 ve %3 diğer kullanıcı şu konuya ileti yazdılar: %4", - "user-posted-topic": "%1 şu yeni konuyu oluşturdu: %2", - "user-edited-post": "%1 şu konudaki bir iletiyi değiştirdi: %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 şu kategoride yeni bir başlık oluşturdu: %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 sizi takip etmeye başladı", "user-started-following-you-dual": "%1 ve %2 sizi takip etmeye başladı.", "user-started-following-you-triple": "%1, %2 and %3 started following you", @@ -71,11 +71,11 @@ "users-csv-exported": "Kullanıcılar csv hazırlandı, indirmek için tıklayınız", "post-queue-accepted": "Sıradaki gönderiniz kabul edildi. Gönderinizi görmek için buraya tıklayın.", "post-queue-rejected": "Sıraya alınmış gönderiniz reddedildi", - "post-queue-notify": "Onay sırasındaki ileti için bir bildirim var:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "E-posta onaylandı", "email-confirmed-message": "E-postanızı onayladığınız için teşekkürler. Hesabınız tamamen aktif edildi.", "email-confirm-error-message": "E-posta adresinizi onaylarken bir hata oluştu. Kodunuz geçersiz ya da eski olabilir.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Onay e-postası gönderildi.", "none": "Hiçbiri", "notification-only": "Sadece Bildirim", diff --git a/public/language/tr/social.json b/public/language/tr/social.json index 835124b5f5..174f1091ca 100644 --- a/public/language/tr/social.json +++ b/public/language/tr/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Facebook ile Giriş Yap", "continue-with-facebook": "Facebook ile devam et", "sign-in-with-linkedin": "LinkedIn ile Giriş Yap", - "sign-up-with-linkedin": "LinkedIn ile Kaydol" + "sign-up-with-linkedin": "LinkedIn ile Kaydol", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/tr/themes/harmony.json b/public/language/tr/themes/harmony.json index 4c233e77d8..b3c6fc0461 100644 --- a/public/language/tr/themes/harmony.json +++ b/public/language/tr/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Deriler", + "light": "Light", + "dark": "Dark", "collapse": "Daralt", "expand": "Genişlet", "sidebar-toggle": "Yan menü değiştirici", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index d423571f7e..9e4c6e8dd1 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 bu başlıktan %3 tarihinde bahsetti", "user-forked-topic-ago": "%1 bu başlığı %3 bölerek ayırdı", "user-forked-topic-on": "%1 bu başlığı %3 tarihinde bölerek ayırdı", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Bu konuda en son kaldığın yere dönmek için tıkla.", "flag-post": "Bu iletiyi şikayet et", "flag-user": "Bu kullanıcıyı şikayet et", @@ -103,6 +105,7 @@ "thread-tools.lock": "Konuyu Kilitle", "thread-tools.unlock": "Konu Kilidini Kaldır", "thread-tools.move": "Başlığı Taşı", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "İletiyi Taşı", "thread-tools.move-all": "Hepsini Taşı", "thread-tools.change-owner": "Sahibini Değiştir", @@ -132,6 +135,7 @@ "pin-modal-help": "Sabitlenen konular için bir bitiş tarihi belirleyebilirsiniz. Eğer bu tarihi boş bırakırsanız, konular siz sabitliğini kaldırana kadar sabitlenmiş olarak kalır.", "load-categories": "Kategoriler Yükleniyor", "confirm-move": "Taşı", + "confirm-crosspost": "Cross-post", "confirm-fork": "Ayır", "bookmark": "Yer imlerine ekle", "bookmarks": "Yer imleri", @@ -141,6 +145,7 @@ "loading-more-posts": "Daha fazla ileti", "move-topic": "Başlığı Taşı", "move-topics": "Başlıkları Taşı", + "crosspost-topic": "Cross-post Topic", "move-post": "İletiyi Taşı", "post-moved": "İleti taşındı!", "fork-topic": "Başlığı Ayır", @@ -163,6 +168,9 @@ "move-topic-instruction": "Hedef kategoriyi seç ve taşı butonuna tıkla", "change-owner-instruction": "Başka kullanıcıya aktarmak istediğiniz iletileri seçiniz!", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Başlık ismini buraya giriniz...", "composer.handle-placeholder": "Kullanıcı adınızı buraya girin", "composer.hide": "Gizle", @@ -174,6 +182,7 @@ "composer.replying-to": "Yanıtlanan Başlık: %1", "composer.new-topic": "Yeni Başlık", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "yükleniyor...", "composer.thumb-url-label": "Başlık fotosu URL adresini yapıştır", "composer.thumb-title": "Bu başlığa bir fotoğraf ekle", @@ -224,5 +233,8 @@ "unread-posts-link": "Okunmamış iletilerin bağlantısı", "thumb-image": "Başlık önizleme görüntüsü", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/tr/user.json b/public/language/tr/user.json index 9b2c469705..f52a181159 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -105,6 +105,10 @@ "show-email": "E-postamı göster", "show-fullname": "Tam ismimi göster", "restrict-chats": "Sadece takip ettiğim kişilerden sohbetleri kabul et", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Özet e-postalarına kaydol", "digest-description": "Bu forum için e-posta güncellemelerine kaydol.", "digest-off": "Kapalı", diff --git a/public/language/tr/world.json b/public/language/tr/world.json index 3753335278..e6694bf507 100644 --- a/public/language/tr/world.json +++ b/public/language/tr/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/uk/admin/advanced/cache.json b/public/language/uk/admin/advanced/cache.json index 7ad4a264d7..997235c671 100644 --- a/public/language/uk/admin/advanced/cache.json +++ b/public/language/uk/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Cache", - "post-cache": "Кеш постів", - "group-cache": "Group Cache", - "local-cache": "Local Cache", - "object-cache": "Object Cache", "percent-full": "Заповнений на %1%", "post-cache-size": "Розмір кешу постів", "items-in-cache": "Елементів у кеші" diff --git a/public/language/uk/admin/dashboard.json b/public/language/uk/admin/dashboard.json index 6f8ce9fa8a..ab440180bb 100644 --- a/public/language/uk/admin/dashboard.json +++ b/public/language/uk/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "Унікальні відвідувачі", "graphs.registered-users": "Зареєстровані користувачі", "graphs.guest-users": "Guest Users", diff --git a/public/language/uk/admin/development/info.json b/public/language/uk/admin/development/info.json index 42f90c8282..037650bbd9 100644 --- a/public/language/uk/admin/development/info.json +++ b/public/language/uk/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "online", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/uk/admin/manage/categories.json b/public/language/uk/admin/manage/categories.json index dfae6ebe3d..2b713f196a 100644 --- a/public/language/uk/admin/manage/categories.json +++ b/public/language/uk/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "Налаштування категорій", "edit-category": "Edit Category", "privileges": "Права", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "Назва категорії", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Опис категорії", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Колір фону", "text-color": "Колір тексту", "bg-image-size": "Розмір фонового зображення", @@ -103,6 +107,11 @@ "alert.create-success": "Категорія успішно створена!", "alert.none-active": "У вас немає активних категорій.", "alert.create": "Створити категорію", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

Ви впевнені, що бажаєте стерти категорію \"%1\"?

Увага! Всі теми та пости в цій категорії буде знищено!

Стирання категорії видалить всі теми та пости і видалить категорію з бази данних. Якщо ви хотіли тимчасово видалити категорію, вам, натомість, варто її просто \"вимкнути\".

", "alert.purge-success": "Категорію стерто!", "alert.copy-success": "Налаштування скопійовано!", diff --git a/public/language/uk/admin/manage/custom-reasons.json b/public/language/uk/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/uk/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/uk/admin/manage/privileges.json b/public/language/uk/admin/manage/privileges.json index f7a482fb59..1dd80523c3 100644 --- a/public/language/uk/admin/manage/privileges.json +++ b/public/language/uk/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Доступ до Тем", "create-topics": "Створювати Теми", "reply-to-topics": "Відповідати на Теми", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Schedule Topics", "tag-topics": "Тегувати Теми", "edit-posts": "Редагувати Пости", diff --git a/public/language/uk/admin/manage/users.json b/public/language/uk/admin/manage/users.json index db97262c43..c9674ddcf8 100644 --- a/public/language/uk/admin/manage/users.json +++ b/public/language/uk/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Delete User(s) and Content", "download-csv": "Скачати CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "Manage Groups", "set-reputation": "Set Reputation", "add-group": "Add Group", @@ -77,9 +78,11 @@ "temp-ban.length": "Length", "temp-ban.reason": "Причина (необов'язково)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Години", "temp-ban.days": "Дні", "temp-ban.explanation": "Уведіть тривалість бану. 0 означатиме постійний бан.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Ви впевнені, що бажаєте забанити цього користувача напостійно?", "alerts.confirm-ban-multi": "Ви впевнені, що бажаєте забанити цих користувачів напостійно?", diff --git a/public/language/uk/admin/settings/activitypub.json b/public/language/uk/admin/settings/activitypub.json index 94f9ad7822..5ab4fa43e8 100644 --- a/public/language/uk/admin/settings/activitypub.json +++ b/public/language/uk/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + "server-filtering": "Filtering", "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", diff --git a/public/language/uk/admin/settings/chat.json b/public/language/uk/admin/settings/chat.json index d2a8f21ca3..d342295954 100644 --- a/public/language/uk/admin/settings/chat.json +++ b/public/language/uk/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Максимальна кількість людей у кімнаті", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ No newline at end of file diff --git a/public/language/uk/admin/settings/email.json b/public/language/uk/admin/settings/email.json index 2d2255df00..ca4b978f3a 100644 --- a/public/language/uk/admin/settings/email.json +++ b/public/language/uk/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Редагувати шаблон листа", "template.select": "Обрати шаблон листа", "template.revert": "Повернути до оригіналу", + "test-smtp-settings": "Test SMTP Settings", "testing": "Тестування листа", + "testing.success": "Test Email Sent.", "testing.select": "Оберіть шаблон листа", "testing.send": "Надіслати тестового листа", - "testing.send-help": "Тестовий лист було направлено на адресу поточного користувача.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Digests", "subscriptions.disable": "Disable email digests", "subscriptions.hour": "Година дайджесту", diff --git a/public/language/uk/admin/settings/notifications.json b/public/language/uk/admin/settings/notifications.json index 2322bf6836..ecb11b9932 100644 --- a/public/language/uk/admin/settings/notifications.json +++ b/public/language/uk/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Сповіщення \"Ласкаво просимо\"", "welcome-notification-link": "Посилання для сповіщення \"Ласкаво просимо\"", "welcome-notification-uid": "Сповіщення \"Ласкаво просимо\" для користувача (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/uk/admin/settings/uploads.json b/public/language/uk/admin/settings/uploads.json index f54640cb29..6a49b7664c 100644 --- a/public/language/uk/admin/settings/uploads.json +++ b/public/language/uk/admin/settings/uploads.json @@ -21,7 +21,13 @@ "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.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Дозволити користувачам завантажувати мініатюри тем", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Розмір мініатюри теми", "allowed-file-extensions": "Допустимі розширення файлів", "allowed-file-extensions-help": "Вкажіть розширеня файлів розділені комою (наприклад, pdf,xls,doc). Пустий список дає дозвіл на будь-які розширення.", diff --git a/public/language/uk/admin/settings/user.json b/public/language/uk/admin/settings/user.json index 5b4f22d223..91969f6cd4 100644 --- a/public/language/uk/admin/settings/user.json +++ b/public/language/uk/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "Показувати електронну пошту", "show-fullname": "Показувати повне ім'я", "restrict-chat": "Дозволяти чат повідомлення лише від користувачів за якими я стежу", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "Відкривати зовнішні посилання у новій вкладці", "topic-search": "Увімкнути пошук у темах", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/uk/admin/settings/web-crawler.json b/public/language/uk/admin/settings/web-crawler.json index f6f3a4b541..69bd0ffc65 100644 --- a/public/language/uk/admin/settings/web-crawler.json +++ b/public/language/uk/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Вимкнути RSS-стрічки", "disable-sitemap-xml": "Вимкнути Sitemap.xml", "sitemap-topics": "Кількість тем для показу в мапі сайту", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Очистити кеш мапи сайту", "view-sitemap": "Переглянути мапу сайту" } \ No newline at end of file diff --git a/public/language/uk/aria.json b/public/language/uk/aria.json index 8e2c565c82..ec7889d2f4 100644 --- a/public/language/uk/aria.json +++ b/public/language/uk/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "User watched tags", "delete-upload-button": "Delete upload button", - "group-page-link-for": "Group page link for %1" + "group-page-link-for": "Group page link for %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/uk/category.json b/public/language/uk/category.json index 9894aa9015..bfb5999b59 100644 --- a/public/language/uk/category.json +++ b/public/language/uk/category.json @@ -1,12 +1,13 @@ { "category": "Категорія", "subcategories": "Підкатегорія", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Новий запис", "guest-login-post": "Увійдіть, щоб постити", "no-topics": " У цій категорії немає жодної теми.
Чому б вам не створити першу?", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "переглядають", "no-replies": "Немає відповідей", "no-new-posts": "Немає нових постів.", diff --git a/public/language/uk/error.json b/public/language/uk/error.json index e19c23dc67..0702946f65 100644 --- a/public/language/uk/error.json +++ b/public/language/uk/error.json @@ -3,6 +3,7 @@ "invalid-json": "Некоректний формат JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Не схоже, що ви увійшли в систему.", "account-locked": "Ваш акаунт тимчасово заблоковано", "search-requires-login": "Для пошуку потрібен акаунт — будь ласка, увійдіть чи зареєструйтесь.", @@ -146,6 +147,7 @@ "post-already-restored": "Цей пост вже відновлено", "topic-already-deleted": "Ця тема вже була видалена", "topic-already-restored": "Ця тема вже була відновлена", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Ви не можете видалити головний пост, натомість видаліть тему.", "topic-thumbnails-are-disabled": "Мініатюри теми вимкнено.", "invalid-file": "Невірний файл", @@ -154,6 +156,8 @@ "about-me-too-long": "Вибачте, але \"Про мене\" не може бути довшим за %1 символ(и).", "cant-chat-with-yourself": "Ви не можете писати самому собі!", "chat-restricted": "Цей користувач обмежив повідомлення. Він має стежити за вами, перш ніж ви зможете спілкуватися з ним", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "You have been blocked by this user.", "chat-disabled": "Чат вимкнено", "too-many-messages": "Ви надіслали забагато повідомлень, зачекайте трішки.", @@ -225,6 +229,7 @@ "no-topics-selected": "Не вибрано жодної теми!", "cant-move-to-same-topic": "Ви не можете перемістити пост до тієї ж самої теми!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Ви не можете заблокувати самого себе!", "cannot-block-privileged": "Ви не можете заблокувати адміністраторів або глобальних модераторів", "cannot-block-guest": "Гості не можуть блокувати інших користувачів", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/uk/global.json b/public/language/uk/global.json index 6278cce5c7..079e665255 100644 --- a/public/language/uk/global.json +++ b/public/language/uk/global.json @@ -68,6 +68,7 @@ "users": "Користувачі", "topics": "Теми", "posts": "Пости", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "Проти", "views": "Перегляди", "posters": "Posters", + "watching": "Watching", "reputation": "Репутація", "lastpost": "Останній допис", "firstpost": "Перший допис", diff --git a/public/language/uk/groups.json b/public/language/uk/groups.json index b2ff1e8a32..205d674f15 100644 --- a/public/language/uk/groups.json +++ b/public/language/uk/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "All groups", "groups": "Групи", "members": "Members", + "x-members": "%1 member(s)", "view-group": "Переглянути групу", "owner": "Власник групи", "new-group": "Створити нову групу", diff --git a/public/language/uk/modules.json b/public/language/uk/modules.json index 476a18f3f0..2924f18eb0 100644 --- a/public/language/uk/modules.json +++ b/public/language/uk/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "Сховати попередній перегляд", "composer.help": "Help", "composer.user-said-in": "%1 написав в %2:", - "composer.user-said": "%1 написав:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Ви впевнені, що хочете скасувати цей пост?", "composer.submit-and-lock": "Надіслати і заблокувати", "composer.toggle-dropdown": "Показати випадаючий список", diff --git a/public/language/uk/notifications.json b/public/language/uk/notifications.json index 6111a139e3..86212dddcb 100644 --- a/public/language/uk/notifications.json +++ b/public/language/uk/notifications.json @@ -22,7 +22,7 @@ "upvote": "Схвалення", "awards": "Awards", "new-flags": "Нові Скарги", - "my-flags": "Скарги, подані на мене", + "my-flags": "My Flags", "bans": "Бани", "new-message-from": "Нове повідомлення від %1", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1 проголосував за ваш пост в %2.", - "upvoted-your-post-in-dual": "%1 та %2 проголосували за ваш пост в %3.", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 поскаржився на профіль користувача (%2)", "user-flagged-user-dual": "%1 та %2 поскаржились на профіль користувача (%3)", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 запостив відповідь на: %2", - "user-posted-to-dual": "%1 та %2 запостили відповіді до: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 запостив нову тему: %2", - "user-edited-post": "%1 has edited a post in %2", - "user-posted-topic-with-tag": "%1 has posted %2 (tagged %3)", - "user-posted-topic-with-tag-dual": "%1 has posted %2 (tagged %3 and %4)", - "user-posted-topic-with-tag-triple": "%1 has posted %2 (tagged %3, %4, and %5)", - "user-posted-topic-with-tag-multiple": "%1 has posted %2 (tagged %3)", - "user-posted-topic-in-category": "%1 has posted a new topic in %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 почав стежити за вами.", "user-started-following-you-dual": "%1 та %2 почали стежити за вами.", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Електронну пошту підтверджено", "email-confirmed-message": "Дякуємо за підтвердження електронної пошти. Ваш акаунт тепер повністю активовано.", "email-confirm-error-message": "При перевірці вашої електронної пошти сталася проблема. Можливо код був недійсним або простроченим.", - "email-confirm-error-message-already-validated": "Your email address was already validated.", "email-confirm-sent": "Підтвердження по електронній пошті було надіслано.", "none": "Немає", "notification-only": "Тільки сповіщення", diff --git a/public/language/uk/social.json b/public/language/uk/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/uk/social.json +++ b/public/language/uk/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn" + "sign-up-with-linkedin": "Sign up with LinkedIn", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/uk/themes/harmony.json b/public/language/uk/themes/harmony.json index 727a1b0553..7143d1b56d 100644 --- a/public/language/uk/themes/harmony.json +++ b/public/language/uk/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", diff --git a/public/language/uk/topic.json b/public/language/uk/topic.json index d7648e7b15..7c2ccc6659 100644 --- a/public/language/uk/topic.json +++ b/public/language/uk/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 referenced this topic on %3", "user-forked-topic-ago": "%1 forked this topic %3", "user-forked-topic-on": "%1 forked this topic on %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Натисніть тут, щоб повернутися до останнього прочитаного посту у цій темі.", "flag-post": "Flag this post", "flag-user": "Flag this user", @@ -103,6 +105,7 @@ "thread-tools.lock": "Заблокувати тему", "thread-tools.unlock": "Розблокувати тему", "thread-tools.move": "Перемістити тему", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Перемістити Пости", "thread-tools.move-all": "Перемістити всі", "thread-tools.change-owner": "Змінити Власника", @@ -132,6 +135,7 @@ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.", "load-categories": "Завантаження категорій", "confirm-move": "Перемістити", + "confirm-crosspost": "Cross-post", "confirm-fork": "Відгалужити", "bookmark": "Закладка", "bookmarks": "Закладки", @@ -141,6 +145,7 @@ "loading-more-posts": "Завантажуємо більше постів", "move-topic": "Перемістити тему", "move-topics": "Перемістити теми", + "crosspost-topic": "Cross-post Topic", "move-post": "Перемістити пост", "post-moved": "Пост переміщено!", "fork-topic": "Відгалужити тему", @@ -163,6 +168,9 @@ "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Клікніть на дописи які ви хочете призначити іншому користувачу", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Уведіть заголовок теми...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -174,6 +182,7 @@ "composer.replying-to": "Відповідь для %1", "composer.new-topic": "Cтворити тему", "composer.editing-in": "Editing post in %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "завантаження...", "composer.thumb-url-label": "Вставте URL мініатюри теми", "composer.thumb-title": "Додати мініатюру цій темі", @@ -224,5 +233,8 @@ "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/uk/user.json b/public/language/uk/user.json index 69d4d130e7..4c918c3cf9 100644 --- a/public/language/uk/user.json +++ b/public/language/uk/user.json @@ -105,6 +105,10 @@ "show-email": "Показувати мою пошту", "show-fullname": "Показувати повне ім'я", "restrict-chats": "Дозволяти чат повідомлення лише від користувачів за якими я стежу", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "Підписатися на дайджест", "digest-description": "Підписатися на оновлення цього форуму по електронній пошті (нові оповіщення та теми) згідно заданого розкладу", "digest-off": "Ніколи", diff --git a/public/language/uk/world.json b/public/language/uk/world.json index 3753335278..e6694bf507 100644 --- a/public/language/uk/world.json +++ b/public/language/uk/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/ur/_DO_NOT_EDIT_FILES_HERE.md b/public/language/ur/_DO_NOT_EDIT_FILES_HERE.md new file mode 100644 index 0000000000..1faf87ad65 --- /dev/null +++ b/public/language/ur/_DO_NOT_EDIT_FILES_HERE.md @@ -0,0 +1,3 @@ +# The files here are not meant to be edited directly + +Please see the → [Internalization README](../README.md). \ No newline at end of file diff --git a/public/language/ur/admin/admin.json b/public/language/ur/admin/admin.json new file mode 100644 index 0000000000..09f3b37699 --- /dev/null +++ b/public/language/ur/admin/admin.json @@ -0,0 +1,18 @@ +{ + "alert.confirm-rebuild-and-restart": "کیا آپ واقعی نوڈ بی بی کو دوبارہ بنانا اور ری اسٹارٹ کرنا چاہتے ہیں؟", + "alert.confirm-restart": "کیا آپ واقعی نوڈ بی بی کو ری اسٹارٹ کرنا چاہتے ہیں؟", + + "acp-title": "%1 | نوڈ بی بی ایڈمنسٹریٹر کنٹرول پینل", + "settings-header-contents": "مواد", + "changes-saved": "تبدیلیاں محفوظ ہو گئیں", + "changes-saved-message": "آپ کی نوڈ بی بی کی ترتیبات میں تبدیلیاں محفوظ ہو گئیں۔", + "changes-not-saved": "تبدیلیاں محفوظ نہیں ہوئیں", + "changes-not-saved-message": "نوڈ بی بی میں آپ کی تبدیلیاں محفوظ کرنے میں ایک مسئلہ پیش آیا۔ (%1)", + "save-changes": "تبدیلیاں محفوظ کریں", + "min": "کم سے کم:", + "max": "زیادہ سے زیادہ:", + "view": "دیکھیں", + "edit": "ترمیم", + "add": "شامل کریں", + "select-icon": "آئیکن منتخب کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/advanced/cache.json b/public/language/ur/admin/advanced/cache.json new file mode 100644 index 0000000000..4a379a4c17 --- /dev/null +++ b/public/language/ur/admin/advanced/cache.json @@ -0,0 +1,6 @@ +{ + "cache": "کیش", + "percent-full": "بھرائی: %1%", + "post-cache-size": "پوسٹ کیش کا سائز", + "items-in-cache": "کیش میں موجود آئٹمز" +} \ No newline at end of file diff --git a/public/language/ur/admin/advanced/database.json b/public/language/ur/admin/advanced/database.json new file mode 100644 index 0000000000..c4bfddd979 --- /dev/null +++ b/public/language/ur/admin/advanced/database.json @@ -0,0 +1,52 @@ +{ + "x-b": "%1 بی", + "x-mb": "%1 ایم بی", + "x-gb": "%1 جی بی", + "uptime-seconds": "فعال وقت سیکنڈز میں", + "uptime-days": "فعال وقت دنوں میں", + + "mongo": "مونگو ڈی بی", + "mongo.version": "مونگو ڈی بی ورژن", + "mongo.storage-engine": "سٹوریج سسٹم", + "mongo.collections": "کلیکشنز", + "mongo.objects": "آبجیکٹس", + "mongo.avg-object-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": "بائٹس ان", + "mongo.bytes-out": "بائٹس آؤٹ", + "mongo.num-requests": "درخواستوں کی تعداد", + "mongo.raw-info": "مونگو ڈی بی سے خام ڈیٹا", + "mongo.unauthorized": "نوڈ بی بی مونگو ڈی بی سے مطلوبہ اعدادوشمار حاصل کرنے میں ناکام رہا۔ براہ کرم یقینی بنائیں کہ نوڈ بی بی کی طرف سے استعمال ہونے والا صارف 'ایڈمن' ڈیٹا بیس کے لیے 'کلسٹر مانیٹر' کردار شامل کرتا ہے۔", + + "redis": "ریڈس", + "redis.version": "ریڈس ورژن", + "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": "ریڈس سے خام ڈیٹا", + + "postgres": "پوسٹ گریس", + "postgres.version": "پوسٹ گریس کیو ایل ورژن", + "postgres.raw-info": "پوسٹ گریس سے خام ڈیٹا" +} diff --git a/public/language/ur/admin/advanced/errors.json b/public/language/ur/admin/advanced/errors.json new file mode 100644 index 0000000000..90576eda48 --- /dev/null +++ b/public/language/ur/admin/advanced/errors.json @@ -0,0 +1,15 @@ +{ + "errors": "غلطیاں", + "figure-x": "شکل %1", + "error-events-per-day": "%1 واقعات فی دن", + "error.404": "صفحہ نہیں ملا (غلطی 404)", + "error.503": "سروس دستیاب نہیں (غلطی 503)", + "manage-error-log": "غلطیوں کے لاگ کا انتظام", + "export-error-log": "غلطیوں کے لاگ کو برآمد کریں (سی ایس وی)", + "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/ur/admin/advanced/events.json b/public/language/ur/admin/advanced/events.json new file mode 100644 index 0000000000..51678462e3 --- /dev/null +++ b/public/language/ur/admin/advanced/events.json @@ -0,0 +1,17 @@ +{ + "events": "واقعات", + "no-events": "کوئی واقعات نہیں", + "control-panel": "واقعات کا کنٹرول پینل", + "delete-events": "واقعات حذف کریں", + "confirm-delete-all-events": "کیا آپ واقعی لاگ میں موجود تمام واقعات کو حذف کرنا چاہتے ہیں؟", + "filters": "فلٹرز", + "filters-apply": "فلٹرز لگائیں", + "filter-type": "واقعہ کی قسم", + "filter-start": "شروع کی تاریخ", + "filter-end": "ختم کی تاریخ", + "filter-user": "صارف کے مطابق فلٹر", + "filter-user.placeholder": "فلٹر کرنے کے لیے صارف کا نام درج کریں…", + "filter-group": "گروپ کے مطابق فلٹر", + "filter-group.placeholder": "فلٹر کرنے کے لیے گروپ کا نام درج کریں…", + "filter-per-page": "فی صفحہ" +} \ No newline at end of file diff --git a/public/language/ur/admin/advanced/logs.json b/public/language/ur/admin/advanced/logs.json new file mode 100644 index 0000000000..02dd594828 --- /dev/null +++ b/public/language/ur/admin/advanced/logs.json @@ -0,0 +1,7 @@ +{ + "logs": "لاگز", + "control-panel": "لاگز کا کنٹرول پینل", + "reload": "لاگز دوبارہ لوڈ کریں", + "clear": "لاگز صاف کریں", + "clear-success": "لاگز صاف ہو گئے!" +} \ No newline at end of file diff --git a/public/language/ur/admin/appearance/customise.json b/public/language/ur/admin/appearance/customise.json new file mode 100644 index 0000000000..0e016c3555 --- /dev/null +++ b/public/language/ur/admin/appearance/customise.json @@ -0,0 +1,20 @@ +{ + "customise": "مرضی کے مطابق بنائیں", + "custom-css": "مرضی کا سی ایس ایس/ساس", + "custom-css.description": "اپنی مرضی کی سی ایس ایس/ساس ڈیکلریشنز یہاں درج کریں۔ یہ تمام دیگر سٹائلز کے بعد لگائی جائیں گی۔", + "custom-css.enable": "مرضی کا سی ایس ایس/ساس فعال کریں", + + "custom-js": "مرضی کا جاوا اسکرپٹ کوڈ", + "custom-js.description": "اپنا مرضی کا جاوا اسکرپٹ کوڈ یہاں درج کریں۔ یہ صفحہ مکمل لوڈ ہونے کے بعد چلایا جائے گا۔", + "custom-js.enable": "مرضی کا جاوا اسکرپٹ کوڈ فعال کریں", + + "custom-header": "مرضی کی ہیڈر", + "custom-header.description": "اپنا مرضی کا ایچ ٹی ایم ایل کوڈ یہاں درج کریں (جیسے کہ میٹا ایلیمنٹس وغیرہ)، یہ آپ کے فورم کے کوڈ میں <head> سیکشن میں شامل ہوں گے۔ اسکرپٹ ایلیمنٹس کی اجازت ہے، لیکن یہ غیر مشورہ ہے، کیونکہ اس کے لیے آپ مرضی کا جاوا اسکرپٹ کوڈ سیکشن استعمال کر سکتے ہیں۔", + "custom-header.enable": "مرضی کی ہیڈر فعال کریں", + + "custom-css.livereload": "فوری ری لوڈ فعال کریں", + "custom-css.livereload.description": "اگر آپ اسے فعال کرتے ہیں، تو آپ کے اکاؤنٹ استعمال کرنے والے ہر ڈیوائس پر تمام سیشنز ری لوڈ ہوں گے جب آپ 'محفوظ کریں' دبائیں گے۔", + "bsvariables": "_variables.scss", + "bsvariables.description": "یہاں آپ بوٹسٹریپ متغیرات کو تبدیل کر سکتے ہیں۔ آپ bootstrap.build جیسا ٹول بھی استعمال کر سکتے ہیں اور اس کا نتیجہ یہاں کاپی کر سکتے ہیں۔
تبدیلیوں کے لیے دوبارہ بنانے اور ری اسٹارٹ کی ضرورت ہے۔", + "bsvariables.enable": "_variables.scss فعال کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/appearance/skins.json b/public/language/ur/admin/appearance/skins.json new file mode 100644 index 0000000000..8980e0ba52 --- /dev/null +++ b/public/language/ur/admin/appearance/skins.json @@ -0,0 +1,18 @@ +{ + "skins": "جلدیں", + "bootswatch-skins": "بوٹس واچ جلدیں", + "custom-skins": "مرضی کی جلدیں", + "add-skin": "جلد شامل کریں", + "save-custom-skins": "مرضی کی جلدیں محفوظ کریں", + "save-custom-skins-success": "مرضی کی جلدیں کامیابی سے محفوظ ہو گئیں", + "custom-skin-name": "مرضی کی جلد کا نام", + "custom-skin-variables": "مرضی کی جلد کے متغیرات", + "loading": "جلدیں لوڈ ہو رہی ہیں…", + "homepage": "ہوم پیج", + "select-skin": "جلد منتخب کریں", + "revert-skin": "جلد واپس کریں", + "current-skin": "موجودہ جلد", + "skin-updated": "جلد تبدیل ہو گئی", + "applied-success": "جلد '%1' کامیابی سے لگائی گئی", + "revert-success": "جلد بنیادی رنگوں کی طرف واپس کر دی گئی۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/appearance/themes.json b/public/language/ur/admin/appearance/themes.json new file mode 100644 index 0000000000..218968383d --- /dev/null +++ b/public/language/ur/admin/appearance/themes.json @@ -0,0 +1,13 @@ +{ + "themes": "تھیمز", + "checking-for-installed": "نصب شدہ تھیمز کی جانچ ہو رہی ہے…", + "homepage": "ہوم پیج", + "select-theme": "تھیم منتخب کریں", + "revert-theme": "تھیم واپس کریں", + "current-theme": "موجودہ تھیم", + "no-themes": "کوئی نصب شدہ تھیمز نہیں ملے", + "revert-confirm": "کیا آپ واقعی نوڈ بی بی کی طے شدہ تھیم کو بحال کرنا چاہتے ہیں؟", + "theme-changed": "تھیم تبدیل ہو گئی", + "revert-success": "آپ نے نوڈ بی بی کی طے شدہ تھیم کو کامیابی سے بحال کر دیا۔", + "restart-to-activate": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری اسٹارٹ کریں تاکہ یہ تھیم مکمل طور پر اثر انداز ہو سکے۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/dashboard.json b/public/language/ur/admin/dashboard.json new file mode 100644 index 0000000000..a5de565b6c --- /dev/null +++ b/public/language/ur/admin/dashboard.json @@ -0,0 +1,102 @@ +{ + "forum-traffic": "فورم ٹریفک", + "page-views": "صفحہ مناظر", + "unique-visitors": "منفرد زائرین", + "logins": "لاگ انز", + "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": "کل", + "stats.today": "آج", + "stats.last-week": "پچھلا ہفتہ", + "stats.this-week": "یہ ہفتہ", + "stats.last-month": "پچھلا مہینہ", + "stats.this-month": "یہ مہینہ", + "stats.all": "شروع سے", + + "updates": "اپ ڈیٹس", + "running-version": "آپ نوڈ بی بی ورژن %1 استعمال کر رہے ہیں۔", + "keep-updated": "ہمیشہ نوڈ بی بی کا تازہ ترین ورژن استعمال کرنے کی کوشش کریں تاکہ تازہ ترین سیکیورٹی بہتری اور مسائل کے حل سے فائدہ اٹھائیں۔", + "up-to-date": "آپ تازہ ترین ورژن استعمال کر رہے ہیں ", + "upgrade-available": "ایک نیا ورژن (%1) دستیاب ہے۔ اگر ممکن ہو تو، نوڈ بی بی اپ ڈیٹ کریں۔", + "prerelease-upgrade-available": "یہ نوڈ بی بی کا ایک پرانا پری ریلیز ورژن ہے۔ ایک نیا ورژن (%1) دستیاب ہے۔ اگر ممکن ہو تو، نوڈ بی بی اپ ڈیٹ کریں۔", + "prerelease-warning": "یہ نوڈ بی بی کا پری ریلیز ورژن ہے۔ غیر متوقع خرابیاں ہو سکتی ہیں۔ ", + "fallback-emailer-not-found": "بیک اپ ای میلر نہیں ملا", + "running-in-development": "فورم ڈیولپمنٹ موڈ میں چل رہا ہے، اس لیے یہ کمزور ہو سکتا ہے۔ براہ کرم اپنے سسٹم ایڈمنسٹریٹر سے رابطہ کریں۔", + "latest-lookup-failed": "نوڈ بی بی کے تازہ ترین دستیاب ورژن کی جانچ نہیں کی جا سکی", + + "notices": "نوٹسز", + "restart-not-required": "ری اسٹارٹ کی ضرورت نہیں", + "restart-required": "ری اسٹارٹ کی ضرورت ہے", + "search-plugin-installed": "تلاش پلگ ان نصب ہے", + "search-plugin-not-installed": "تلاش پلگ ان نصب نہیں ہے", + "search-plugin-tooltip": "تلاش کی فعالیت کو فعال کرنے کے لیے پلگ انز صفحہ سے تلاش پلگ ان نصب کریں", + + "control-panel": "سسٹم کنٹرول", + "rebuild-and-restart": "دوبارہ بنائیں اور ری اسٹارٹ کریں", + "restart": "ری اسٹارٹ", + "restart-warning": "نوڈ بی بی کو دوبارہ بنانے اور ری اسٹارٹ کرنے سے چند سیکنڈ کے لیے تمام کنکشنز منقطع ہو جائیں گے۔", + "restart-disabled": "نوڈ بی بی کے دوبارہ بنانے اور ری اسٹارٹ کی سہولیات غیر فعال ہیں، کیونکہ لگتا ہے کہ نوڈ بی بی مناسب ڈیمن کے ذریعے نہیں چل رہا۔", + "maintenance-mode": "مینٹیننس موڈ", + "maintenance-mode-title": "نوڈ بی بی کو مینٹیننس موڈ سیٹ کرنے کے لیے یہاں کلک کریں", + "dark-mode": "ڈارک موڈ", + "realtime-chart-updates": "ریئل ٹائم چارٹ اپ ڈیٹس", + + "active-users": "فعال صارفین", + "active-users.users": "صارفین", + "active-users.guests": "مہمان", + "active-users.total": "کل", + "active-users.connections": "کنکشنز", + + "guest-registered-users": "مہمان بمقابلہ رجسٹرڈ صارفین", + "guest": "مہمان", + "registered": "رجسٹرڈ", + + "user-presence": "صارفین کی موجودگی", + "on-categories": "زمرہ جات کی فہرست میں", + "reading-posts": "پوسٹس پڑھ رہے ہیں", + "browsing-topics": "موضوعات براؤز کر رہے ہیں", + "recent": "حالیہ", + "unread": "غیر پڑھا", + + "high-presence-topics": "زیادہ موجودگی والے موضوعات", + "popular-searches": "مقبول تلاشیں", + + "graphs.page-views": "صفحہ مناظر", + "graphs.page-views-registered": "رجسٹرڈ صارفین کے صفحہ مناظر", + "graphs.page-views-guest": "مہمانوں کے صفحہ مناظر", + "graphs.page-views-bot": "بوٹس کے صفحہ مناظر", + "graphs.page-views-ap": "ایکٹیویٹی پب سے صفحہ مناظر", + "graphs.unique-visitors": "منفرد زائرین", + "graphs.registered-users": "رجسٹرڈ صارفین", + "graphs.guest-users": "مہمان", + "last-restarted-by": "آخری بار ری اسٹارٹ کیا گیا", + "no-users-browsing": "کوئی براؤز کرنے والے صارفین نہیں", + + "back-to-dashboard": "ڈیش بورڈ پر واپس", + "details.no-users": "منتخب کردہ مدت میں کوئی نئے صارفین رجسٹر نہیں ہوئے", + "details.no-topics": "منتخب کردہ مدت میں کوئی نئے موضوعات شائع نہیں ہوئے", + "details.no-searches": "منتخب کردہ مدت میں کوئی تلاشیں نہیں کی گئیں", + "details.no-logins": "منتخب کردہ مدت میں کوئی لاگ انز رجسٹر نہیں ہوئے", + "details.logins-static": "نوڈ بی بی %1 دنوں تک سیشن ڈیٹا محفوظ رکھتا ہے، اس لیے درج ذیل جدول میں صرف آخری فعال سیشنز دیکھے جا سکتے ہیں", + "details.logins-login-time": "لاگ ان کا وقت", + "start": "شروع", + "end": "ختم", + "filter": "فلٹر", + "view-as-json": "جیسن کے طور پر دیکھیں", + "expand-analytics": "تجزیات پھیلائیں", + "clear-search-history": "تلاش کی تاریخ صاف کریں", + "clear-search-history-confirm": "کیا آپ واقعی تلاش کی تاریخ صاف کرنا چاہتے ہیں؟", + "search-term": "تلاش کی اصطلاح", + "search-count": "تعداد", + "view-all": "سب دیکھیں" +} diff --git a/public/language/ur/admin/development/info.json b/public/language/ur/admin/development/info.json new file mode 100644 index 0000000000..b6b18ab615 --- /dev/null +++ b/public/language/ur/admin/development/info.json @@ -0,0 +1,26 @@ +{ + "you-are-on": "آپ %1:%2 پر ہیں", + "ip": "آئی پی %1", + "nodes-responded": "%1 نوڈز نے %2 ملی سیکنڈز میں جواب دیا!", + "host": "سرور", + "primary": "بنیادی / ٹاسکس", + "pid": "پروسیس آئی ڈی", + "nodejs": "نوڈ جے ایس", + "online": "آن لائن", + "git": "گٹ", + "process-memory": "rss/heap used", + "system-memory": "سسٹم میموری", + "used-memory-process": "پروسیس کی استعمال شدہ میموری", + "used-memory-os": "سسٹم کی استعمال شدہ میموری", + "total-memory-os": "کل سسٹم میموری", + "load": "سسٹم کا بوجھ", + "cpu-usage": "سی پی یو کا استعمال", + "uptime": "فعال وقت", + + "registered": "رجسٹرڈ", + "sockets": "ساکٹس", + "connection-count": "کنکشنز کی تعداد", + "guests": "مہمان", + + "info": "معلومات" +} \ No newline at end of file diff --git a/public/language/ur/admin/development/logger.json b/public/language/ur/admin/development/logger.json new file mode 100644 index 0000000000..6bc918549d --- /dev/null +++ b/public/language/ur/admin/development/logger.json @@ -0,0 +1,13 @@ +{ + "logger": "لاگ", + "logger-settings": "لاگ کی ترتیبات", + "description": "اگر آپ یہاں چیک مارک لگائیں گے، تو آپ اپنے ٹرمنل میں لاگ دیکھیں گے۔ اگر آپ کوئی پاتھ بتائیں گے، تو اس کے بجائے لاگز فائل میں محفوظ ہوں گے۔ ایچ ٹی ٹی پی کے ذریعے لاگنگ آپ کے فورم کو کب، کون، اور کس طرح کے لوگ وزٹ کر رہے ہیں اس کے اعدادوشمار حاصل کرنے کے لیے مفید ہے۔ ایچ ٹی ٹی پی درخواستوں کو ٹریک کرنے کے علاوہ، ہم socket.io کے واقعات کو بھی ٹریک کر سکتے ہیں۔ Socket.io لاگنگ، redis-cli کے ساتھ مل کر، نوڈ بی بی کے کام کرنے کے طریقے کو سمجھنے کے لیے بہت مفید ہو سکتی ہے۔", + "explanation": "ریئل ٹائم میں لاگز کو فعال یا غیر فعال کرنے کے لیے، بس لاگ کی ترتیبات میں چیک مارک لگائیں یا ہٹائیں۔ ری اسٹارٹ کی ضرورت نہیں ہے۔", + "enable-http": "ایچ ٹی ٹی پی لاگنگ فعال کریں", + "enable-socket": "socket.io واقعات کے لاگز فعال کریں", + "file-path": "لاگ فائل کا پاتھ", + "file-path-placeholder": "/پاتھ/ٹو/لاگ/فائل.log ::: اگر خالی ہو، تو لاگ ٹرمنل میں آؤٹ پٹ ہوگا", + + "control-panel": "لاگ کا کنٹرول پینل", + "update-settings": "لاگ کی ترتیبات اپ ڈیٹ کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/extend/plugins.json b/public/language/ur/admin/extend/plugins.json new file mode 100644 index 0000000000..9a183c1a80 --- /dev/null +++ b/public/language/ur/admin/extend/plugins.json @@ -0,0 +1,58 @@ +{ + "plugins": "پلگ انز", + "trending": "رجحانات", + "installed": "نصب شدہ", + "active": "فعال", + "inactive": "غیر فعال", + "out-of-date": "پرانا", + "none-found": "کوئی پلگ انز نہیں ملے۔", + "none-active": "کوئی فعال پلگ انز نہیں۔", + "find-plugins": "پلگ انز تلاش کریں", + + "plugin-search": "پلگ ان تلاش", + "plugin-search-placeholder": "پلگ ان تلاش کریں…", + "submit-anonymous-usage": "پلگ ان کے استعمال کے گمنام ڈیٹا بھیجیں", + "reorder-plugins": "پلگ انز کو دوبارہ ترتیب دیں", + "order-active": "فعال پلگ انز کو ترتیب دیں", + "dev-interested": "کیا آپ نوڈ بی بی کے لیے پلگ انز لکھنے میں دلچسپی رکھتے ہیں؟", + "docs-info": "پلگ ان بنانے کے بارے میں مکمل دستاویزات نوڈ بی بی دستاویزات پورٹل پر مل سکتی ہیں۔", + + "order.description": "کچھ پلگ انز بہترین طریقے سے کام کرتے ہیں اگر انہیں دوسرے پلگ انز سے پہلے یا بعد میں نصب کیا جائے۔", + "order.explanation": "پلگ انز اس ترتیب سے لوڈ ہوتے ہیں جو یہاں بتائی گئی ہے، اوپر سے نیچے تک۔", + + "plugin-item.themes": "تھیمز", + "plugin-item.deactivate": "غیر فعال کریں", + "plugin-item.activate": "فعال کریں", + "plugin-item.install": "نصب کریں", + "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": "یہ پلگ ان نوڈ بی بی %1 کے ساتھ کام کرتا ہے", + "plugin-item.not-compatible": "اس پلگ ان کے پاس مطابقت کی معلومات نہیں ہیں۔ براہ کرم یقینی بنائیں کہ یہ آپ کے اصلی سرور پر نصب کرنے سے پہلے کام کرتا ہے۔", + + "alert.enabled": "پلگ ان فعال ہو گیا", + "alert.disabled": "پلگ ان غیر فعال ہو گیا", + "alert.upgraded": "پلگ ان اپ گریڈ ہو گیا", + "alert.installed": "پلگ ان نصب ہو گیا", + "alert.uninstalled": "پلگ ان ہٹایا گیا", + "alert.activate-success": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری لوڈ کریں تاکہ یہ پلگ ان مکمل طور پر فعال ہو جائے۔", + "alert.deactivate-success": "پلگ ان کامیابی سے غیر فعال ہو گیا۔", + "alert.upgrade-success": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری لوڈ کریں تاکہ یہ پلگ ان مکمل طور پر اپ ڈیٹ ہو جائے۔", + "alert.install-success": "پلگ ان کامیابی سے نصب ہو گیا، براہ کرم اسے فعال کریں", + "alert.uninstall-success": "پلگ ان کامیابی سے غیر فعال اور ہٹایا گیا۔", + "alert.suggest-error": "

نوڈ بی بی پیکیج مینیجر سے رابطہ نہیں کر سکا۔ کیا آپ تازہ ترین ورژن کی تنصیب کے ساتھ جاری رکھنا چاہتے ہیں؟

سرور نے واپس کیا (%1): %2
", + "alert.package-manager-unreachable": "

نوڈ بی بی پیکیج مینیجر سے رابطہ نہیں کر سکا۔ فی الحال اپ گریڈ کی سفارش نہیں کی جاتی۔

", + "alert.incompatible": "

آپ کا نوڈ بی بی ورژن (ورژن %1) اس پلگ ان کا زیادہ سے زیادہ ورژن %2 استعمال کر سکتا ہے۔ براہ کرم نوڈ بی بی کو اپ ڈیٹ کریں اگر آپ اس پلگ ان کا نیا ورژن نصب کرنا چاہتے ہیں۔

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

مطابقت کی معلومات نہیں

اس پلگ ان نے آپ کے نوڈ بی بی ورژن کے ساتھ مطابقت کے لیے کوئی مخصوص ورژن نہیں بتایا۔ ہم مکمل مطابقت کی ضمانت نہیں دے سکتے اور ہو سکتا ہے کہ آپ کا نوڈ بی بی صحیح طریقے سے شروع نہ ہو۔

اگر نوڈ بی بی شروع نہیں ہوتا، تو درج ذیل کمانڈ استعمال کریں:

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

کیا آپ اس پلگ ان کے تازہ ترین ورژن کی تنصیب کے ساتھ جاری رکھنا چاہتے ہیں؟

", + "alert.reorder": "پلگ انز کو دوبارہ ترتیب دیا گیا", + "alert.reorder-success": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری اسٹارٹ کریں تاکہ یہ عمل مکمل ہو جائے۔", + + "license.title": "پلگ ان کی лиценس معلومات", + "license.intro": "پلگ ان '%1' '%2' лиценس استعمال کرتا ہے۔ براہ کرم лиценس کے شرائط کو پڑھیں اور یقینی بنائیں کہ آپ انہیں سمجھتے ہیں، اس سے پہلے کہ آپ پلگ ان کو فعال کریں۔", + "license.cta": "کیا آپ اس پلگ ان کو فعال کرنے کے ساتھ جاری رکھنا چاہتے ہیں؟" +} diff --git a/public/language/ur/admin/extend/rewards.json b/public/language/ur/admin/extend/rewards.json new file mode 100644 index 0000000000..1346827606 --- /dev/null +++ b/public/language/ur/admin/extend/rewards.json @@ -0,0 +1,17 @@ +{ + "rewards": "انعامات", + "add-reward": "انعام شامل کریں", + "condition-if-users": "اگر صارف کا", + "condition-is": "ہے:", + "condition-then": "تو:", + "max-claims": "انعام کتنی بار حاصل کیا جا سکتا ہے", + "zero-infinite": "0 = لامحدود بار", + "select-reward": "انعام منتخب کریں", + "delete": "حذف", + "enable": "فعال کریں", + "disable": "غیر فعال کریں", + + "alert.delete-success": "انعام کامیابی سے حذف ہو گیا", + "alert.no-inputs-found": "غلط انعام — کچھ بھی درج نہیں کیا گیا!", + "alert.save-success": "انعامات کامیابی سے محفوظ ہو گئے" +} \ No newline at end of file diff --git a/public/language/ur/admin/extend/widgets.json b/public/language/ur/admin/extend/widgets.json new file mode 100644 index 0000000000..9a7e9ff69d --- /dev/null +++ b/public/language/ur/admin/extend/widgets.json @@ -0,0 +1,37 @@ +{ + "widgets": "ویجٹس", + "available": "دستیاب ویجٹس", + "explanation": "ڈراپ ڈاؤن مینو سے ایک ویجٹ منتخب کریں، پھر اسے بائیں جانب موجود ٹیمپلیٹس میں سے کسی ویجٹ ایریا میں گھسیٹ کر چھوڑیں۔", + "none-installed": "کوئی ویجٹس نہیں ملے! پلگ انز کنٹرول پینل میں بنیادی ویجٹس پلگ ان کو فعال کریں۔", + "clone-from": "ویجٹس کو کلون کریں از", + "containers.available": "دستیاب کنٹینرز", + "containers.explanation": "کسی ویجٹ پر گھسیٹیں اور چھوڑیں", + "containers.none": "کوئی نہیں", + "container.well": "ویب", + "container.jumbotron": "جمبوٹران", + "container.card": "کارڈ", + "container.card-header": "کارڈ ہیڈر", + "container.card-body": "کارڈ باڈی", + "container.title": "عنوان", + "container.body": "مواد", + "container.alert": "انتباہ", + + "alert.confirm-delete": "کیا آپ واقعی اس ویجٹ کو حذف کرنا چاہتے ہیں؟", + "alert.updated": "ویجٹس اپ ڈیٹ ہو گئے", + "alert.update-success": "ویجٹس کامیابی سے اپ ڈیٹ ہو گئے", + "alert.clone-success": "ویجٹس کامیابی سے کلون ہو گئے", + + "error.select-clone": "کلون کرنے کے لیے ایک صفحہ منتخب کریں", + + "title": "عنوان", + "title.placeholder": "عنوان (صرف کچھ کنٹینرز میں دکھایا جاتا ہے)", + "container": "کنٹینر", + "container.placeholder": "کنٹینر گھسیٹیں اور چھوڑیں یا یہاں ایچ ٹی ایم ایل درج کریں۔", + "show-to-groups": "گروپس کو دکھائیں", + "hide-from-groups": "گروپس سے چھپائیں", + "start-date": "شروع کی تاریخ", + "end-date": "ختم کی تاریخ", + "hide-on-mobile": "موبائل ڈیوائسز پر چھپائیں", + "hide-drafts": "ڈرافٹس چھپائیں", + "show-drafts": "ڈرافٹس دکھائیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/admins-mods.json b/public/language/ur/admin/manage/admins-mods.json new file mode 100644 index 0000000000..3e68321b97 --- /dev/null +++ b/public/language/ur/admin/manage/admins-mods.json @@ -0,0 +1,13 @@ +{ + "manage-admins-and-mods": "ایڈمنسٹریٹرز اور ماڈریٹرز کا انتظام", + "administrators": "ایڈمنسٹریٹرز", + "global-moderators": "عالمی ماڈریٹرز", + "moderators": "ماڈریٹرز", + "no-global-moderators": "کوئی عالمی ماڈریٹرز نہیں", + "no-sub-categories": "کوئی ذیلی زمرہ جات نہیں", + "view-children": "ذیلی زمرہ جات دیکھیں (%1)", + "no-moderators": "کوئی ماڈریٹرز نہیں", + "add-administrator": "ایڈمنسٹریٹر شامل کریں", + "add-global-moderator": "عالمی ماڈریٹر شامل کریں", + "add-moderator": "ماڈریٹر شامل کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json new file mode 100644 index 0000000000..006428fca4 --- /dev/null +++ b/public/language/ur/admin/manage/categories.json @@ -0,0 +1,131 @@ +{ + "manage-categories": "زمرہ جات کا انتظام", + "add-category": "زمرہ شامل کریں", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", + "jump-to": "پر جائیں…", + "settings": "زمرہ کی ترتیبات", + "edit-category": "زمرہ ترمیم کریں", + "privileges": "اختیارات", + "back-to-categories": "زمرہ جات پر واپس", + "id": "Category ID", + "name": "زمرہ کا نام", + "handle": "زمرہ کا شناخت کنندہ", + "handle.help": "زمرہ کا شناخت کنندہ اس زمرہ کو دیگر نیٹ ورکس میں پیش کرنے کے لیے استعمال ہوتا ہے، جیسے کہ صارف نام۔ اس شناخت کنندہ کا موجودہ صارف نام یا صارف گروپ سے مماثل نہیں ہونا چاہیے۔", + "description": "زمرہ کی تفصیل", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", + "bg-color": "پس منظر کا رنگ", + "text-color": "متن کا رنگ", + "bg-image-size": "پس منظر تصویر کا سائز", + "custom-class": "مرضی کی کلاس", + "num-recent-replies": "حالیہ جوابات کی تعداد", + "ext-link": "خارجی لنک", + "subcategories-per-page": "فی صفحہ ذیلی زمرہ جات کی تعداد", + "is-section": "اس زمرہ کو سیکشن کے طور پر استعمال کریں", + "post-queue": "پوسٹ قطار", + "tag-whitelist": "اجازت شدہ ٹیگز کی فہرست", + "upload-image": "تصویر اپ لوڈ کریں", + "upload": "اپ لوڈ", + "delete-image": "حذف", + "category-image": "زمرہ کی تصویر", + "image-and-icon": "تصویر اور آئیکن", + "parent-category": "بنیادی زمرہ", + "optional-parent-category": "(اختیاری) بنیادی زمرہ", + "top-level": "اعلیٰ سطح", + "parent-category-none": "(کوئی نہیں)", + "copy-parent": "بنیادی سے نقل کریں", + "copy-settings": "سے ترتیبات نقل کریں", + "optional-clone-settings": "(اختیاری) زمرہ سے ترتیبات نقل کریں", + "clone-children": "ذیلی زمرہ جات اور ترتیبات کلون کریں", + "purge": "زمرہ حذف کریں", + + "enable": "فعال کریں", + "disable": "غیر فعال کریں", + "edit": "ترمیم", + "analytics": "تجزیات", + "federation": "فیڈریشن", + + "view-category": "زمرہ دیکھیں", + "set-order": "ترتیب محفوظ کریں", + "set-order-help": "زمرہ کے لیے پوزیشن سیٹ کرنے سے وہ مطلوبہ جگہ پر منتقل ہو جائے گا اور اگر ضروری ہو تو دیگر زمرہ جات کی جگہوں کو تبدیل کر دے گا۔ سب سے چھوٹا ممکنہ نمبر 1 ہے، جو زمرہ کو سب سے اوپر رکھے گا۔", + + "select-category": "زمرہ منتخب کریں", + "set-parent-category": "بنیادی زمرہ سیٹ کریں", + + "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.inheritance-exception": "یہ گروپ رجسٹرڈ صارفین کے گروپ سے اختیارات وراثت میں نہیں لیتا", + "privileges.banned-user-inheritance": "پابندی شدہ صارفین پابندی شدہ صارفین کے گروپ سے اختیارات وراثت میں لیتے ہیں", + "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": "اگر رجسٹرڈ صارفین گروپ کو کوئی اختیار دیا جاتا ہے، تو باقی تمام گروپس اسے طے شدہ اختیار کے طور پر حاصل کرتے ہیں، چاہے وہ انہیں خاص طور پر نہ دیا گیا ہو۔ آپ یہ طے شدہ اختیار دیکھ رہے ہیں کیونکہ تمام صارفین رجسٹرڈ صارفین گروپ کے رکن ہیں، اس لیے ایک ہی اختیارات کو مزید گروہوں کو دینے کی ضرورت نہیں ہے۔", + "privileges.copy-success": "اختیارات نقل ہو گئے!", + + "analytics.back": "زمرہ جات کی فہرست پر واپس", + "analytics.title": "زمرہ '%1' کے لیے تجزیاتی ڈیٹا", + "analytics.pageviews-hourly": "شکل 1 – اس زمرہ کے لیے گھنٹہ وار صفحہ مناظر", + "analytics.pageviews-daily": "شکل 2 – اس زمرہ کے لیے روزانہ صفحہ مناظر", + "analytics.topics-daily": "شکل 3 – اس زمرہ میں روزانہ موضوعات کی تعداد", + "analytics.posts-daily": "شکل 4 – اس زمرہ میں روزانہ پوسٹس کی تعداد", + + "federation.title": "زمرہ '%1' کے لیے فیڈریشن ترتیبات", + "federation.disabled": "ویب سائٹ کے لیے فیڈریشن غیر فعال ہے، اس لیے زمرہ کی فیڈریشن ترتیبات ناقابل رسائی ہیں۔", + "federation.disabled-cta": "فیڈریشن ترتیبات →", + "federation.syncing-header": "ہم آہنگی", + "federation.syncing-intro": "ایک زمرہ ایکٹیویٹی پب پروٹوکول کے ذریعے 'ذرائع کے گروپ' کو فالو کر سکتا ہے۔ اگر نیچے دیے گئے کسی بھی ذریعہ سے مواد موصول ہوتا ہے، تو وہ خود بخود اس زمرہ میں شامل ہو جائے گا۔", + "federation.syncing-caveat": "نوٹ: یہاں ہم آہنگی کی ترتیبات یک طرفہ ہم آہنگی قائم کرتی ہیں۔ نوڈ بی بی ذریعہ کو سبسکرائب/فالو کرنے کی کوشش کرے گا، لیکن اس کے برعکس کوئی مفروضہ نہیں ہونا چاہیے۔", + "federation.syncing-none": "یہ زمرہ فی الحال کسی کو فالو نہیں کر رہا۔", + "federation.syncing-add": "کے ساتھ ہم آہنگی کریں…", + "federation.syncing-actorUri": "ذریعہ", + "federation.syncing-follow": "فالو کریں", + "federation.syncing-unfollow": "فالو بند کریں", + "federation.followers": "اس زمرہ کو فالو کرنے والے ریموٹ صارفین", + "federation.followers-handle": "شناخت کنندہ", + "federation.followers-id": "آئی ڈی", + "federation.followers-none": "کوئی فالوورز نہیں۔", + "federation.followers-autofill": "خودکار بھریں", + + "alert.created": "بنایا گیا", + "alert.create-success": "زمرہ کامیابی سے بنایا گیا!", + "alert.none-active": "آپ کے کوئی فعال زمرہ جات نہیں ہیں۔", + "alert.create": "زمرہ بنائیں", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", + "alert.confirm-purge": "

کیا آپ واقعی زمرہ '%1' کو حذف کرنا چاہتے ہیں؟

انتباہ! اس زمرہ کے تمام موضوعات اور پوسٹس حذف ہو جائیں گی!

زمرہ حذف کرنے سے تمام موضوعات اور پوسٹس ہٹ جائیں گی، اور زمرہ ڈیٹا بیس سے حذف ہو جائے گا۔ اگر آپ زمرہ کو عارضی طور پر ہٹانا چاہتے ہیں، تو آپ اسے صرف 'غیر فعال' کر سکتے ہیں۔

", + "alert.purge-success": "زمرہ حذف ہو گیا!", + "alert.copy-success": "ترتیبات نقل ہو گئیں!", + "alert.set-parent-category": "بنیادی زمرہ سیٹ کریں", + "alert.updated": "زمرہ جات اپ ڈیٹ ہو گئے", + "alert.updated-success": "شناخت کنندہ %1 کے ساتھ زمرہ جات کامیابی سے اپ ڈیٹ ہو گئے۔", + "alert.upload-image": "زمرہ کے لیے تصویر اپ لوڈ کریں", + "alert.find-user": "صارف تلاش کریں", + "alert.user-search": "یہاں صارف تلاش کریں…", + "alert.find-group": "گروپ تلاش کریں", + "alert.group-search": "یہاں گروپ تلاش کریں…", + "alert.not-enough-whitelisted-tags": "اجازت شدہ ٹیگز کم ہیں۔ آپ کو مزید اجازت شدہ ٹیگز بنانے کی ضرورت ہے!", + "collapse-all": "سب سمیٹیں", + "expand-all": "سب پھیلائیں", + "disable-on-create": "بنانے پر غیر فعال کریں", + "no-matches": "کوئی مماثلت نہیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/custom-reasons.json b/public/language/ur/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/ur/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/digest.json b/public/language/ur/admin/manage/digest.json new file mode 100644 index 0000000000..9724bf4451 --- /dev/null +++ b/public/language/ur/admin/manage/digest.json @@ -0,0 +1,22 @@ +{ + "lead": "نیچے ڈائجسٹ بھیجنے کے اعدادوشمار اور اوقات دکھائے گئے ہیں۔", + "disclaimer": "براہ کرم نوٹ کریں کہ ای میل کی ترسیل کی کوئی ضمانت نہیں ہے، کیونکہ ای میل ٹیکنالوجی کی نوعیت ایسی ہے۔ بہت سی چیزیں اس بات پر اثر انداز ہوتی ہیں کہ آیا بھیجا گیا ای میل واقعی وصول کنندہ تک پہنچتا ہے، جیسے کہ سرور کی ساکھ، بلاک شدہ آئی پی ایڈریسز، یا DKIM/SPF/DMARC کی ترتیب۔", + "disclaimer-continued": "کامیاب ترسیل کا مطلب ہے کہ پیغام نوڈ بی بی سے کامیابی سے بھیجا گیا اور وصول کنندہ کے سرور نے اس کی تصدیق کی۔ اس کا مطلب یہ نہیں کہ ای میل وصول کنندہ کے ان باکس میں پہنچ گیا۔ بہتر نتائج کے لیے، میں ایک خصوصی ای میل بھیجنے کی سروس، جیسے کہ SendGrid، استعمال کرنے کی سفارش کرتا ہوں۔", + + "user": "صارف", + "subscription": "سبسکرپشن کی قسم", + "last-delivery": "آخری کامیاب ترسیل", + "default": "سسٹم کے لیے طے شدہ", + "default-help": "سسٹم کے لیے طے شدہ کا مطلب ہے کہ صارف نے ڈائجسٹ کے لیے عالمی فورم کی ترتیب کے طور پر کوئی دوسری ترتیبات دستی طور پر منتخب نہیں کی، جو فی الحال '%1' ہے۔", + "resend": "ڈائجسٹ دوبارہ بھیجیں", + "resend-all-confirm": "کیا آپ واقعی ڈائجسٹ کو دستی طور پر بھیجنے کا عمل شروع کرنا چاہتے ہیں؟", + "resent-single": "ڈائجسٹ کا دستی طور پر دوبارہ بھیجنا مکمل ہو گیا", + "resent-day": "روزانہ ڈائجسٹ دوبارہ بھیجا گیا", + "resent-week": "ہفتہ وار ڈائجسٹ دوبارہ بھیجا گیا", + "resent-biweek": "دو ہفتہ وار ڈائجسٹ دوبارہ بھیجا گیا", + "resent-month": "ماہانہ ڈائجسٹ دوبارہ بھیجا گیا", + "null": "کبھی نہیں", + "manual-run": "ڈائجسٹ کا دستی طور پر بھیجنا:", + + "no-delivery-data": "ترسیل کے کوئی ڈیٹا نہیں" +} diff --git a/public/language/ur/admin/manage/groups.json b/public/language/ur/admin/manage/groups.json new file mode 100644 index 0000000000..d74d14de3a --- /dev/null +++ b/public/language/ur/admin/manage/groups.json @@ -0,0 +1,49 @@ +{ + "manage-groups": "گروپس کا انتظام", + "add-group": "گروپ شامل کریں", + "edit-group": "گروپ ترمیم کریں", + "back-to-groups": "گروپس پر واپس", + "view-group": "گروپ دیکھیں", + "icon-and-title": "آئیکن اور عنوان", + "name": "گروپ کا نام", + "badge": "بیج", + "properties": "خصوصیات", + "description": "گروپ کی تفصیل", + "member-count": "اراکین کی تعداد", + "system": "سسٹم", + "hidden": "چھپا ہوا", + "private": "نجی", + "edit": "ترمیم", + "delete": "حذف", + "privileges": "اختیارات", + "members-csv": "اراکین (سی ایس وی)", + "search-placeholder": "تلاش", + "create": "گروپ بنائیں", + "description-placeholder": "گروپ کی مختصر تفصیل", + "create-button": "بنائیں", + + "alerts.create-failure": "اوہ!

گروپ بنانے میں ایک مسئلہ پیش آیا۔ براہ کرم بعد میں دوبارہ کوشش کریں!

", + "alerts.confirm-delete": "کیا آپ واقعی اس گروپ کو حذف کرنا چاہتے ہیں؟", + + "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": "کوئی صارفین نہیں ملے", + "edit.confirm-remove-user": "کیا آپ واقعی اس صارف کو ہٹانا چاہتے ہیں؟" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/privileges.json b/public/language/ur/admin/manage/privileges.json new file mode 100644 index 0000000000..890c834e65 --- /dev/null +++ b/public/language/ur/admin/manage/privileges.json @@ -0,0 +1,67 @@ +{ + "manage-privileges": "اختیارات کا انتظام", + "discard-changes": "تبدیلیاں مسترد کریں", + "global": "عالمی", + "admin": "ایڈمن", + "group-privileges": "گروپس کے لیے اختیارات", + "user-privileges": "صارفین کے لیے اختیارات", + "edit-privileges": "اختیارات ترمیم کریں", + "select-clear-all": "سب منتخب کریں/سب صاف کریں", + "chat": "گفتگو", + "chat-with-privileged": "اعلیٰ اختیارات والے سے گفتگو", + "upload-images": "تصاویر اپ لوڈ کریں", + "upload-files": "فائلیں اپ لوڈ کریں", + "signature": "دستخط", + "ban": "پابندی", + "mute": "خاموش", + "invite": "دعوت بھیجیں", + "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": "موضوعات میں جواب دیں", + "crosspost-topics": "Cross-post Topics", + "schedule-topics": "موضوعات شیڈول کریں", + "tag-topics": "موضوعات پر ٹیگز لگائیں", + "edit-posts": "پوسٹس ترمیم کریں", + "view-edit-history": "ترمیمی تاریخ دیکھیں", + "delete-posts": "پوسٹس حذف کریں", + "view-deleted": "حذف شدہ پوسٹس دیکھیں", + "upvote-posts": "پوسٹس کے لیے مثبت ووٹ", + "downvote-posts": "پوسٹس کے لیے منفی ووٹ", + "delete-topics": "موضوعات حذف کریں", + "purge": "صاف کریں", + "moderate": "ماڈریٹ", + "admin-dashboard": "ڈیش بورڈ", + "admin-categories": "زمرہ جات", + "admin-privileges": "اختیارات", + "admin-users": "صارفین", + "admin-admins-mods": "ایڈمنسٹریٹرز اور ماڈریٹرز", + "admin-groups": "گروپس", + "admin-tags": "ٹیگز", + "admin-settings": "ترتیبات", + + "alert.confirm-moderate": "کیا آپ واقعی اس صارف گروپ کو ماڈریشن کا اختیار دینا چاہتے ہیں؟ یہ گروپ عوامی ہے اور کوئی بھی اس میں آزادانہ طور پر شامل ہو سکتا ہے۔", + "alert.confirm-admins-mods": "کیا آپ واقعی اس صارف/گروپ کو 'ایڈمنسٹریٹرز اور ماڈریٹرز' کا اختیار دینا چاہتے ہیں؟ اس اختیار کے حامل صارفین دوسرے گروپس کے اختیارات تبدیل کر سکتے ہیں، بشمول سپر ایڈمنسٹریٹر کا اختیار دینا۔", + "alert.confirm-save": "براہ کرم ان اختیارات کو محفوظ کرنے کی اپنی خواہش کی تصدیق کریں", + "alert.confirm-discard": "کیا آپ واقعی اختیارات کی تبدیلیوں کو مسترد کرنا چاہتے ہیں؟", + "alert.discarded": "اختیارات کی تبدیلیاں مسترد کر دی گئیں", + "alert.confirm-copyToAll": "کیا آپ واقعی اس %1 سیٹ کو تمام زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.confirm-copyToAllGroup": "کیا آپ واقعی اس گروپ کے %1 سیٹ کو تمام زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.confirm-copyToChildren": "کیا آپ واقعی اس %1 سیٹ کو تمام ذیلی زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.confirm-copyToChildrenGroup": "کیا آپ واقعی اس گروپ کے %1 سیٹ کو تمام ذیلی زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.no-undo": "یہ عمل ناقابل واپسی ہے۔", + "alert.admin-warning": "ایڈمنسٹریٹرز کے پاس طے شدہ طور پر تمام اختیارات ہوتے ہیں", + "alert.copyPrivilegesFrom-title": "نقل کرنے کے لیے زمرہ منتخب کریں", + "alert.copyPrivilegesFrom-warning": "یہ منتخب کردہ زمرہ سے %1 نقل کرے گا۔", + "alert.copyPrivilegesFromGroup-warning": "یہ منتخب کردہ زمرہ سے اس گروپ کے %1 سیٹ کو نقل کرے گا۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/registration.json b/public/language/ur/admin/manage/registration.json new file mode 100644 index 0000000000..0f98371113 --- /dev/null +++ b/public/language/ur/admin/manage/registration.json @@ -0,0 +1,20 @@ +{ + "queue": "قطار", + "description": "رجسٹریشن کی قطار میں کوئی صارفین نہیں ہیں۔
اس فعالیت کو فعال کرنے کے لیے، ترتیبات → صارف → صارفین کی رجسٹریشن پر جائیں اور رجسٹریشن کی قسم کو 'ایڈمنسٹریٹر کی منظوری' پر سیٹ کریں۔", + + "list.name": "نام", + "list.email": "ای میل", + "list.ip": "آئی پی ایڈریس", + "list.time": "وقت", + "list.username-spam": "تکرار: %1 ظاہر ہونا: %2 اعتماد: %3", + "list.email-spam": "تکرار: %1 ظاہر ہونا: %2", + "list.ip-spam": "تکرار: %1 ظاہر ہونا: %2", + + "invitations": "دعوتیں", + "invitations.description": "نیچے بھیجی گئی دعوتوں کی مکمل فہرست ملے گی۔ فہرست میں ای میل یا صارف نام تلاش کرنے کے لیے 'Ctrl-F' استعمال کریں۔

صارف نام اس صارف کے ای میل کے دائیں جانب دکھایا جائے گا جنہوں نے اپنی دعوت قبول کی ہو۔", + "invitations.inviter-username": "دعوت دینے والے کا صارف نام", + "invitations.invitee-email": "مدعو کیے گئے کا ای میل", + "invitations.invitee-username": "مدعو کیے گئے کا صارف نام (اگر رجسٹرڈ ہو)", + + "invitations.confirm-delete": "کیا آپ واقعی اس دعوت کو حذف کرنا چاہتے ہیں؟" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/tags.json b/public/language/ur/admin/manage/tags.json new file mode 100644 index 0000000000..e3a104f17b --- /dev/null +++ b/public/language/ur/admin/manage/tags.json @@ -0,0 +1,20 @@ +{ + "manage-tags": "ٹیگز کا انتظام", + "none": "فورم میں ابھی تک ٹیگ شدہ موضوعات نہیں ہیں۔", + "bg-color": "پس منظر کا رنگ", + "text-color": "متن کا رنگ", + "description": "کلک یا گھسیٹ کر ٹیگز منتخب کریں۔ ایک سے زیادہ ٹیگز منتخب کرنے کے لیے CTRL استعمال کریں۔", + "create": "ٹیگ بنائیں", + "add-tag": "ٹیگ شامل کریں", + "modify": "ٹیگز ترمیم کریں", + "rename": "ٹیگز کا نام تبدیل کریں", + "delete": "منتخب ٹیگز حذف کریں", + "search": "ٹیگز تلاش کریں…", + "settings": "ٹیگز کی ترتیبات", + "name": "ٹیگ کا نام", + + "alerts.editing": "ٹیگ(s) ترمیم ہو رہا ہے", + "alerts.confirm-delete": "کیا آپ واقعی منتخب ٹیگز کو حذف کرنا چاہتے ہیں؟", + "alerts.update-success": "ٹیگ تبدیل ہو گیا!", + "reset-colors": "طے شدہ رنگ بحال کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/uploads.json b/public/language/ur/admin/manage/uploads.json new file mode 100644 index 0000000000..2e93264f61 --- /dev/null +++ b/public/language/ur/admin/manage/uploads.json @@ -0,0 +1,12 @@ +{ + "manage-uploads": "اپ لوڈز کا انتظام", + "upload-file": "فائل اپ لوڈ کریں", + "filename": "فائل کا نام", + "usage": "پوسٹس میں استعمال", + "orphaned": "غیر استعمال شدہ", + "size/filecount": "سائز / فائلوں کی تعداد", + "confirm-delete": "کیا آپ واقعی اس فائل کو حذف کرنا چاہتے ہیں؟", + "filecount": "%1 فائلیں", + "new-folder": "نیا فولڈر", + "name-new-folder": "نئے فولڈر کا نام درج کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/user-custom-fields.json b/public/language/ur/admin/manage/user-custom-fields.json new file mode 100644 index 0000000000..852009baaa --- /dev/null +++ b/public/language/ur/admin/manage/user-custom-fields.json @@ -0,0 +1,28 @@ +{ + "title": "صارف کے حسب ضرورت فیلڈز کا انتظام", + "create-field": "فیلڈ بنائیں", + "edit-field": "فیلڈ میں ترمیم کریں", + "manage-custom-fields": "حسب ضرورت فیلڈز کا انتظام", + "type-of-input": "ان پٹ کی قسم", + "key": "کلید", + "name": "نام", + "icon": "آئیکن", + "type": "قسم", + "min-rep": "کم از کم ساکھ", + "input-type-text": "ان پٹ (متن)", + "input-type-link": "ان پٹ (لنک)", + "input-type-number": "ان پٹ (نمبر)", + "input-type-date": "ان پٹ (تاریخ)", + "input-type-select": "انتخاب", + "input-type-select-multi": "متعدد انتخاب", + "select-options": "اختیارات", + "select-options-help": "منتخب عنصر کے لیے ہر سطر پر ایک آپشن شامل کریں", + "minimum-reputation": "کم از کم ساکھ", + "minimum-reputation-help": "اگر صارف کی ساکھ مقررہ مقدار سے کم ہو تو وہ اس فیلڈ کو استعمال نہیں کر سکے گا", + "delete-field-confirm-x": "کیا آپ واقعی حسب ضرورت فیلڈ '%1' کو حذف کرنا چاہتے ہیں؟", + "custom-fields-saved": "حسب ضرورت فیلڈز محفوظ کر دیے گئے ہیں", + "visibility": "مرئیت", + "visibility-all": "ہر کوئی فیلڈ دیکھ سکتا ہے", + "visibility-loggedin": "صرف لاگ ان صارفین فیلڈ دیکھ سکتے ہیں", + "visibility-privileged": "صرف اعلیٰ اختیارات والے صارفین (جیسے ایڈمنسٹریٹرز اور ماڈریٹرز) فیلڈ دیکھ سکتے ہیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/users.json b/public/language/ur/admin/manage/users.json new file mode 100644 index 0000000000..9365e4057d --- /dev/null +++ b/public/language/ur/admin/manage/users.json @@ -0,0 +1,155 @@ +{ + "manage-users": "صارفین کا انتظام", + "users": "صارفین", + "edit": "عمل", + "make-admin": "ایڈمنسٹریٹر کے حقوق دینا", + "remove-admin": "ایڈمنسٹریٹر کے حقوق ہٹانا", + "change-email": "ای میل تبدیل کریں", + "new-email": "نیا ای میل", + "validate-email": "ای میل کی تصدیق", + "send-validation-email": "تصدیقی ای میل بھیجیں", + "change-password": "پاس ورڈ تبدیل کریں", + "password-reset-email": "پاس ورڈ بحالی کا ای میل بھیجیں", + "force-password-reset": "پاس ورڈ کو زبردستی بحال کریں اور صارف کو سائن آؤٹ کریں", + "ban": "پابندی", + "ban-users": "صارف/صارفین پر پابندی لگائیں", + "temp-ban": "صارف/صارفین پر عارضی پابندی لگائیں", + "unban": "صارف/صارفین کی پابندی ہٹائیں", + "reset-lockout": "لاک آؤٹ ری سیٹ کریں", + "reset-flags": "رپورٹس منسوخ کریں", + "delete": "حذف", + "delete-users": "صارف/صارفین کو حذف کریں", + "delete-content": "صارف/صارفین کے مواد کو حذف کریں", + "purge": "صارف/صارفین اور مواد کو حذف کریں", + "download-csv": "CSV فارمیٹ میں ڈاؤن لوڈ کریں", + "custom-user-fields": "مرضی کے صارف فیلڈز", + "custom-reasons": "Custom Reasons", + "manage-groups": "گروپس کا انتظام", + "set-reputation": "ساکھ سیٹ کریں", + "add-group": "گروپ شامل کریں", + "create": "صارف بنائیں", + "invite": "ای میل کے ذریعے دعوت دیں", + "new": "نیا صارف", + "filter-by": "فلٹر بمطابق", + "pills.unvalidated": "ای میل تصدیق شدہ نہیں", + "pills.validated": "تصدیق شدہ", + "pills.banned": "پابندی شدہ", + + "50-per-page": "50 فی صفحہ", + "100-per-page": "100 فی صفحہ", + "250-per-page": "250 فی صفحہ", + "500-per-page": "500 فی صفحہ", + + "search.uid": "صارف شناخت کنندہ کے ذریعے", + "search.uid-placeholder": "تلاش کرنے کے لیے صارف شناخت کنندہ درج کریں", + "search.username": "صارف نام کے ذریعے", + "search.username-placeholder": "تلاش کرنے کے لیے صارف نام درج کریں", + "search.email": "ای میل کے ذریعے", + "search.email-placeholder": "تلاش کرنے کے لیے ای میل درج کریں", + "search.ip": "آئی پی ایڈریس کے ذریعے", + "search.ip-placeholder": "تلاش کرنے کے لیے آئی پی ایڈریس درج کریں", + "search.not-found": "صارف نہیں ملا!", + + "inactive.3-months": "3 ماہ", + "inactive.6-months": "6 ماہ", + "inactive.12-months": "12 ماہ", + + "users.uid": "صارف آئی ڈی", + "users.username": "صارف نام", + "users.email": "ای میل", + "users.no-email": "(کوئی ای میل نہیں)", + "users.validated": "تصدیق شدہ", + "users.not-validated": "غیر تصدیق شدہ", + "users.validation-pending": "تصدیق زیر التوا", + "users.validation-expired": "تصدیق کی میعاد ختم", + "users.ip": "آئی پی ایڈریس", + "users.postcount": "پوسٹس کی تعداد", + "users.reputation": "ساکھ", + "users.flags": "رپورٹس", + "users.joined": "شامل ہوا", + "users.last-online": "آخری بار آن لائن", + "users.banned": "پابندی شدہ", + + "create.username": "صارف نام", + "create.email": "ای میل", + "create.email-placeholder": "اس صارف کا ای میل", + "create.password": "پاس ورڈ", + "create.password-confirm": "پاس ورڈ کی تصدیق کریں", + + "temp-ban.length": "دورانیہ", + "temp-ban.reason": "وجہ (اختیاری)", + "temp-ban.select-reason": "Select a reason", + "temp-ban.hours": "گھنٹے", + "temp-ban.days": "دن", + "temp-ban.explanation": "پابندی کا دورانیہ درج کریں۔ 0 کی قیمت پابندی کو مستقل بنائے گی۔", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", + + "alerts.confirm-ban": "کیا آپ واقعی اس صارف پر مستقل پابندی لگانا چاہتے ہیں؟", + "alerts.confirm-ban-multi": "کیا آپ واقعی ان صارفین پر مستقل پابندی لگانا چاہتے ہیں؟", + "alerts.ban-success": "صارف/صارفین پر پابندی لگائی گئی!", + "alerts.button-ban-x": "%1 صارف/صارفین پر پابندی لگائیں", + "alerts.unban-success": "صارف/صارفین کی پابندی ہٹائی گئی!", + "alerts.lockout-reset-success": "لاک آؤٹ/لاک آؤٹس ری سیٹ ہو گئے!", + "alerts.password-change-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.password-reset-email-sent": "پاس ورڈ بحالی کا ای میل بھیج دیا گیا۔", + "alerts.confirm-delete": "انتباہ!

کیا آپ واقعی صارف/صارفین کو حذف کرنا چاہتے ہیں؟

یہ عمل ناقابل واپسی ہے! صرف صارف/صارفین کا پروفائل حذف ہوگا، ان کی پوسٹس اور موضوعات باقی رہیں گے۔

", + "alerts.delete-success": "صارف/صارفین حذف ہو گئے!", + "alerts.confirm-delete-content": "انتباہ!

کیا آپ واقعی اس صارف یا ان صارفین کے مواد کو حذف کرنا چاہتے ہیں؟

یہ عمل ناقابل واپسی ہے! صارفین کے پروفائلز باقی رہیں گے، لیکن ان کی تمام پوسٹس اور موضوعات حذف ہو جائیں گے۔

", + "alerts.delete-content-success": "صارف/صارفین کا مواد حذف ہو گیا!", + "alerts.confirm-purge": "انتباہ!

کیا آپ واقعی صارف/صارفین اور ان کے مواد کو حذف کرنا چاہتے ہیں؟

یہ عمل ناقابل واپسی ہے! تمام صارفی ڈیٹا اور مواد ختم ہو جائے گا!

", + "alerts.create": "صارف بنائیں", + "alerts.button-create": "بنائیں", + "alerts.button-cancel": "منسوخ", + "alerts.button-change": "تبدیل", + "alerts.error-passwords-different": "پاس ورڈز مختلف ہیں!", + "alerts.error-x": "خرابی

%1

", + "alerts.create-success": "صارف بنایا گیا!", + + "alerts.prompt-email": "ای میلز: ", + "alerts.email-sent-to": "%1 کو تصدیقی ای میل بھیجا گیا", + "alerts.x-users-found": "صارفین ملے: %1 (%2 سیکنڈ)", + "alerts.select-a-single-user-to-change-email": "ای میل تبدیل کرنے کے لیے ایک صارف منتخب کریں", + "export": "برآمد", + "export-users-fields-title": "CSV کے لیے فیلڈز منتخب کریں", + "export-field-email": "ای میل", + "export-field-username": "صارف نام", + "export-field-uid": "صارف شناخت کنندہ", + "export-field-ip": "آئی پی ایڈریس", + "export-field-joindate": "شامل ہونے کی تاریخ", + "export-field-lastonline": "آخری بار آن لائن", + "export-field-lastposttime": "آخری جواب کا وقت", + "export-field-reputation": "ساکھ", + "export-field-postcount": "پوسٹس کی تعداد", + "export-field-topiccount": "موضوعات کی تعداد", + "export-field-profileviews": "پروفائل مناظر", + "export-field-followercount": "فالوورز کی تعداد", + "export-field-followingcount": "فالو کیے جانے والوں کی تعداد", + "export-field-fullname": "مکمل نام", + "export-field-website": "ویب سائٹ", + "export-field-location": "مقام", + "export-field-birthday": "سالگرہ", + "export-field-signature": "دستخط", + "export-field-aboutme": "صارف کے بارے میں", + + "export-users-started": "صارفین کو CSV فارمیٹ میں برآمد کیا جا رہا ہے… اس میں کچھ وقت لگ سکتا ہے۔ جب یہ مکمل ہو جائے گا تو آپ کو اطلاع دی جائے گی۔", + "export-users-completed": "صارفین CSV فارمیٹ میں برآمد ہو گئے، ڈاؤن لوڈ کے لیے کلک کریں۔", + "email": "ای میل", + "password": "پاس ورڈ", + "manage": "انتظام" +} \ No newline at end of file diff --git a/public/language/ur/admin/menu.json b/public/language/ur/admin/menu.json new file mode 100644 index 0000000000..cd15806540 --- /dev/null +++ b/public/language/ur/admin/menu.json @@ -0,0 +1,93 @@ +{ + "section-dashboard": "ڈیش بورڈ", + "dashboard/overview": "جائزہ", + "dashboard/logins": "لاگ انز", + "dashboard/users": "صارفین", + "dashboard/topics": "موضوعات", + "dashboard/searches": "تلاشیں", + "section-general": "عمومی", + + "section-manage": "انتظام", + "manage/categories": "زمرہ جات", + "manage/privileges": "اختیارات", + "manage/tags": "ٹیگز", + "manage/users": "صارفین", + "manage/admins-mods": "ایڈمنسٹریٹرز اور ماڈریٹرز", + "manage/registration": "رجسٹریشن کی قطار", + "manage/flagged-content": "رپورٹ کیا گیا مواد", + "manage/post-queue": "پوسٹس کی قطار", + "manage/groups": "گروپس", + "manage/ip-blacklist": "IP ایڈریسز کا بلیک لسٹ", + "manage/uploads": "اپ لوڈز", + "manage/digest": "خلاصے", + + "section-settings": "ترتیبات", + "settings/general": "عمومی", + "settings/homepage": "ہوم پیج", + "settings/navigation": "نیویگیشن", + "settings/reputation": "ساکھ اور رپورٹس", + "settings/email": "ای میل", + "settings/user": "صارفین", + "settings/group": "گروپس", + "settings/guest": "مہمان", + "settings/uploads": "اپ لوڈز", + "settings/languages": "زبانیں", + "settings/post": "پوسٹس", + "settings/chat": "چیتس", + "settings/pagination": "صفحہ بندی", + "settings/tags": "ٹیگز", + "settings/notifications": "نوٹیفکیشنز", + "settings/api": "API کے ذریعے رسائی", + "settings/activitypub": "فیڈریشن (ActivityPub)", + "settings/sounds": "آوازیں", + "settings/social": "سوشل", + "settings/cookies": "کوکیز", + "settings/web-crawler": "ویب کرالر", + "settings/sockets": "ساکٹس", + "settings/advanced": "اعلیٰ", + + "settings.page-title": "%1 کی ترتیبات", + + "section-appearance": "ظاہری شکل", + "appearance/themes": "تھیمز", + "appearance/skins": "جلدیں", + "appearance/customise": "اپنی مرضی کا مواد (HTML/JS/CSS)", + + "section-extend": "توسیع", + "extend/plugins": "پلگ انز", + "extend/widgets": "ویجٹس", + "extend/rewards": "انعامات", + + "section-social-auth": "سوشل تصدیق", + + "section-plugins": "پلگ انز", + "extend/plugins.install": "پلگ انز انسٹال کریں", + + "section-advanced": "اعلیٰ", + "advanced/database": "ڈیٹا بیس", + "advanced/events": "ایونٹس", + "advanced/hooks": "ہکس", + "advanced/logs": "لاگس", + "advanced/errors": "غلطیاں", + "advanced/cache": "کیش", + "development/logger": "لاگ", + "development/info": "معلومات", + + "rebuild-and-restart-forum": "فورم کو دوبارہ بنائیں اور دوبارہ شروع کریں", + "rebuild-and-restart": "دوبارہ بنائیں اور دوبارہ شروع کریں", + "restart-forum": "فورم دوبارہ شروع کریں", + "restart": "دوبارہ شروع کریں", + "logout": "لاگ آؤٹ", + "view-forum": "فورم دیکھیں", + + "search.placeholder": "ترتیبات تلاش کریں", + "search.no-results": "کوئی نتائج نہیں…", + "search.search-forum": "فورم میں تلاش کریں ", + "search.keep-typing": "مزید نتائج دیکھنے کے لیے لکھتے رہیں…", + "search.start-typing": "نتائج حاصل کرنے کے لیے لکھنا شروع کریں…", + + "connection-lost": "%1 سے رابطہ منقطع ہو گیا۔ ہم آپ کو دوبارہ جوڑنے کی کوشش کر رہے ہیں…", + + "alerts.version": "NodeBB ورژن %1 استعمال ہو رہا ہے", + "alerts.upgrade": "v%1 پر اپ گریڈ کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/activitypub.json b/public/language/ur/admin/settings/activitypub.json new file mode 100644 index 0000000000..9f2b98f72a --- /dev/null +++ b/public/language/ur/admin/settings/activitypub.json @@ -0,0 +1,48 @@ +{ + "intro-lead": "فیڈریشن کیا ہے؟", + "intro-body": "نوڈ بی بی دیگر نوڈ بی بی تنصیبات کے ساتھ رابطہ قائم کر سکتا ہے جو اس کی حمایت کرتی ہیں۔ یہ ایک پروٹوکول کے ذریعے حاصل کیا جاتا ہے جسے ایکٹیویٹی پب کہا جاتا ہے۔ اگر فعال ہو، تو نوڈ بی بی دیگر ایپلی کیشنز اور ویب سائٹس کے ساتھ بھی رابطہ قائم کر سکے گا جو ایکٹیویٹی پب استعمال کرتی ہیں (جیسے کہ ماسٹوڈن، پیئر ٹیوب وغیرہ)۔", + "general": "عام", + "pruning": "مواد ہٹانا", + "content-pruning": "ریموٹ مواد کو محفوظ رکھنے کے دنوں کی تعداد", + "content-pruning-help": "نوٹ کریں کہ ریموٹ مواد جو کسی سرگرمی (جوابات یا مثبت/منفی ووٹ) کا حصہ رہا ہو، محفوظ رہے گا۔ (0 = غیر فعال)", + "user-pruning": "ریموٹ صارف اکاؤنٹس کو کیش کرنے کے دنوں کی تعداد", + "user-pruning-help": "ریموٹ صارف اکاؤنٹس صرف اس صورت میں ہٹائے جائیں گے اگر انہوں نے کچھ پوسٹ نہ کیا ہو۔ بصورت دیگر، وہ دوبارہ حاصل کیے جائیں گے۔ (0 = غیر فعال)", + "enabled": "فیڈریشن فعال کریں", + "enabled-help": "اگر فعال ہو، تو یہ نوڈ بی بی پوری فیڈیورس میں ایکٹیویٹی پب استعمال کرنے والے تمام کلائنٹس کے ساتھ رابطہ قائم کر سکے گا۔", + "allowLoopback": "لوکل لوپ بیک پروسیسنگ کی اجازت دیں", + "allowLoopback-help": "صرف ڈیبگنگ کے لیے مفید۔ اسے غیر فعال رکھنا بہتر ہے۔", + + "probe": "ایپلی کیشن میں کھولیں", + "probe-enabled": "کیا ایکٹیویٹی پب کی حمایت کرنے والی چیزوں کو نوڈ بی بی میں کھولنے کی کوشش کی جائے", + "probe-enabled-help": "اگر فعال ہو، تو نوڈ بی بی ہر خارجی لنک کو چیک کرے گا کہ آیا وہ ایکٹیویٹی پب کی حمایت کرتا ہے اور اگر ہاں، تو اسے براہ راست نوڈ بی بی میں لوڈ کرے گا۔", + "probe-timeout": "پروب ٹائم آؤٹ (ملی سیکنڈز)", + "probe-timeout-help": "(طے شدہ: 2000) اگر پروب کو مقررہ وقت کے اندر جواب نہ ملے، تو صارف کو براہ راست لنک کے ایڈریس پر بھیجا جائے گا۔ اگر ویب سائٹس سست جواب دیتی ہیں اور آپ انہیں زیادہ وقت دینا چاہتے ہیں تو بڑا نمبر سیٹ کریں۔", + + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + + "server-filtering": "فلٹرنگ", + "count": "یہ نوڈ بی بی فی الحال %1 سرور(ز) کے بارے میں جانتا ہے", + "server.filter-help": "ان سرورز کی نشاندہی کریں جن کے ساتھ آپ نہیں چاہتے کہ آپ کا نوڈ بی بی رابطہ قائم کرے۔ یا آپ اس کے بجائے مخصوص سرورز کی نشاندہی کر سکتے ہیں جن کے ساتھ رابطہ کی اجازت ہے۔ دونوں اختیارات دستیاب ہیں، لیکن آپ صرف ایک کا انتخاب کر سکتے ہیں۔", + "server.filter-help-hostname": "نیچے صرف سرورز کے نام درج کریں (جیسے example.org)، ہر سرور ایک الگ لائن پر ہونا چاہیے۔", + "server.filter-allow-list": "اسے اجازت یافتہ سرورز کی فہرست کے طور پر استعمال کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/advanced.json b/public/language/ur/admin/settings/advanced.json new file mode 100644 index 0000000000..59c3dec7fe --- /dev/null +++ b/public/language/ur/admin/settings/advanced.json @@ -0,0 +1,47 @@ +{ + "maintenance-mode": "بحالی کا موڈ", + "maintenance-mode.help": "جب فورم بحالی کے موڈ میں ہوتا ہے، تو تمام درخواستیں ایک سٹیٹک ویٹنگ پیج پر ری ڈائریکٹ ہو جاتی ہیں، سوائے ایڈمنسٹریٹرز کے، جو ویب سائٹ کو معمول کے مطابق استعمال کر سکتے ہیں۔", + "maintenance-mode.status": "بحالی کے موڈ کے لیے سٹیٹس کوڈ", + "maintenance-mode.message": "بحالی کا پیغام", + "maintenance-mode.groups-exempt-from-maintenance-mode": "بحالی کے موڈ سے مستثنیٰ گروپس منتخب کریں", + "headers": "ہیڈرز", + "headers.allow-from": "NodeBB کو iFrame میں رکھنے کے لیے 'ALLOW-FROM' سیٹ کریں", + "headers.csp-frame-ancestors": "NodeBB کو iFrame میں رکھنے کے لیے 'Content-Security-Policy frame-ancestors' ہیڈر سیٹ کریں", + "headers.csp-frame-ancestors-help": "'none' (کچھ نہیں)، 'self' (خود - طے شدہ) یا اجازت شدہ ایڈریسز کی فہرست۔", + "headers.powered-by": "NodeBB سے بھیجے جانے والے 'Powered by' ہیڈر کو حسب ضرورت بنائیں", + "headers.acao": "Access-Control-Allow-Origin", + "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", + "headers.coep": "Cross-Origin-Embedder-Policy", + "headers.coep-help": "جب فعال ہو (طے شدہ)، ہیڈر کی قیمت require-corp ہوگی", + "headers.coop": "Cross-Origin-Opener-Policy", + "headers.corp": "Cross-Origin-Resource-Policy", + "headers.permissions-policy": "Permissions-Policy", + "headers.permissions-policy-help": "'permissions-policy' ہیڈر میں قیمت سیٹ کرنے کی اجازت دیتا ہے، جیسے کہ 'geolocation=*, camera=()'۔ مزید معلومات کے لیے یہاں دیکھیں۔", + "hsts": "سخت ٹرانسپورٹ سیکیورٹی", + "hsts.enabled": "HSTS فعال کریں (تجویز کردہ)", + "hsts.maxAge": "HSTS کی زیادہ سے زیادہ عمر", + "hsts.subdomains": "HSTS ہیڈر میں ذیلی ڈومینز شامل کریں", + "hsts.preload": "HSTS ہیڈر کے لیے پری لوڈنگ کی اجازت دیں", + "hsts.help": "اگر یہ فعال ہو، تو اس ویب کے لیے 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 بوجھ کے اضافے کے لیے زیادہ حساس ہوگا، لیکن چیک کو بہت زیادہ حساس بھی بنا سکتا ہے۔ (ری اسٹارٹ درکار ہے۔)", + + "sockets.settings": "ویب ساکٹ کی ترتیبات", + "sockets.max-attempts": "دوبارہ رابطہ کرنے کی زیادہ سے زیادہ کوششیں", + "sockets.default-placeholder": "طے شدہ: %1", + "sockets.delay": "دوبارہ رابطہ کرنے میں تاخیر", + + "compression.settings": "کمپریشن کی ترتیبات", + "compression.enable": "کمپریشن فعال کریں", + "compression.help": "یہ ترتیب 'gzip' کے ذریعے کمپریشن کو فعال کرتی ہے۔ مصروف ویب سائٹس کے لیے کمپریشن کا بہترین طریقہ ریورس پراکسی لیول پر ہوتا ہے۔ لیکن ٹیسٹنگ کے مقاصد کے لیے، آپ اسے یہاں بھی فعال کر سکتے ہیں۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/api.json b/public/language/ur/admin/settings/api.json new file mode 100644 index 0000000000..2272599c1c --- /dev/null +++ b/public/language/ur/admin/settings/api.json @@ -0,0 +1,29 @@ +{ + "tokens": "شناختی نشان", + "settings": "ترتیبات", + "lead-text": "اس صفحہ پر آپ نوڈ بی بی میں لکھنے کے لیے API تک رسائی کو ترتیب دے سکتے ہیں۔", + "intro": "طے شدہ طور پر، لکھنے کا API صارفین کو ان کے سیشن کوکی کے ذریعے تصدیق کرتا ہے، لیکن نوڈ بی بی اس صفحہ کے ٹوکنز کا استعمال کرتے ہوئے 'Bearer' طریقہ سے تصدیق کی بھی حمایت کرتا ہے۔", + "warning": "احتیاط کریں – ٹوکنز کو پاس ورڈز کی طرح سمجھیں۔ اگر کوئی ان تک رسائی حاصل کر لیتا ہے، تو وہ آپ کے اکاؤنٹ تک رسائی حاصل کر سکتا ہے۔", + "docs": "API کی مکمل دستاویزات تک رسائی کے لیے یہاں کلک کریں", + + "require-https": "API کا استعمال صرف HTTPS کے ذریعے کریں", + "require-https-caveat": "نوٹ: کچھ معاملات میں، جب لوڈ بیلنسرز استعمال کیے جاتے ہیں، تو نوڈ بی بی کو درخواستیں HTTP کے ذریعے بھیجی جا سکتی ہیں – اس صورت میں یہ ترتیب غیر فعال رہنی چاہیے۔", + + "uid": "صارف آئی ڈی", + "token": "شناختی نشان", + "uid-help-text": "اس کوڈ کے ساتھ منسلک کرنے کے لیے صارف آئی ڈی بتائیں۔ اگر آئی ڈی 0 ہو، تو اسے ماسٹر کوڈ سمجھا جائے گا، جو _uid پیرامیٹر کے ذریعے دیگر صارفین کی شناخت اختیار کر سکتا ہے۔", + "description": "تفصیل", + "last-seen": "آخری بار دیکھا گیا", + "created": "بنایا گیا", + "create-token": "شناختی نشان بنائیں", + "update-token": "شناختی نشان اپ ڈیٹ کریں", + "master-token": "ماسٹر شناخت نشان", + "last-seen-never": "یہ کلید کبھی استعمال نہیں ہوئی۔", + "no-description": "کوئی تفصیل نہیں۔", + "actions": "عمل", + "edit": "ترمیم", + "roll": "دوبارہ بنائیں", + + "delete-confirm": "کیا آپ واقعی اس شناخت نشان کو حذف کرنا چاہتے ہیں؟ اس کے بعد اسے بحال نہیں کیا جا سکے گا۔", + "roll-confirm": "کیا آپ واقعی اس شناخت نشان کو دوبارہ بنانا چاہتے ہیں؟ پرانا فوراً ہٹ جائے گا اور اسے بحال نہیں کیا جا سکے گا۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/chat.json b/public/language/ur/admin/settings/chat.json new file mode 100644 index 0000000000..cff3ea08e7 --- /dev/null +++ b/public/language/ur/admin/settings/chat.json @@ -0,0 +1,15 @@ +{ + "zero-is-disabled": "اس پابندی کو غیر فعال کرنے کے لیے 0 درج کریں", + "chat-settings": "گفتگو کی ترتیبات", + "disable": "گفتگو کو غیر فعال کریں", + "disable-editing": "گفتگو کے پیغامات کی ترمیم اور حذف کو غیر فعال کریں", + "disable-editing-help": "یہ پابندی ایڈمنسٹریٹرز اور عالمی ماڈریٹرز پر اثر نہیں کرتی", + "max-length": "گفتگو کے پیغامات کی زیادہ سے زیادہ لمبائی", + "max-length-remote": "ریموٹ گفتگو کے پیغامات کی زیادہ سے زیادہ لمبائی", + "max-length-remote-help": "یہ قیمت عام طور پر مقامی صارفین کے لیے پابندی سے زیادہ ہونی چاہیے، کیونکہ ریموٹ پیغامات عام طور پر ناگزیر طور پر لمبے ہوتے ہیں (@مینشنز وغیرہ کی وجہ سے)۔", + "max-chat-room-name-length": "گفتگو کے کمروں کے ناموں کی زیادہ سے زیادہ لمبائی", + "max-room-size": "گفتگو کے کمرے میں صارفین کی زیادہ سے زیادہ تعداد", + "delay": "گفتگو کے پیغامات کے درمیان وقت (ملی سیکنڈز)", + "restrictions.seconds-edit-after": "گفتگو کے پیغامات کے ترمیم کے لیے سیکنڈز کی تعداد", + "restrictions.seconds-delete-after": "گفتگو کے پیغامات کے حذف کے لیے سیکنڈز کی تعداد" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/cookies.json b/public/language/ur/admin/settings/cookies.json new file mode 100644 index 0000000000..443dbee344 --- /dev/null +++ b/public/language/ur/admin/settings/cookies.json @@ -0,0 +1,13 @@ +{ + "eu-consent": "ای یو رضامندی", + "consent.enabled": "فعال", + "consent.message": "اطلاعی پیغام", + "consent.acceptance": "قبولیت کا پیغام", + "consent.link-text": "پالیسی ٹیکسٹ کا لنک", + "consent.link-url": "پالیسی ایڈریس کا لنک", + "consent.blank-localised-default": "NodeBB کے طے شدہ ترجمہ شدہ ڈیٹا استعمال کرنے کے لیے اسے خالی چھوڑ دیں", + "settings": "ترتیبات", + "cookie-domain": "سیشن کوکی کا ڈومین", + "max-user-sessions": "صارف کے لیے زیادہ سے زیادہ فعال سیشنز", + "blank-default": "طے شدہ قیمت استعمال کرنے کے لیے خالی چھوڑ دیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/email.json b/public/language/ur/admin/settings/email.json new file mode 100644 index 0000000000..f09376a92c --- /dev/null +++ b/public/language/ur/admin/settings/email.json @@ -0,0 +1,60 @@ +{ + "email-settings": "ای میل کی ترتیبات", + "address": "ای میل ایڈریس", + "address-help": "مندرجہ ذیل ای میل ایڈریس وہی ہے جو وصول کنندہ 'From' اور 'Reply-To' فیلڈز میں دیکھے گا۔", + "from": "'From' فیلڈ کے لیے نام", + "from-help": "ای میل میں دکھایا جانے والا بھیجنے والے کا نام۔", + + "confirmation-settings": "تصدیق", + "confirmation.expiry": "تصدیقی لنک کی میعاد، گھنٹوں میں", + + "smtp-transport": "SMTP ٹرانسپورٹ", + "smtp-transport.enabled": "SMTP ٹرانسپورٹ فعال کریں", + "smtp-transport-help": "آپ معروف خدمات کی فہرست سے انتخاب کر سکتے ہیں، یا دستی طور پر ایک درج کر سکتے ہیں۔", + "smtp-transport.service": "سروس منتخب کریں", + "smtp-transport.service-custom": "مرضی کی سروس", + "smtp-transport.service-help": "اوپر دیے گئے سروس کے نام کو منتخب کریں تاکہ اس کے معروف ڈیٹا کو استعمال کیا جا سکے۔ یا 'مرضی کی سروس' منتخب کریں اور نیچے اس کے تفصیلات درج کریں۔", + "smtp-transport.gmail-warning1": "اگر آپ Gmail استعمال کر رہے ہیں، تو آپ کو NodeBB کے تصدیقی ڈیٹا استعمال کرنے کے لیے 'ایپلیکیشن پاس ورڈ' بنانا ہوگا۔ آپ اسے ایپلیکیشن پاس ورڈز صفحہ پر بنا سکتے ہیں۔", + "smtp-transport.gmail-warning2": "اس حل کے بارے میں مزید معلومات کے لیے، براہ کرم NodeMailer کے اس مسئلے کے بارے میں یہ مضمون دیکھیں۔ ایک اور حل یہ ہوگا کہ تیسری پارٹی کی ای میل پلگ ان استعمال کی جائے، جیسے کہ 'SendGrid'، 'Mailgun' وغیرہ۔ یہاں دستیاب پلگ انز دیکھیں۔", + "smtp-transport.auto-enable-toast": "ایسا لگتا ہے کہ آپ ایسی فعالیت ترتیب دے رہے ہیں جس کے لیے SMTP ٹرانسپورٹ کی ضرورت ہے۔ ہم نے آپ کے لیے 'SMTP ٹرانسپورٹ' ترتیب کو فعال کر دیا ہے۔", + "smtp-transport.host": "SMTP سرور", + "smtp-transport.port": "SMTP پورٹ", + "smtp-transport.security": "رابطے کی سیکیورٹی", + "smtp-transport.security-encrypted": "خفیہ کردہ", + "smtp-transport.security-starttls": "StartTLS", + "smtp-transport.security-none": "کوئی نہیں", + "smtp-transport.username": "صارف نام", + "smtp-transport.username-help": "Gmail سروس کے لیے، یہاں مکمل ای میل ایڈریس درج کریں، خاص طور پر اگر آپ Google Apps کے زیر انتظام ڈومین استعمال کر رہے ہیں۔", + "smtp-transport.password": "پاس ورڈ", + "smtp-transport.pool": "پولڈ کنکشنز فعال کریں", + "smtp-transport.pool-help": "پولڈ کنکشنز ہر ای میل کے لیے نیا کنکشن بنانے سے روکتے ہیں۔ یہ ترتیب صرف اس وقت اثر کرتی ہے جب 'SMTP ٹرانسپورٹ' فعال ہو۔", + "smtp-transport.allow-self-signed": "خود دستخط شدہ سرٹیفکیٹس کی اجازت دیں", + "smtp-transport.allow-self-signed-help": "اس ترتیب کو فعال کرنے سے خود دستخط شدہ اور غیر درست TLS سرٹیفکیٹس استعمال کرنے کی اجازت ملے گی۔", + "smtp-transport.test-success": "SMTP Test email sent successfully.", + + "template": "ای میل ٹیمپلیٹ میں ترمیم کریں", + "template.select": "ای میل ٹیمپلیٹ منتخب کریں", + "template.revert": "اصل پر واپس جائیں", + "test-smtp-settings": "Test SMTP Settings", + "testing": "ای میل ٹیسٹنگ", + "testing.success": "Test Email Sent.", + "testing.select": "ای میل ٹیمپلیٹ منتخب کریں", + "testing.send": "ٹیسٹ ای میل بھیجیں", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", + "subscriptions": "ای میل ڈائجسٹ", + "subscriptions.disable": "ای میل ڈائجسٹ غیر فعال کریں", + "subscriptions.hour": "بھیجنے کا وقت", + "subscriptions.hour-help": "براہ کرم ایک عدد درج کریں جو ڈائجسٹ ای میلز بھیجنے کے وقت کو ظاہر کرے (مثال کے طور پر 0 آدھی رات کے لیے، 17 شام 5 بجے کے لیے)۔ نوٹ کریں کہ یہ وقت سرور کے ٹائم زون کے مطابق ہے اور ہو سکتا ہے کہ آپ کے سسٹم کے کلاک سے مطابقت نہ رکھتا ہو۔
سرور کا تخمینی وقت ہے:
اگلا روزانہ ڈائجسٹ بھیجنے کا شیڈول ہے: ", + "notifications.remove-images": "ای میل اطلاعات سے تصاویر ہٹائیں", + "require-email-address": "نئے صارفین کے لیے ای میل ایڈریس لازمی ہے", + "require-email-address-warning": "طے شدہ طور پر، صارفین ای میل ایڈریس فیلڈ کو خالی چھوڑ کر ای میل ایڈریس داخل نہ کرنے کا انتخاب کر سکتے ہیں۔ اگر آپ اسے فعال کرتے ہیں، تو نئے صارفین کو رجسٹریشن مکمل کرنے اور فورم تک رسائی کے لیے ای میل فراہم کرنا اور تصدیق کرنا لازمی ہوگا۔ اس کا مطلب یہ نہیں کہ صارف درست ای میل ایڈریس درج کرے گا یا یہ کہ وہ ان کا ہوگا۔", + "send-validation-email": "ای میل شامل یا تبدیل ہونے پر تصدیقی ای میلز بھیجیں", + "include-unverified-emails": "ایسے وصول کنندگان کو ای میلز بھیجیں جنہوں نے اپنے ای میل کی واضح طور پر تصدیق نہیں کی", + "include-unverified-warning": "جن صارفین کے رجسٹریشن کے ساتھ ای میل منسلک ہے، اسے تصدیق شدہ سمجھا جاتا ہے۔ لیکن کچھ حالات میں ایسا نہیں ہوتا (مثال کے طور پر جب دوسرے سسٹم سے رجسٹریشن استعمال کی جاتی ہے، لیکن دیگر صورتوں میں بھی)، اس ترتیب کو اپنے خطرے پر فعال کریں – غیر تصدیق شدہ ایڈریسز پر ای میلز بھیجنا بعض مقامی اینٹی اسپام قوانین کی خلاف ورزی کر سکتا ہے۔", + "prompt": "صارفین کو اپنا ای میل درج کرنے یا تصدیق کرنے کی یاد دہانی کریں", + "prompt-help": "اگر صارف کا ای میل سیٹ نہیں ہے، یا اگر اس کی تصدیق نہیں ہوئی، تو اس کی سکرین پر ایک تنبیہی پیغام دکھایا جائے گا۔", + "sendEmailToBanned": "پابندی شدہ صارفین کو بھی ای میلز بھیجیں" +} diff --git a/public/language/ur/admin/settings/general.json b/public/language/ur/admin/settings/general.json new file mode 100644 index 0000000000..51e14ab67f --- /dev/null +++ b/public/language/ur/admin/settings/general.json @@ -0,0 +1,63 @@ +{ + "general-settings": "عام ترتیبات", + "on-this-page": "اس صفحہ پر:", + "site-settings": "ویب سائٹ کی ترتیبات", + "title": "ویب سائٹ کا عنوان", + "title.short": "مختصر عنوان", + "title.short-placeholder": "اگر مختصر عنوان بیان نہیں کیا گیا تو ویب سائٹ کا عنوان استعمال کیا جائے گا", + "title.url": "عنوان کا ایڈریس", + "title.url-placeholder": "ویب سائٹ کے عنوان کا ایڈریس", + "title.url-help": "جب صارف عنوان پر کلک کرتا ہے، تو وہ اس ایڈریس پر منتقل ہو جائے گا۔ اگر خالی ہو، تو صارف فورم کے ہوم پیج پر بھیجا جائے گا۔ نوٹ: یہ وہ بیرونی ایڈریس نہیں جو ای میلز میں استعمال ہوتا ہے۔ وہ config.json فائل میں url پراپرٹی سے سیٹ کیا جاتا ہے۔", + "title.name": "آپ کی کمیونٹی کا نام", + "title.show-in-header": "ویب سائٹ کا عنوان ہیڈر میں دکھائیں", + "browser-title": "براؤزر کا عنوان", + "browser-title-help": "اگر براؤزر کا عنوان بیان نہیں کیا گیا تو ویب سائٹ کا عنوان استعمال کیا جائے گا", + "title-layout": "عنوان کا ترتیب", + "title-layout-help": "براؤزر کے عنوان کی ساخت کیسے ہوگی، جیسے کہ: {pageTitle} | {browserTitle}", + "description.placeholder": "آپ کی کمیونٹی کا مختصر تفصیل", + "description": "ویب سائٹ کی تفصیل", + "keywords": "ویب سائٹ کے کلیدی الفاظ", + "keywords-placeholder": "آپ کی کمیونٹی کی وضاحت کرنے والے کلیدی الفاظ، کوموں سے الگ کیے گئے۔", + "logo-and-icons": "ویب سائٹ کا لوگو اور آئیکنز", + "logo.image": "تصویر", + "logo.image-placeholder": "فورم کے ہیڈر میں دکھائے جانے والے لوگو کا پاتھ", + "logo.upload": "اپ لوڈ", + "logo.url": "لوگو کا ایڈریس", + "logo.url-placeholder": "ویب سائٹ کے لوگو کا ایڈریس", + "logo.url-help": "جب صارف لوگو پر کلک کرتا ہے، تو وہ اس ایڈریس پر منتقل ہو جائے گا۔ اگر خالی ہو، تو صارف فورم کے ہوم پیج پر بھیجا جائے گا۔
نوٹ: یہ وہ بیرونی ایڈریس نہیں جو ای میلز میں استعمال ہوتا ہے۔ وہ config.json فائل میں url پراپرٹی سے سیٹ کیا جاتا ہے۔", + "logo.alt-text": "متبادل متن", + "log.alt-text-placeholder": "رسائی کے لیے متبادل متن", + "favicon": "ویب سائٹ کا فیویکن", + "favicon.upload": "اپ لوڈ", + "pwa": "پروگریسو ویب ایپلیکیشن", + "touch-icon": "ٹچ آئیکن", + "touch-icon.upload": "اپ لوڈ", + "touch-icon.help": "تجویز کردہ سائز اور فارمیٹ: 512x512، صرف PNG فارمیٹ میں۔ اگر ٹچ آئیکن بیان نہیں کیا گیا تو NodeBB ویب سائٹ کے فیویکن کا استعمال کرے گا۔", + "maskable-icon": "ماسک ایبل آئیکن (ہوم اسکرین کے لیے)", + "maskable-icon.help": "تجویز کردہ سائز اور فارمیٹ: 512x512، صرف PNG فارمیٹ میں۔ اگر ماسک ایبل آئیکن بیان نہیں کیا گیا تو NodeBB ٹچ آئیکن کا استعمال کرے گا۔", + "outgoing-links": "باہر جانے والے لنکس", + "outgoing-links.warning-page": "باہری لنکس پر کلک کرنے پر تنبیہی صفحہ دکھائیں", + "search": "تلاش", + "search-default-in": "میں تلاش کریں", + "search-default-in-quick": "فوری تلاش میں", + "search-default-sort-by": "ترتیب دیں بمطابق", + "outgoing-links.whitelist": "وہ ڈومینز جن کے لیے تنبیہی صفحہ نہ دکھایا جائے", + "site-colors": "ویب سائٹ کے رنگوں کے میٹا ڈیٹا", + "theme-color": "تھیم کا رنگ", + "background-color": "پس منظر کا رنگ", + "background-color-help": "وہ رنگ جو ہوم اسکرین کے پس منظر کے طور پر استعمال کیا جائے گا جب ویب سائٹ ایپلیکیشن کے طور پر انسٹال کی جاتی ہے", + "undo-timeout": "واپسی کا وقت", + "undo-timeout-help": "کچھ عمل، جیسے کہ موضوعات منتقل کرنا، ماڈریٹر اس مخصوص وقت کے اندر واپس کر سکتے ہیں۔ واپسی کو مکمل طور پر غیر فعال کرنے کے لیے 0 سیٹ کریں۔", + "topic-tools": "موضوعات کے اوزار", + "home-page": "ہوم پیج", + "home-page-route": "ہوم پیج کا راستہ", + "home-page-description": "منتخب کریں کہ جب صارفین فورم کے مرکزی ایڈریس پر جائیں تو کون سا صفحہ دکھایا جائے۔", + "custom-route": "مرضی کا راستہ", + "allow-user-home-pages": "صارفین کے ہوم پیجز کی اجازت دیں", + "home-page-title": "ہوم پیج کا عنوان (طے شدہ: 'ہوم')", + "default-language": "طے شدہ زبان", + "auto-detect": "مہمانوں کے لیے زبان کا خودکار پتہ لگانا", + "default-language-help": "طے شدہ زبان آپ کے فورم کو دیکھنے والے تمام صارفین کے لیے زبان کی ترتیبات کا تعین کرتی ہے۔
انفرادی صارفین اپنے پروفائل کی ترتیبات کے صفحہ سے اپنی زبان تبدیل کر سکتے ہیں۔", + "post-sharing": "پوسٹس کا اشتراک", + "info-plugins-additional": "پلگ انز پوسٹس کے اشتراک کے لیے اضافی نیٹ ورکس شامل کر سکتے ہیں۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/group.json b/public/language/ur/admin/settings/group.json new file mode 100644 index 0000000000..c22d1ae59f --- /dev/null +++ b/public/language/ur/admin/settings/group.json @@ -0,0 +1,13 @@ +{ + "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/ur/admin/settings/navigation.json b/public/language/ur/admin/settings/navigation.json new file mode 100644 index 0000000000..74e8178c8d --- /dev/null +++ b/public/language/ur/admin/settings/navigation.json @@ -0,0 +1,26 @@ +{ + "navigation": "نیویگیشن", + "icon": "آئیکن:", + "change-icon": "تبدیل کریں", + "route": "راستہ:", + "tooltip": "ٹول ٹپ:", + "text": "متن:", + "text-class": "متن کلاس: اختیاری", + "class": "کلاس: اختیاری", + "id": "شناختی: اختیاری", + + "properties": "خصوصیات:", + "show-to-groups": "گروپس کو دکھائیں:", + "open-new-window": "نئی ونڈو میں کھولیں", + "dropdown": "ڈراپ ڈاؤن مینو", + "dropdown-placeholder": "نیچے ڈراپ ڈاؤن مینو کے عناصر درج کریں۔ مثال:
<li><a class="dropdown-item" href="https://myforum.com">لنک 1</a></li>", + + "btn.delete": "حذف", + "btn.disable": "غیر فعال کریں", + "btn.enable": "فعال کریں", + + "available-menu-items": "دستیاب مینو آئٹمز", + "custom-route": "مرضی کا راستہ", + "core": "بنیادی", + "plugin": "پلگ ان" +} diff --git a/public/language/ur/admin/settings/notifications.json b/public/language/ur/admin/settings/notifications.json new file mode 100644 index 0000000000..8350eb4f6e --- /dev/null +++ b/public/language/ur/admin/settings/notifications.json @@ -0,0 +1,9 @@ +{ + "notifications": "اطلاعات", + "welcome-notification": "خوش آمدید اطلاع", + "welcome-notification-link": "خوش آمدید اطلاع کے لیے لنک", + "welcome-notification-uid": "خوش آمدید اطلاع کے لیے صارف آئی ڈی", + "post-queue-notification-uid": "پوسٹ کیو کے لیے صارف آئی ڈی", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/pagination.json b/public/language/ur/admin/settings/pagination.json new file mode 100644 index 0000000000..2a192b6542 --- /dev/null +++ b/public/language/ur/admin/settings/pagination.json @@ -0,0 +1,12 @@ +{ + "pagination": "صفحہ بندی کی ترتیبات", + "enable": "موضوعات اور پوسٹس کو لامحدود سکرولنگ کے بجائے صفحات میں تقسیم کریں۔", + "posts": "پوسٹس میں صفحہ بندی", + "topics": "موضوعات میں صفحہ بندی", + "posts-per-page": "فی صفحہ پوسٹس", + "max-posts-per-page": "فی صفحہ زیادہ سے زیادہ پوسٹس", + "categories": "زمرہ جات کی صفحہ بندی", + "topics-per-page": "فی صفحہ موضوعات", + "max-topics-per-page": "فی صفحہ زیادہ سے زیادہ موضوعات", + "categories-per-page": "فی صفحہ زمرہ جات کی تعداد" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/post.json b/public/language/ur/admin/settings/post.json new file mode 100644 index 0000000000..8b83a6bdce --- /dev/null +++ b/public/language/ur/admin/settings/post.json @@ -0,0 +1,64 @@ +{ + "general": "عام", + "sorting": "پوسٹس کی ترتیب", + "sorting.post-default": "پوسٹس کی طے شدہ ترتیب", + "sorting.oldest-to-newest": "سب سے پرانے پہلے", + "sorting.newest-to-oldest": "سب سے نئے پہلے", + "sorting.recently-replied": "حال ہی میں جواب دیے گئے پہلے", + "sorting.recently-created": "حال ہی میں بنائے گئے پہلے", + "sorting.most-votes": "سب سے زیادہ ووٹس والے پہلے", + "sorting.most-posts": "سب سے زیادہ پوسٹس والے پہلے", + "sorting.most-views": "سب سے زیادہ مناظر والے پہلے", + "sorting.topic-default": "موضوعات کی طے شدہ ترتیب", + "length": "پوسٹس کی لمبائی", + "post-queue": "پوسٹ کیو", + "restrictions": "پوسٹنگ کے پابندی", + "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-edit-after": "سیکنڈز کی تعداد جن کے دوران پوسٹس ترمیم کی جا سکتی ہیں (0 = غیر فعال)", + "restrictions.seconds-delete-after": "سیکنڈز کی تعداد جن کے دوران پوسٹس حذف کی جا سکتی ہیں (0 = غیر فعال)", + "restrictions.replies-no-delete": "جوابات کی تعداد جس کے بعد صارفین اپنے موضوعات حذف نہیں کر سکتے (0 = غیر فعال)", + "restrictions.title-length": "عنوان کی لمبائی", + "restrictions.post-length": "پوسٹس کی لمبائی", + "restrictions.days-until-stale": "وہ دنوں کی تعداد جس کے بعد موضوع پرانا سمجھا جاتا ہے", + "restrictions.stale-help": "اگر کوئی موضوع 'پرانا' قرار دیا گیا ہو، تو اس میں لکھنے کی کوشش کرنے والے صارفین کو تنبیہی پیغام ملے گا (0 = غیر فعال)", + "timestamp": "وقت", + "timestamp.cut-off": "تاریخ کے بعد استعمال کریں (دنوں میں)", + "timestamp.cut-off-help": "تاریخیں اور اوقات نسبتاً دکھائے جائیں گے (مثال کے طور پر '3 گھنٹے پہلے' یا '5 دن پہلے')، اور متعدد زبانوں میں ترجمہ کیے جائیں گے۔ ایک خاص وقت کے بعد، یہ متن صارف کی زبان کے مطابق تاریخ اور وقت دکھانا شروع کر دے گا (مثال کے طور پر '5 نومبر 2016 15:30')۔
(طے شدہ: 30، یعنی ایک ماہ)۔ اگر 0 سیٹ کیا گیا تو ہمیشہ تاریخیں دکھائی جائیں گی، اور اگر فیلڈ خالی چھوڑا گیا تو وقت ہمیشہ نسبتاً ہوگا۔", + "timestamp.necro-threshold": "مردہ حد (دنوں میں)", + "timestamp.necro-threshold-help": "اگر پوسٹس کے درمیان وقت مردہ حد سے زیادہ ہو تو پوسٹس کے درمیان ایک پیغام دکھایا جائے گا۔ (طے شدہ: 7، یعنی ایک ہفتہ)۔ غیر فعال کرنے کے لیے 0 سیٹ کریں۔
", + "timestamp.topic-views-interval": "موضوعات کے مناظر کی تعداد بڑھانے کا وقفہ (منٹوں میں)", + "timestamp.topic-views-interval-help": "موضوعات کے مناظر کی تعداد اس ترتیب کے مطابق ہر X منٹ میں ایک بار بڑھے گی۔", + "teaser": "نمائشی پوسٹ", + "teaser.last-post": "آخری – آخری پوسٹ دکھائیں، یا اگر کوئی جوابات نہ ہوں تو ابتدائی پوسٹ۔", + "teaser.last-reply": "آخری – آخری جواب دکھائیں، یا اگر کوئی جوابات نہ ہوں تو 'کوئی جوابات نہیں'۔", + "teaser.first": "پہلی", + "showPostPreviewsOnHover": "ماؤس ہاور کرنے پر پوسٹس کا مختصر پیش نظارہ دکھائیں", + "unread-and-recent": "حال ہی کے اور غیر پڑھے ہوئے کے لیے ترتیبات", + "unread.cutoff": "پوسٹس کی عمر جس کے بعد وہ غیر پڑھے ہوئے میں نہیں دکھائی جاتیں (دنوں میں)", + "unread.min-track-last": "موضوع میں پوسٹس کی کم سے کم تعداد جس کے بعد آخری پڑھی ہوئی کو ٹریک کرنا شروع کیا جائے", + "recent.max-topics": "حال ہی کے موضوعات کی زیادہ سے زیادہ تعداد", + "recent.categoryFilter.disable": "/recent صفحہ پر نظرانداز کیے گئے زمرہ جات کی موضوعات کی فلٹرنگ غیر فعال کریں", + "signature": "دستخطوں کی ترتیبات", + "signature.disable": "دستخط غیر فعال کریں", + "signature.no-links": "دستخطوں میں لنکس کی اجازت نہ دیں", + "signature.no-images": "دستخطوں میں تصاویر کی اجازت نہ دیں", + "signature.hide-duplicates": "موضوعات میں دہرائے گئے دستخط چھپائیں", + "signature.max-length": "دستخطوں کی زیادہ سے زیادہ لمبائی", + "composer": "کمپوزر کی ترتیبات", + "composer-help": "مندرجہ ذیل ترتیبات نئی موضوعات بنانے یا موجودہ موضوعات میں جواب دینے کے لیے صارفین کے ذریعے استعمال ہونے والے پوسٹ کمپوزر کے عنصر کی فعالیت اور/یا ظاہری شکل کا تعین کرتی ہیں۔", + "composer.show-help": "مدد کا ٹیب دکھائیں", + "composer.enable-plugin-help": "پلگ انز کو مدد کے ٹیب میں مواد شامل کرنے کی اجازت دیں", + "composer.custom-help": "مرضی کا مدد کا متن", + "backlinks": "بیک لنکس", + "backlinks.enabled": "موضوعات میں بیک لنکس فعال کریں", + "backlinks.help": "اگر پوسٹ میں کسی دوسرے موضوع کی طرف حوالہ ہو تو اس پوسٹ کی طرف ایک لنک اس مخصوص وقت کے ساتھ رکھا جائے گا۔", + "ip-tracking": "آئی پی ایڈریس ریکارڈنگ", + "ip-tracking.each-post": "ہر پوسٹ کے لیے آئی پی ایڈریس ریکارڈ کریں", + "enable-post-history": "پوسٹس کی تاریخ فعال کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/reputation.json b/public/language/ur/admin/settings/reputation.json new file mode 100644 index 0000000000..ad7cd77e43 --- /dev/null +++ b/public/language/ur/admin/settings/reputation.json @@ -0,0 +1,43 @@ +{ + "reputation": "ساکھ کی ترتیبات", + "disable": "ساکھ کا نظام غیر فعال کریں", + "disable-down-voting": "منفی ووٹنگ کی ممانعت", + "upvote-visibility": "مثبت ووٹس کی نمائش", + "upvote-visibility-all": "ہر کوئی مثبت ووٹس دیکھ سکتا ہے", + "upvote-visibility-loggedin": "صرف لاگ ان صارفین مثبت ووٹس دیکھ سکتے ہیں", + "upvote-visibility-privileged": "صرف اعلیٰ اختیارات والے صارفین (جیسے ایڈمنسٹریٹرز اور ماڈریٹرز) مثبت ووٹس دیکھ سکتے ہیں", + "downvote-visibility": "منفی ووٹس کی نمائش", + "downvote-visibility-all": "ہر کوئی منفی ووٹس دیکھ سکتا ہے", + "downvote-visibility-loggedin": "صرف لاگ ان صارفین منفی ووٹس دیکھ سکتے ہیں", + "downvote-visibility-privileged": "صرف اعلیٰ اختیارات والے صارفین (جیسے ایڈمنسٹریٹرز اور ماڈریٹرز) منفی ووٹس دیکھ سکتے ہیں", + "thresholds": "سرگرمی کی حدیں", + "min-rep-upvote": "پوسٹس کے لیے مثبت ووٹنگ کے لیے کم سے کم ساکھ درکار", + "upvotes-per-day": "ایک دن میں مثبت ووٹس (لامحدود کے لیے 0 سیٹ کریں)", + "upvotes-per-user-per-day": "ایک صارف کے لیے ایک دن میں مثبت ووٹس (لامحدود کے لیے 0 سیٹ کریں)", + "min-rep-downvote": "پوسٹس کے لیے منفی ووٹنگ کے لیے کم سے کم ساکھ درکار", + "downvotes-per-day": "ایک دن میں منفی ووٹس (لامحدود کے لیے 0 سیٹ کریں)", + "downvotes-per-user-per-day": "ایک صارف کے لیے ایک دن میں منفی ووٹس (لامحدود کے لیے 0 سیٹ کریں)", + "min-rep-chat": "گفتگو میں پیغامات بھیجنے کے لیے کم سے کم ساکھ درکار", + "min-rep-post-links": "لنکس پوسٹ کرنے کے لیے کم سے کم ساکھ درکار", + "min-rep-flag": "پوسٹس کی رپورٹنگ کے لیے کم سے کم ساکھ درکار", + "min-rep-aboutme": "صارف کے پروفائل میں 'میرے بارے میں' فیلڈ شامل کرنے کے لیے کم سے کم ساکھ درکار", + "min-rep-signature": "صارف کے پروفائل میں 'دستخط' فیلڈ شامل کرنے کے لیے کم سے کم ساکھ درکار", + "min-rep-profile-picture": "صارف کے پروفائل میں پروفائل تصویر شامل کرنے کے لیے کم سے کم ساکھ درکار", + "min-rep-cover-picture": "صارف کے پروفائل میں کور تصویر شامل کرنے کے لیے کم سے کم ساکھ درکار", + + "flags": "رپورٹس کی ترتیبات", + "flags.limit-per-target": "ایک ہی چیز کی رپورٹنگ کی زیادہ سے زیادہ تعداد", + "flags.limit-per-target-placeholder": "طے شدہ: 0", + "flags.limit-per-target-help": "جب کوئی پوسٹ یا صارف کئی بار رپورٹ کیا جاتا ہے، تو یہ ایک مشترکہ رپورٹ میں شامل ہو جاتا ہے۔ اس ترتیب کو صفر سے زیادہ قیمت پر سیٹ کریں تاکہ ایک پوسٹ یا صارف کے لیے جمع ہونے والی رپورٹس کی تعداد کو محدود کیا جا سکے۔", + "flags.limit-post-flags-per-day": "ایک دن میں صارف کے ذریعے رپورٹ کی جا سکنے والی پوسٹس کی زیادہ سے زیادہ تعداد", + "flags.limit-post-flags-per-day-help": "غیر فعال کرنے کے لیے 0 سیٹ کریں (طے شدہ: 10)", + "flags.limit-user-flags-per-day": "ایک دن میں صارف کے ذریعے رپورٹ کیے جا سکنے والے صارفین کی زیادہ سے زیادہ تعداد", + "flags.limit-user-flags-per-day-help": "غیر فعال کرنے کے لیے 0 سیٹ کریں (طے شدہ: 10)", + "flags.auto-flag-on-downvote-threshold": "پوسٹس کی خودکار رپورٹنگ کے لیے منفی ووٹس کی تعداد", + "flags.auto-flag-on-downvote-threshold-help": "غیر فعال کرنے کے لیے 0 سیٹ کریں (طے شدہ: 0)", + "flags.auto-resolve-on-ban": "جب صارف پر پابندی لگائی جاتی ہے تو اس کی تمام رپورٹس خودکار طور پر ہٹائیں", + "flags.action-on-resolve": "جب رپورٹنگ حل کی جاتی ہے تو درج ذیل کریں", + "flags.action-on-reject": "جب رپورٹنگ مسترد کی جاتی ہے تو درج ذیل کریں", + "flags.action.nothing": "کچھ نہ کریں", + "flags.action.rescind": "ماڈریٹرز/ایڈمنسٹریٹرز کو بھیجی گئی اطلاع منسوخ کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/sockets.json b/public/language/ur/admin/settings/sockets.json new file mode 100644 index 0000000000..a8bfe7c497 --- /dev/null +++ b/public/language/ur/admin/settings/sockets.json @@ -0,0 +1,6 @@ +{ + "reconnection": "دوبارہ رابطہ کی ترتیبات", + "max-attempts": "دوبارہ رابطہ کرنے کی زیادہ سے زیادہ کوششیں", + "default-placeholder": "طے شدہ: %1", + "delay": "دوبارہ رابطہ کرنے میں تاخیر" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/sounds.json b/public/language/ur/admin/settings/sounds.json new file mode 100644 index 0000000000..2de9782640 --- /dev/null +++ b/public/language/ur/admin/settings/sounds.json @@ -0,0 +1,9 @@ +{ + "notifications": "اطلاعات", + "chat-messages": "گفتگو کے پیغامات", + "play-sound": "چلائیں", + "incoming-message": "آنے والا پیغام", + "outgoing-message": "جانے والا پیغام", + "upload-new-sound": "نئی آواز اپ لوڈ کریں", + "saved": "ترتیبات محفوظ ہو گئیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/tags.json b/public/language/ur/admin/settings/tags.json new file mode 100644 index 0000000000..e7c2af1694 --- /dev/null +++ b/public/language/ur/admin/settings/tags.json @@ -0,0 +1,13 @@ +{ + "tag": "ٹیگز کی ترتیبات", + "link-to-manage": "ٹیگز کا انتظام", + "system-tags": "سسٹم ٹیگز", + "system-tags-help": "صرف اعلیٰ اختیارات والے صارفین ہی ان ٹیگز کو استعمال کر سکیں گے۔", + "tags-per-topic": "موضوع کے لیے ٹیگز کی تعداد", + "min-per-topic": "موضوع کے لیے کم سے کم ٹیگز", + "max-per-topic": "موضوع کے لیے زیادہ سے زیادہ ٹیگز", + "min-length": "ٹیگز کی کم سے کم لمبائی", + "max-length": "ٹیگز کی زیادہ سے زیادہ لمبائی", + "related-topics": "متعلقہ موضوعات", + "max-related-topics": "زیادہ سے زیادہ متعلقہ موضوعات جو دکھائے جائیں (اگر تھیم اس کی حمایت کرتی ہو)" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/uploads.json b/public/language/ur/admin/settings/uploads.json new file mode 100644 index 0000000000..38dcd8e8c7 --- /dev/null +++ b/public/language/ur/admin/settings/uploads.json @@ -0,0 +1,52 @@ +{ + "posts": "پوسٹس", + "orphans": "غیر استعمال شدہ فائلیں", + "private": "اپ لوڈ کی گئی فائلیں نجی ہوں", + "strip-exif-data": "EXIF ڈیٹا ہٹائیں", + "preserve-orphaned-uploads": "پوسٹ حذف ہونے کے بعد بھی اپ لوڈ کی گئی فائلوں کو ڈسک پر رکھیں", + "orphanExpiryDays": "غیر استعمال شدہ فائلوں کو رکھنے کے دنوں کی تعداد", + "orphanExpiryDays-help": "اس تعداد کے دنوں کے بعد غیر استعمال شدہ اپ لوڈ کی گئی فائلیں حذف کر دی جائیں گی۔
اس فعالیت کو غیر فعال کرنے کے لیے 0 سیٹ کریں یا خالی چھوڑ دیں۔", + "private-extensions": "نجی ہونے والی فائل ایکسٹینشنز", + "private-uploads-extensions-help": "نجی ہونے والی فائل ایکسٹینشنز کی فہرست کوموں سے الگ کرکے درج کریں (مثال کے طور پر pdf,xls,doc)۔ اگر یہ فیلڈ خالی چھوڑا جائے تو تمام فائلیں نجی ہوں گی۔", + "resize-image-width-threshold": "تصاویر کو اگر وہ مخصوص چوڑائی سے زیادہ ہوں تو ری سائز کریں", + "resize-image-width-threshold-help": "(پکسلز میں؛ طے شدہ: 2000 پکسلز۔ 0 = غیر فعال)", + "resize-image-width": "تصاویر کا سائز مخصوص چوڑائی تک کم کریں", + "resize-image-width-help": "(پکسلز میں؛ طے شدہ: 760 پکسلز۔ 0 = غیر فعال)", + "resize-image-keep-original": "ری سائزنگ کے بعد اصل تصویر رکھیں", + "resize-image-quality": "تصاویر کی ری سائزنگ کا معیار", + "resize-image-quality-help": "ری سائز کی گئی تصاویر کے فائل سائز کو کم کرنے کے لیے کم معیار استعمال کریں۔", + "max-file-size": "فائلوں کا زیادہ سے زیادہ سائز (KiB میں)", + "max-file-size-help": "(کیبی بائٹس میں؛ طے شدہ: 2048 KiB)", + "reject-image-width": "تصاویر کی زیادہ سے زیادہ چوڑائی (پکسلز میں)", + "reject-image-width-help": "جن تصاویر کی چوڑائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔", + "reject-image-height": "تصاویر کی زیادہ سے زیادہ اونچائی (پکسلز میں)", + "reject-image-height-help": "جن تصاویر کی اونچائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", + "allow-topic-thumbnails": "صارفین کو موضوعات کے لیے تھمب نیلز اپ لوڈ کرنے کی اجازت دیں", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", + "topic-thumb-size": "موضوعات کے تھمب نیلز کا سائز", + "allowed-file-extensions": "اجازت شدہ فائل ایکسٹینشنز", + "allowed-file-extensions-help": "فائل ایکسٹینشنز کوموں سے الگ کرکے درج کریں (مثال: pdf,xls,doc)۔ اگر فہرست خالی ہو تو تمام فائل ایکسٹینشنز کی اجازت ہوگی۔", + "upload-limit-threshold": "صارفین کے اپ لوڈز کو محدود کریں:", + "upload-limit-threshold-per-minute": "%1 منٹ کے لیے", + "upload-limit-threshold-per-minutes": "%1 منٹوں کے لیے", + "profile-avatars": "پروفائل تصاویر", + "allow-profile-image-uploads": "صارفین کو پروفائل تصاویر اپ لوڈ کرنے کی اجازت دیں", + "convert-profile-image-png": "اپ لوڈ کی گئی پروفائل تصاویر کو PNG فارمیٹ میں تبدیل کریں", + "default-avatar": "طے شدہ حسب ضرورت تصویر", + "upload": "اپ لوڈ", + "profile-image-dimension": "پروفائل تصویر کا سائز", + "profile-image-dimension-help": "(پکسلز میں؛ طے شدہ: 128 پکسلز)", + "max-profile-image-size": "پروفائل تصویر کا زیادہ سے زیادہ فائل سائز", + "max-profile-image-size-help": "(کیبی بائٹس میں؛ طے شدہ: 256 KiB)", + "max-cover-image-size": "کور تصویر کا زیادہ سے زیادہ فائل سائز", + "max-cover-image-size-help": "(کیبی بائٹس میں؛ طے شدہ: 2048 KiB)", + "keep-all-user-images": "پروفائل تصاویر اور کور تصاویر کے پرانے ورژن سرور پر رکھیں", + "profile-covers": "پروفائل کورز", + "default-covers": "طے شدہ کور تصاویر", + "default-covers-help": "ان اکاؤنٹس کے لیے طے شدہ کور تصاویر (کوموں سے الگ کیے گئے) شامل کریں جنہوں نے کوئی کور تصویر اپ لوڈ نہیں کی۔" +} diff --git a/public/language/ur/admin/settings/user.json b/public/language/ur/admin/settings/user.json new file mode 100644 index 0000000000..a33c3201f3 --- /dev/null +++ b/public/language/ur/admin/settings/user.json @@ -0,0 +1,98 @@ +{ + "authentication": "تصدیق", + "email-confirm-interval": "صارف دوبارہ تصدیقی ای میل نہیں بھیج سکتا جب تک کہ", + "email-confirm-interval2": "منٹ گزر نہ جائیں", + "allow-login-with": "لاگ ان کی اجازت دیں بذریعہ", + "allow-login-with.username-email": "صارف نام یا ای میل", + "allow-login-with.username": "صرف صارف نام", + "account-settings": "اکاؤنٹ کی ترتیبات", + "gdpr-enabled": "GDPR رضامندی کی درخواست کو فعال کریں", + "gdpr-enabled-help": "اگر یہ فعال ہو، تو تمام نئے رجسٹرڈ صارفین کو جنرل ڈیٹا پروٹیکشن ریگولیشن (GDPR) کے مطابق ڈیٹا جمع کرنے اور استعمال کے اعدادوشمار کے لیے اپنی رضامندی واضح طور پر دینی ہوگی۔ نوٹ: GDPR کو فعال کرنے سے موجودہ صارفین کو رضامندی دینے کی ضرورت نہیں ہوتی۔ اگر آپ یہ چاہتے ہیں، تو آپ کو GDPR پلگ ان انسٹال کرنا ہوگا۔", + "disable-username-changes": "صارف نام کی تبدیلی کو غیر فعال کریں", + "disable-email-changes": "ای میل کی تبدیلی کو غیر فعال کریں", + "disable-password-changes": "پاس ورڈ کی تبدیلی کو غیر فعال کریں", + "allow-account-deletion": "اکاؤنٹ حذف کرنے کی اجازت دیں", + "hide-fullname": "صارفین سے مکمل نام چھپائیں", + "hide-email": "صارفین سے ای میل چھپائیں", + "show-fullname-as-displayname": "اگر موجود ہو تو صارف کا مکمل نام دکھائیں", + "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-time-days": "دن", + "session-time-seconds": "سیکنڈز", + "session-time-help": "یہ قیمتیں صارفین کے لاگ ان رہنے کی مدت کا تعین کرنے کے لیے استعمال ہوتی ہیں اگر وہ لاگ ان کے وقت 'مجھے یاد رکھیں' پر نشان لگاتے ہیں۔ نوٹ کریں کہ ان میں سے صرف ایک قیمت استعمال ہوگی۔ اگر سیکنڈز کی کوئی قیمت نہیں ہے، تو دنوں کی قیمت استعمال ہوگی۔ اگر دنوں کی بھی کوئی قیمت نہیں ہے، تو طے شدہ قیمت 14 دن استعمال ہوگی۔", + "session-duration": "سیشن کی مدت اگر 'مجھے یاد رکھیں' پر نشان نہ لگایا گیا ہو (سیکنڈز میں)", + "session-duration-help": "طے شدہ طور پر (یا اگر قیمت 0 ہو) صارف اس وقت تک لاگ ان رہے گا جب تک کہ اس کا سیشن ختم نہ ہو جائے (عام طور پر جب براؤزر یا ٹیب بند ہو جائے)۔ اگر آپ بالکل درست وقت (سیکنڈز میں) متعین کرنا چاہتے ہیں جس کے بعد صارف کا سیشن ختم ہو جائے تو اس ترتیب کا استعمال کریں۔", + "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کوئی رجسٹریشن نہیں — صارفین رجسٹر نہیں کر سکتے۔
", + "registration-approval-type.help": "عام — صارفین فوری طور پر رجسٹر ہو جاتے ہیں۔
\nایڈمنسٹریٹر کی منظوری — صارفین کی رجسٹریشنز منظوری کی قطار میں رکھی جاتی ہیں، جن کا ایڈمنسٹریٹرز جائزہ لیتے ہیں۔
\nIP ایڈریس کے لحاظ سے ایڈمنسٹریٹر کی منظوری — نئے صارفین عام طریقے سے رجسٹر ہوتے ہیں، لیکن جن کے IP ایڈریس سے پہلے ہی دوسرے اکاؤنٹس رجسٹر ہو چکے ہیں انہیں ایڈمنسٹریٹر کی منظوری کی ضرورت ہوتی ہے۔
", + "registration-queue-auto-approve-time": "خودکار منظوری کا وقت", + "registration-queue-auto-approve-time-help": "صارف کے خودکار طور پر منظور ہونے سے پہلے گھنٹوں کی تعداد۔ 0 = غیر فعال۔", + "registration-queue-show-average-time": "نئے صارف کی منظوری کا اوسط وقت صارفین کو دکھائیں", + "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": "صرف ان صارفین سے پیغامات کی اجازت دیں جنہیں میں فالو کرتا ہوں", + "disable-incoming-chats": "آنے والے پیغامات غیر فعال کریں", + "outgoing-new-tab": "باہری لنکس کو نئے ٹیب میں کھولیں", + "topic-search": "موضوعات میں تلاش کو فعال کریں", + "update-url-with-post-index": "موضوعات دیکھتے وقت ایڈریس بار کو پوسٹ نمبر کے ساتھ اپ ڈیٹ کریں", + "digest-freq": "ڈائجسٹ کے لیے سبسکرائب کریں", + "digest-freq.off": "غیر فعال", + "digest-freq.daily": "روزانہ", + "digest-freq.weekly": "ہفتہ وار", + "digest-freq.biweekly": "ہر دو ہفتوں بعد", + "digest-freq.monthly": "ماہانہ", + "email-chat-notifs": "اگر میں آن لائن نہ ہوں تو نئے گفتگو کے پیغام کی صورت میں ای میل بھیجیں", + "email-post-notif": "جن موضوعات کے لیے میں سبسکرائب ہوں ان میں جواب آنے پر ای میل بھیجیں", + "follow-created-topics": "آپ کے بنائے ہوئے موضوعات کو فالو کریں", + "follow-replied-topics": "جن موضوعات میں آپ جواب دیتے ہیں ان کو فالو کریں", + "default-notification-settings": "اطلاعات کی طے شدہ ترتیبات", + "categoryWatchState": "زمرہ جات کی نگرانی کے لیے طے شدہ حالت", + "categoryWatchState.tracking": "فالو کریں", + "categoryWatchState.notwatching": "نگرانی نہ کریں", + "categoryWatchState.ignoring": "نظرانداز کریں", + "restrictions-new": "نئے صارفین کے لیے پابندیاں", + "restrictions.rep-threshold": "اس پابندی کو ہٹانے کے لیے درکار ساکھ", + "restrictions.seconds-between-new": "نئے صارفین کے لیے پوسٹس کے درمیان سیکنڈز کی تعداد", + "restrictions.seconds-before-new": "نئے صارفین کے پہلی بار پوسٹ کرنے سے پہلے سیکنڈز کی تعداد", + "restrictions.seconds-edit-after-new": "نئے صارفین کے ذریعے پوسٹس کی ترمیم کے لیے سیکنڈز کی تعداد (0 = غیر فعال)", + "restrictions.milliseconds-between-messages": "نئے صارفین کے لیے گفتگو کے پیغامات کے درمیان وقت (ملی سیکنڈز)", + "restrictions.groups-exempt-from-new-user-restrictions": "نئے صارفین کی پابندیوں سے مستثنیٰ گروپس منتخب کریں", + "guest-settings": "مہمانوں کی ترتیبات", + "handles.enabled": "مہمانوں کے لیے ناموں کی اجازت دیں", + "handles.enabled-help": "یہ خصوصیت ایک نیا فیلڈ فراہم کرتی ہے جو مہمانوں کو ہر پوسٹ کے لیے ایک نام منتخب کرنے کی اجازت دیتی ہے۔ اگر غیر فعال ہو، تو سب کا نام صرف 'مہمان' ہوگا۔", + "topic-views.enabled": "مہمان موضوعات کے مناظر کی تعداد میں حصہ ڈالیں", + "reply-notifications.enabled": "مہمان اپنے جوابات کے لیے اطلاعات بھیجنے کا سبب بن سکیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/web-crawler.json b/public/language/ur/admin/settings/web-crawler.json new file mode 100644 index 0000000000..6492b832ce --- /dev/null +++ b/public/language/ur/admin/settings/web-crawler.json @@ -0,0 +1,11 @@ +{ + "crawlability-settings": "کرال ایبلٹی کی ترتیبات", + "robots-txt": "حسب ضرورت 'Robots.txt' فائل طے شدہ فائل استعمال کرنے کے لیے خالی چھوڑ دیں", + "sitemap-feed-settings": "ویب سائٹ کے نقشے اور فیڈز کی ترتیبات", + "disable-rss-feeds": "RSS فیڈز کو غیر فعال کریں", + "disable-sitemap-xml": "ویب سائٹ کا نقشہ ('Sitemap.xml') غیر فعال کریں", + "sitemap-topics": "ویب سائٹ کے نقشے میں دکھائے جانے والے موضوعات کی تعداد", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", + "clear-sitemap-cache": "ویب سائٹ کے نقشے کا کیش صاف کریں", + "view-sitemap": "ویب سائٹ کا نقشہ دیکھیں" +} \ No newline at end of file diff --git a/public/language/ur/aria.json b/public/language/ur/aria.json new file mode 100644 index 0000000000..93035daebb --- /dev/null +++ b/public/language/ur/aria.json @@ -0,0 +1,10 @@ +{ + "post-sort-option": "پوسٹس کی ترتیب کے لیے ترتیبات، %1", + "topic-sort-option": "موضوعات کی ترتیب کے لیے ترتیبات، %1", + "user-avatar-for": "%1 کے لیے صارف کا اوتار", + "profile-page-for": "%1 کے لیے پروفائل صفحہ", + "user-watched-tags": "صارف کی طرف سے دیکھے گئے ٹیگز", + "delete-upload-button": "اپ لوڈ کو حذف کرنے کا بٹن", + "group-page-link-for": "%1 کے لیے گروپ صفحہ کا لنک", + "show-crossposts": "Show Cross-posts" +} \ No newline at end of file diff --git a/public/language/ur/category.json b/public/language/ur/category.json new file mode 100644 index 0000000000..0a73abeb9f --- /dev/null +++ b/public/language/ur/category.json @@ -0,0 +1,30 @@ +{ + "category": "زمرہ", + "subcategories": "ذیلی زمرہ جات", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", + "handle.description": "اس زمرے کو کھلی سوشل نیٹ ورک سے %1 شناخت کنندہ کے ذریعے فالو کیا جا سکتا ہے", + "new-topic-button": "نیا موضوع", + "guest-login-post": "پوسٹ کرنے کے لیے لاگ ان کریں", + "no-topics": "اس زمرے میں ابھی تک کوئی موضوعات نہیں ہیں۔
کیوں نہ آپ ایک بنائیں؟", + "no-followers": "اس ویب سائٹ پر کوئی بھی اس زمرے کو فالو یا نگرانی نہیں کر رہا۔ اس زمرے کو فالو یا نگرانی شروع کریں تاکہ اس کے بارے میں اطلاعات موصول ہوں۔", + "browsing": "براؤزنگ", + "no-replies": "کوئی جوابات نہیں", + "no-new-posts": "کوئی نئی پوسٹس نہیں۔", + "watch": "نگرانی", + "ignore": "نظرانداز", + "watching": "آپ نگرانی کر رہے ہیں", + "tracking": "آپ فالو کر رہے ہیں", + "not-watching": "آپ نگرانی نہیں کر رہے", + "ignoring": "آپ نظرانداز کر رہے ہیں", + "watching.description": "میں نئے موضوعات کے لیے اطلاعات موصول کرنا چاہتا ہوں۔
میں چاہتا ہوں کہ موضوعات غیر پڑھے ہوئے اور حالیہ فہرستوں میں دکھائی دیں۔", + "tracking.description": "موضوعات غیر پڑھے ہوئے اور حالیہ فہرستوں میں دکھائی دیں", + "not-watching.description": "موضوعات غیر پڑھے ہوئے فہرست میں نہ دکھائی دیں، صرف حالیہ فہرست میں", + "ignoring.description": "موضوعات نہ تو غیر پڑھے ہوئے میں دکھائی دیں اور نہ ہی حالیہ فہرست میں", + "watching.message": "آپ اب اس زمرے اور اس کے ذیلی زمرہ جات میں نئی چیزوں کی نگرانی کر رہے ہیں", + "tracking.message": "آپ اب اس زمرے اور اس کے ذیلی زمرہ جات میں نئی چیزوں کو فالو کر رہے ہیں", + "notwatching.message": "آپ اب اس زمرے اور اس کے ذیلی زمرہ جات میں نئی چیزوں کی نگرانی نہیں کر رہے", + "ignoring.message": "آپ اب اس زمرے اور اس کے تمام ذیلی زمرہ جات میں نئی چیزوں کو نظرانداز کر رہے ہیں", + "watched-categories": "نگرانی شدہ زمرہ جات", + "x-more-categories": "مزید %1 زمرہ جات" +} \ No newline at end of file diff --git a/public/language/ur/email.json b/public/language/ur/email.json new file mode 100644 index 0000000000..4edf2583df --- /dev/null +++ b/public/language/ur/email.json @@ -0,0 +1,61 @@ +{ + "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": "آپ نے اپنے ای میل ایڈریس کو تبدیل کرنے یا تصدیق کرنے کی درخواست کی ہے", + "email.verify.text2": "سیکیورٹی وجوہات کی بنا پر، ہم آپ کے ای میل ایڈریس کو صرف اس وقت تبدیل یا تصدیق کر سکتے ہیں جب اس کی ملکیت ای میل کے ذریعے پہلے سے ثابت ہو چکی ہو۔ اگر آپ نے یہ درخواست نہیں کی، تو آپ کو کچھ کرنے کی ضرورت نہیں ہے۔", + "email.verify.text3": "اس ای میل ایڈریس کی تصدیق کے بعد، ہم آپ کے موجودہ ایڈریس کو اس (%1) سے تبدیل کر دیں گے۔", + "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.unread-rooms": "غیر پڑھے ہوئے کمرے", + "digest.room-name-unreadcount": "%1 (%2 غیر پڑھے ہوئے)", + "digest.latest-topics": "%1 سے تازہ ترین موضوعات", + "digest.top-topics": "%1 سے سب سے دلچسپ موضوعات", + "digest.popular-topics": "%1 سے مقبول موضوعات", + "digest.cta": "%1 دیکھنے کے لیے یہاں کلک کریں", + "digest.unsub.info": "یہ ڈائجسٹ آپ کو آپ کی سبسکرپشن کی ترتیبات کی وجہ سے بھیجا گیا ہے۔", + "digest.day": "دن", + "digest.week": "ہفتہ", + "digest.month": "ماہ", + "digest.subject": "%1 کے لیے ڈائجسٹ", + "digest.title.day": "آپ کا روزانہ ڈائجسٹ", + "digest.title.week": "آپ کا ہفتہ وار ڈائجسٹ", + "digest.title.month": "آپ کا ماہانہ ڈائجسٹ", + "notif.chat.new-message-from-user": "صارف '%1' سے نیا پیغام", + "notif.chat.new-message-from-user-in-room": "%1 سے کمرے %2 میں نیا پیغام", + "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": "یہ ایک ٹیسٹ ای میل ہے تاکہ یہ تصدیق کی جا سکے کہ آپ کے NodeBB کے لیے ای میل بھیجنے والا صحیح طریقے سے ترتیب دیا گیا ہے۔", + "test.text1": "یہ ایک ٹیسٹ ای میل ہے تاکہ یہ تصدیق کی جا سکے کہ آپ کے NodeBB کے لیے ای میل بھیجنے والا صحیح طریقے سے ترتیب دیا گیا ہے۔", + "unsub.cta": "ان ترتیبات کو تبدیل کرنے کے لیے یہاں کلک کریں", + "unsubscribe": "سبسکرپشن ختم کریں", + "unsub.success": "آپ کو اب %1 کے میلنگ لسٹ سے ای میلز موصول نہیں ہوں گے", + "unsub.failure.title": "سبسکرپشن ختم نہیں کیا جا سکا", + "unsub.failure.message": "بدقسمتی سے، ہم آپ کو میلنگ لسٹ سے سبسکرپشن ختم نہیں کر سکے، کیونکہ لنک میں کوئی مسئلہ تھا۔ تاہم، آپ اپنی ای میل ترجیحات کو صارف کی ترتیبات میں تبدیل کر سکتے ہیں۔

(غلطی: %1)", + "banned.subject": "آپ کو %1 سے بلاک کر دیا گیا ہے", + "banned.text1": "صارف %1 کو %2 سے بلاک کر دیا گیا ہے۔", + "banned.text2": "یہ پابندی %1 تک نافذ رہے گی۔", + "banned.text3": "آپ پر پابندی لگنے کی وجہ یہ ہے:", + "closing": "شکریہ!" +} \ No newline at end of file diff --git a/public/language/ur/error.json b/public/language/ur/error.json new file mode 100644 index 0000000000..e23a7719b3 --- /dev/null +++ b/public/language/ur/error.json @@ -0,0 +1,269 @@ +{ + "invalid-data": "غلط ڈیٹا", + "invalid-json": "غلط JSON", + "wrong-parameter-type": "پراپرٹی '%1' کے لیے %3 قسم کی قدر متوقع تھی، لیکن اس کے بجائے %2 موصول ہوا", + "required-parameters-missing": "اس API کال سے ضروری پیرامیٹرز غائب ہیں: %1", + "reserved-ip-address": "محفوظ شدہ رینجز سے IP ایڈریسز پر نیٹ ورک کی درخواستوں کی اجازت نہیں ہے۔", + "not-logged-in": "ایسا لگتا ہے کہ آپ نے لاگ ان نہیں کیا۔", + "account-locked": "آپ کا اکاؤنٹ عارضی طور پر مقفل کر دیا گیا ہے", + "search-requires-login": "تلاش کے لیے رجسٹرڈ اکاؤنٹ درکار ہے! براہ کرم لاگ ان کریں یا رجسٹر کریں!", + "goback": "پچھلے صفحے پر واپس جانے کے لیے 'بیک' دبائیں", + "invalid-cid": "غلط زمرہ شناخت کنندہ", + "invalid-tid": "غلط موضوع شناخت کنندہ", + "invalid-pid": "غلط پوسٹ شناخت کنندہ", + "invalid-uid": "غلط صارف شناخت کنندہ", + "invalid-mid": "غلط گفتگو پیغام شناخت کنندہ", + "invalid-date": "ایک درست تاریخ متعین کی جانی چاہیے", + "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": "غلط ایڈریس", + "invalid-event": "غلط ایونٹ: %1", + "local-login-disabled": "مقامی لاگ ان سسٹم غیر مراعات یافتہ اکاؤنٹس کے لیے غیر فعال ہے۔", + "csrf-invalid": "ہم آپ کو لاگ ان نہیں کر سکے، غالباً کیونکہ آپ کا سیشن ختم ہو چکا ہے۔ براہ کرم دوبارہ کوشش کریں", + "invalid-path": "غلط راستہ", + "folder-exists": "اس نام کا فولڈر پہلے سے موجود ہے", + "invalid-pagination-value": "غلط صفحہ بندی کی قدر، یہ %1 اور %2 کے درمیان ہونی چاہیے", + "username-taken": "صارف نام پہلے سے لیا جا چکا ہے", + "email-taken": "ای میل ایڈریس پہلے سے لیا جا چکا ہے۔", + "email-nochange": "درج کردہ ای میل موجودہ ای میل جیسا ہی ہے۔", + "email-invited": "اس ای میل پر پہلے سے دعوت بھیجی جا چکی ہے", + "email-not-confirmed": "کچھ زمرہ جات اور موضوعات میں پوسٹنگ اس وقت تک ممکن نہیں ہوگی جب تک آپ کا ای میل تصدیق نہ ہو جائے۔ تصدیقی ای میل بھیجنے کے لیے یہاں کلک کریں۔", + "email-not-confirmed-chat": "جب تک آپ کا ای میل تصدیق نہ ہو جائے، آپ گفتگو میں لکھ نہیں سکیں گے۔ براہ کرم اپنے ای میل کی تصدیق کے لیے یہاں کلک کریں۔", + "email-not-confirmed-email-sent": "آپ کا ای میل ابھی تک تصدیق شدہ نہیں ہے۔ براہ کرم اپنے ان باکس میں تصدیقی ای میل چیک کریں۔ جب تک آپ کا ای میل تصدیق نہ ہو جائے، آپ پیغامات پوسٹ یا گفتگو میں لکھ نہیں سکیں گے۔", + "no-email-to-confirm": "آپ نے کوئی ای میل متعین نہیں کیا۔ اکاؤنٹ کی بحالی کے لیے ای میل ضروری ہے، اور کچھ زمرہ جات میں لکھنے کے لیے بھی اس کی ضرورت ہو سکتی ہے۔ ای میل درج کرنے کے لیے یہاں کلک کریں۔", + "user-doesnt-have-email": "صارف '%1' نے کوئی ای میل متعین نہیں کیا۔", + "email-confirm-failed": "ہم آپ کے ای میل کی تصدیق نہیں کر سکے۔ براہ کرم بعد میں دوبارہ کوشش کریں۔", + "confirm-email-already-sent": "تصدیقی ای میل پہلے سے بھیج دیا گیا ہے۔ براہ کرم نئی ای میل بھیجنے سے پہلے %1 منٹ انتظار کریں۔", + "confirm-email-expired": "تصدیقی ای میل کی میعاد ختم ہو چکی ہے", + "sendmail-not-found": "’sendmail‘ کا قابل عمل فائل نہیں مل سکا۔ براہ کرم یقینی بنائیں کہ یہ انسٹال ہے اور NodeBB کو چلانے والے صارف کے لیے قابل عمل ہے۔", + "digest-not-enabled": "اس صارف کے لیے ڈائجسٹ فعال نہیں ہیں، یا سسٹم کی طے شدہ ترتیب یہ ہے کہ ڈائجسٹ نہ بھیجیں", + "username-too-short": "صارف نام بہت چھوٹا ہے", + "username-too-long": "صارف نام بہت لمبا ہے", + "password-too-long": "پاس ورڈ بہت لمبا ہے", + "reset-rate-limited": "پاس ورڈ ری سیٹ کی بہت زیادہ درخواستیں (ریٹ کی حد ہے)", + "reset-same-password": "براہ کرم موجودہ پاس ورڈ سے مختلف پاس ورڈ استعمال کریں", + "user-banned": "صارف پر پابندی لگائی گئی ہے", + "user-banned-reason": "معذرت، اس اکاؤنٹ پر پابندی لگائی گئی ہے (وجہ: %1)", + "user-banned-reason-until": "معذرت، اس اکاؤنٹ پر %1 تک پابندی لگائی گئی ہے (وجہ: %2)", + "user-too-new": "معذرت، لیکن آپ کو اپنی پہلی پوسٹ کرنے سے پہلے کم از کم %1 سیکنڈ انتظار کرنا ہوگا", + "blacklisted-ip": "معذرت، لیکن آپ کا IP ایڈریس اس کمیونٹی میں استعمال کے لیے ممنوع ہے۔ اگر آپ کو لگتا ہے کہ یہ غلطی ہے، تو براہ کرم ایڈمنسٹریٹر سے رابطہ کریں۔", + "cant-blacklist-self-ip": "آپ اپنا IP ایڈریس بلیک لسٹ میں شامل نہیں کر سکتے", + "ban-expiry-missing": "براہ کرم اس پابندی کے لیے اختتامی تاریخ متعین کریں", + "no-category": "زمرہ موجود نہیں ہے", + "no-topic": "موضوع موجود نہیں ہے", + "no-post": "پوسٹ موجود نہیں ہے", + "no-group": "گروپ موجود نہیں ہے", + "no-user": "صارف موجود نہیں ہے", + "no-teaser": "ٹیزر موجود نہیں ہے", + "no-flag": "رپورٹ موجود نہیں ہے", + "no-chat-room": "گفتگو کا کمرہ موجود نہیں ہے", + "no-privileges": "آپ کے پاس اس عمل کے لیے کافی اختیارات نہیں ہیں۔", + "category-disabled": "زمرہ غیر فعال ہے", + "post-deleted": "پوسٹ حذف کر دی گئی ہے", + "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 ساکھ حاصل نہ کر لیں – براہ کرم دوبارہ پوسٹ کرنے سے پہلے کچھ دیر انتظار کریں", + "too-many-posts-newbie-minutes": "ایک نئے صارف کے طور پر، آپ ہر %1 منٹ میں ایک بار پوسٹ کر سکتے ہیں جب تک کہ آپ %2 ساکھ حاصل نہ کر لیں – براہ کرم دوبارہ پوسٹ کرنے سے پہلے کچھ دیر انتظار کریں", + "already-posting": "آپ اس وقت پوسٹ کر رہے ہیں", + "tag-too-short": "براہ کرم لمبا ٹیگ درج کریں۔ ٹیگز میں کم از کم %1 حروف ہونے چاہئیں", + "tag-too-long": "براہ کرم مختصر ٹیگ درج کریں۔ ٹیگز میں %1 حروف سے زیادہ نہیں ہونے چاہئیں", + "tag-not-allowed": "ٹیگ کی اجازت نہیں ہے", + "not-enough-tags": "ناکافی ٹیگز۔ موضوعات میں کم از کم %1 ٹیگ ہونا چاہیے", + "too-many-tags": "بہت زیادہ ٹیگز۔ موضوعات میں %1 ٹیگز سے زیادہ نہیں ہو سکتے", + "cant-use-system-tag": "آپ اس سسٹم ٹیگ کو استعمال نہیں کر سکتے۔", + "cant-remove-system-tag": "آپ اس سسٹم ٹیگ کو ہٹا نہیں سکتے۔", + "still-uploading": "براہ کرم اپ لوڈ مکمل ہونے کا انتظار کریں۔", + "file-too-big": "فائل کا زیادہ سے زیادہ اجازت شدہ سائز %1 KB ہے – براہ کرم چھوٹی فائل اپ لوڈ کریں", + "guest-upload-disabled": "مہمانوں کے لیے اپ لوڈ کی اجازت نہیں ہے", + "cors-error": "CORS کی غلط ترتیبات کی وجہ سے تصویر اپ لوڈ نہیں کی جا سکی", + "upload-ratelimit-reached": "آپ نے ایک ساتھ بہت زیادہ فائلیں اپ لوڈ کی ہیں۔ براہ کرم بعد میں دوبارہ کوشش کریں۔", + "upload-error-fallback": "تصویر اپ لوڈ نہیں کی جا سکی – %1", + "scheduling-to-past": "مستقبل کی تاریخ منتخب کریں۔", + "invalid-schedule-date": "درست تاریخ اور وقت درج کریں۔", + "cant-pin-scheduled": "طے شدہ موضوعات کو پن یا ان پن نہیں کیا جا سکتا۔", + "cant-merge-scheduled": "طے شدہ موضوعات کو ضم نہیں کیا جا سکتا۔", + "cant-move-posts-to-scheduled": "پوسٹس کو طے شدہ موضوع میں منتقل نہیں کیا جا سکتا۔", + "cant-move-from-scheduled-to-existing": "طے شدہ موضوع سے پوسٹس کو موجودہ موضوع میں منتقل نہیں کیا جا سکتا۔", + "already-bookmarked": "آپ نے اس پوسٹ کو پہلے سے بک مارک کیا ہوا ہے", + "already-unbookmarked": "آپ نے اس پوسٹ سے بک مارک پہلے سے ہٹا دیا ہے", + "cant-ban-other-admins": "آپ دوسرے ایڈمنسٹریٹرز پر پابندی نہیں لگا سکتے!", + "cant-mute-other-admins": "آپ دوسرے ایڈمنسٹریٹرز کو خاموش نہیں کر سکتے!", + "user-muted-for-hours": "آپ کو خاموش کر دیا گیا ہے۔ آپ %1 گھنٹوں کے بعد دوبارہ پوسٹ کر سکیں گے", + "user-muted-for-minutes": "آپ کو خاموش کر دیا گیا ہے۔ آپ %1 منٹوں کے بعد دوبارہ پوسٹ کر سکیں گے", + "cant-make-banned-users-admin": "آپ پابندی والے صارفین کو ایڈمنسٹریٹر کے حقوق نہیں دے سکتے۔", + "cant-remove-last-admin": "آپ واحد ایڈمنسٹریٹر ہیں۔ اپنے آپ کو ایڈمنسٹریٹر سے ہٹانے سے پہلے دوسرے صارف کو ایڈمنسٹریٹر بنائیں", + "account-deletion-disabled": "اکاؤنٹ حذف کرنا ممنوع ہے", + "cant-delete-admin": "اس اکاؤنٹ سے ایڈمنسٹریٹر کے حقوق ہٹائیں اسے حذف کرنے سے پہلے۔", + "already-deleting": "پہلے سے حذف ہو رہا ہے", + "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": "آپ اس وقت اس گروپ کو نہیں چھوڑ سکتے", + "group-user-not-pending": "صارف کی اس گروپ میں شامل ہونے کی کوئی زیر التواء درخواست نہیں ہے۔", + "gorup-user-not-invited": "صارف کو اس گروپ میں شامل ہونے کی دعوت نہیں دی گئی۔", + "post-already-deleted": "یہ پوسٹ پہلے سے حذف ہو چکی ہے", + "post-already-restored": "یہ پوسٹ پہلے سے بحال ہو چکی ہے", + "topic-already-deleted": "یہ موضوع پہلے سے حذف ہو چکا ہے", + "topic-already-restored": "یہ موضوع پہلے سے بحال ہو چکا ہے", + "topic-already-crossposted": "This topic has already been cross-posted there.", + "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-allow-list-user-already-added": "یہ صارف پہلے سے اجازت شدہ فہرست میں ہے", + "chat-deny-list-user-already-added": "یہ صارف پہلے سے ممنوعہ فہرست میں ہے", + "chat-user-blocked": "آپ کو اس صارف نے بلاک کر دیا ہے۔", + "chat-disabled": "گفتگو کا نظام غیر فعال ہے", + "too-many-messages": "آپ نے بہت زیادہ پیغامات بھیج دیے ہیں۔ براہ کرم کچھ دیر انتظار کریں۔", + "invalid-chat-message": "غلط پیغام", + "chat-message-too-long": "گفتگو کے پیغامات %1 حروف سے زیادہ لمبے نہیں ہو سکتے۔", + "cant-edit-chat-message": "آپ کو اس پیغام کو ترمیم کرنے کا اختیار نہیں ہے", + "cant-delete-chat-message": "آپ کو اس پیغام کو حذف کرنے کا اختیار نہیں ہے", + "chat-edit-duration-expired": "آپ اپنے گفتگو کے پیغامات کو پوسٹ کرنے کے %1 سیکنڈ تک ترمیم کر سکتے ہیں", + "chat-delete-duration-expired": "آپ اپنے گفتگو کے پیغامات کو پوسٹ کرنے کے %1 سیکنڈ تک حذف کر سکتے ہیں", + "chat-deleted-already": "یہ پیغام پہلے سے حذف ہو چکا ہے۔", + "chat-restored-already": "یہ پیغام پہلے سے بحال ہو چکا ہے۔", + "chat-room-does-not-exist": "گفتگو کا کمرہ موجود نہیں ہے۔", + "cant-add-users-to-chat-room": "گفتگو کے کمرے میں صارفین شامل نہیں کیے جا سکتے۔", + "cant-remove-users-from-chat-room": "گفتگو کے کمرے سے صارفین ہٹائے نہیں جا سکتے۔", + "chat-room-name-too-long": "کمرا کا نام بہت لمبا ہے۔ نام %1 حروف سے زیادہ لمبے نہیں ہو سکتے۔", + "remote-chat-received-too-long": "آپ کو %1 سے ایک پیغام موصول ہوا، لیکن یہ بہت لمبا تھا اور اسے مسترد کر دیا گیا۔", + "already-voting-for-this-post": "آپ نے اس پوسٹ کے لیے پہلے سے ووٹ دیا ہے۔", + "reputation-system-disabled": "ساکھ کا نظام غیر فعال ہے۔", + "downvoting-disabled": "منفی ووٹنگ غیر فعال ہے", + "not-enough-reputation-to-chat": "گفتگو میں حصہ لینے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-to-upvote": "مثبت ووٹ دینے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-to-downvote": "منفی ووٹ دینے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-to-post-links": "لنکس پوسٹ کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-to-flag": "اس پوسٹ کی رپورٹ کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-min-rep-website": "ویب سائٹ شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-min-rep-aboutme": "اپنے بارے میں معلومات شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-min-rep-signature": "دستخط شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-min-rep-profile-picture": "پروفائل تصویر شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-min-rep-cover-picture": "کور تصویر شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "not-enough-reputation-custom-field": "%2 کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے", + "custom-user-field-value-too-long": "حسب ضرورت فیلڈ کی قدر بہت لمبی ہے، %1", + "custom-user-field-select-value-invalid": "حسب ضرورت فیلڈ میں منتخب کردہ آپشن غلط ہے، %1", + "custom-user-field-invalid-text": "حسب ضرورت فیلڈ میں متن غلط ہے، %1", + "custom-user-field-invalid-link": "حسب ضرورت فیلڈ میں لنک غلط ہے، %1", + "custom-user-field-invalid-number": "حسب ضرورت فیلڈ میں نمبر غلط ہے، %1", + "custom-user-field-invalid-date": "حسب ضرورت فیلڈ میں تاریخ غلط ہے، %1", + "invalid-custom-user-field": "غلط حسب ضرورت فیلڈ۔ '%1' پہلے سے NodeBB کے ذریعے استعمال ہو رہا ہے", + "post-already-flagged": "آپ نے اس پوسٹ کی پہلے سے رپورٹ کی ہوئی ہے", + "user-already-flagged": "آپ نے اس صارف کی پہلے سے رپورٹ کی ہوئی ہے", + "post-flagged-too-many-times": "اس پوسٹ کو پہلے سے دوسرے لوگوں نے رپورٹ کیا ہے", + "user-flagged-too-many-times": "اس صارف کو پہلے سے دوسرے لوگوں نے رپورٹ کیا ہے", + "too-many-post-flags-per-day": "آپ ایک دن میں زیادہ سے زیادہ %1 پوسٹس رپورٹ کر سکتے ہیں", + "too-many-user-flags-per-day": "آپ ایک دن میں زیادہ سے زیادہ %1 صارفین رپورٹ کر سکتے ہیں", + "cant-flag-privileged": "آپ اعلیٰ اختیارات والے صارفین (ماڈریٹرز، گلوبل ماڈریٹرز، ایڈمنسٹریٹرز) کے پروفائلز یا مواد کی رپورٹ نہیں کر سکتے", + "cant-locate-flag-report": "رپورٹ نہیں مل سکی", + "self-vote": "آپ اپنی پوسٹ کے لیے ووٹ نہیں دے سکتے", + "too-many-upvotes-today": "آپ ایک دن میں زیادہ سے زیادہ %1 بار مثبت ووٹ دے سکتے ہیں", + "too-many-upvotes-today-user": "آپ ایک صارف کے لیے ایک دن میں زیادہ سے زیادہ %1 بار مثبت ووٹ دے سکتے ہیں", + "too-many-downvotes-today": "آپ ایک دن میں زیادہ سے زیادہ %1 بار منفی ووٹ دے سکتے ہیں", + "too-many-downvotes-today-user": "آپ ایک صارف کے لیے ایک دن میں زیادہ سے زیادہ %1 بار منفی ووٹ دے سکتے ہیں", + "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": "کوئی صارف منتخب نہیں کیا گیا", + "no-groups-selected": "کوئی گروپ منتخب نہیں کیا گیا", + "invalid-home-page-route": "غلط ہوم پیج کا راستہ", + "invalid-session": "سیشن ختم ہو چکا ہے", + "invalid-session-text": "ایسا لگتا ہے کہ آپ کا لاگ ان سیشن ختم ہو چکا ہے۔ براہ کرم صفحہ ریفریش کریں۔", + "session-mismatch": "سیشن کا عدم مطابقت", + "session-mismatch-text": "ایسا لگتا ہے کہ آپ کا لاگ ان سیشن اب سرور سے مطابقت نہیں رکھتا۔ براہ کرم صفحہ ریفریش کریں۔", + "no-topics-selected": "کوئی موضوعات منتخب نہیں کیے گئے!", + "cant-move-to-same-topic": "پوسٹ کو اسی موضوع میں منتقل نہیں کیا جا سکتا!", + "cant-move-topic-to-same-category": "موضوع کو اسی زمرے میں منتقل نہیں کیا جا سکتا!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", + "cannot-block-self": "آپ خود کو بلاک نہیں کر سکتے!", + "cannot-block-privileged": "آپ ایڈمنسٹریٹرز اور گلوبل ماڈریٹرز کو بلاک نہیں کر سکتے", + "cannot-block-guest": "مہمان دوسرے صارفین کو بلاک نہیں کر سکتے", + "already-blocked": "یہ صارف پہلے سے بلاک ہے", + "already-unblocked": "یہ صارف پہلے سے ان بلاک ہے", + "no-connection": "ایسا لگتا ہے کہ آپ کے انٹرنیٹ کنکشن میں کوئی مسئلہ ہے", + "socket-reconnect-failed": "اس وقت سرور دستیاب نہیں ہے۔ دوبارہ کوشش کرنے کے لیے یہاں کلک کریں، یا بعد میں دوبارہ کوشش کریں۔", + "invalid-plugin-id": "غلط پلگ ان شناخت کنندہ", + "plugin-not-whitelisted": "پلگ ان انسٹال نہیں کیا جا سکتا – صرف NodeBB کے پیکیج مینیجر سے منظور شدہ پلگ انز کو ACP کے ذریعے انسٹال کیا جا سکتا ہے", + "cannot-toggle-system-plugin": "آپ سسٹم پلگ ان کی حالت کو تبدیل نہیں کر سکتے", + "plugin-installation-via-acp-disabled": "ACP کے ذریعے پلگ انز کی تنصیب غیر فعال ہے", + "plugins-set-in-configuration": "آپ پلگ ان کی حالت کو تبدیل نہیں کر سکتے کیونکہ یہ اس کے آپریشن کے دوران متعین ہوتی ہے (config.json، ماحولیاتی متغیرات، یا رن ٹائم آرگومنٹس کے ذریعے)۔ اس کے بجائے آپ کنفیگریشن تبدیل کر سکتے ہیں۔", + "theme-not-set-in-configuration": "جب کنفیگریشن میں فعال پلگ انز متعین کیے جاتے ہیں، تو تھیمز کو تبدیل کرنے کے لیے نئی تھیم کو فعال پلگ انز میں شامل کرنا ہوتا ہے، اس سے پہلے کہ اسے ACP میں اپ ڈیٹ کیا جائے", + "topic-event-unrecognized": "موضوع کا ایونٹ '%1' نامعلوم ہے", + "category.handle-taken": "زمرہ کی شناخت پہلے سے لی جا چکی ہے۔ براہ کرم کوئی اور شناخت کنندہ منتخب کریں۔", + "cant-set-child-as-parent": "ذیلی زمرہ کو بنیادی زمرہ کے طور پر متعین نہیں کیا جا سکتا", + "cant-set-self-as-parent": "زمرہ کو خود کا بنیادی زمرہ نہیں بنایا جا سکتا", + "api.master-token-no-uid": "ایک ماسٹر شناخت کنندہ موصول ہوا بغیر درخواست کے جسم میں متعلقہ '_uid' فیلڈ کے", + "api.400": "آپ نے جمع کرائے گئے درخواست کے ڈیٹا میں کچھ غلط تھا۔", + "api.401": "کوئی سیشن نہیں ملا۔ براہ کرم لاگ ان کریں اور دوبارہ کوشش کریں۔", + "api.403": "آپ کو اس کمانڈ کو انجام دینے کی اجازت نہیں ہے", + "api.404": "غلط API کمانڈ", + "api.413": "The request payload is too large", + "api.426": "لکھنے کی API درخواستوں کے لیے HTTPS درکار ہے۔ براہ کرم اپنی درخواست HTTPS کے ذریعے دوبارہ بھیجیں", + "api.429": "آپ نے بہت زیادہ درخواستیں کی ہیں۔ براہ کرم بعد میں دوبارہ کوشش کریں۔", + "api.500": "آپ کی درخواست پر عملدرآمد کے دوران ایک غیر متوقع خرابی پیش آئی۔", + "api.501": "جس راستے کو آپ کال کرنے کی کوشش کر رہے ہیں وہ ابھی تک موجود نہیں ہے۔ براہ کرم کل دوبارہ کوشش کریں۔", + "api.503": "جس راستے کو آپ کال کرنے کی کوشش کر رہے ہیں وہ فی الحال سرور کی ترتیبات کی وجہ سے دستیاب نہیں ہے۔", + "api.reauth-required": "جس وسائل تک آپ رسائی حاصل کرنے کی کوشش کر رہے ہیں اس کے لیے (دوبارہ) تصدیق درکار ہے۔", + "activitypub.not-enabled": "اس سرور پر فیڈریشن فعال نہیں ہے", + "activitypub.invalid-id": "داخل کردہ شناخت کنندہ کو تسلیم نہیں کیا جا سکتا – یہ غلط ہو سکتا ہے۔", + "activitypub.get-failed": "مخصوص مواد حاصل نہیں کیا جا سکا۔", + "activitypub.pubKey-not-found": "عوامی کلید کو تسلیم نہیں کیا جا سکا، اس لیے ڈیٹا کی تصدیق نہیں کی جا سکی۔", + "activitypub.origin-mismatch": "موصول شدہ آبجیکٹ کا اصل بھیجنے والے کے اصل سے مطابقت نہیں رکھتا", + "activitypub.actor-mismatch": "موصول شدہ عمل کو متوقع سے مختلف ذریعہ سے انجام دیا جا رہا ہے۔", + "activitypub.not-implemented": "درخواست کو مسترد کر دیا گیا کیونکہ اس کا یا اس کے کسی حصے کو اس سرور کے ذریعے سپورٹ نہیں کیا جاتا جس کی طرف یہ ہدایت کی گئی ہے" +} \ No newline at end of file diff --git a/public/language/ur/flags.json b/public/language/ur/flags.json new file mode 100644 index 0000000000..62a6fb2641 --- /dev/null +++ b/public/language/ur/flags.json @@ -0,0 +1,101 @@ +{ + "state": "حالت", + "report": "رپورٹ", + "reports": "رپورٹس", + "first-reported": "پہلی رپورٹ", + "no-flags": "ہورے! کوئی رپورٹس نہیں ملیں۔", + "x-flags-found": "رپورٹس ملیں: %1۔", + "assignee": "تفویض کردہ", + "update": "اپ ڈیٹ", + "updated": "اپ ڈیٹ ہوا", + "resolved": "حل شدہ", + "report-added": "شامل کیا گیا", + "report-rescinded": "منسوخ", + "target-purged": "اس رپورٹ سے متعلق مواد حذف کر دیا گیا ہے اور اب دستیاب نہیں ہے۔", + "target-aboutme-empty": "اس صارف نے اپنے بارے میں سیکشن میں کچھ نہیں بھرا۔", + + "graph-label": "روزانہ ٹیگز", + "quick-filters": "فوری فلٹرز", + "filter-active": "اس رپورٹس کی فہرست میں ایک یا زیادہ فلٹرز موجود ہیں", + "filter-reset": "فلٹرز ہٹائیں", + "filters": "فلٹرز کی ترتیبات", + "filter-reporterId": "رپورٹ کرنے والا", + "filter-targetUid": "رپورٹ کیا گیا", + "filter-type": "رپورٹ کی قسم", + "filter-type-all": "سب کچھ", + "filter-type-post": "پوسٹ", + "filter-type-user": "صارف", + "filter-state": "حالت", + "filter-assignee": "تفویض کردہ", + "filter-cid": "زمرہ", + "filter-quick-mine": "مجھے تفویض کردہ", + "filter-cid-all": "تمام زمرہ جات", + "apply-filters": "فلٹرز کا اطلاق کریں", + "more-filters": "مزید فلٹرز", + "fewer-filters": "کم فلٹرز", + + "quick-actions": "فوری اقدامات", + "flagged-user": "رپورٹ کیا گیا صارف", + "view-profile": "پروفائل دیکھیں", + "start-new-chat": "نئی گفتگو شروع کریں", + "go-to-target": "رپورٹ کا ہدف دیکھیں", + "assign-to-me": "مجھے تفویض کریں", + "delete-post": "پوسٹ حذف کریں", + "purge-post": "پوسٹ صاف کریں", + "restore-post": "پوسٹ بحال کریں", + "delete": "رپورٹ حذف کریں", + + "user-view": "پروفائل دیکھیں", + "user-edit": "پروفائل ترمیم کریں", + + "notes": "رپورٹ کے نوٹس", + "add-note": "نوٹ شامل کریں", + "edit-note": "نوٹ ترمیم کریں", + "no-notes": "کوئی اشتراک شدہ نوٹس نہیں ہیں۔", + "delete-note-confirm": "کیا آپ واقعی اس رپورٹ کے نوٹ کو حذف کرنا چاہتے ہیں؟", + "delete-flag-confirm": "کیا آپ واقعی اس رپورٹ کو حذف کرنا چاہتے ہیں؟", + "note-added": "نوٹ شامل کر دیا گیا", + "note-deleted": "نوٹ حذف کر دیا گیا", + "flag-deleted": "رپورٹ حذف کر دی گئی", + + "history": "اکاؤنٹ اور رپورٹس کی تاریخ", + "no-history": "رپورٹ کی کوئی تاریخ نہیں ہے۔", + + "state-all": "تمام حالتیں", + "state-open": "نیا/کھلا", + "state-wip": "کام جاری ہے", + "state-resolved": "حل شدہ", + "state-rejected": "مسترد", + "no-assignee": "کوئی تفویض نہیں", + + "sort": "ترتیب دیں بذریعہ", + "sort-newest": "پہلے نئے", + "sort-oldest": "پہلے پرانے", + "sort-reports": "پہلے سب سے زیادہ رپورٹس والے", + "sort-all": "تمام اقسام کی رپورٹس…", + "sort-posts-only": "صرف پوسٹس…", + "sort-downvotes": "سب سے زیادہ منفی ووٹس", + "sort-upvotes": "سب سے زیادہ مثبت ووٹس", + "sort-replies": "سب سے زیادہ جوابات", + + "modal-title": "مواد کی رپورٹنگ", + "modal-body": "براہ کرم %1 %2 کی رپورٹنگ کی وجہ بتائیں جائزے کے لیے۔ یا اگر قابل اطلاق ہو تو فوری رپورٹنگ بٹنوں میں سے کوئی استعمال کریں۔", + "modal-reason-spam": "سپام", + "modal-reason-offensive": "ناگوار", + "modal-reason-other": "دیگر (نیچے بیان کریں)", + "modal-reason-custom": "اس مواد کی رپورٹنگ کی وجہ…", + "modal-notify-remote": "اس رپورٹ کو %1 پر بھیجنا", + "modal-submit": "رپورٹ جمع کرائیں", + "modal-submit-success": "مواد کو ماڈریٹرز کو رپورٹ کر دیا گیا ہے۔", + + "modal-confirm-rescind": "رپورٹ منسوخ کریں؟", + + "bulk-actions": "اجتماعی اقدامات", + "bulk-resolve": "رپورٹ(س) حل کریں", + "confirm-purge": "کیا آپ واقعی ان رپورٹس کو مستقل طور پر حذف کرنا چاہتے ہیں؟", + "purge-cancelled": "رپورٹ کا حذف منسوخ کر دیا گیا", + "bulk-purge": "رپورٹ(س) حذف کریں", + "bulk-success": "%1 رپورٹس اپ ڈیٹ ہو گئی ہیں", + "flagged-timeago": "رپورٹ کیا گیا ", + "auto-flagged": "[خودکار رپورٹ] %1 منفی ووٹس موصول ہوئے۔" +} \ No newline at end of file diff --git a/public/language/ur/global.json b/public/language/ur/global.json new file mode 100644 index 0000000000..e7932799d2 --- /dev/null +++ b/public/language/ur/global.json @@ -0,0 +1,155 @@ +{ + "home": "ہوم", + "search": "تلاش", + "buttons.close": "بند کریں", + "403.title": "رسائی مسترد", + "403.message": "ایسا لگتا ہے کہ آپ نے ایک ایسے صفحے پر جانے کی کوشش کی ہے جس تک آپ کی رسائی نہیں ہے۔", + "403.login": "شاید آپ کو لاگ ان کرنے کی کوشش کرنی چاہیے؟", + "404.title": "نہیں ملا", + "404.message": "ایسا لگتا ہے کہ آپ نے ایک ایسے صفحے پر جانے کی کوشش کی ہے جو موجود نہیں ہے۔
واپس ہوم پیج پر جائیں۔
", + "500.title": "اندرونی خرابی۔", + "500.message": "اوہ! ایسا لگتا ہے کہ کچھ غلط ہو گیا!", + "400.title": "غلط درخواست۔", + "400.message": "یہ لنک خراب لگتا ہے۔ براہ کرم اسے چیک کریں اور دوبارہ کوشش کریں۔
یا واپس ہوم پیج پر جائیں۔
", + "register": "رجسٹریشن", + "login": "لاگ ان", + "please-log-in": "براہ کرم لاگ ان کریں", + "logout": "لاگ آؤٹ", + "posting-restriction-info": "اس وقت پوسٹنگ صرف رجسٹرڈ صارفین کے لیے اجازت ہے۔ لاگ ان کرنے کے لیے یہاں کلک کریں۔", + "welcome-back": "واپس خوش آمدید", + "you-have-successfully-logged-in": "آپ نے کامیابی سے لاگ ان کیا ہے", + "save-changes": "تبدیلیاں محفوظ کریں", + "save": "محفوظ کریں", + "create": "بنائیں", + "cancel": "منسوخ", + "close": "بند کریں", + "pagination": "صفحہ بندی", + "pagination.previouspage": "پچھلا صفحہ", + "pagination.nextpage": "اگلا صفحہ", + "pagination.firstpage": "پہلا صفحہ", + "pagination.lastpage": "آخری صفحہ", + "pagination.out-of": "%1 از %2", + "pagination.enter-index": "پوسٹ نمبر پر جائیں", + "pagination.go-to-page": "صفحہ پر جائیں", + "pagination.page-x": "صفحہ %1", + "header.brand-logo": "برانڈ لوگو", + "header.admin": "ایڈمن", + "header.categories": "زمرہ جات", + "header.recent": "حالیہ", + "header.unread": "غیر پڑھے ہوئے", + "header.tags": "ٹیگز", + "header.popular": "مقبول", + "header.top": "سب سے زیادہ پسند کیے گئے", + "header.users": "صارفین", + "header.groups": "گروپس", + "header.chats": "گفتگو", + "header.notifications": "اطلاعات", + "header.search": "تلاش", + "header.profile": "پروفائل", + "header.account": "اکاؤنٹ", + "header.navigation": "نیویگیشن", + "header.manage": "انتظام", + "header.drafts": "مسودات", + "header.world": "جهان", + "notifications.loading": "اطلاعات لوڈ ہو رہی ہیں", + "chats.loading": "گفتگو لوڈ ہو رہی ہے", + "drafts.loading": "مسودات لوڈ ہو رہے ہیں", + "motd.welcome": "NodeBB میں خوش آمدید، مستقبل کے ڈسکشن سسٹم۔", + "alert.success": "ہو گیا", + "alert.error": "خرابی", + "alert.warning": "انتباہ", + "alert.info": "معلومات", + "alert.banned": "پابندی", + "alert.banned.message": "آپ پر ابھی پابندی لگائی گئی ہے۔ سسٹم تک آپ کی رسائی محدود کر دی گئی ہے۔", + "alert.unbanned": "پابندی ہٹائی گئی", + "alert.unbanned.message": "آپ کی پابندی ہٹا دی گئی ہے", + "alert.unfollow": "آپ اب %1 کو فالو نہیں کر رہے!", + "alert.follow": "آپ %1 کو فالو کر رہے ہیں!", + "users": "صارفین", + "topics": "موضوعات", + "posts": "پوسٹس", + "crossposts": "Cross-posts", + "x-posts": "%1 پوسٹس", + "x-topics": "%1 موضوعات", + "x-reputation": "%1 ساکھ", + "best": "بہترین", + "controversial": "متنازعہ", + "votes": "ووٹس", + "x-votes": "%1 ووٹس", + "voters": "ووٹرز", + "upvoters": "مثبت ووٹ دینے والے", + "upvoted": "مثبت ووٹس کے ساتھ", + "downvoters": "منفی ووٹ دینے والے", + "downvoted": "منفی ووٹس کے ساتھ", + "views": "مشاہدات", + "posters": "پوسٹرز", + "watching": "نگرانی کرنے والے", + "reputation": "ساکھ", + "lastpost": "آخری پوسٹ", + "firstpost": "پہلی پوسٹ", + "about": "کے بارے میں", + "read-more": "مزید", + "more": "مزید", + "none": "کوئی نہیں", + "posted-ago-by-guest": "%1 پہلے مہمان نے پوسٹ کیا", + "posted-ago-by": "%1 پہلے %2 نے پوسٹ کیا", + "posted-ago": "%1 پہلے پوسٹ کیا گیا", + "posted-in": "%1 میں پوسٹ کیا گیا", + "posted-in-by": "%1 میں %2 نے پوسٹ کیا", + "posted-in-ago": "%1 میں %2 پہلے پوسٹ کیا گیا", + "posted-in-ago-by": "%1 میں %2 پہلے %3 نے پوسٹ کیا", + "user-posted-ago": "%1 نے %2 پہلے پوسٹ کیا", + "guest-posted-ago": "مہمان نے %1 پہلے پوسٹ کیا", + "last-edited-by": "%1 نے آخری بار ترمیم کیا", + "edited-timestamp": "%1 ترمیم کیا گیا", + "norecentposts": "کوئی حالیہ پوسٹس نہیں", + "norecenttopics": "کوئی حالیہ موضوعات نہیں", + "recentposts": "حالیہ پوسٹس", + "recentips": "حالیہ استعمال شدہ IP ایڈریسز", + "moderator-tools": "ماڈریٹر ٹولز", + "status": "حالت", + "online": "آن لائن", + "away": "دور", + "dnd": "تنگ نہ کریں", + "invisible": "غیر مرئی", + "offline": "آف لائن", + "remote-user": "یہ صارف اس فورم سے باہر ہے", + "email": "ای میل", + "language": "زبان", + "guest": "مہمان", + "guests": "مہمانوں", + "former-user": "سابق صارف", + "system-user": "سسٹم", + "unknown-user": "نامعلوم صارف", + "updated.title": "فورم اپ ڈیٹ ہو گیا", + "updated.message": "یہ فورم ابھی تازہ ترین ورژن میں اپ ڈیٹ ہوا ہے۔ صفحہ ریفریش کرنے کے لیے یہاں کلک کریں۔", + "privacy": "رازداری", + "follow": "فالو", + "unfollow": "فالو ختم کریں", + "delete-all": "سب کچھ حذف کریں", + "map": "نقشہ", + "sessions": "لاگ ان سیشنز", + "ip-address": "IP ایڈریس", + "enter-page-number": "صفحہ نمبر درج کریں", + "upload-file": "فائل اپ لوڈ کریں", + "upload": "اپ لوڈ", + "uploads": "اپ لوڈز", + "allowed-file-types": "اجازت شدہ فائل کی اقسام ہیں: %1", + "unsaved-changes": "آپ کے پاس غیر محفوظ تبدیلیاں ہیں۔ کیا آپ واقعی یہ صفحہ چھوڑنا چاہتے ہیں؟", + "reconnecting-message": "ایسا لگتا ہے کہ %1 سے آپ کا کنکشن منقطع ہو گیا ہے۔ براہ کرم انتظار کریں جب تک ہم آپ کو دوبارہ جوڑنے کی کوشش کرتے ہیں۔", + "play": "چلائیں", + "cookies.message": "یہ ویب سائٹ اپنی خدمات بہترین طریقے سے فراہم کرنے کے لیے کوکیز استعمال کرتی ہے۔", + "cookies.accept": "سمجھ گیا!", + "cookies.learn-more": "مزید جانیں", + "edited": "ترمیم شدہ", + "disabled": "غیر فعال", + "select": "منتخب کریں", + "selected": "منتخب شدہ", + "copied": "کاپی کیا گیا", + "user-search-prompt": "صارف کو تلاش کرنے کے لیے ٹائپ کرنا شروع کریں…", + "hidden": "چھپا ہوا", + "sort": "ترتیب", + "actions": "عمل", + "rss-feed": "RSS فیڈ", + "skip-to-content": "مواد پر جائیں" +} \ No newline at end of file diff --git a/public/language/ur/groups.json b/public/language/ur/groups.json new file mode 100644 index 0000000000..b81da6da50 --- /dev/null +++ b/public/language/ur/groups.json @@ -0,0 +1,68 @@ +{ + "group": "Group", + "all-groups": "تمام گروپس", + "groups": "گروپس", + "members": "اراکین", + "x-members": "%1 member(s)", + "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 کا رکن بننے کی درخواست کی ہے", + "cover-save": "محفوظ کریں", + "cover-saving": "محفوظ ہو رہا ہے", + "details.title": "گروپ کی تفصیلات", + "details.members": "اراکین کی فہرست", + "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.member-post-cids": "زمرہ جات کے شناخت کنندہ جن سے پوسٹس دکھائی جائیں", + "details.badge-preview": "بیج کا پیش نظارہ", + "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.delete-group": "گروپ حذف کریں", + "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 نے گروپ %2 چھوڑ دیا", + "membership.reject": "مسترد کریں", + "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/ur/ip-blacklist.json b/public/language/ur/ip-blacklist.json new file mode 100644 index 0000000000..b4f2fe2828 --- /dev/null +++ b/public/language/ur/ip-blacklist.json @@ -0,0 +1,19 @@ +{ + "lead": "یہاں آپ اپنی IP ایڈریس بلیک لسٹ ترتیب دے سکتے ہیں۔", + "description": "کبھی کبھار کسی صارف کے پروفائل پر پابندی لگانا کافی نہیں ہوتا۔ ایسی صورتوں میں، فورم کی حفاظت کا بہترین طریقہ مخصوص IP ایڈریس یا ایڈریسز کے گروپ کے لیے فورم تک رسائی کو محدود کرنا ہے۔ اس بلیک لسٹ میں آپ پریشانی والے IP ایڈریسز یا پورے CIDR بلاک کو شامل کر سکتے ہیں، اور یہ ایڈریسز سسٹم میں لاگ ان یا نئے پروفائلز رجسٹر نہیں کر سکیں گے۔", + "active-rules": "فعال قواعد", + "validate": "بلیک لسٹ کی تصدیق کریں", + "apply": "بلیک لسٹ کا اطلاق کریں", + "hints": "سینٹیکٹک ہینٹس", + "hint-1": "ہر لائن پر ایک IP ایڈریس درج کریں۔ آپ IP ایڈریسز کے گروپس کو شامل کر سکتے ہیں اگر وہ CIDR فارمیٹ پر عمل کریں (مثلاً 192.168.100.0/22)۔", + "hint-2": "آپ لائن کے شروع میں # علامت لگا کر تبصرے شامل کر سکتے ہیں۔", + + "validate.x-valid": "درست قواعد: %1 از %2۔", + "validate.x-invalid": "درج ذیل %1 قواعد غلط ہیں:", + + "alerts.applied-success": "بلیک لسٹ کا اطلاق ہو گیا", + + "analytics.blacklist-hourly": "شکل 1 – فی گھنٹہ بلیک لسٹ ہٹس", + "analytics.blacklist-daily": "شکل 2 – فی دن بلیک لسٹ ہٹس", + "ip-banned": "IP ایڈریس پر پابندی" +} \ No newline at end of file diff --git a/public/language/ur/language.json b/public/language/ur/language.json new file mode 100644 index 0000000000..4693356e14 --- /dev/null +++ b/public/language/ur/language.json @@ -0,0 +1,5 @@ +{ + "name": "اردو", + "code": "ur", + "dir": "rtl" +} \ No newline at end of file diff --git a/public/language/ur/login.json b/public/language/ur/login.json new file mode 100644 index 0000000000..7f2a7d901f --- /dev/null +++ b/public/language/ur/login.json @@ -0,0 +1,12 @@ +{ + "username-email": "صارف نام / ای میل", + "username": "صارف نام", + "remember-me": "مجھے یاد رکھیں؟", + "forgot-password": "پاس ورڈ بھول گئے؟", + "alternative-logins": "دوسرے لاگ ان کے طریقے", + "failed-login-attempt": "ناکام لاگ ان کی کوشش", + "login-successful": "آپ نے کامیابی سے لاگ ان کیا!", + "dont-have-account": "کیا آپ کا اکاؤنٹ نہیں ہے؟", + "logged-out-due-to-inactivity": "آپ غیر فعالیت کی وجہ سے ایڈمنسٹریٹو کنٹرول پینل سے خودکار طور پر لاگ آؤٹ ہو گئے ہیں۔", + "caps-lock-enabled": "کیپٹل لاک فعال ہے" +} \ No newline at end of file diff --git a/public/language/ur/modules.json b/public/language/ur/modules.json new file mode 100644 index 0000000000..98a93d7ae8 --- /dev/null +++ b/public/language/ur/modules.json @@ -0,0 +1,135 @@ +{ + "chat.room-id": "کمرہ %1", + "chat.chatting-with": "کے ساتھ گفتگو", + "chat.placeholder": "یہاں پیغام درج کریں یا تصاویر ڈریگ اینڈ ڈراپ کریں", + "chat.placeholder.mobile": "پیغام درج کریں", + "chat.placeholder.message-room": "پیغام #%1", + "chat.scroll-up-alert": "تازہ ترین پیغامات کی طرف", + "chat.usernames-and-x-others": "%1 اور %2 دیگر", + "chat.chat-with-usernames": "%1 کے ساتھ گفتگو", + "chat.chat-with-usernames-and-x-others": "%1 اور %2 دیگر کے ساتھ گفتگو", + "chat.send": "بھیجیں", + "chat.no-active": "آپ کے کوئی جاری گفتگو نہیں ہیں۔", + "chat.user-typing-1": "%1 لکھ رہا ہے…", + "chat.user-typing-2": "%1 اور %2 لکھ رہے ہیں…", + "chat.user-typing-3": "%1، %2 اور %3 لکھ رہے ہیں…", + "chat.user-typing-n": "%1، %2 اور %3 دیگر لکھ رہے ہیں…", + "chat.user-has-messaged-you": "%1 نے آپ کو پیغام بھیجا۔", + "chat.replying-to": "%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.three-months": "3 ماہ", + "chat.delete-message-confirm": "کیا آپ واقعی اس پیغام کو حذف کرنا چاہتے ہیں؟", + "chat.retrieving-users": "صارفین حاصل کیے جا رہے ہیں…", + "chat.view-users-list": "صارفین کی فہرست دیکھیں", + "chat.pinned-messages": "پن کیے گئے پیغامات", + "chat.no-pinned-messages": "کوئی پن کیے گئے پیغامات نہیں", + "chat.pin-message": "پیغام پن کریں", + "chat.unpin-message": "پیغام ان پن کریں", + "chat.public-rooms": "عوامی کمرے (%1)", + "chat.private-rooms": "نجی کمرے (%1)", + "chat.create-room": "گفتگو کا کمرہ بنائیں", + "chat.private.option": "نجی (صرف کمرے میں شامل کیے گئے صارفین کے لیے نظر آتا ہے)", + "chat.public.option": "عوامی (منتخب گروپس کے تمام صارفین کے لیے نظر آتا ہے)", + "chat.public.groups-help": "تمام صارفین کے لیے نظر آنے والا گفتگو کا کمرہ بنانے کے لیے فہرست سے رجسٹرڈ صارفین کا گروپ منتخب کریں۔", + "chat.manage-room": "گفتگو کے کمرے کا انتظام", + "chat.add-user": "صارف شامل کریں", + "chat.notification-settings": "اطلاعات کی ترتیبات", + "chat.default-notification-setting": "طے شدہ اطلاعاتی ترتیبات", + "chat.join-leave-messages": "شامل ہونے/چھوڑنے کے پیغامات", + "chat.notification-setting-room-default": "کمرے کے لیے طے شدہ", + "chat.notification-setting-none": "کوئی اطلاعات نہیں", + "chat.notification-setting-at-mention-only": "صرف @مینشنز", + "chat.notification-setting-all-messages": "تمام پیغامات", + "chat.select-groups": "گروپس منتخب کریں", + "chat.add-user-help": "یہاں آپ صارفین کو تلاش کر سکتے ہیں۔ جب کوئی صارف منتخب کیا جاتا ہے، اسے گفتگو میں شامل کیا جائے گا۔ نیا صارف اس سے پہلے کے پیغامات نہیں دیکھ سکے گا جو اس کے شامل ہونے سے پہلے لکھے گئے تھے۔ صرف کمرے کے مالکان () صارفین کو کمرے سے ہٹا سکتے ہیں۔", + "chat.confirm-chat-with-dnd-user": "یہ صارف 'تنگ نہ کریں' حالت میں ہے۔ کیا آپ واقعی اس سے گفتگو کرنا چاہتے ہیں؟", + "chat.room-name-optional": "کمرے کا نام (اختیاری)", + "chat.rename-room": "کمرہ دوبارہ نام دیں", + "chat.rename-placeholder": "اپنے کمرے کا نام یہاں درج کریں", + "chat.rename-help": "یہاں متعین کیا گیا کمرے کا نام اس کے تمام شرکاء کو نظر آئے گا۔", + "chat.leave": "چھوڑیں", + "chat.leave-room": "کمرہ چھوڑیں", + "chat.leave-prompt": "کیا آپ واقعی اس گفتگو کو چھوڑنا چاہتے ہیں؟", + "chat.leave-help": "اگر آپ اس گفتگو کو چھوڑتے ہیں تو آپ اس کے بعد کے پیغامات نہیں دیکھ سکیں گے۔ اگر آپ کو دوبارہ شامل کیا جاتا ہے تو آپ اس سے پہلے کی گفتگو کی تاریخ نہیں دیکھ سکیں گے۔", + "chat.delete": "حذف کریں", + "chat.delete-room": "گفتگو کا کمرہ حذف کریں", + "chat.delete-prompt": "کیا آپ واقعی اس گفتگو کے کمرے کو حذف کرنا چاہتے ہیں؟", + "chat.in-room": "اس کمرے میں", + "chat.kick": "نکالیں", + "chat.show-ip": "IP ایڈریس دکھائیں", + "chat.copy-text": "متن کاپی کریں", + "chat.copy-link": "لنک کاپی کریں", + "chat.owner": "کمرے کا مالک", + "chat.grant-rescind-ownership": "مالکانہ حقوق دینا/واپس لینا", + "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.help": "مدد", + "composer.user-said-in": "%1 نے %2 میں کہا:", + "composer.user-said": "%1 [said](%2):", + "composer.discard": "کیا آپ واقعی اس پوسٹ کو مسترد کرنا چاہتے ہیں؟", + "composer.submit-and-lock": "پوسٹ کریں اور لاک کریں", + "composer.toggle-dropdown": "ڈراپ ڈاؤن ٹوگل کریں", + "composer.uploading": "%1 اپ لوڈ ہو رہا ہے", + "composer.formatting.bold": "بولڈ", + "composer.formatting.italic": "ایتھیلک", + "composer.formatting.heading": "ہیڈنگ", + "composer.formatting.heading1": "ہیڈنگ 1", + "composer.formatting.heading2": "ہیڈنگ 2", + "composer.formatting.heading3": "ہیڈنگ 3", + "composer.formatting.heading4": "ہیڈنگ 4", + "composer.formatting.heading5": "ہیڈنگ 5", + "composer.formatting.heading6": "ہیڈنگ 6", + "composer.formatting.list": "فہرست", + "composer.formatting.strikethrough": "سٹرائیک تھرو", + "composer.formatting.code": "کوڈ", + "composer.formatting.link": "لنک", + "composer.formatting.picture": "تصویر کا لنک", + "composer.upload-picture": "تصویر اپ لوڈ کریں", + "composer.upload-file": "فائل اپ لوڈ کریں", + "composer.zen-mode": "زین موڈ", + "composer.select-category": "زمرہ منتخب کریں", + "composer.textarea.placeholder": "اپنی پوسٹ کا مواد یہاں درج کریں۔ آپ تصاویر کو بھی ڈریگ اینڈ ڈراپ کر سکتے ہیں۔", + "composer.post-queue-alert": "ہیلو👋!
یہ فورم ایک ایسی سسٹم استعمال کرتا ہے جس میں پوسٹس کو قطار میں شامل کیا جاتا ہے۔ چونکہ آپ ایک نئے صارف ہیں، آپ کی پوسٹ اس وقت تک چھپی رہے گی جب تک کہ اسے ماڈریٹر کی طرف سے منظور نہ کر لیا جائے۔", + "composer.schedule-for": "موضوع کے لیے شیڈول کریں", + "composer.schedule-date": "تاریخ", + "composer.schedule-time": "وقت", + "composer.cancel-scheduling": "شیڈولنگ منسوخ کریں", + "composer.change-schedule-date": "تاریخ تبدیل کریں", + "composer.set-schedule-date": "تاریخ متعین کریں", + "composer.discard-all-drafts": "تمام مسودات حذف کریں", + "composer.no-drafts": "آپ کے پاس کوئی مسودات نہیں ہیں", + "composer.discard-draft-confirm": "کیا آپ اس مسودے کو حذف کرنا چاہتے ہیں؟", + "composer.remote-pid-editing": "دور دراز پوسٹ کی ترمیم", + "composer.remote-pid-content-immutable": "دور دراز پوسٹس کا مواد ترمیم نہیں کیا جا سکتا۔ آپ صرف موضوع کا عنوان اور ٹیگز تبدیل کر سکتے ہیں۔", + "bootbox.ok": "ٹھیک ہے", + "bootbox.cancel": "منسوخ", + "bootbox.confirm": "تصدیق", + "bootbox.submit": "جمع کرائیں", + "bootbox.send": "بھیجیں", + "cover.dragging-title": "تصویر کی ترتیب", + "cover.dragging-message": "تصویر کو مطلوبہ پوزیشن پر منتقل کریں اور 'محفوظ کریں' دبائیں", + "cover.saved": "تصویر اور اس کی پوزیشن محفوظ کر دی گئی", + "thumbs.modal.title": "موضوعات کی آئیکنز کا انتظام", + "thumbs.modal.no-thumbs": "کوئی آئیکنز نہیں ملیں۔", + "thumbs.modal.resize-note": "نوٹ: یہ فورم موضوعات کی آئیکنز کو زیادہ سے زیادہ %1px چوڑائی تک ری سائز کرنے کے لیے ترتیب دیا گیا ہے", + "thumbs.modal.add": "آئیکن شامل کریں", + "thumbs.modal.remove": "آئیکن ہٹائیں", + "thumbs.modal.confirm-remove": "کیا آپ واقعی اس آئیکن کو ہٹانا چاہتے ہیں؟" +} \ No newline at end of file diff --git a/public/language/ur/notifications.json b/public/language/ur/notifications.json new file mode 100644 index 0000000000..817914ede7 --- /dev/null +++ b/public/language/ur/notifications.json @@ -0,0 +1,106 @@ +{ + "title": "اطلاعات", + "no-notifs": "آپ کے پاس کوئی نئی اطلاعات نہیں ہیں", + "see-all": "تمام اطلاعات دیکھیں", + "mark-all-read": "سب کو پڑھا ہوا نشان زد کریں", + "back-to-home": "%1 پر واپس", + "outgoing-link": "خارجی لنک", + "outgoing-link-message": "آپ %1 چھوڑ رہے ہیں", + "continue-to": "%1 پر جاری رکھیں", + "return-to": "%1 پر واپس جائیں", + "new-notification": "آپ کے پاس ایک نئی اطلاع ہے", + "you-have-unread-notifications": "آپ کے پاس غیر پڑھی ہوئی اطلاعات ہیں", + "all": "تمام", + "topics": "موضوعات", + "tags": "ٹیگز", + "categories": "زمرہ جات", + "replies": "جوابات", + "chat": "گفتگو", + "group-chat": "گروپ گفتگو", + "public-chat": "عوامی گفتگو", + "follows": "فالوز", + "upvote": "مثبت ووٹس", + "awards": "ایوارڈز", + "new-flags": "نئی رپورٹس", + "my-flags": "My Flags", + "bans": "پابندیاں", + "new-message-from": "%1 سے نیا پیغام", + "new-messages-from": "%2 سے %1 نئے پیغامات", + "new-message-in": "%1 میں نیا پیغام", + "new-messages-in": "%2 میں %1 نئے پیغامات", + "user-posted-in-public-room": "%1 نے %3 میں لکھا", + "user-posted-in-public-room-dual": "%1 اور %2 نے %4 میں لکھا", + "user-posted-in-public-room-triple": "%1، %2 اور %3 نے %5 میں لکھا", + "user-posted-in-public-room-multiple": "%1، %2 اور %3 دیگر نے %5 میں لکھا", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", + "moved-your-post": "%1 نے آپ کی پوسٹ کو %2 میں منتقل کیا", + "moved-your-topic": "%1 نے %2 منتقل کیا", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", + "user-flagged-user": "%1 نے صارف پروفائل (%2) رپورٹ کیا", + "user-flagged-user-dual": "%1 اور %2 نے صارف پروفائل (%3) رپورٹ کیا", + "user-flagged-user-triple": "%1، %2 اور %3 نے صارف پروفائل (%4) رپورٹ کیا", + "user-flagged-user-multiple": "%1، %2 اور %3 دیگر نے صارف پروفائل (%4) رپورٹ کیا", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", + "user-started-following-you": "%1 نے آپ کو فالو کرنا شروع کیا۔", + "user-started-following-you-dual": "%1 اور %2 نے آپ کو فالو کرنا شروع کیا۔", + "user-started-following-you-triple": "%1، %2 اور %3 نے آپ کو فالو کرنا شروع کیا۔", + "user-started-following-you-multiple": "%1، %2 اور %3 دیگر نے آپ کو فالو کرنا شروع کیا۔", + "new-register": "%1 نے رجسٹریشن کی درخواست بھیجی۔", + "new-register-multiple": "%1 رجسٹریشن کی درخواستیں جائزے کے منتظر ہیں۔", + "flag-assigned-to-you": "رپورٹ %1 آپ کو تفویض کی گئی ہے", + "post-awaiting-review": "پوسٹ جائزے کے منتظر ہے", + "profile-exported": "%1 کا پروفائل ایکسپورٹ کر دیا گیا ہے، ڈاؤن لوڈ کے لیے کلک کریں", + "posts-exported": "%1 کی پوسٹس ایکسپورٹ کر دی گئی ہیں، ڈاؤن لوڈ کے لیے کلک کریں", + "uploads-exported": "%1 کے اپ لوڈز ایکسپورٹ کر دیے گئے ہیں، ڈاؤن لوڈ کے لیے کلک کریں", + "users-csv-exported": "صارفین کو CSV فارمیٹ میں ایکسپورٹ کیا گیا ہے، ڈاؤن لوڈ کے لیے کلک کریں", + "post-queue-accepted": "آپ کی قطار میں موجود پوسٹ قبول کر لی گئی ہے۔ اسے دیکھنے کے لیے یہاں کلک کریں۔", + "post-queue-rejected": "آپ کی قطار میں موجود پوسٹ مسترد کر دی گئی ہے۔", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", + "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-topic-with-tag": "جب ایک نیا موضوع اس ٹیگ کے ساتھ پوسٹ کیا جاتا ہے جسے آپ فالو کرتے ہیں", + "notificationType-new-topic-in-category": "جب ایک نیا موضوع اس زمرے میں پوسٹ کیا جاتا ہے جسے آپ دیکھتے ہیں", + "notificationType-new-reply": "جب کسی موضوع میں نیا جواب پوسٹ کیا جاتا ہے جسے آپ دیکھتے ہیں", + "notificationType-post-edit": "جب کسی موضوع میں پوسٹ ترمیم کی جاتی ہے جسے آپ دیکھتے ہیں", + "notificationType-follow": "جب کوئی آپ کو فالو کرنا شروع کرتا ہے", + "notificationType-new-chat": "جب آپ کو گفتگو میں پیغام موصول ہوتا ہے", + "notificationType-new-group-chat": "جب آپ کو گروپ گفتگو میں پیغام موصول ہوتا ہے", + "notificationType-new-public-chat": "جب آپ کو عوامی گروپ گفتگو میں پیغام موصول ہوتا ہے", + "notificationType-group-invite": "جب آپ کو گروپ کی دعوت موصول ہوتی ہے", + "notificationType-group-leave": "جب کوئی صارف آپ کے گروپ کو چھوڑتا ہے", + "notificationType-group-request-membership": "جب کوئی آپ کے گروپ میں شامل ہونے کی درخواست کرتا ہے جس کے آپ مالک ہیں", + "notificationType-new-register": "جب کوئی رجسٹریشن کی قطار میں شامل ہوتا ہے", + "notificationType-post-queue": "جب ایک نئی پوسٹ قطار میں شامل کی جاتی ہے", + "notificationType-new-post-flag": "جب ایک پوسٹ رپورٹ کی جاتی ہے", + "notificationType-new-user-flag": "جب ایک صارف رپورٹ کیا جاتا ہے", + "notificationType-new-reward": "جب آپ کو ایک نیا ایوارڈ ملتا ہے", + "activitypub.announce": "%1 نے آپ کی پوسٹ کو %2 میں اپنے فالوورز کے ساتھ شیئر کیا۔", + "activitypub.announce-dual": "%1 اور %2 نے آپ کی پوسٹ کو %3 میں اپنے فالوورز کے ساتھ شیئر کیا۔", + "activitypub.announce-triple": "%1، %2 اور %3 نے آپ کی پوسٹ کو %4 میں اپنے فالوورز کے ساتھ شیئر کیا۔", + "activitypub.announce-multiple": "%1، %2 اور %3 دیگر نے آپ کی پوسٹ کو %4 میں اپنے فالوورز کے ساتھ شیئر کیا۔" +} \ No newline at end of file diff --git a/public/language/ur/pages.json b/public/language/ur/pages.json new file mode 100644 index 0000000000..8f2bc4f24f --- /dev/null +++ b/public/language/ur/pages.json @@ -0,0 +1,71 @@ +{ + "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": "پوسٹس کی قطار", + "registration-queue": "رجسٹریشن کی قطار", + "users/online": "آن لائن صارفین", + "users/latest": "تازہ ترین صارفین", + "users/sort-posts": "سب سے زیادہ پوسٹس والے صارفین", + "users/sort-reputation": "سب سے زیادہ ساکھ والے صارفین", + "users/banned": "پابندی شدہ صارفین", + "users/most-flags": "سب سے زیادہ رپورٹ شدہ صارفین", + "users/search": "صارفین کی تلاش", + "notifications": "اطلاعات", + "tags": "ٹیگز", + "tag": "موضوعات جن پر '%1' کا ٹیگ لگا ہوا ہے", + "register": "اکاؤنٹ رجسٹر کریں", + "registration-complete": "رجسٹریشن مکمل ہو گئی", + "login": "اپنے اکاؤنٹ میں لاگ ان کریں", + "reset": "اپنے اکاؤنٹ کا پاس ورڈ ری سیٹ کریں", + "categories": "زمرہ جات", + "groups": "گروپس", + "group": "گروپ %1", + "chats": "گفتگو", + "chat": "%1 کے ساتھ گفتگو", + "flags": "رپورٹس", + "flag-details": "رپورٹ %1 کی تفصیلات", + "world": "جهان", + "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": "%1 کی تازہ ترین پوسٹس", + "account/topics": "%1 کے بنائے ہوئے موضوعات", + "account/groups": "%1 کے گروپس", + "account/watched-categories": "%1 کے دیکھے ہوئے زمرہ جات", + "account/watched-tags": "%1 کے دیکھے ہوئے ٹیگز", + "account/bookmarks": "%1 کی بک مارک کردہ پوسٹس", + "account/settings": "صارف کی ترتیبات", + "account/settings-of": "%1 کی ترتیبات تبدیل کی جا رہی ہیں", + "account/watched": "%1 کے دیکھے ہوئے موضوعات", + "account/ignored": "%1 کے نظر انداز کردہ موضوعات", + "account/read": "%1 کے پڑھے ہوئے موضوعات", + "account/upvoted": "%1 کی طرف سے مثبت ووٹ دی گئی پوسٹس", + "account/downvoted": "%1 کی طرف سے منفی ووٹ دی گئی پوسٹس", + "account/best": "%1 کی بہترین پوسٹس", + "account/controversial": "%1 کی متنازعہ پوسٹس", + "account/blocks": "%1 کے لیے بلاک شدہ صارفین", + "account/uploads": "%1 کے اپ لوڈز", + "account/sessions": "لاگ ان سیشنز", + "account/shares": "%1 کی شیئر کردہ موضوعات", + "confirm": "ای میل کی تصدیق ہو گئی", + "maintenance.text": "%1 فی الحال دیکھ بھال کے تحت ہے۔
براہ کرم بعد میں واپس آئیں۔", + "maintenance.messageIntro": "مزید برآں، ایڈمنسٹریٹر نے یہ پیغام چھوڑا ہے:", + "throttled.text": "%1 فی الحال ضرورت سے زیادہ بوجھ کی وجہ سے ناقابل رسائی ہے۔ براہ کرم بعد میں دوبارہ آئیں۔" +} \ No newline at end of file diff --git a/public/language/ur/post-queue.json b/public/language/ur/post-queue.json new file mode 100644 index 0000000000..fac19e77f3 --- /dev/null +++ b/public/language/ur/post-queue.json @@ -0,0 +1,43 @@ + +{ + "post-queue": "پوسٹس کی قطار", + "no-queued-posts": "پوسٹس کی قطار میں کچھ بھی نہیں ہے۔", + "no-single-post": "آپ جس موضوع یا پوسٹ کی تلاش کر رہے ہیں وہ اب قطار میں نہیں ہے۔ ممکنہ طور پر اسے یا تو منظور کر لیا گیا ہے یا حذف کر دیا گیا ہے۔", + "enabling-help": "اس وقت پوسٹس کی قطار غیر فعال ہے۔ اس فعالیت کو فعال کرنے کے لیے، ترتیبات → پوسٹس → پوسٹس کی قطار پر جائیں اور پوسٹس کی قطار کو فعال کریں۔", + "back-to-list": "پوسٹس کی قطار پر واپس", + "public-intro": "اگر آپ کے پاس قطار میں انتظار کرنے والی پوسٹس ہیں، وہ یہاں دکھائی جائیں گی۔", + "public-description": "یہ فورم اس طرح ترتیب دیا گیا ہے کہ نئے صارفین کی پوسٹس خودکار طور پر قطار میں شامل ہو جاتی ہیں تاکہ ماڈریٹر کی منظوری کا انتظار کیا جائے۔
اگر آپ کے پاس منظوری کے لیے قطار میں انتظار کرنے والی پوسٹس ہیں، آپ انہیں یہاں دیکھ سکیں گے۔", + "user": "صارف", + "when": "کب", + "category": "زمرہ", + "title": "عنوان", + "content": "مواد", + "posted": "پوسٹ کیا گیا", + "reply-to": "«%1» کا جواب", + "content-editable": "مواد کو ترمیم کرنے کے لیے اس پر کلک کریں", + "category-editable": "زمرہ کو ترمیم کرنے کے لیے اس پر کلک کریں", + "title-editable": "عنوان کو ترمیم کرنے کے لیے اس پر کلک کریں", + "reply": "جواب", + "topic": "موضوع", + "accept": "قبول کریں", + "reject": "مسترد کریں", + "remove": "ہٹائیں", + "notify": "اطلاع دیں", + "notify-user": "صارف کو اطلاع دیں", + "confirm-reject": "کیا آپ اس پوسٹ کو مسترد کرنا چاہتے ہیں؟", + "confirm-remove": "کیا آپ اس پوسٹ کو ہٹانا چاہتے ہیں؟", + "bulk-actions": "اجتماعی اقدامات", + "accept-all": "سب کو قبول کریں", + "accept-selected": "منتخب کردہ کو قبول کریں", + "reject-all": "سب کو مسترد کریں", + "reject-all-confirm": "کیا آپ واقعی تمام پوسٹس کو مسترد کرنا چاہتے ہیں؟", + "reject-selected": "منتخب کردہ کو مسترد کریں", + "reject-selected-confirm": "کیا آپ واقعی %1 منتخب کردہ پوسٹس کو مسترد کرنا چاہتے ہیں؟", + "remove-all": "سب کو ہٹائیں", + "remove-all-confirm": "کیا آپ واقعی تمام پوسٹس کو ہٹانا چاہتے ہیں؟", + "remove-selected": "منتخب کردہ کو ہٹائیں", + "remove-selected-confirm": "کیا آپ واقعی %1 منتخب کردہ پوسٹس کو ہٹانا چاہتے ہیں؟", + "bulk-accept-success": "منظور شدہ پوسٹس: %1", + "bulk-reject-success": "مسترد شدہ پوسٹس: %1", + "links-in-this-post": "اس پوسٹ میں لنکس" +} \ No newline at end of file diff --git a/public/language/ur/recent.json b/public/language/ur/recent.json new file mode 100644 index 0000000000..d66c5488b1 --- /dev/null +++ b/public/language/ur/recent.json @@ -0,0 +1,13 @@ +{ + "title": "حالیہ", + "day": "دن", + "week": "ہفتہ", + "month": "مہینہ", + "year": "سال", + "alltime": "ہر وقت", + "no-recent-topics": "کوئی حالیہ موضوعات نہیں ہیں۔", + "no-popular-topics": "کوئی مقبول موضوعات نہیں ہیں۔", + "load-new-posts": "نئی پوسٹس لوڈ کریں", + "uncategorized.title": "تمام معلوم موضوعات", + "uncategorized.intro": "یہ صفحہ اس فورم کے موصول ہونے والے تمام موضوعات کی ایک تاریخی فہرست دکھاتا ہے۔
نیچے دیے گئے موضوعات میں خیالات اور رائے کو کسی بھی طرح سے فلٹر نہیں کیا گیا اور یہ اس ویب سائٹ کے خیالات اور رائے سے مطابقت نہیں رکھتے ہو سکتے ہیں۔" +} \ No newline at end of file diff --git a/public/language/ur/register.json b/public/language/ur/register.json new file mode 100644 index 0000000000..8800aa75b4 --- /dev/null +++ b/public/language/ur/register.json @@ -0,0 +1,33 @@ +{ + "register": "رجسٹریشن", + "already-have-account": "کیا آپ کا پہلے سے اکاؤنٹ ہے؟", + "cancel-registration": "رجسٹریشن منسوخ کریں", + "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": "رجسٹریشن کا دوسرا طریقہ", + "terms-of-use": "استعمال کے شرائط", + "agree-to-terms-of-use": "میں استعمال کے شرائط سے اتفاق کرتا ہوں", + "terms-of-use-error": "آپ کو استعمال کے شرائط سے اتفاق کرنا ہوگا", + "registration-added-to-queue": "آپ کی رجسٹریشن منظوری کے لیے قطار میں شامل کر دی گئی ہے۔ جب ایڈمنسٹریٹر اسے منظور کرے گا تو آپ کو ایک ای میل موصول ہوگا۔", + "registration-queue-average-time": "نئے اراکین کی منظوری کے لیے اوسط وقت %1 گھنٹے اور %2 منٹ ہے۔", + "registration-queue-auto-approve-time": "اس فورم میں آپ کی رکنیت تقریباً %1 گھنٹوں میں مکمل طور پر فعال ہو جائے گی۔", + "interstitial.intro": "ہمیں آپ کے اکاؤنٹ کو اپ ڈیٹ کرنے سے پہلے کچھ اضافی معلومات کی ضرورت ہے…", + "interstitial.intro-new": "ہمیں آپ کا اکاؤنٹ بنانے سے پہلے کچھ اضافی معلومات کی ضرورت ہے…", + "interstitial.errors-found": "براہ کرم درج کردہ معلومات کا جائزہ لیں:", + "gdpr-agree-data": "میں اس بات سے اتفاق کرتا ہوں کہ اس ویب سائٹ پر میری ذاتی معلومات کو جمع اور پروسیس کیا جائے۔", + "gdpr-agree-email": "میں اس ویب سائٹ سے ڈائجسٹ اور اطلاعات کے ای میلز وصول کرنے سے اتفاق کرتا ہوں۔", + "gdpr-consent-denied": "آپ کو اس ویب سائٹ کو اپنی معلومات جمع/پروسیس کرنے اور آپ کو ای میلز بھیجنے کی اجازت دینی ہوگی۔", + "invite.error-admin-only": "براہ راست رجسٹریشن غیر فعال ہے۔ مزید تفصیلات کے لیے براہ کرم ایڈمنسٹریٹر سے رابطہ کریں۔", + "invite.error-invite-only": "براہ راست رجسٹریشن غیر فعال ہے۔ اس فورم تک رسائی کے لیے آپ کو پہلے سے رجسٹرڈ صارف سے دعوت حاصل کرنی ہوگی۔", + "invite.error-invalid-data": "رجسٹریشن کے لیے موصول ہونے والا ڈیٹا ہمارے ریکارڈز سے مطابقت نہیں رکھتا۔ براہ کرم مزید تفصیلات کے لیے ایڈمنسٹریٹر سے رابطہ کریں۔" +} \ No newline at end of file diff --git a/public/language/ur/reset_password.json b/public/language/ur/reset_password.json new file mode 100644 index 0000000000..6346614abe --- /dev/null +++ b/public/language/ur/reset_password.json @@ -0,0 +1,18 @@ +{ + "reset-password": "پاس ورڈ کی تجدید", + "update-password": "پاس ورڈ تبدیل کریں", + "password-changed.title": "پاس ورڈ تبدیل کر دیا گیا", + "password-changed.message": "

پاس ورڈ کامیابی سے ری سیٹ ہو گیا ہے۔ براہ کرم دوبارہ لاگ ان کریں۔", + "wrong-reset-code.title": "غلط ری سیٹ کوڈ", + "wrong-reset-code.message": "موصول ہونے والا ری سیٹ کوڈ غلط تھا۔ براہ کرم دوبارہ کوشش کریں یا نیا ری سیٹ کوڈ کی درخواست کریں۔", + "new-password": "نیا پاس ورڈ", + "repeat-password": "پاس ورڈ کی تصدیق کریں", + "changing-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/ur/rewards.json b/public/language/ur/rewards.json new file mode 100644 index 0000000000..aed43ef915 --- /dev/null +++ b/public/language/ur/rewards.json @@ -0,0 +1,10 @@ +{ + "awarded-x-reputation": "آپ نے %1 ساکھ کے پوائنٹس حاصل کیے", + "awarded-group-membership": "آپ کو گروپ %1 میں شامل کیا گیا ہے", + + "essentials/user.reputation-conditional-value": "(ساکھ %1 %2)", + "essentials/user.postcount-conditional-value": "(پوسٹس کی تعداد %1 %2)", + "essentials/user.lastonline-conditional-value": "(آخری بار آن لائن %1 %2)", + "essentials/user.joindate-conditional-value": "(شامل ہونے کی تاریخ %1 %2)", + "essentials/user.daysregistered-conditional-value": "(رجسٹریشن کے دنوں کی تعداد %1 %2)" +} \ No newline at end of file diff --git a/public/language/ur/search.json b/public/language/ur/search.json new file mode 100644 index 0000000000..85f3afc883 --- /dev/null +++ b/public/language/ur/search.json @@ -0,0 +1,110 @@ +{ + "type-to-search": "یہاں تلاش کے لئے لکھیں", + "results-matching": "%1 نتیجہ (نتائج)، „%2“ سے مماثل، (%3 سیکنڈ)", + "no-matches": "کوئی مماثلت نہیں", + "advanced-search": "اعلیٰ تلاش", + "in": "میں", + "in-titles": "عنوانات میں", + "in-titles-posts": "عنوانات اور پوسٹس میں", + "in-posts": "پوسٹس میں", + "in-bookmarks": "بک مارکس میں", + "in-categories": "زمرہ جات میں", + "in-users": "صارفین میں", + "in-tags": "ٹیگز میں", + "categories": "زمرہ جات", + "all-categories": "تمام زمرہ جات", + "categories-x": "زمرہ جات: %1", + "categories-watched-categories": "زمرہ جات: دیکھے گئے زمرہ جات", + "type-a-category": "زمرہ درج کریں", + "tags": "ٹیگز", + "tags-x": "ٹیگز: %1", + "type-a-tag": "ٹیگ درج کریں", + "match-words": "الفاظ کی مماثلت", + "match-all-words": "تمام الفاظ کی مماثلت", + "match-any-word": "کسی ایک لفظ کی مماثلت", + "all": "تمام", + "any": "کوئی بھی", + "posted-by": "کی طرف سے پوسٹ کیا گیا", + "posted-by-usernames": "کی طرف سے پوسٹ کیا گیا: %1", + "type-a-username": "صارف کا نام درج کریں", + "search-child-categories": "ذیلی زمرہ جات کی تلاش", + "has-tags": "ٹیگز ہیں", + "reply-count": "جوابات کی تعداد", + "replies": "جوابات", + "replies-atleast-count": "جوابات: کم از کم %1", + "replies-atmost-count": "جوابات: زیادہ سے زیادہ %1", + "at-least": "کم از کم", + "at-most": "زیادہ سے زیادہ", + "relevance": "مطابقت", + "time": "وقت", + "post-time": "پوسٹ کا وقت", + "votes": "ووٹ", + "newer-than": "اس سے نئے", + "older-than": "اس سے پرانے", + "any-date": "کوئی بھی تاریخ", + "yesterday": "کل", + "one-week": "ایک ہفتہ", + "two-weeks": "دو ہفتے", + "one-month": "ایک ماہ", + "three-months": "تین ماہ", + "six-months": "چھ ماہ", + "one-year": "ایک سال", + "time-newer-than-86400": "وقت: کل سے اب تک", + "time-older-than-86400": "وقت: کل سے پہلے", + "time-newer-than-604800": "وقت: ایک ہفتے سے نئے", + "time-older-than-604800": "وقت: ایک ہفتے سے پرانے", + "time-newer-than-1209600": "وقت: دو ہفتوں سے نئے", + "time-older-than-1209600": "وقت: دو ہفتوں سے پرانے", + "time-newer-than-2592000": "وقت: ایک ماہ سے نئے", + "time-older-than-2592000": "وقت: ایک ماہ سے پرانے", + "time-newer-than-7776000": "وقت: تین ماہ سے نئے", + "time-older-than-7776000": "وقت: تین ماہ سے پرانے", + "time-newer-than-15552000": "وقت: چھ ماہ سے نئے", + "time-older-than-15552000": "وقت: چھ ماہ سے پرانے", + "time-newer-than-31104000": "وقت: ایک سال سے نئے", + "time-older-than-31104000": "وقت: ایک سال سے پرانے", + "sort-by": "ترتیب دیں بمطابق", + "sort": "ترتیب", + "last-reply-time": "آخری جواب کا وقت", + "topic-title": "موضوع کا عنوان", + "topic-votes": "موضوع کے لئے ووٹ", + "number-of-replies": "جوابات کی تعداد", + "number-of-views": "دیکھنے کی تعداد", + "topic-start-date": "موضوع کی شروعاتی تاریخ", + "username": "صارف کا نام", + "category": "زمرہ", + "descending": "نازل ترتیب میں", + "ascending": "صعودی ترتیب میں", + "sort-by-relevance-desc": "ترتیب دیں بمطابق: مطابقت، نازل ترتیب میں", + "sort-by-relevance-asc": "ترتیب دیں بمطابق: مطابقت، صعودی ترتیب میں", + "sort-by-timestamp-desc": "ترتیب دیں بمطابق: پوسٹ کا وقت، نازل ترتیب میں", + "sort-by-timestamp-asc": "ترتیب دیں بمطابق: پوسٹ کا وقت، صعودی ترتیب میں", + "sort-by-votes-desc": "ترتیب دیں بمطابق: ووٹوں کی تعداد، نازل ترتیب میں", + "sort-by-votes-asc": "ترتیب دیں بمطابق: ووٹوں کی تعداد، صعودی ترتیب میں", + "sort-by-topic.lastposttime-desc": "ترتیب دیں بمطابق: آخری جواب کا وقت، نازل ترتیب میں", + "sort-by-topic.lastposttime-asc": "ترتیب دیں بمطابق: آخری جواب کا وقت، صعودی ترتیب میں", + "sort-by-topic.title-desc": "ترتیب دیں بمطابق: موضوع کا عنوان، نازل ترتیب میں", + "sort-by-topic.title-asc": "ترتیب دیں بمطابق: موضوع کا عنوان، صعودی ترتیب میں", + "sort-by-topic.postcount-desc": "ترتیب دیں بمطابق: جوابات کی تعداد، نازل ترتیب میں", + "sort-by-topic.postcount-asc": "ترتیب دیں بمطابق: جوابات کی تعداد، صعودی ترتیب میں", + "sort-by-topic.viewcount-desc": "ترتیب دیں بمطابق: دیکھنے کی تعداد، نازل ترتیب میں", + "sort-by-topic.viewcount-asc": "ترتیب دیں بمطابق: دیکھنے کی تعداد، صعودی ترتیب میں", + "sort-by-topic.votes-desc": "ترتیب دیں بمطابق: موضوع کے ووٹوں کی تعداد، نازل ترتیب میں", + "sort-by-topic.votes-asc": "ترتیب دیں بمطابق: موضوع کے ووٹوں کی تعداد، صعودی ترتیب میں", + "sort-by-topic.timestamp-desc": "ترتیب دیں بمطابق: موضوع کی شروعاتی تاریخ، نازل ترتیب میں", + "sort-by-topic.timestamp-asc": "ترتیب دیں بمطابق: موضوع کی شروعاتی تاریخ، صعودی ترتیب میں", + "sort-by-user.username-desc": "ترتیب دیں بمطابق: صارف کا نام، نازل ترتیب میں", + "sort-by-user.username-asc": "ترتیب دیں بمطابق: صارف کا نام، صعودی ترتیب میں", + "sort-by-category.name-desc": "ترتیب دیں بمطابق: زمرہ، نازل ترتیب میں", + "sort-by-category.name-asc": "ترتیب دیں بمطابق: زمرہ، صعودی ترتیب میں", + "save": "محفوظ کریں", + "save-preferences": "ترجیحات محفوظ کریں", + "clear-preferences": "ترجیحات صاف کریں", + "search-preferences-saved": "تلاش کی ترجیحات محفوظ ہو گئیں", + "search-preferences-cleared": "تلاش کی ترجیحات صاف ہو گئیں", + "show-results-as": "نتائج کو اس طرح دکھائیں", + "show-results-as-topics": "نتائج کو موضوعات کے طور پر دکھائیں", + "show-results-as-posts": "نتائج کو پوسٹس کے طور پر دکھائیں", + "see-more-results": "مزید نتائج دکھائیں (%1)", + "search-in-category": "„%1“ میں تلاش کریں" +} \ No newline at end of file diff --git a/public/language/ur/social.json b/public/language/ur/social.json new file mode 100644 index 0000000000..e457268ea3 --- /dev/null +++ b/public/language/ur/social.json @@ -0,0 +1,14 @@ +{ + "sign-in-with-twitter": "ٹوئٹر کے ساتھ لاگ ان کریں", + "sign-up-with-twitter": "ٹوئٹر کے ساتھ رجسٹر کریں", + "sign-in-with-github": "گٹ ہب کے ساتھ لاگ ان کریں", + "sign-up-with-github": "گٹ ہب کے ساتھ رجسٹر کریں", + "sign-in-with-google": "گوگل کے ساتھ لاگ ان کریں", + "sign-up-with-google": "گوگل کے ساتھ رجسٹر کریں", + "log-in-with-facebook": "فیس بک کے ساتھ لاگ ان کریں", + "continue-with-facebook": "فیس بک کے ساتھ جاری رکھیں", + "sign-in-with-linkedin": "لنکڈ ان کے ساتھ لاگ ان کریں", + "sign-up-with-linkedin": "لنکڈ ان کے ساتھ رجسٹر کریں", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" +} \ No newline at end of file diff --git a/public/language/ur/success.json b/public/language/ur/success.json new file mode 100644 index 0000000000..4c6e1a13ac --- /dev/null +++ b/public/language/ur/success.json @@ -0,0 +1,7 @@ +{ + "success": "ہو گیا", + "topic-post": "آپ نے کامیابی سے پوسٹ کیا۔", + "post-queued": "آپ کی پوسٹ منظوری کے لیے قطار میں رکھی گئی ہے۔ جب اسے منظور یا مسترد کیا جائے گا تو آپ کو اطلاع موصول ہوگی۔", + "authentication-successful": "کامیاب تصدیق", + "settings-saved": "ترتیبات محفوظ کر دی گئیں!" +} \ No newline at end of file diff --git a/public/language/ur/tags.json b/public/language/ur/tags.json new file mode 100644 index 0000000000..e0b0e3ffe7 --- /dev/null +++ b/public/language/ur/tags.json @@ -0,0 +1,17 @@ +{ + "all-tags": "تمام ٹیگز", + "no-tag-topics": "اس ٹیگ کے ساتھ کوئی موضوعات نہیں ہیں۔", + "no-tags-found": "کوئی ٹیگز نہیں ملے", + "tags": "ٹیگز", + "enter-tags-here": "%1 – %2 حروف کے ساتھ ٹیگز درج کریں۔", + "enter-tags-here-short": "ٹیگز درج کریں...", + "no-tags": "ابھی تک کوئی ٹیگز نہیں ہیں۔", + "select-tags": "ٹیگز منتخب کریں", + "tag-whitelist": "اجازت شدہ ٹیگز کی فہرست", + "watching": "دیکھ رہے ہیں", + "not-watching": "نہیں دیکھ رہے", + "watching.description": "میں نئے موضوعات کے لیے اطلاعات حاصل کرنا چاہتا ہوں۔", + "not-watching.description": "میں نئے موضوعات کے لیے اطلاعات حاصل نہیں کرنا چاہتا۔", + "following-tag.message": "اب آپ کو اطلاعات موصول ہوں گی جب کوئی اس ٹیگ کے ساتھ موضوع پوسٹ کرے گا۔", + "not-following-tag.message": "آپ کو اطلاعات موصول نہیں ہوں گی جب کوئی اس ٹیگ کے ساتھ موضوع پوسٹ کرے گا۔" +} \ No newline at end of file diff --git a/public/language/ur/themes/harmony.json b/public/language/ur/themes/harmony.json new file mode 100644 index 0000000000..d20f2278a1 --- /dev/null +++ b/public/language/ur/themes/harmony.json @@ -0,0 +1,25 @@ +{ + "theme-name": "ہارمنی تھیم", + "skins": "سکنز", + "light": "Light", + "dark": "Dark", + "collapse": "سمیٹیں", + "expand": "پھیلائیں", + "sidebar-toggle": "سائڈبار ٹوگل", + "login-register-to-search": "تلاش کرنے کے لیے لاگ ان کریں یا رجسٹر کریں۔", + "settings.title": "تھیم کی ترتیبات", + "settings.enableQuickReply": "فوری جوابات فعال کریں", + "settings.enableBreadcrumbs": "زمرہ جات اور موضوعات کے صفحات پر بریڈ کرمبس دکھائیں", + "settings.enableBreadcrumbs.why": "بریڈ کرمبس زیادہ تر صفحات پر آسان نیویگیشن کے لیے دکھائی دیتے ہیں۔ زمرہ جات اور موضوعات کے صفحات کا بنیادی ڈیزائن زیادہ عمومی صفحات پر واپس جانے کے دیگر طریقے فراہم کرتا ہے، لیکن اگر آپ چاہیں تو بصری بھرتی کو کم کرنے کے لیے بریڈ کرمبس کا ڈسپلے بند کر سکتے ہیں۔", + "settings.centerHeaderElements": "ہیڈر عناصر کو وسط میں رکھیں", + "settings.mobileTopicTeasers": "موبائل ڈیوائسز پر موضوعات کے ٹیززر دکھائیں", + "settings.stickyToolbar": "سٹیٹک ٹولبار", + "settings.stickyToolbar.help": "موضوعات اور زمرہ جات کے صفحات پر ٹولبار ہمیشہ صفحہ کے اوپری حصے میں رہے گی", + "settings.topicSidebarTools": "موضوعات کے لیے سائڈبار ٹولز", + "settings.topicSidebarTools.help": "یہ ترتیب ویب سائٹ کے ڈیسک ٹاپ ورژن کے استعمال کے دوران موضوعات کے ٹولز کو سائڈبار میں منتقل کر دے گی", + "settings.autohideBottombar": "موبائل ڈیوائسز کے لیے نیویگیشن بار کو خودکار طور پر چھپائیں", + "settings.autohideBottombar.help": "جب صفحہ نیچے سکرول کیا جائے گا تو موبائل نیویگیشن بار چھپ جائے گی", + "settings.topMobilebar": "موبائل نیویگیشن بار کو اوپر منتقل کریں", + "settings.openSidebars": "سائڈبارز کھولیں", + "settings.chatModals": "گفتگو کے ونڈوز فعال کریں" +} \ No newline at end of file diff --git a/public/language/ur/themes/persona.json b/public/language/ur/themes/persona.json new file mode 100644 index 0000000000..4233d7dfbc --- /dev/null +++ b/public/language/ur/themes/persona.json @@ -0,0 +1,10 @@ +{ + "settings.title": "تھیم کی ترتیبات", + "settings.intro": "یہاں آپ تھیم کی ترتیبات کو تبدیل کر سکتے ہیں۔ یہ ترتیبات ہر ڈیوائس پر الگ سے محفوظ کی جاتی ہیں، لہذا آپ اپنے مختلف ڈیوائسز (فون، ٹیبلٹ، ڈیسک ٹاپ وغیرہ) پر مختلف ترتیبات رکھ سکتے ہیں۔", + "settings.mobile-menu-side": "موبائل ڈیوائس پر مینو کس طرف سے کھلے گا اس کا انتخاب کریں", + "settings.autoHidingNavbar": "سکرولنگ کے دوران نیویگیشن بار کو خودکار طور پر چھپائیں", + "settings.autoHidingNavbar-xs": "بہت چھوٹی اسکرینز (مثلاً پورٹریٹ موڈ میں فون)", + "settings.autoHidingNavbar-sm": "چھوٹی اسکرینز (مثلاً فونز، کچھ ٹیبلٹس)", + "settings.autoHidingNavbar-md": "درمیانی سائز کی اسکرینز (مثلاً لینڈسکیپ موڈ میں ٹیبلٹس)", + "settings.autoHidingNavbar-lg": "بڑی اسکرینز (مثلاً لیپ ٹاپس اور ڈیسک ٹاپس)" +} \ No newline at end of file diff --git a/public/language/ur/top.json b/public/language/ur/top.json new file mode 100644 index 0000000000..d98a236c05 --- /dev/null +++ b/public/language/ur/top.json @@ -0,0 +1,4 @@ +{ + "title": "سب سے مقبول", + "no-top-topics": "کوئی سب سے مقبول موضوعات نہیں ہیں" +} \ No newline at end of file diff --git a/public/language/ur/topic.json b/public/language/ur/topic.json new file mode 100644 index 0000000000..4ef539fdc1 --- /dev/null +++ b/public/language/ur/topic.json @@ -0,0 +1,240 @@ +{ + "topic": "موضوع", + "title": "عنوان", + "no-topics-found": "کوئی موضوعات نہیں ملے!", + "no-posts-found": "کوئی پوسٹس نہیں ملیں!", + "post-is-deleted": "پوسٹ حذف کر دی گئی!", + "topic-is-deleted": "موضوع حذف کر دیا گیا!", + "profile": "پروفائل", + "posted-by": "%1 کی طرف سے پوسٹ کیا گیا", + "posted-by-guest": "مہمان کی طرف سے پوسٹ کیا گیا", + "chat": "چیت", + "notify-me": "اس موضوع میں نئے جوابات کے لیے نوٹیفکیشنز حاصل کریں", + "quote": "اقتباس", + "reply": "جواب", + "replies-to-this-post": "%1 جوابات", + "one-reply-to-this-post": "1 جواب", + "last-reply-time": "آخری جواب", + "reply-options": "جواب کے اختیارات", + "reply-as-topic": "نئے موضوع میں جواب", + "guest-login-reply": "جواب دینے کے لیے لاگ ان کریں", + "login-to-view": "🔒 اسے دیکھنے کے لیے لاگ ان کریں", + "edit": "ترمیم کریں", + "delete": "حذف کریں", + "delete-event": "ایونٹ حذف کریں", + "delete-event-confirm": "کیا آپ واقعی اس ایونٹ کو حذف کرنا چاہتے ہیں؟", + "purge": "صاف کریں", + "restore": "بحال کریں", + "move": "منتقل کریں", + "change-owner": "مالک تبدیل کریں", + "manage-editors": "ایڈیٹرز کا انتظام کریں", + "fork": "تقسیم کریں", + "link": "لنک", + "share": "شیئر کریں", + "tools": "ٹولز", + "locked": "مقفل", + "pinned": "پن کیا گیا", + "pinned-with-expiry": "%1 تک پن کیا گیا", + "scheduled": "طے شدہ", + "deleted": "حذف شدہ", + "moved": "منتقل شدہ", + "moved-from": "%1 سے منتقل کیا گیا", + "copy-code": "کوڈ کاپی کریں", + "copy-ip": "IP ایڈریس کاپی کریں", + "ban-ip": "IP ایڈریس بلاک کریں", + "view-history": "ترمیمی تاریخ", + "wrote-ago": " لکھا", + "wrote-on": " پر لکھا", + "replied-to-user-ago": "%3 کو جواب دیا", + "replied-to-user-on": "%3 کو پر جواب دیا", + "user-locked-topic-ago": "%1 نے اس موضوع کو %2 پر مقفل کیا", + "user-locked-topic-on": "%1 نے اس موضوع کو %2 پر مقفل کیا", + "user-unlocked-topic-ago": "%1 نے اس موضوع کو %2 پر کھول دیا", + "user-unlocked-topic-on": "%1 نے اس موضوع کو %2 پر کھول دیا", + "user-pinned-topic-ago": "%1 نے اس موضوع کو %2 پر پن کیا", + "user-pinned-topic-on": "%1 نے اس موضوع کو %2 پر پن کیا", + "user-unpinned-topic-ago": "%1 نے اس موضوع کو %2 پر ان پن کیا", + "user-unpinned-topic-on": "%1 نے اس موضوع کو %2 پر ان پن کیا", + "user-deleted-topic-ago": "%1 نے اس موضوع کو %2 پر حذف کیا", + "user-deleted-topic-on": "%1 نے اس موضوع کو %2 پر حذف کیا", + "user-restored-topic-ago": "%1 نے اس موضوع کو %2 پر بحال کیا", + "user-restored-topic-on": "%1 نے اس موضوع کو %2 پر بحال کیا", + "user-moved-topic-from-ago": "%1 نے اس موضوع کو %2 سے %3 پر منتقل کیا", + "user-moved-topic-from-on": "%1 نے اس موضوع کو %2 سے %3 پر منتقل کیا", + "user-shared-topic-ago": "%1 نے اس موضوع کو %2 پر شیئر کیا", + "user-shared-topic-on": "%1 نے اس موضوع کو %2 پر شیئر کیا", + "user-queued-post-ago": "%1 نے اس پوسٹ کو منظوری کے لیے قطار میں %3 پر شامل کیا", + "user-queued-post-on": "%1 نے اس پوسٹ کو قطار میں منظوری کے لیے %3 پر شامل کیا", + "user-referenced-topic-ago": "%1 نے اس موضوع کی طرف حوالہ دیا %3 پر", + "user-referenced-topic-on": "%1 نے اس موضوع کی طرف حوالہ دیا %3 پر", + "user-forked-topic-ago": "%1 نے اس موضوع کو تقسیم کیا %3 پر", + "user-forked-topic-on": "%1 نے اس موضوع کو تقسیم کیا %3 پر", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", + "bookmark-instructions": "اس موضوع میں آخری پڑھی گئی پوسٹ پر واپس جانے کے لیے یہاں کلک کریں۔", + "flag-post": "اس پوسٹ کی رپورٹ کریں", + "flag-user": "اس صارف کی رپورٹ کریں", + "already-flagged": "پہلے سے رپورٹ کیا جا چکا ہے", + "view-flag-report": "رپورٹ دیکھیں", + "resolve-flag": "رپورٹ حل کریں", + "merged-message": "یہ موضوع %2 میں ضم کر دیا گیا", + "forked-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": "موضوع کو ان پن کریں", + "thread-tools.lock": "موضوع کو مقفل کریں", + "thread-tools.unlock": "موضوع کو کھولیں", + "thread-tools.move": "موضوع منتقل کریں", + "thread-tools.crosspost": "Crosspost Topic", + "thread-tools.move-posts": "پوسٹس منتقل کریں", + "thread-tools.move-all": "سب منتقل کریں", + "thread-tools.change-owner": "مالک تبدیل کریں", + "thread-tools.manage-editors": "ایڈیٹرز کا انتظام کریں", + "thread-tools.select-category": "زمرہ منتخب کریں", + "thread-tools.fork": "موضوع تقسیم کریں", + "thread-tools.tag": "موضوع پر ٹیگ لگائیں", + "thread-tools.delete": "موضوع حذف کریں", + "thread-tools.delete-posts": "پوسٹس حذف کریں", + "thread-tools.delete-confirm": "کیا آپ واقعی اس موضوع کو حذف کرنا چاہتے ہیں؟", + "thread-tools.restore": "موضوع بحال کریں", + "thread-tools.restore-confirm": "کیا آپ واقعی اس موضوع کو بحال کرنا چاہتے ہیں؟", + "thread-tools.purge": "موضوع صاف کریں", + "thread-tools.purge-confirm": "کیا آپ واقعی اس موضوع کو صاف کرنا چاہتے ہیں؟", + "thread-tools.merge-topics": "موضوعات ضم کریں", + "thread-tools.merge": "موضوع ضم کریں", + "topic-move-success": "موضوع جلد ہی „%1“ میں منتقل ہو جائے گا۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔", + "topic-move-multiple-success": "موضوعات جلد ہی „%1“ میں منتقل ہو جائیں گے۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔", + "topic-move-all-success": "تمام موضوعات جلد ہی „%1“ میں منتقل ہو جائیں گے۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔", + "topic-move-undone": "موضوع کی منتقلی منسوخ کر دی گئی", + "topic-move-posts-success": "پوسٹس جلد ہی منتقل ہو جائیں گی۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔", + "topic-move-posts-undone": "پوسٹس کی منتقلی منسوخ کر دی گئی", + "post-delete-confirm": "کیا آپ واقعی اس پوسٹ کو حذف کرنا چاہتے ہیں؟", + "post-restore-confirm": "کیا آپ واقعی اس پوسٹ کو بحال کرنا چاہتے ہیں؟", + "post-purge-confirm": "کیا آپ واقعی اس پوسٹ کو صاف کرنا چاہتے ہیں؟", + "pin-modal-expiry": "ختم ہونے کی تاریخ", + "pin-modal-help": "اگر آپ چاہیں تو یہاں پن کیے گئے موضوعات کے لیے ختم ہونے کی تاریخ بتا سکتے ہیں۔ آپ اس فیلڈ کو خالی بھی چھوڑ سکتے ہیں، اس صورت میں موضوع اس وقت تک پن رہے گا جب تک اسے دستی طور پر ان پن نہ کیا جائے۔", + "load-categories": "زمرہ جات لوڈ کریں", + "confirm-move": "منتقل کریں", + "confirm-crosspost": "Cross-post", + "confirm-fork": "تقسیم کریں", + "bookmark": "بک مارک", + "bookmarks": "بک مارکس", + "bookmarks.has-no-bookmarks": "آپ نے ابھی تک کسی پوسٹ کے لیے بک مارکس محفوظ نہیں کیے۔", + "copy-permalink": "مستقل لنک کاپی کریں", + "go-to-original": "اصل پوسٹ دیکھیں", + "loading-more-posts": "مزید پوسٹس لوڈ ہو رہی ہیں", + "move-topic": "موضوع منتقل کریں", + "move-topics": "موضوعات منتقل کریں", + "crosspost-topic": "Cross-post Topic", + "move-post": "پوسٹ منتقل کریں", + "post-moved": "پوسٹ منتقل کر دی گئی!", + "fork-topic": "موضوع تقسیم کریں", + "enter-new-topic-title": "نئے موضوع کا عنوان درج کریں", + "fork-topic-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ تقسیم کرنا چاہتے ہیں، نئے موضوع کا نام درج کریں، اور „موضوع تقسیم کریں“ پر کلک کریں", + "fork-no-pids": "کوئی پوسٹس منتخب نہیں کی گئیں!", + "no-posts-selected": "کوئی پوسٹس منتخب نہیں کی گئیں!", + "x-posts-selected": "منتخب پوسٹس: %1", + "x-posts-will-be-moved-to-y": "%1 پوسٹس „%2“ میں منتقل ہو جائیں گی", + "fork-pid-count": "منتخب پوسٹس: %1", + "fork-success": "موضوع کامیابی سے تقسیم کر دیا گیا! تقسیم شدہ موضوع پر جانے کے لیے یہاں کلک کریں۔", + "delete-posts-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ حذف/صاف کرنا چاہتے ہیں", + "merge-topics-instruction": "ان موضوعات پر کلک کریں جنہیں آپ ضم کرنا چاہتے ہیں، یا انہیں تلاش کریں", + "merge-topic-list-title": "ضم کیے جانے والے موضوعات کی فہرست", + "merge-options": "ضم کرنے کے اختیارات", + "merge-select-main-topic": "مرکزی موضوع منتخب کریں", + "merge-new-title-for-topic": "موضوع کے لیے نیا عنوان", + "topic-id": "موضوع کی شناخت", + "move-posts-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ منتقل کرنا چاہتے ہیں، پھر موضوع کی شناخت درج کریں یا ہدف موضوع پر جائیں", + "move-topic-instruction": "ہدف زمرہ منتخب کریں اور „منتقل کریں“ پر کلک کریں", + "change-owner-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ دوسرے صارف کو منتقل کرنا چاہتے ہیں", + "manage-editors-instruction": "نیچے ان صارفین کو نامزد کریں جو اس پوسٹ کو ترمیم کر سکتے ہیں۔", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", + "composer.title-placeholder": "یہاں اپنے موضوع کا عنوان درج کریں...", + "composer.handle-placeholder": "یہاں نام درج کریں", + "composer.hide": "چھپائیں", + "composer.discard": "مسترد کریں", + "composer.submit": "شائع کریں", + "composer.additional-options": "اضافی اختیارات", + "composer.post-later": "بعد میں پوسٹ کریں", + "composer.schedule": "طے کریں", + "composer.replying-to": "%1 کو جواب", + "composer.new-topic": "نیا موضوع", + "composer.editing-in": "%1 میں پوسٹ کی ترمیم", + "composer.untitled-topic": "Untitled Topic", + "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": "ترتیب دیں بمطابق", + "oldest-to-newest": "پہلے سب سے پرانے", + "newest-to-oldest": "پہلے سب سے نئے", + "recently-replied": "پہلے تازہ ترین جوابات والے", + "recently-created": "پہلے تازہ ترین بنائے گئے", + "most-votes": "پہلے سب سے زیادہ ووٹ والے", + "most-posts": "پہلے سب سے زیادہ پوسٹس والے", + "most-views": "پہلے سب سے زیادہ نظاروں والے", + "stale.title": "اس کے بجائے نیا موضوع بنائیں؟", + "stale.warning": "جس موضوع میں آپ جواب دے رہے ہیں وہ کافی پرانا ہے۔ کیا آپ اس کے بجائے ایک نیا موضوع بنانا چاہیں گے اور اپنے جواب میں اس کا حوالہ دیں گے؟", + "stale.create": "نیا موضوع بنائیں", + "stale.reply-anyway": "پھر بھی اس موضوع میں جواب دیں", + "link-back": "جواب: [%1](%2)", + "diffs.title": "ترمیمی تاریخ", + "diffs.description": "اس پوسٹ کی %1 ورژنز ہیں۔ نیچے کسی بھی ورژن پر کلک کریں تاکہ اس وقت کا مواد دیکھیں۔", + "diffs.no-revisions-description": "اس پوسٹ کی %1 ورژنز ہیں۔", + "diffs.current-revision": "موجودہ ورژن", + "diffs.original-revision": "اصل ورژن", + "diffs.restore": "اس ورژن کو بحال کریں", + "diffs.restore-description": "اس ورژن کی بحالی کے بعد اس پوسٹ کی ترمیمی تاریخ میں ایک نیا ورژن شامل ہو جائے گا۔", + "diffs.post-restored": "پوسٹ کو کامیابی سے پچھلے ورژن میں بحال کر دیا گیا", + "diffs.delete": "اس ورژن کو حذف کریں", + "diffs.deleted": "ورژن حذف کر دیا گیا", + "timeago-later": "%1 بعد میں", + "timeago-earlier": "%1 پہلے", + "first-post": "پہلی پوسٹ", + "last-post": "آخری پوسٹ", + "go-to-my-next-post": "میری اگلی پوسٹ پر جائیں", + "no-more-next-post": "اس موضوع میں آپ کی مزید پوسٹس نہیں ہیں", + "open-composer": "ایڈیٹر کھولیں", + "post-quick-reply": "فوری جواب", + "navigator.index": "پوسٹ %1 از %2", + "navigator.unread": "%1 غیر پڑھے ہوئے", + "upvote-post": "پوسٹ کے لیے مثبت ووٹ", + "downvote-post": "پوسٹ کے لیے منفی ووٹ", + "post-tools": "پوسٹس کے ٹولز", + "unread-posts-link": "غیر پڑھے ہوئے پوسٹس کا لنک", + "thumb-image": "موضوع کی آئیکن", + "announcers": "شیئرز", + "announcers-x": "شیئرز (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" +} \ No newline at end of file diff --git a/public/language/ur/unread.json b/public/language/ur/unread.json new file mode 100644 index 0000000000..46001ee599 --- /dev/null +++ b/public/language/ur/unread.json @@ -0,0 +1,16 @@ +{ + "title": "غیر پڑھے ہوئے", + "no-unread-topics": "کوئی غیر پڑھے ہوئے موضوعات نہیں ہیں۔", + "load-more": "مزید لوڈ کریں", + "mark-as-read": "پڑھا ہوا نشان زد کریں", + "mark-as-unread": "غیر پڑھا ہوا نشان زد کریں", + "selected": "منتخب کردہ", + "all": "تمام", + "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/ur/uploads.json b/public/language/ur/uploads.json new file mode 100644 index 0000000000..bccdcb10f9 --- /dev/null +++ b/public/language/ur/uploads.json @@ -0,0 +1,9 @@ +{ + "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/ur/user.json b/public/language/ur/user.json new file mode 100644 index 0000000000..a9dde7f93c --- /dev/null +++ b/public/language/ur/user.json @@ -0,0 +1,234 @@ +{ + "user-menu": "صارف مینو", + "banned": "بلاک کیا گیا", + "unbanned": "بلاک ہٹایا گیا", + "muted": "خاموش کیا گیا", + "unmuted": "خاموشی ہٹائی گئی", + "offline": "آف لائن", + "deleted": "حذف کیا گیا", + "username": "صارف کا نام", + "joindate": "شمولیت کی تاریخ", + "postcount": "پوسٹس کی تعداد", + "email": "ای میل", + "confirm-email": "ای میل کی تصدیق کریں", + "account-info": "اکاؤنٹ کی معلومات", + "admin-actions-label": "انتظامی اقدامات", + "ban-account": "اکاؤنٹ بلاک کریں", + "ban-account-confirm": "کیا آپ واقعی اس صارف کو بلاک کرنا چاہتے ہیں؟", + "unban-account": "اکاؤنٹ کا بلاک ہٹائیں", + "mute-account": "اکاؤنٹ کو خاموش کریں", + "unmute-account": "اکاؤنٹ کی خاموشی ہٹائیں", + "delete-account": "اکاؤنٹ حذف کریں", + "delete-account-as-admin": "اکاؤنٹ حذف کریں", + "delete-content": "اکاؤنٹ کے مواد کو حذف کریں", + "delete-all": "اکاؤنٹ اور مواد کو حذف کریں", + "delete-account-confirm": "کیا آپ واقعی اپنی پوسٹس کو گمنام کرنا اور اپنا اکاؤنٹ حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے اور آپ اپنے ڈیٹا کو دوبارہ بحال نہیں کر سکیں گے۔

اس اکاؤنٹ کو ختم کرنے کی تصدیق کے لیے اپنا پاس ورڈ درج کریں۔", + "delete-this-account-confirm": "کیا آپ واقعی اس اکاؤنٹ کو حذف کرنا چاہتے ہیں، لیکن اس کے مواد کو برقرار رکھنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے۔ پوسٹس گمنام ہو جائیں گی اور آپ حذف شدہ اکاؤنٹ اور پوسٹس کے درمیان رابطہ دوبارہ بحال نہیں کر سکیں گے

", + "delete-account-content-confirm": "کیا آپ واقعی اس اکاؤنٹ کے مواد (پوسٹس/موضوعات/اپ لوڈز) کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے اور آپ کوئی بھی ڈیٹا بحال نہیں کر سکیں گے۔

", + "delete-all-confirm": "کیا آپ واقعی اس اکاؤنٹ اور اس کے تمام مواد (پوسٹس/موضوعات/اپ لوڈز) کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے اور آپ کوئی بھی ڈیٹا بحال نہیں کر سکیں گے۔

", + "account-deleted": "اکاؤنٹ حذف کر دیا گیا", + "account-content-deleted": "اکاؤنٹ کا مواد حذف کر دیا گیا", + "fullname": "مکمل نام", + "website": "ویب سائٹ", + "location": "مقام", + "age": "عمر", + "joined": "شامل ہوئے", + "lastonline": "آخری بار آن لائن", + "profile": "پروفائل", + "profile-views": "پروفائل کے نظارے", + "reputation": "ساکھ", + "bookmarks": "بک مارکس", + "watched-categories": "دیکھے گئے زمرہ جات", + "watched-tags": "دیکھے گئے ٹیگز", + "change-all": "سب کچھ تبدیل کریں", + "watched": "دیکھے گئے", + "ignored": "نظر انداز کیے گئے", + "read": "پڑھے گئے", + "default-category-watch-state": "زمرہ جات کے مشاہدے کے لیے طے شدہ حالت", + "followers": "پیروی کرنے والے", + "following": "پیروی کر رہے ہیں", + "shares": "شیئرز", + "blocks": "بلاکس", + "blocked-users": "بلاک کیے گئے صارفین", + "block-toggle": "بلاک کو ٹوگل کریں", + "block-user": "صارف کو بلاک کریں", + "unblock-user": "صارف کا بلاک ہٹائیں", + "aboutme": "میرے بارے میں", + "signature": "دستخط", + "birthday": "سالگرہ", + "chat": "چیت", + "chat-with": "%1 کے ساتھ بات چیت جاری رکھیں", + "new-chat-with": "%1 کے ساتھ نئی بات چیت شروع کریں", + "view-remote": "اصل دیکھیں", + "flag-profile": "پروفائل کی رپورٹ کریں", + "profile-flagged": "پہلے سے رپورٹ کیا جا چکا ہے", + "follow": "پیروی کریں", + "unfollow": "پیروی بند کریں", + "cancel-follow": "پیروی کی درخواست منسوخ کریں", + "more": "مزید", + "profile-update-success": "پروفائل کامیابی سے اپ ڈیٹ ہو گیا!", + "change-picture": "تصویر تبدیل کریں", + "change-username": "صارف کا نام تبدیل کریں", + "change-email": "ای میل تبدیل کریں", + "email-updated": "ای میل تبدیل ہو گئی", + "email-same-as-password": "براہ کرم جاری رکھنے کے لیے اپنا موجودہ پاس ورڈ درج کریں – آپ نے اپنی نئی ای میل دوبارہ درج کی", + "edit": "ترمیم کریں", + "edit-profile": "پروفائل ترمیم کریں", + "default-picture": "طے شدہ آئیکن", + "uploaded-picture": "اپ لوڈ کی گئی تصویر", + "upload-new-picture": "نئی تصویر اپ لوڈ کریں", + "upload-new-picture-from-url": "یو آر ایل سے نئی تصویر اپ لوڈ کریں", + "current-password": "موجودہ پاس ورڈ", + "new-password": "نیا پاس ورڈ", + "change-password": "پاس ورڈ تبدیل کریں", + "change-password-error": "غلط پاس ورڈ!", + "change-password-error-wrong-current": "آپ کا موجودہ پاس ورڈ غلط ہے!", + "change-password-error-same-password": "آپ کا نیا پاس ورڈ موجودہ پاس ورڈ کے ساتھ مماثل ہے۔ براہ کرم نیا پاس ورڈ استعمال کریں۔", + "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": "کاٹ کر اپ لوڈ کریں", + "avatar-background-colour": "تصویر کا پس منظر رنگ", + "settings": "ترتیبات", + "show-email": "میری ای میل دکھائیں", + "show-fullname": "میرا مکمل نام دکھائیں", + "restrict-chats": "صرف ان صارفین سے پیغامات کی اجازت دیں جن کی میں پیروی کرتا ہوں", + "disable-incoming-chats": "آنے والے پیغامات کو غیر فعال کریں ", + "chat-allow-list": "درج ذیل صارفین سے پیغامات کی اجازت دیں", + "chat-deny-list": "درج ذیل صارفین سے پیغامات منع کریں", + "chat-list-add-user": "صارف شامل کریں", + "digest-label": "خلاصوں کے لیے سبسکرائب کریں", + "digest-description": "اس فورم کے بارے میں ای میل کے ذریعے خبروں (نئے نوٹیفکیشنز اور موضوعات) کے لیے سبسکرائب کریں، منتخب کردہ شیڈول کے مطابق", + "digest-off": "بند", + "digest-daily": "روزانہ", + "digest-weekly": "ہفتہ وار", + "digest-biweekly": "ہر دو ہفتے بعد", + "digest-monthly": "ماہانہ", + "has-no-follower": "اس صارف کے کوئی پیروکار نہیں ہیں :(", + "follows-no-one": "یہ صارف کسی کی پیروی نہیں کرتا :(", + "has-no-posts": "اس صارف نے ابھی تک کچھ بھی پوسٹ نہیں کیا۔", + "has-no-best-posts": "اس صارف کو ابھی تک اپنی پوسٹس کے لیے مثبت ووٹ نہیں ملے۔", + "has-no-topics": "اس صارف نے ابھی تک کوئی موضوعات نہیں بنائے۔", + "has-no-watched-topics": "اس صارف نے ابھی تک کوئی موضوعات نہیں دیکھے۔", + "has-no-ignored-topics": "اس صارف نے ابھی تک کوئی موضوعات کو نظر انداز نہیں کیا۔", + "has-no-read-topics": "اس صارف نے ابھی تک کوئی موضوعات نہیں پڑھے۔", + "has-no-upvoted-posts": "اس صارف نے ابھی تک مثبت ووٹ نہیں کیا۔", + "has-no-downvoted-posts": "اس صارف نے ابھی تک منفی ووٹ نہیں کیا۔", + "has-no-controversial-posts": "اس صارف کی ابھی تک منفی ووٹوں والی کوئی پوسٹس نہیں ہیں۔", + "has-no-blocks": "آپ نے کسی کو بلاک نہیں کیا۔", + "has-no-shares": "اس صارف نے کوئی موضوع شیئر نہیں کیا۔", + "email-hidden": "ای میل چھپی ہوئی ہے", + "hidden": "چھپا ہوا", + "paginate-description": "موضوعات اور پوسٹس کو صفحات پر تقسیم کریں، لامتناہی سکرولنگ کے بجائے", + "topics-per-page": "فی صفحہ موضوعات", + "posts-per-page": "فی صفحہ پوسٹس", + "category-topic-sort": "زمرہ میں موضوعات کی ترتیب", + "topic-post-sort": "موضوع میں پوسٹس کی ترتیب", + "max-items-per-page": "زیادہ سے زیادہ %1", + "acp-language": "ایڈمن پیج کی زبان", + "notifications": "نوٹیفکیشنز", + "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": "غیر فعال", + "browsing": "صفحات کی ترتیبات", + "open-links-in-new-tab": "بیرونی لنکس کو نئی ونڈو میں کھولیں", + "enable-topic-searching": "موضوعات میں تلاش کو فعال کریں", + "topic-search-help": "اگر فعال ہو، موضوع کی تلاش براؤزر کے معیاری تلاش کے رویے کو بدل دے گی اور آپ کو پورے موضوع کی تلاش کی اجازت دے گی، نہ کہ صرف اسکرین پر نظر آنے والا مواد", + "update-url-with-post-index": "موضوعات دیکھتے وقت ایڈریس بار کو پوسٹ نمبر کے ساتھ اپ ڈیٹ کریں", + "scroll-to-my-post": "جواب پوسٹ کرنے کے بعد نئی پوسٹ دکھائیں", + "follow-topics-you-reply-to": "جن موضوعات میں آپ جواب دیتے ہیں ان کی پیروی کریں", + "follow-topics-you-create": "جن موضوعات کو آپ بناتے ہیں ان کی پیروی کریں", + "grouptitle": "گروپ کا عنوان", + "group-order-help": "ایک گروپ منتخب کریں اور عنوانات کو دوبارہ ترتیب دینے کے لیے تیر کا استعمال کریں", + "show-group-title": "گروپ کا عنوان دکھائیں", + "hide-group-title": "گروپ کا عنوان چھپائیں", + "order-group-up": "گروپ کو اوپر منتقل کریں", + "order-group-down": "گروپ کو نیچے منتقل کریں", + "no-group-title": "کوئی گروپ عنوان نہیں", + "select-skin": "جلد منتخب کریں", + "default": "طے شدہ (%1)", + "no-skin": "کوئی جلد نہیں", + "select-homepage": "ہوم پیج منتخب کریں", + "homepage": "ہوم پیج", + "homepage-description": "فورم کے لیے ہوم پیج کے طور پر استعمال کرنے کے لیے ایک صفحہ منتخب کریں، یا „کچھ نہیں“ طے شدہ استعمال کے لیے۔", + "custom-route": "اپنی مرضی کے ہوم پیج کا راستہ", + "custom-route-help": "یہاں راستے کا نام درج کریں، بغیر آگے کی ترچھی لکیر کے (مثال: „recent“ یا \"category/2/general-discussion\")", + "sso.title": "ایک بار لاگ ان خدمات", + "sso.associated": "کے ساتھ منسلک", + "sso.not-associated": "کے ساتھ منسلک کرنے کے لیے یہاں کلک کریں", + "sso.dissociate": "رابطہ منقطع کریں", + "sso.dissociate-confirm-title": "منقطع کرنے کی تصدیق", + "sso.dissociate-confirm": "کیا آپ واقعی اپنے اکاؤنٹ کو „%1“ سے منقطع کرنا چاہتے ہیں؟", + "info.latest-flags": "تازہ ترین رپورٹس", + "info.profile": "پروفائل", + "info.post": "پوسٹ", + "info.view-flag": "رپورٹ دیکھیں", + "info.reported-by": "رپورٹ کیا گیا:", + "info.no-flags": "کوئی رپورٹ شدہ پوسٹس نہیں ملیں", + "info.ban-history": "حالیہ پابندیوں کی تاریخ", + "info.no-ban-history": "اس صارف پر کبھی پابندی نہیں لگائی گئی", + "info.banned-until": "%1 تک پابندی", + "info.banned-expiry": "ختم ہونے کی تاریخ", + "info.ban-expired": "پابندی ختم ہو گئی", + "info.banned-permanently": "مستقل پابندی", + "info.banned-reason-label": "وجہ", + "info.banned-no-reason": "کوئی وجہ نہیں بتائی گئی۔", + "info.mute-history": "حالیہ خاموشیوں کی تاریخ", + "info.no-mute-history": "اس صارف کو کبھی خاموش نہیں کیا گیا", + "info.muted-until": "%1 تک خاموش", + "info.muted-expiry": "ختم ہونے کی تاریخ", + "info.muted-no-reason": "کوئی وجہ نہیں بتائی گئی۔", + "info.username-history": "صارف کے ناموں کی تاریخ", + "info.email-history": "ای میلوں کی تاریخ", + "info.moderation-note": "ماڈریٹر نوٹ", + "info.moderation-note.success": "ماڈریٹر نوٹ محفوظ ہو گیا", + "info.moderation-note.add": "نوٹ شامل کریں", + "sessions.description": "اس صفحے پر آپ اس فورم پر اپنی فعال سیشنز دیکھ سکتے ہیں اور اگر چاہیں تو انہیں منسوخ کر سکتے ہیں۔ آپ اپنے اکاؤنٹ سے لاگ آؤٹ کرکے موجودہ سیشن منسوخ کر سکتے ہیں۔", + "revoke-session": "سیشن منسوخ کریں", + "browser-version-on-platform": "%1 %2 پر %3", + "consent.title": "آپ کے حقوق اور رضامندی", + "consent.lead": "یہ عوامی فورم ذاتی معلومات جمع اور پروسیس کرتا ہے۔", + "consent.intro": "ہم اس معلومات کو صرف آپ کے فورم کے ساتھ تعامل کو ذاتی بنانے اور آپ کی پوسٹس کو آپ کے صارف اکاؤنٹ سے جوڑنے کے لیے استعمال کرتے ہیں۔ رجسٹریشن کے دوران آپ کو صارف کا نام اور ای میل درج کرنا ہوگا، لیکن اگر آپ چاہیں تو ویب سائٹ پر اپنا صارف پروفائل مکمل کرنے کے لیے اضافی معلومات فراہم کر سکتے ہیں۔

ہم اس معلومات کو اس وقت تک محفوظ رکھتے ہیں جب تک آپ کا صارف اکاؤنٹ موجود ہے۔ آپ کسی بھی وقت اپنا اکاؤنٹ حذف کرکے اس کی رضامندی واپس لے سکتے ہیں۔ آپ کسی بھی وقت „حقوق اور رضامندی“ صفحے کے ذریعے ویب سائٹ پر درج کردہ معلومات کی کاپی مانگ سکتے ہیں۔

اگر آپ کے کوئی سوالات یا خدشات ہیں، تو آپ فورم کے ایڈمن ٹیم سے رابطہ کر سکتے ہیں۔", + "consent.email-intro": "ہم کبھی کبھار آپ کے رجسٹرڈ ای میل پر ای میلز بھیج سکتے ہیں تاکہ آپ کو بتائیں کہ کیا ہو رہا ہے، یا آپ کو مطلع کریں کہ کوئی نئی چیز ہے جو آپ سے متعلق ہے۔ آپ صارف کی ترتیبات کے صفحے کے ذریعے خلاصوں کی تعدد کو اپنی مرضی کے مطابق بنا سکتے ہیں (اور انہیں بند بھی کر سکتے ہیں)، اور یہ بھی منتخب کر سکتے ہیں کہ آپ کو ای میل کے ذریعے کون سے نوٹیفکیشنز موصول ہوں۔", + "consent.digest-frequency": "جب تک آپ اسے اپنی صارف ترتیبات میں تبدیل نہ کریں، یہ کمیونٹی آپ کو ہر %1 پر ای میل کے ذریعے خلاصے بھیجے گی۔", + "consent.digest-off": "جب تک آپ اسے اپنی صارف ترتیبات میں تبدیل نہ کریں، یہ کمیونٹی آپ کو ای میل کے ذریعے خلاصے نہیں بھیجے گی۔", + "consent.received": "آپ نے اس ویب سائٹ کو آپ کی ذاتی معلومات جمع کرنے اور پروسیس کرنے کی رضامندی دی ہے۔ کوئی اضافی عمل کی ضرورت نہیں ہے۔", + "consent.not-received": "آپ نے اپنی معلومات جمع کرنے اور پروسیس کرنے کی رضامندی نہیں دی۔ ویب سائٹ کی انتظامیہ ڈیٹا تحفظ کے تقاضوں کو پورا کرنے کے لیے کسی بھی وقت آپ کا اکاؤنٹ حذف کر سکتی ہے۔", + "consent.give": "رضHامندی دیں", + "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": "پروفائل ایکسپورٹ کریں (.json)", + "consent.export-profile-success": "پروفائل ایکسپورٹ ہو رہا ہے… تیار ہونے پر آپ کو نوٹیفکیشن موصول ہوگا۔", + "consent.export-uploads": "اپ لوڈ کردہ مواد ایکسپورٹ کریں (.zip)", + "consent.export-uploads-success": "اپ لوڈ کردہ مواد ایکسپورٹ ہو رہا ہے… تیار ہونے پر آپ کو نوٹیفکیشن موصول ہوگا۔", + "consent.export-posts": "پوسٹس ایکسپورٹ کریں (.csv)", + "consent.export-posts-success": "پوسٹس ایکسپورٹ ہو رہی ہیں… تیار ہونے پر آپ کو نوٹیفکیشن موصول ہوگا۔", + "emailUpdate.intro": "نیچے اپنا ای میل درج کریں۔ یہ فورم شیڈول شدہ خلاصوں اور نوٹیفکیشنز کے لیے ای میل استعمال کرتا ہے، اور بھولے ہوئے پاس ورڈ کی صورت میں اکاؤنٹ کی بحالی کے لیے بھی۔", + "emailUpdate.optional": "یہ فیلڈ لازمی نہیں ہے۔ آپ کو ای میل ایڈریس فراہم کرنے کی ضرورت نہیں ہے، لیکن تصدیق شدہ ای میل کے بغیر، آپ اپنا اکاؤنٹ کسی مسئلے کی صورت میں بحال نہیں کر سکیں گے، نہ ہی آپ اپنے ای میل کے ساتھ لاگ ان کر سکیں گے۔", + "emailUpdate.required": "یہ فیلڈ لازمی ہے۔", + "emailUpdate.change-instructions": "ہم آپ کے دیے گئے ای میل پر ایک تصدیقی ای میل بھیجیں گے، جس میں ایک منفرد لنک ہوگا۔ اس لنک پر عمل کرنے پر آپ کے اس ای میل کے مالک ہونے کی تصدیق ہو جائے گی اور یہ آپ کے اکاؤنٹ سے منسلک ہو جائے گی۔ آپ اپنے اکاؤنٹ کے صفحے سے اس ای میل کو کسی بھی وقت تبدیل کر سکتے ہیں۔", + "emailUpdate.password-challenge": "اپنا پاس ورڈ درج کریں تاکہ یہ تصدیق ہو کہ اکاؤنٹ آپ کا ہے۔", + "emailUpdate.pending": "آپ کا ای میل ابھی تک تصدیق شدہ نہیں ہے، حالانکہ اس پر ایک تصدیقی ای میل بھیج دیا گیا ہے۔ اگر آپ اسے منسوخ کرنا چاہتے ہیں اور نیا درخواست دینا چاہتے ہیں، تو نیچے دیا گیا فارم پُر کریں۔" +} \ No newline at end of file diff --git a/public/language/ur/users.json b/public/language/ur/users.json new file mode 100644 index 0000000000..22809a70dd --- /dev/null +++ b/public/language/ur/users.json @@ -0,0 +1,26 @@ +{ + "all-users": "تمام صارفین", + "followed-users": "فالو کیے گئے صارفین", + "latest-users": "تازہ ترین صارفین", + "top-posters": "سب سے زیادہ پوسٹس والے", + "most-reputation": "سب سے زیادہ ساکھ والے", + "most-flags": "سب سے زیادہ رپورٹس والے", + "search": "تلاش", + "enter-username": "تلاش کرنے کے لیے صارف نام درج کریں", + "search-user-for-chat": "گفتگو شروع کرنے کے لیے صارف تلاش کریں", + "load-more": "مزید لوڈ کریں", + "users-found-search-took": "%1 صارفین ملے! تلاش میں %2 سیکنڈ لگے۔", + "filter-by": "فلٹر کریں", + "online-only": "صرف آن لائن والے", + "invite": "دعوت دیں", + "prompt-email": "ای میلز:", + "groups-to-join": "دعوت قبول کرنے کے بعد شامل ہونے والے گروپس:", + "invitation-email-sent": "%1 کو ایک تصدیقی ای میل بھیج دیا گیا ہے", + "user-list": "صارفین کی فہرست", + "recent-topics": "حالیہ موضوعات", + "popular-topics": "مقبول موضوعات", + "unread-topics": "غیر پڑھے ہوئے موضوعات", + "categories": "زمرہ جات", + "tags": "ٹیگز", + "no-users-found": "کوئی صارفین نہیں ملے!" +} \ No newline at end of file diff --git a/public/language/ur/world.json b/public/language/ur/world.json new file mode 100644 index 0000000000..056371c5ff --- /dev/null +++ b/public/language/ur/world.json @@ -0,0 +1,25 @@ +{ + "name": "جهان", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", + "help": "مدد", + + "help.title": "یہ صفحہ کیا ہے؟", + "help.intro": "فیڈی ورس میں آپ کے اپنے گوشے میں خوش آمدید۔", + "help.fediverse": "„فیڈی ورس“ ایک ایسی نیٹ ورک ہے جس میں باہم مربوط ایپلیکیشنز اور ویب سائٹس ایک دوسرے سے بات چیت کرتی ہیں اور جن کے صارفین ایک دوسرے کو دیکھ سکتے ہیں۔ یہ فورم فیڈریٹڈ ہے اور اس سوشل نیٹ ورک (جسے „فیڈی ورس“ کہا جاتا ہے) کے ساتھ تعامل کر سکتا ہے۔ یہ صفحہ فیڈی ورس میں آپ کا اپنا گوشہ ہے۔ اس میں آپ صرف ان موضوعات کو دیکھیں گے جو ان صارفین نے بنائے یا شیئر کیے ہیں جنہیں آپ فالو کرتے ہیں۔", + "help.build": "شروع میں یہاں زیادہ موضوعات نہیں ہو سکتے۔ یہ معمول کی بات ہے۔ جب آپ دوسرے صارفین کو فالو کرنا شروع کریں گے تو آپ یہاں زیادہ مواد دیکھنا شروع کریں گے۔", + "help.federating": "اسی طرح، اگر اس فورم سے باہر کے صارفین آپ کو فالو کرنا شروع کر دیں، تو آپ کی پوسٹس ان کے ایپلیکیشنز اور ویب سائٹس پر ظاہر ہونا شروع ہو جائیں گی۔", + "help.next-generation": "یہ سوشل نیٹ ورک کی نئی نسل ہے۔ آج ہی سے حصہ ڈالنا شروع کریں!", + + "onboard.title": "فیڈی ورس کی طرف آپ کی کھڑکی…", + "onboard.what": "یہ آپ کی ذاتی نوعیت کی کیٹیگری ہے جو صرف اس فورم سے باہر کے مواد پر مشتمل ہے۔ یہاں وہ چیزیں ظاہر ہوتی ہیں جو آپ کے فالو کیے ہوئے لوگوں نے بنائیں یا شیئر کیں۔", + "onboard.why": "اس فورم سے باہر بہت کچھ ہو رہا ہے، اور ہر چیز آپ کے مفادات سے مطابقت نہیں رکھتی۔ اس لیے مخصوص لوگوں کو فالو کرنا یہ ظاہر کرنے کا بہترین طریقہ ہے کہ آپ ان سے مزید دیکھنا چاہتے ہیں۔", + "onboard.how": "اس دوران، آپ اس فورم کے قابل رسائی مواد کو دیکھنے کے لیے اوپر کے بٹن استعمال کر سکتے ہیں۔ اس طرح آپ نئے مواد کی دریافت شروع کر سکتے ہیں!", + + "category-search": "Find a category..." +} \ No newline at end of file diff --git a/public/language/vi/admin/admin.json b/public/language/vi/admin/admin.json index 854e80eb92..2b0c8a4986 100644 --- a/public/language/vi/admin/admin.json +++ b/public/language/vi/admin/admin.json @@ -1,5 +1,5 @@ { - "alert.confirm-rebuild-and-restart": "Bạn có chắc chắn muốn xây dựng lại và khởi động lại NodeBB?", + "alert.confirm-rebuild-and-restart": "Bạn có chắc muốn dựng lại và khởi động lại NodeBB?", "alert.confirm-restart": "Bạn có chắc muốn khởi động lại NodeBB", "acp-title": "%1 | Bảng Điểu Khiển Quản Trị NodeBB", diff --git a/public/language/vi/admin/advanced/cache.json b/public/language/vi/admin/advanced/cache.json index d1c5839d64..40735bf879 100644 --- a/public/language/vi/admin/advanced/cache.json +++ b/public/language/vi/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "Bộ nhớ đệm", - "post-cache": "Bộ Đệm Bài Đăng", - "group-cache": "Bộ Đệm Nhóm", - "local-cache": "Bộ Đệm Cục Bộ", - "object-cache": "Bộ Đệm Đối Tượng", "percent-full": "%1% Đầy", "post-cache-size": "Kích Cỡ Bộ Đệm Bài Đăng", "items-in-cache": "Các Mục trong Bộ Đệm" diff --git a/public/language/vi/admin/dashboard.json b/public/language/vi/admin/dashboard.json index 6d2a736380..d12d7771e2 100644 --- a/public/language/vi/admin/dashboard.json +++ b/public/language/vi/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "Đã Đăng Ký Xem Trang", "graphs.page-views-guest": "Khách Xem Trang", "graphs.page-views-bot": "Bot Xem Trang", + "graphs.page-views-ap": "Lượt Xem Trang ActivityPub", "graphs.unique-visitors": "Khách Độc Lập", "graphs.registered-users": "Thành Viên Chính Thức", "graphs.guest-users": "Người dùng khách", diff --git a/public/language/vi/admin/development/info.json b/public/language/vi/admin/development/info.json index f8e2480b94..4a49ec9ee9 100644 --- a/public/language/vi/admin/development/info.json +++ b/public/language/vi/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "trực tuyến", "git": "git", - "process-memory": "xử lý bộ nhớ", + "process-memory": "rss/heap used", "system-memory": "bộ nhớ hệ thống", "used-memory-process": "Đã sử dụng bộ nhớ theo quy trình", "used-memory-os": "Bộ nhớ hệ thống đã sử dụng", diff --git a/public/language/vi/admin/manage/admins-mods.json b/public/language/vi/admin/manage/admins-mods.json index 650ce0a501..2aa9cc083d 100644 --- a/public/language/vi/admin/manage/admins-mods.json +++ b/public/language/vi/admin/manage/admins-mods.json @@ -1,5 +1,5 @@ { - "manage-admins-and-mods": "Quản lý Quản Trị Viên & Người Điều Hành", + "manage-admins-and-mods": "Quản lý Quản Trị Viên & Người Kiểm Duyệt", "administrators": "Quản Trị Viên", "global-moderators": "Người Kiểm Duyệt Chung", "moderators": "Người điều hành", diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index d89112626c..adb8895367 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Quản lý Danh mục", "add-category": "Thêm Danh Mục", + "add-local-category": "Thêm danh mục Cục Bộ", + "add-remote-category": "Thêm danh mục Từ Xa", + "remove": "Xóa", + "rename": "Đổi tên", "jump-to": "Chuyển tới...", "settings": "Cài Đặt Chuyên Mục", "edit-category": "Sửa Danh Mục", "privileges": "Đặc quyền", "back-to-categories": "Quay lại danh mục", - "name": "Tên Chuyên Mục", + "id": "ID Danh Mục", + "name": "Tên Danh Mục", "handle": "Xử Lý Danh Mục", "handle.help": "Xử lý danh mục của bạn được sử dụng làm đại diện cho danh mục này trên các mạng khác, tương tự như tên đăng nhập. Thẻ điều khiển danh mục không được khớp với tên người dùng hoặc nhóm người dùng hiện có.", "description": "Mô Tả Chuyên Mục", - "federatedDescription": "Mô Tả Liên Kết", - "federatedDescription.help": "Văn bản này sẽ được thêm vào mô tả danh mục khi được truy vấn bởi các trang web/ứng dụng khác.", - "federatedDescription.default": "Đây là một danh mục diễn đàn có chứa thảo luận tại chỗ. Bạn có thể bắt đầu các cuộc thảo luận mới bằng cách đề cập đến thể loại này.", + "topic-template": "Mẫu Chủ Đề", + "topic-template.help": "Xác định mẫu cho các chủ đề mới được tạo trong danh mục này.", "bg-color": "Màu Nền", "text-color": "Màu Chữ", "bg-image-size": "Kích Thước Hình Nền", @@ -103,6 +107,11 @@ "alert.create-success": "Đã tạo chuyên mục thành công!", "alert.none-active": "Bạn không có chuyên mục hoạt động.", "alert.create": "Tạo Chuyên Mục", + "alert.add": "Thêm Danh Mục", + "alert.add-help": "Các danh mục từ xa có thể được thêm vào danh sách danh sách bằng cách chỉ định cách xử lý của chúng.

Ghi chú — Danh mục từ xa có thể không phản ánh tất cả các chủ đề được xuất bản trừ khi có ít nhất một người dùng cục bộ theo dõi/xem nó.", + "alert.rename": "Đổi tên Danh Mục Từ Xa", + "alert.rename-help": "Nhập tên mới cho danh mục này. Để trống để khôi phục tên gốc.", + "alert.confirm-remove": "Bạn có chắc muốn xóa danh mục này? Bạn có thể thêm nó trở lại bất kỳ lúc nào.", "alert.confirm-purge": "

Bạn có chắc muốn loại bỏ danh mục \"%1\" này không?

Cảnh báo! Tất cả chủ đề và bài đăng trong danh mục này sẽ bị xóa!

Xóa danh mục sẽ xóa tất cả các chủ đề và bài đăng, đồng thời xóa danh mục khỏi cơ sở dữ liệu. Nếu bạn muốn xóa một danh mụctạm thời, thay vào đó bạn sẽ muốn \"vô hiệu hóa\" danh mục.

", "alert.purge-success": "Đã loại bỏ chuyên mục!", "alert.copy-success": "Đã Sao Chép Cài Đặt!", diff --git a/public/language/vi/admin/manage/custom-reasons.json b/public/language/vi/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/vi/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/vi/admin/manage/privileges.json b/public/language/vi/admin/manage/privileges.json index 69b3e159b0..6b56cdc2d5 100644 --- a/public/language/vi/admin/manage/privileges.json +++ b/public/language/vi/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "Truy Cập Chủ Đề", "create-topics": "Tạo Chủ Đề", "reply-to-topics": "Trả Lời Chủ Đề", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "Lên Lịch Chủ Đề", "tag-topics": "Gắn Thẻ Chủ Đề", "edit-posts": "Chỉnh Sửa Bài Đăng", @@ -55,7 +56,7 @@ "alert.confirm-discard": "Bạn có chắc chắn muốn hủy các thay đổi đặc quyền của mình không?", "alert.discarded": "Đã hủy bỏ thay đổi đặc quyền", "alert.confirm-copyToAll": "Bạn có chắc muốn áp dụng cài đặt %1 cho tất cả danh mục?", - "alert.confirm-copyToAllGroup": "Are you sure you wish to apply this group's set of %1 to all categories?", + "alert.confirm-copyToAllGroup": "Bạn có chắc muốn áp dụng cài đặt %1 của nhóm này cho tất cả danh mục?", "alert.confirm-copyToChildren": "Bạn có chắc chắn muốn áp dụng các cài đặt này %1 cho tất cả danh mục hậu duệ (con) ?", "alert.confirm-copyToChildrenGroup": "Bạn có chắc muốn áp dụng cài đặt %1 của nhóm này cho tất cả danh mục con?", "alert.no-undo": "Hành động này không thể hoàn tác.", diff --git a/public/language/vi/admin/manage/users.json b/public/language/vi/admin/manage/users.json index 8a0a2b0806..38b773ee1d 100644 --- a/public/language/vi/admin/manage/users.json +++ b/public/language/vi/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "Xóa Người DùngNội Dung", "download-csv": "Tải về CSV", "custom-user-fields": "Trường Người Dùng Tùy Chỉnh", + "custom-reasons": "Custom Reasons", "manage-groups": "Quản Lý Nhóm", "set-reputation": "Đặt Uy Tín", "add-group": "Thêm Nhóm", @@ -77,9 +78,11 @@ "temp-ban.length": "Dài", "temp-ban.reason": "Lý do (Không bắt buộc)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "Giờ", "temp-ban.days": "Ngày", "temp-ban.explanation": "Nhập khoảng thời gian cho lệnh cấm. Lưu ý rằng thời gian bằng 0 sẽ là một lệnh cấm vĩnh viễn.", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "Bạn có chắc muốn cấm người dùng này mãi mãi?", "alerts.confirm-ban-multi": "Bạn có chắc muốn cấm những người dùng này mãi mãi?", @@ -119,7 +122,7 @@ "alerts.create-success": "Đã tạo người dùng!", "alerts.prompt-email": "Thư điện tử:", - "alerts.email-sent-to": "Email mời đã được gửi đến %1", + "alerts.email-sent-to": "Một email mời đã được gửi đến %1", "alerts.x-users-found": "Tìm được %1 người, (%2 giây)", "alerts.select-a-single-user-to-change-email": "Chọn một người dùng để thay đổi email", "export": "Xuất", diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index a2502f039a..c1a9a54a7e 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "Hết Thời Gian Tra Cứu (mili giây)", "probe-timeout-help": "(Mặc định: 2000) Nếu truy vấn tra cứu không nhận được phản hồi trong khung thời gian đã đặt, thay vào đó sẽ đưa người dùng đến liên kết trực tiếp. Điều chỉnh con số này cao hơn nếu các trang web phản hồi chậm và bạn muốn dành thêm thời gian.", + "rules": "Phân loại", + "rules-intro": "Nội dung được phát hiện thông qua ActivityPub có thể được tự động phân loại dựa trên các quy tắc nhất định (ví dụ: hashtag)", + "rules.modal.title": "Cách nó hoạt động", + "rules.modal.instructions": "Bất kỳ nội dung đến nào cũng được kiểm tra theo các quy tắc phân loại này và nội dung phù hợp được tự động chuyển sang danh mục lựa chọn.

N.B. Nội dung đã được phân loại (nghĩa là trong một danh mục từ xa) sẽ không thông qua các quy tắc này.", + "rules.add": "Thêm Quy Tắc Mới", + "rules.help-hashtag": "Các chủ đề có chứa hashtag không phân biệt chữ hoa chữ thường này sẽ khớp. Không nhập ký tự #", + "rules.help-user": "Các chủ đề do người dùng đã nhập đã tạo sẽ khớp. Nhập tên người dùng hoặc ID đầy đủ (vd: bob@example.org hoặc https://example.org/users/bob.", + "rules.type": "Loại", + "rules.value": "Giá trị", + "rules.cid": "Danh mục", + + "relays": "Chuyển tiếp", + "relays.intro": "Một chuyển tiếp cải thiện khám phá nội dung đến và từ nodeBB của bạn. Đăng ký một chuyển tiếp có nghĩa là nội dung nhận được bởi chuyển tiếp được chuyển tiếp ở đây và nội dung được đăng ở đây được cung cấp bên ngoài bởi chuyển tiếp.", + "relays.warning": "Lưu ý: Chuyển tiếp có thể gửi lượng lưu lượng truy cập lớn và có thể tăng gánh nặng lưu trữ và xử lý.", + "relays.litepub": "NodeBB tuân theo tiêu chuẩn chuyển tiếp kiểu LitePub. URL bạn nhập ở đây sẽ kết thúc với /actor.", + "relays.add": "Thêm Chuyển Tiếp Mới", + "relays.relay": "Chuyển tiếp", + "relays.state": "Trạng thái", + "relays.state-0": "Đang đợi", + "relays.state-1": "Chỉ nhận", + "relays.state-2": "Kích hoạt", + "server-filtering": "Lọc", "count": "NodeBB này hiện đã biết về %1 máy chủ", "server.filter-help": "Chỉ định các máy chủ mà bạn muốn cấm liên kết với NodeBB của mình. Ngoài ra, bạn có thể chọn tham gia có chọn lọc cho phép liên kết có chọn lọc với các máy chủ cụ thể. Cả hai tùy chọn đều được hỗ trợ, mặc dù chúng loại trừ lẫn nhau.", diff --git a/public/language/vi/admin/settings/api.json b/public/language/vi/admin/settings/api.json index fb878a94b3..4f797f8da9 100644 --- a/public/language/vi/admin/settings/api.json +++ b/public/language/vi/admin/settings/api.json @@ -10,7 +10,7 @@ "require-https-caveat": "Ghi chú: Một số cài đặt liên quan đến bộ cân bằng tải có thể ủy quyền các yêu cầu của họ tới NodeBB bằng HTTP, trong trường hợp đó tùy chọn này vẫn bị vô hiệu hóa.", "uid": "ID Người Dùng", - "token": "Token", + "token": "Mã truy cập", "uid-help-text": "Ghi rõ ID người dùng liên kết với mã truy cập. Nếu ID người dùng là 0, nó sẽ là môt mã truy cập cao cấp, có thể giả định danh tính của những người dùng khác dựa trên tham số _uid", "description": "Mô tả", "last-seen": "Nhìn thấy lần cuối", diff --git a/public/language/vi/admin/settings/chat.json b/public/language/vi/admin/settings/chat.json index 629a0d498e..6b72a54d47 100644 --- a/public/language/vi/admin/settings/chat.json +++ b/public/language/vi/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Độ dài tối đa tên phòng trò chuyện", "max-room-size": "Số lượng người dùng tối đa trong phòng trò chuyện", "delay": "Thời gian giữa các tin nhắn trò chuyện (ms)", - "notification-delay": "Độ trễ thông báo tin nhắn trò chuyện", - "notification-delay-help": "Các tin nhắn bổ sung được gửi trong khoảng thời gian này sẽ được đối chiếu và người dùng sẽ được thông báo một lần trong mỗi khoảng thời gian trì hoãn. Đặt giá trị này thành 0 để tắt độ trễ.", "restrictions.seconds-edit-after": "Số giây mà một tin nhắn trò chuyện sẽ vẫn có thể chỉnh sửa được.", "restrictions.seconds-delete-after": "Số giây một tin nhắn trò chuyện sẽ vẫn có thể bị xóa." } \ No newline at end of file diff --git a/public/language/vi/admin/settings/email.json b/public/language/vi/admin/settings/email.json index 09b26c7649..5ea7eec8aa 100644 --- a/public/language/vi/admin/settings/email.json +++ b/public/language/vi/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Việc gộp các kết nối ngăn NodeBB tạo kết nối mới cho mọi email. Tùy chọn này chỉ áp dụng nếu Truyền tải SMTP được bật.", "smtp-transport.allow-self-signed": "Cho phép chứng chỉ tự ký", "smtp-transport.allow-self-signed-help": "Kích hoạt cài đặt này sẽ cho phép bạn sử dụng các chứng chỉ TLS tự ký hoặc không hợp lệ.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "Sửa Mẫu Email", "template.select": "Chọn Mẫu Email", "template.revert": "Hoàn Nguyên về Bản Gốc", + "test-smtp-settings": "Test SMTP Settings", "testing": "Email Kiểm Tra", + "testing.success": "Test Email Sent.", "testing.select": "Chọn Mẫu Email", "testing.send": "Gửi Email Kiểm Tra", - "testing.send-help": "Email kiểm tra sẽ được gửi đến địa chỉ email của người dùng hiện đang đăng nhập.", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "Email Bản Tóm Tắt", "subscriptions.disable": "Tắt email bản tóm tắt", "subscriptions.hour": "Giờ Tóm Tắt", diff --git a/public/language/vi/admin/settings/general.json b/public/language/vi/admin/settings/general.json index 0637405db9..a45f6a551f 100644 --- a/public/language/vi/admin/settings/general.json +++ b/public/language/vi/admin/settings/general.json @@ -58,6 +58,6 @@ "default-language": "Ngôn Ngữ Mặc Định", "auto-detect": "Tự Phát Hiện Cài Đặt Ngôn Ngữ Cho Khách", "default-language-help": "Ngôn ngữ mặc định là cài đặt ngôn ngữ cho tất cả người dùng diễn đàn của bạn.
Người dùng cá nhân có thể thay đổi ngôn ngữ họ thích trong cài đặt tài khoản", - "post-sharing": "Chia sẻ bài viết", - "info-plugins-additional": "Plugin có thể thêm các mạng bổ sung để chia sẻ bài viết." + "post-sharing": "Chia Sẻ Bài Đăng", + "info-plugins-additional": "Plugin có thể thêm các mạng bổ sung để chia sẻ bài đăng." } \ No newline at end of file diff --git a/public/language/vi/admin/settings/notifications.json b/public/language/vi/admin/settings/notifications.json index f153ecfe8c..4a556625c7 100644 --- a/public/language/vi/admin/settings/notifications.json +++ b/public/language/vi/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "Thông Báo Chào Mừng", "welcome-notification-link": "Liên Kết Thông Báo Chào Mừng", "welcome-notification-uid": "Thông Báo Chào Mừng Người Dùng (UID)", - "post-queue-notification-uid": "Người Dùng Đợi Đăng (UID)" + "post-queue-notification-uid": "Người Dùng Đợi Đăng (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/vi/admin/settings/uploads.json b/public/language/vi/admin/settings/uploads.json index f89c13a955..94eee4f0b8 100644 --- a/public/language/vi/admin/settings/uploads.json +++ b/public/language/vi/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "Hình ảnh rộng hơn giá trị này sẽ bị từ chối.", "reject-image-height": "Chiều Cao Ảnh Tối Đa (pixel)", "reject-image-height-help": "Hình ảnh cao hơn giá trị này sẽ bị từ chối.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Cho phép người dùng tải lên ảnh thumbnails chủ đề", + "show-post-uploads-as-thumbnails": "Hiển thị bài đăng tải lên dưới dạng hình thu nhỏ", "topic-thumb-size": "Kích Cỡ Ảnh Thumbnails Chủ Đề", "allowed-file-extensions": "Cho Phép Phần Mở Rộng Tệp", "allowed-file-extensions-help": "Nhập danh sách phần mở rộng tệp phân tách bằng dấu phẩy ở đây (VD: pdf,xls,doc). Để trống là cho phép tất cả.", diff --git a/public/language/vi/admin/settings/user.json b/public/language/vi/admin/settings/user.json index 8785652865..b08c60385a 100644 --- a/public/language/vi/admin/settings/user.json +++ b/public/language/vi/admin/settings/user.json @@ -6,8 +6,8 @@ "allow-login-with.username-email": "Tên Đăng Nhập hoặc Email", "allow-login-with.username": "Chỉ Tên Đăng Nhập", "account-settings": "Cài Đặt Tài Khoản", - "gdpr-enabled": "Bật đồng ý thu thâp GDPR", - "gdpr-enabled-help": "Khi được bật, tất cả những người đăng ký mới sẽ được yêu cầu đồng ý rõ ràng cho việc thu thập và sử dụng dữ liệu theo Quy định chung về bảo vệ dữ liệu (GDPR). Ghi chú: Bật GDPR không buộc người dùng đã có từ trước phải đồng ý. Để làm như vậy, bạn sẽ cần cài đặt plugin GDPR.", + "gdpr-enabled": "Bật cho phép thu thập GDPR", + "gdpr-enabled-help": "Khi được bật, tất cả những ai đăng ký mới sẽ được yêu cầu đồng ý rõ ràng cho việc thu thập và sử dụng dữ liệu theo Quy định chung về bảo vệ dữ liệu (GDPR). Ghi chú: Bật GDPR không buộc người dùng đã có từ trước phải đồng ý. Để làm như vậy, bạn sẽ cần cài đặt plugin GDPR.", "disable-username-changes": "Tắt thay đổi tên đăng nhập", "disable-email-changes": "Tắt thay đổi email", "disable-password-changes": "Tắt thay đổi mật khẩu", @@ -64,6 +64,7 @@ "show-email": "Hiển thị email", "show-fullname": "Hiển thị tên đầy đủ", "restrict-chat": "Chỉ cho phép tin nhắn trò chuyện từ những người dùng tôi theo dõi", + "disable-incoming-chats": "Tắt tin nhắn trò chuyện đến", "outgoing-new-tab": "Mở các liên kết đi trong tab mới", "topic-search": "Bật Tìm Kiếm Trong Chủ Đề", "update-url-with-post-index": "Cập nhật url với chỉ mục bài đăng trong khi lướt xem các chủ đề", diff --git a/public/language/vi/admin/settings/web-crawler.json b/public/language/vi/admin/settings/web-crawler.json index 245ddd9400..7b9a5e6fa8 100644 --- a/public/language/vi/admin/settings/web-crawler.json +++ b/public/language/vi/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "Tắt Nguồn Cấp RSS", "disable-sitemap-xml": "Tắt Sitemap.xml", "sitemap-topics": "Số lượng Chủ đề để hiển thị trong Sơ đồ trang web", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "Xóa Bộ Đệm Sơ Đồ Trang Web", "view-sitemap": "Xem Sơ Đồ Trang" } \ No newline at end of file diff --git a/public/language/vi/aria.json b/public/language/vi/aria.json index 25d06e5ed4..b82f396907 100644 --- a/public/language/vi/aria.json +++ b/public/language/vi/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Trang hồ sơ cho người dùng %1", "user-watched-tags": "Thẻ người dùng đã xem", "delete-upload-button": "Xóa nút tải lên", - "group-page-link-for": "Liên kết trang nhóm cho %1" + "group-page-link-for": "Liên kết trang nhóm cho %1", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/vi/category.json b/public/language/vi/category.json index 951d4b91a5..eb43072ed6 100644 --- a/public/language/vi/category.json +++ b/public/language/vi/category.json @@ -1,12 +1,13 @@ { "category": "Danh mục", "subcategories": "Danh mục phụ", - "uncategorized": "Chưa có danh mục", - "uncategorized.description": "Các chủ đề không phù hợp với bất kỳ danh mục hiện có nào", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "Có thể theo dõi danh mục này từ mạng xã hội mở thông qua xử lý %1", "new-topic-button": "Chủ Đề Mới", "guest-login-post": "Đăng nhập để đăng bài", "no-topics": "Không có chủ đề nào trong danh mục này.
Sao bạn không thử đăng?", + "no-followers": "Không ai trên trang web này đang theo dõi hoặc xem danh mục này. Theo dõi hoặc xem danh mục này để bắt đầu nhận cập nhật.", "browsing": "lướt xem", "no-replies": "Không có ai trả lời", "no-new-posts": "Không bài đăng mới.", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 3f745d452d..9449d7f0dd 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -3,6 +3,7 @@ "invalid-json": "JSON không hợp lệ", "wrong-parameter-type": "Giá trị của loại %3 được mong đợi cho thuộc tính `%1`, nhưng thay vào đó, %2 đã được nhận", "required-parameters-missing": "Các thông số bắt buộc bị thiếu trong lệnh gọi API này: %1", + "reserved-ip-address": "Không được phép yêu cầu mạng đến phạm vi IP dành riêng.", "not-logged-in": "Có vẻ như bạn chưa đăng nhập.", "account-locked": "Tài khoản của bạn tạm thời bị khóa", "search-requires-login": "Tìm kiếm yêu cầu một tài khoản - vui lòng đăng nhập hoặc đăng ký.", @@ -67,8 +68,8 @@ "no-chat-room": "Phòng trò chuyện không tồn tại", "no-privileges": "Bạn không đủ đặc quyền cho hành động này.", "category-disabled": "Chuyên mục bị khóa", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "Đã xóa bài", + "topic-locked": "Đã khóa chủ đề", "post-edit-duration-expired": "Bạn chỉ được phép chỉnh sửa bài đăng trong %1 giây sau khi đăng", "post-edit-duration-expired-minutes": "Bạn chỉ được phép sửa các bài viết sau khi đăng %1 phút(s)", "post-edit-duration-expired-minutes-seconds": "Bạn chỉ được phép sửa các bài viết sau khi đăng %1 phút(s) %2 giây(s)", @@ -83,8 +84,8 @@ "post-delete-duration-expired-hours-minutes": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giờ(s) 2 phút(s)", "post-delete-duration-expired-days": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s)", "post-delete-duration-expired-days-hours": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s) %2 giờ(s)", - "cant-delete-topic-has-reply": "Bạn không thể xóa chủ đề vì đã có 1 bình luận", - "cant-delete-topic-has-replies": "Bạn không thể xóa chủ đề này vì đã có %1 bình luận", + "cant-delete-topic-has-reply": "Bạn không thể xóa chủ đề của bạn sau khi nó có câu trả lời", + "cant-delete-topic-has-replies": "Bạn không thể xóa chủ đề của bạn sau khi nó có %1 trả lời", "content-too-short": "Vui lòng nhập một bài viết dài hơn. Bài viết phải chứa ít nhất %1 ký tự.", "content-too-long": "Hãy nhập một bài đăng ngắn hơn. Bài đăng không thể dài hơn %1 ký tự.", "title-too-short": "Hãy nhập tiêu đề dài hơn. Tiêu đề nên có ít nhất %1 ký tự.", @@ -126,7 +127,7 @@ "already-deleting": "Đã sẵn sàng xóa", "invalid-image": "Hình ảnh không hợp lệ", "invalid-image-type": "Định dạng ảnh không hợp lệ. Các loại được phép là: %1", - "invalid-image-extension": "Định dạng ảnh không hợp lệ", + "invalid-image-extension": "Phần mở rộng ảnh không hợp lệ", "invalid-file-type": "Loại tệp không hợp lệ. Loại cho phép là: %1", "invalid-image-dimensions": "Độ phân giải của ảnh quá lớn", "group-name-too-short": "Tên nhóm quá ngắn", @@ -136,7 +137,7 @@ "group-already-member": "Đã là thành viên của nhóm.", "group-not-member": "Không phải thành viên nhóm này.", "group-needs-owner": "Yêu cầu phải có ít nhất một chủ nhóm", - "group-already-invited": "Thành viên này đã được mời", + "group-already-invited": "Người dùng này đã được mời", "group-already-requested": "Yêu cầu tham gia thành viên của bạn đã được gửi.", "group-join-disabled": "Bạn không thể tham gia nhóm này vào lúc này", "group-leave-disabled": "Bạn không thể rời khỏi nhóm này vào lúc này", @@ -146,6 +147,7 @@ "post-already-restored": "Bài viết này đã được khôi phục", "topic-already-deleted": "Chủ đề này đã bị xóa", "topic-already-restored": "Chủ đề này đã được khôi phục", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "Bạn không thể xoá bài viết chính, thay vào đó vui lòng xóa chủ đề", "topic-thumbnails-are-disabled": "Ảnh Thumbnails chủ đề đã bị tắt", "invalid-file": "Tệp Không Hợp Lệ", @@ -154,9 +156,11 @@ "about-me-too-long": "Xin lỗi, giới thiệu bản thân bạn không thể dài hơn %1 ký tự.", "cant-chat-with-yourself": "Bạn không thể trò chuyện với chính bạn!", "chat-restricted": "Người dùng này đã hạn chế tin nhắn trò chuyện của họ. Họ phải theo dõi bạn trước khi bạn có thể trò chuyện với họ", + "chat-allow-list-user-already-added": "Người dùng này đã có trong danh sách cho phép của bạn", + "chat-deny-list-user-already-added": "Người dùng này đã có trong danh sách từ chối của bạn", "chat-user-blocked": "Bạn đã bị chặn bởi người dùng này.", "chat-disabled": "Hệ thống trò chuyện bị tắt", - "too-many-messages": "Bạn đã gửi quá nhiều tin nhắn, vui lòng đợi trong giây lát.", + "too-many-messages": "Bạn đã gửi quá nhiều tin nhắn, vui lòng đợi một lúc.", "invalid-chat-message": "Tin nhắn trò chuyện không hợp lệ", "chat-message-too-long": "Tin nhắn trò chuyện không được dài hơn %1 ký tự.", "cant-edit-chat-message": "Bạn không được phép sửa tin nhắn này", @@ -199,7 +203,7 @@ "too-many-user-flags-per-day": "Bạn chỉ được gắn cờ %1 người dùng mỗi ngày", "cant-flag-privileged": "Bạn không có quyền gắn cờ hồ sơ hay nội dung của người dùng đặc quyền (kiểm duyệt viên/người kiểm duyệt chung/quản trị viên)", "cant-locate-flag-report": "Không thể định vị báo cáo cờ", - "self-vote": "Bạn không thể tự bầu cho bài đăng của mình", + "self-vote": "Bạn không thể tự bình chọn bài đăng của mình", "too-many-upvotes-today": "Bạn chỉ có thể ủng hộ %1 lần một ngày", "too-many-upvotes-today-user": "Bạn chỉ được ủng hộ người dùng %1 lần một ngày", "too-many-downvotes-today": "Bạn chỉ có thể phản đối %1 lần một ngày", @@ -222,9 +226,10 @@ "invalid-session-text": "Có vẻ như phiên đăng nhập của bạn không còn hoạt động. Vui lòng làm mới trang này.", "session-mismatch": "‎Phiên Không Khớp‎", "session-mismatch-text": "Có vẻ như phiên đăng nhập của bạn không còn khớp với máy chủ. Vui lòng làm mới trang này.", - "no-topics-selected": "Không có chủ đề nào đang được chọn!", + "no-topics-selected": "Không chọn chủ đề!", "cant-move-to-same-topic": "Bạn không thể đưa bài đăng vào cùng chủ đề!", "cant-move-topic-to-same-category": "Không thể di chuyển chủ đề đến cùng danh mục!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "Bạn không thể tự khóa bạn!", "cannot-block-privileged": "Bạn không thể khóa quản trị viên hay người kiểm duyệt chung.", "cannot-block-guest": "Khách không thể chặn người dùng khác", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Không thể truy cập máy chủ vào lúc này. Nhấp vào đây để thử lại hoặc thử lại sau", "invalid-plugin-id": "ID plugin không hợp lệ", "plugin-not-whitelisted": "Không thể cài đặt plugin – chỉ có plugin được Quản Lý Gói NodeBB đưa vào danh sách trắng mới có thể được cài đặt qua ACP", + "cannot-toggle-system-plugin": "Bạn không thể chuyển đổi trạng thái của một plugin hệ thống", "plugin-installation-via-acp-disabled": "Cài đặt plugin qua ACP bị tắt", "plugins-set-in-configuration": "Bạn không được phép thay đổi trạng thái plugin vì chúng được xác định trong thời gian chạy (config.json, biến môi trường hoặc đối số đầu cuối), thay vào đó hãy sửa đổi cấu hình.", "theme-not-set-in-configuration": "Khi xác định các plugin hoạt động trong cấu hình, thay đổi giao diện buộc phải thêm giao diện mới vào danh sách các plugin hoạt động trước khi cập nhật nó trong ACP", @@ -246,6 +252,7 @@ "api.401": "Một phiên đăng nhập hợp lệ không được tìm thấy. Hãy đăng nhập và thử lại.", "api.403": "Bạn không được phép thực hiện lệnh gọi này", "api.404": "Lệnh gọi API không hợp lệ", + "api.413": "The request payload is too large", "api.426": "HTTPS là bắt buộc đối với các yêu cầu đối với api viết, vui lòng gửi lại yêu cầu của bạn qua HTTPS", "api.429": "Bạn đã đưa ra quá nhiều yêu cầu, vui lòng thử lại sau", "api.500": "Đã xảy ra lỗi không mong muốn khi cố gắng thực hiện yêu cầu của bạn.", diff --git a/public/language/vi/flags.json b/public/language/vi/flags.json index bc746b52f7..1ed4630497 100644 --- a/public/language/vi/flags.json +++ b/public/language/vi/flags.json @@ -29,7 +29,7 @@ "filter-assignee": "Ủy nhiệm", "filter-cid": "Chuyên mục", "filter-quick-mine": "Được giao cho tôi", - "filter-cid-all": "Tất cả chuyên mục", + "filter-cid-all": "Tất cả các danh mục", "apply-filters": "Áp Dụng Bộ Lọc", "more-filters": "Thêm Bộ Lọc", "fewer-filters": "Bớt Bộ Lọc", diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 5619e2eb98..adaf20785a 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -68,6 +68,7 @@ "users": "Người dùng", "topics": "Chủ Đề", "posts": "Bài Viết", + "crossposts": "Cross-posts", "x-posts": "%1 bài đăng", "x-topics": "%1 chủ để", "x-reputation": "%1 uy tín", @@ -82,6 +83,7 @@ "downvoted": "Phản đối", "views": "Lượt xem", "posters": "Người đăng bài", + "watching": "Đang xem", "reputation": "Uy tín", "lastpost": "Bài viết cuối cùng", "firstpost": "Bài viết đầu tiên", @@ -98,7 +100,7 @@ "posted-in-ago-by": "được đăng trong %1 %2 bởi %3", "user-posted-ago": "%1 đã đăng %2", "guest-posted-ago": "Khách đã đăng %1", - "last-edited-by": "chỉnh sửa lần cuối bởi %1", + "last-edited-by": "chỉnh sửa cuối bởi %1", "edited-timestamp": "Đã Sửa %1", "norecentposts": "Không Bài Nào Gần Đây", "norecenttopics": "Không Chủ Đề Gần Đây", @@ -133,19 +135,19 @@ "upload": "Tải lên", "uploads": "Tải lên", "allowed-file-types": "Loại cho phép là %1", - "unsaved-changes": "Có một vài thay đổi chưa được lưu. Bạn muốn rời đi ngay?", - "reconnecting-message": "Có vẻ như bạn đã mất kết nối tới %1, vui lòng đợi một lúc để chúng tôi thử kết nối lại.", - "play": "Chơi", + "unsaved-changes": "Bạn có những thay đổi chưa lưu. Bạn có chắc muốn điều hướng đi?", + "reconnecting-message": "Có vẻ như bạn đã mất kết nối tới %1, hãy đợi trong khi chúng tôi cố gắng kết nối lại.", + "play": "Phát", "cookies.message": "Trang web này sử dụng cookie để đảm bảo bạn có được trải nghiệm tốt.", - "cookies.accept": "Đã rõ!", - "cookies.learn-more": "Xem thêm", + "cookies.accept": "Hiểu rồi!", + "cookies.learn-more": "Tìm Hiểu Thêm", "edited": "Đã Sửa", "disabled": "Đã tắt", "select": "Chọn", "selected": "Đã chọn", "copied": "Đã sao chép", "user-search-prompt": "Nhập để tìm kiếm thành viên", - "hidden": "Ẩn", + "hidden": "Đã ẩn", "sort": "Xếp", "actions": "Hành Động", "rss-feed": "Nguồn RSS", diff --git a/public/language/vi/groups.json b/public/language/vi/groups.json index 1fdef7475c..e8c0da765c 100644 --- a/public/language/vi/groups.json +++ b/public/language/vi/groups.json @@ -1,35 +1,37 @@ { + "group": "Group", "all-groups": "Tất cả các nhóm", "groups": "Nhóm", "members": "Thành Viên", - "view-group": "Xem nhóm", + "x-members": "%1 member(s)", + "view-group": "Xem Nhóm", "owner": "Người Sở Hữu Nhóm", - "new-group": "Tạo nhóm mới", + "new-group": "Tạo Nhóm Mới", "no-groups-found": "Không có nhóm nào để xem", "pending.accept": "Chấp nhận", "pending.reject": "Từ chối", - "pending.accept-all": "Chấp nhận tất cả", - "pending.reject-all": "Từ chối tất cả", + "pending.accept-all": "Chấp Nhận Hết", + "pending.reject-all": "Từ Chối Hết", "pending.none": "Không có ai đang chờ duyệt tham gia nhóm", "invited.none": "Không có thành viên nào được mời vào lúc này", "invited.uninvite": "Từ Chối Lời Mời", "invited.search": "Tìm một người để mời vào nhóm", "invited.notification-title": "Bạn đã được mời tham gia %1", - "request.notification-title": "Yêu cầu tham gia nhóm từ %1", - "request.notification-text": "%1 yêu cầu chấp nhận để trở thành thành viên của %2", + "request.notification-title": "Yêu Cầu Làm Thành Viên Nhóm từ %1", + "request.notification-text": "%1 đã yêu cầu trở thành thành viên của %2", "cover-save": "Lưu", "cover-saving": "Đang lưu", "details.title": "Chi Tiết Nhóm", "details.members": "Danh Sách Thành Viên", "details.pending": "Thành Viên Đang Chờ", "details.invited": "Thành Viên Đã Mời", - "details.has-no-posts": "Thành viên nhóm này chưa đăng bài viết nào.", - "details.latest-posts": "Bài viết mới nhất", + "details.has-no-posts": "Các thành viên của nhóm này chưa đăng bài nào.", + "details.latest-posts": "Bài Viết Mới Nhất", "details.private": "Riêng tư", "details.disableJoinRequests": "Tắt yêu cầu tham gia", "details.disableLeave": "Không cho phép người dùng rời khỏi nhóm", "details.grant": "Cấp/Huỷ bỏ quyền sở hữu", - "details.kick": "Đá ra", + "details.kick": "Loại ra", "details.kick-confirm": "Bạn có chắc chắn muốn xoá thành viên này khỏi nhóm?", "details.add-member": "Thêm Thành Viên", "details.owner-options": "Quản Trị Nhóm", @@ -62,5 +64,5 @@ "upload-group-cover": "Tải lên ảnh bìa nhóm", "bulk-invite-instructions": "Nhập danh sách tên đăng nhập, phân tách bằng dấu phẩy, để mời vào nhóm", "bulk-invite": "Mời Nhiều", - "remove-group-cover-confirm": "Bạn có chắc rằng muốn xoá ảnh bìa không?" + "remove-group-cover-confirm": "Bạn có chắc muốn xoá ảnh bìa không?" } \ No newline at end of file diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index b6911f8a21..6834051f81 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -14,12 +14,12 @@ "chat.user-typing-2": "%1%2 đang viết ...", "chat.user-typing-3": "%1, %2%3 đang viết ...", "chat.user-typing-n": "%1, %2%3 người khác đang viết ...", - "chat.user-has-messaged-you": "%1 đã nhắn tin cho bạn.", + "chat.user-has-messaged-you": "%1 đã gửi tin nhắn cho bạn.", "chat.replying-to": "Đang trả lời %1", "chat.see-all": "Tất cả trò chuyện", "chat.mark-all-read": "Đánh dấu tất cả đã đọc", "chat.no-messages": "Hãy chọn người nhận để xem lịch sử tin nhắn trò chuyện", - "chat.no-users-in-room": "Không có người nào trong phòng này.", + "chat.no-users-in-room": "Không có ai trong phòng này", "chat.recent-chats": "Trò Chuyện Gần Đây", "chat.contacts": "Liên hệ", "chat.message-history": "Lịch Sử Tin Nhắn", @@ -48,6 +48,7 @@ "chat.add-user": "Thêm Người", "chat.notification-settings": "Cài Đặt Thông Báo", "chat.default-notification-setting": "Cài Đặt Thông Báo Mặc Định", + "chat.join-leave-messages": "Tham gia/Rời đi Tin Nhắn", "chat.notification-setting-room-default": "Phòng Mặc Định", "chat.notification-setting-none": "Không thông báo", "chat.notification-setting-at-mention-only": "Chỉ khi @đề cập", @@ -80,10 +81,10 @@ "composer.show-preview": "Hiện Xem Thử", "composer.hide-preview": "Ẩn Xem Thử", "composer.help": "Trợ giúp", - "composer.user-said-in": "%1 đã nói trong %2:", - "composer.user-said": "%1 đã nói:", + "composer.user-said-in": "%1 nói trong %2:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "Bạn có chắc muốn loại bỏ bài đăng này?", - "composer.submit-and-lock": "Đăng và Khoá", + "composer.submit-and-lock": "Gửi và Khoá", "composer.toggle-dropdown": "Chuyển Đổi Thả Xuống", "composer.uploading": "Đang tải lên %1", "composer.formatting.bold": "Đậm", @@ -100,7 +101,7 @@ "composer.formatting.code": "Mã", "composer.formatting.link": "Liên kết", "composer.formatting.picture": "Liên Kết Ảnh", - "composer.upload-picture": "Tải ảnh lên", + "composer.upload-picture": "Tải Lên Ảnh", "composer.upload-file": "Tải Lên Tệp", "composer.zen-mode": "Chế Độ Zen", "composer.select-category": "Chọn chuyên mục", @@ -122,7 +123,7 @@ "bootbox.confirm": "Xác nhận", "bootbox.submit": "Gửi", "bootbox.send": "Gửi", - "cover.dragging-title": "Điều chỉnh vị trí ảnh cover", + "cover.dragging-title": "Chỉnh Vị Trí Ảnh Bìa", "cover.dragging-message": "Kéo ảnh cover vào vị trí mong muốn và bấm \"Lưu\"", "cover.saved": "Đã lưu ảnh bìa và vị trí ảnh", "thumbs.modal.title": "Quản lý ảnh mô tả chủ đề", diff --git a/public/language/vi/notifications.json b/public/language/vi/notifications.json index c4130f1b69..c01856d57f 100644 --- a/public/language/vi/notifications.json +++ b/public/language/vi/notifications.json @@ -22,7 +22,7 @@ "upvote": "Ủng hộ", "awards": "Giải thưởng", "new-flags": "Cảnh báo mới", - "my-flags": "Cảnh báo dành cho tôi", + "my-flags": "My Flags", "bans": "Cấm", "new-message-from": "Tin nhắn mới từ %1", "new-messages-from": "%1 tin nhắn mới từ %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1%2 đã viết vào %4", "user-posted-in-public-room-triple": "%1, %2%3 đã viết vào %5", "user-posted-in-public-room-multiple": "%1, %2 và %3 người khác đã viết vào %5", - "upvoted-your-post-in": "%1 đã ủng hộ bài của bạn trong %2.", - "upvoted-your-post-in-dual": "%1%2 đã ủng hộ bài của bạn trong %3.", - "upvoted-your-post-in-triple": "%1, %2%3 đã ủng hộ bài của bạn trong %4.", - "upvoted-your-post-in-multiple": "%1, %2 và %3 người khác đã ủng hộ bài của bạn trong %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "moved-your-post": "%1 đã chuyển bài viết của bạn tới %2", "moved-your-topic": "%1 đã chuyển %2", - "user-flagged-post-in": "%1 gắn cờ 1 bài trong %2", - "user-flagged-post-in-dual": "%1%2 đã gắn cờ một bài viết trong %3", - "user-flagged-post-in-triple": "%1, %2%3 gắn cờ bài đăng trong %4", - "user-flagged-post-in-multiple": "%1, %2 và %3 người khác đã gắn cờ bài của bạn trong %4", + "user-flagged-post-in": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", + "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", + "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 đã gắn cờ một hồ sơ người dùng (%2)", "user-flagged-user-dual": "%1%2 đã gắn cờ một hồ sơ người dùng (%3)", "user-flagged-user-triple": "%1, %2%3 đã gắn cờ một hồ sơ người dùng (%4)", "user-flagged-user-multiple": "%1, %2 và %3 người khác đã gắn cờ một hồ sơ người dùng (%4)", - "user-posted-to": "%1 đã đăng một trả lời cho: %2", - "user-posted-to-dual": "%1%2 đã đăng trả lời cho: %3", - "user-posted-to-triple": "%1, %2%3 đã đăng trả lời đến: %4", - "user-posted-to-multiple": "%1, %2 và %3 người khác đã đăng trả lời đến: %4", - "user-posted-topic": "%1 đã đăng một chủ đề mới: %2", - "user-edited-post": "%1 đã chỉnh sửa một bài đăng trong %2", - "user-posted-topic-with-tag": "%1 đã đăng %2 (đã gắn thẻ %3)", - "user-posted-topic-with-tag-dual": "%1 đã đăng %2 (gắn thẻ %3 và %4)", - "user-posted-topic-with-tag-triple": "%1 đã đăng %2 (gắn thẻ %3, %4, và %5)", - "user-posted-topic-with-tag-multiple": "%1 đã đăng %2 (gắn thẻ %3)", - "user-posted-topic-in-category": "%1 đã đăng chủ đề mới trong %2", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1 bắt đầu theo dõi bạn.", "user-started-following-you-dual": "%1%2 bắt đầu theo dõi bạn.", "user-started-following-you-triple": "%1, %2%3 bắt đầu theo dõi bạn.", @@ -71,11 +71,11 @@ "users-csv-exported": "Đã xuất csv người dùng, nhấp để tải xuống", "post-queue-accepted": "Bài đăng đã xếp hàng của bạn được chấp nhận. Bấm vào đây để xem bài của bạn.", "post-queue-rejected": "Bài đăng đã xếp hàng của bạn đã bị từ chối", - "post-queue-notify": "Bài đăng đã xếp hàng nhận được thông báo:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "Đã Xác Nhận Email", "email-confirmed-message": "Cảm ơn bạn đã xác nhận email. Tài khoản của bạn được kích hoạt đầy đủ.", "email-confirm-error-message": "Đã có lỗi khi xác nhận địa chỉ email. Có lẽ mã không hợp lệ hoặc đã hết hạn.", - "email-confirm-error-message-already-validated": "Địa chỉ email của bạn đã được xác thực.", "email-confirm-sent": "Đã gửi email xác nhận.", "none": "Trống", "notification-only": "Chỉ Thông Báo", @@ -96,8 +96,8 @@ "notificationType-group-request-membership": "Khi ai đó yêu cầu tham gia một nhóm bạn sở hữu", "notificationType-new-register": "Khi ai đó được thêm vào xếp hàng đợi đăng ký", "notificationType-post-queue": "Khi một bài đăng mới xếp hàng đợi", - "notificationType-new-post-flag": "Khi bài đăng bị gắn cờ cảnh báo", - "notificationType-new-user-flag": "Khi người dùng bị gắn cờ cảnh báo", + "notificationType-new-post-flag": "Khi một bài đăng bị gắn cờ", + "notificationType-new-user-flag": "Khi một người dùng bị gắn cờ", "notificationType-new-reward": "Khi bạn kiếm được phần thưởng mới", "activitypub.announce": "%1 đã chia sẻ bài đăng của bạn trong %2 đến người theo dõi của họ.", "activitypub.announce-dual": "%1%2 đã chia sẻ bài đăng của bạn trong %3 đến người theo dõi họ.", diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json index 851486de52..f260251b9b 100644 --- a/public/language/vi/pages.json +++ b/public/language/vi/pages.json @@ -1,10 +1,10 @@ { "home": "Trang chủ", "unread": "Chủ Đề Chưa Đọc", - "popular-day": "Chủ đề nổi bật hôm nay", - "popular-week": "Chủ đề nội bật tuần này", - "popular-month": "Chủ đề nổi bật tháng này", - "popular-alltime": "Chủ đề nổi bật mọi thời đại", + "popular-day": "Chủ đề phổ biến hôm nay", + "popular-week": "Chủ đề phổ biến tuần này", + "popular-month": "Chủ đề phổ biến tháng này", + "popular-alltime": "Chủ đề phổ biến mọi lúc", "recent": "Chủ Đề Gần Đây", "top-day": "Chủ đề được bình chọn nhiều hôm nay", "top-week": "Chủ đề được bình chọn nhiều tuần này", @@ -24,7 +24,7 @@ "users/search": "Tìm Kiếm Người Dùng", "notifications": "Thông báo", "tags": "Thẻ", - "tag": "Các chủ đề được gắn thẻ bên dưới "%1"", + "tag": "Chủ đề được gắn thẻ bên dưới "%1"", "register": "Đăng ký một tài khoản", "registration-complete": "Đăng ký hoàn tất", "login": "Đăng nhập vào tài khoản của bạn", @@ -42,11 +42,11 @@ "account/edit/username": "Chỉnh sửa tên đăng nhập của \"%1\"", "account/edit/email": "Chỉnh sửa email của \"%1\"", "account/info": "Thông Tin Tài Khoản", - "account/following": "Thành viên %1 đang theo dõi", - "account/followers": "Thành viên đang theo dõi %1", - "account/posts": "Bài viết được đăng bởi %1", + "account/following": "Người %1 theo dõi", + "account/followers": "Những người theo dõi %1", + "account/posts": "Bài viết làm bởi %1", "account/latest-posts": "Bài viết mới nhất do %1", - "account/topics": "Chủ đề được tạo bởi %1", + "account/topics": "Chủ đề tạo bởi %1", "account/groups": "Nhóm của %1", "account/watched-categories": "Danh Mục Đã Xem Của %1", "account/watched-tags": "%1's Thẻ Đã Xem", @@ -64,7 +64,7 @@ "account/uploads": "Tải lên bởi %1", "account/sessions": "Phiên Đăng Nhập", "account/shares": "Chủ đề được chia sẻ bởi %1", - "confirm": "Đã xác nhận email", + "confirm": "Đã Xác Nhận Email", "maintenance.text": "%1 hiện đang bảo trì.
Vui lòng quay lại vào lúc khác.", "maintenance.messageIntro": "Ngoài ra, quản trị viên đã để lại thông báo này:", "throttled.text": "%1 hiện không khả dụng do quá tải. Vui lòng quay lại vào lúc khác." diff --git a/public/language/vi/post-queue.json b/public/language/vi/post-queue.json index 0a7e124bcd..84f04594b8 100644 --- a/public/language/vi/post-queue.json +++ b/public/language/vi/post-queue.json @@ -9,7 +9,7 @@ "public-description": "Diễn đàn này được cấu hình tự động xếp hàng các bài đăng từ tài khoản mới, chờ người điều hành phê duyệt.
Nếu bạn đã xếp hàng các bài đăng đợi phê duyệt, bạn sẽ có thể xem chúng ở đây.", "user": "Người dùng", "when": "Khi", - "category": "Chuyên mục", + "category": "Danh mục", "title": "Tiêu đề", "content": "Nội dung", "posted": "Đã đăng", diff --git a/public/language/vi/reset_password.json b/public/language/vi/reset_password.json index 0bad833f41..a8cfb41fc3 100644 --- a/public/language/vi/reset_password.json +++ b/public/language/vi/reset_password.json @@ -1,10 +1,10 @@ { "reset-password": "Đặt Lại Mật Khẩu", "update-password": "Cập Nhật Mật Khẩu", - "password-changed.title": "Mật Khẩu Đã Được Thay Đổi", + "password-changed.title": "Đã Thay Đổi Mật Khẩu", "password-changed.message": "

Đặt lại mật khẩu thành công, hãy đăng nhập lại.", "wrong-reset-code.title": "Mã Đặt Lại Không Đúng", - "wrong-reset-code.message": "Mã thiết lập lại không đúng. Xin hãy thử lại, hoặc yêu cầu một mã thiết lập lại khác.", + "wrong-reset-code.message": "Mã đặt lại nhận được không chính xác. Vui lòng thử lại, hoặc yêu cầu mã đặt lại mới.", "new-password": "Mật Khẩu Mới", "repeat-password": "Xác Nhận Mật Khẩu", "changing-password": "Thay Đổi Mật Khẩu", diff --git a/public/language/vi/search.json b/public/language/vi/search.json index 56361bb4d5..43b468535f 100644 --- a/public/language/vi/search.json +++ b/public/language/vi/search.json @@ -33,12 +33,12 @@ "replies": "Trả lời", "replies-atleast-count": "Trả lời: Ít nhất %1", "replies-atmost-count": "Trả lời: Nhiều nhất là %1", - "at-least": "Tối thiểu", + "at-least": "Ít nhất", "at-most": "Nhiều nhất", "relevance": "Mức độ liên quan", "time": "Thời gian", "post-time": "Thời gian đăng bài", - "votes": "Phiếu bầu", + "votes": "Bình chọn", "newer-than": "Mới hơn", "older-than": "Cũ hơn", "any-date": "Ngày bất kỳ", @@ -67,10 +67,10 @@ "sort": "Sắp xếp", "last-reply-time": "Thời gian trả lời lần cuối", "topic-title": "Tiêu đề chủ đề", - "topic-votes": "Phiếu bầu chủ đề", + "topic-votes": "Bình chọn chủ đề", "number-of-replies": "Số lượt trả lời", "number-of-views": "Số lượt xem", - "topic-start-date": "Ngày bắt đầu chủ đề", + "topic-start-date": "Ngày tạo chủ đề", "username": "Tên đăng nhập", "category": "Danh mục", "descending": "Theo thứ tự giảm dần", diff --git a/public/language/vi/social.json b/public/language/vi/social.json index 2a1194b3fd..ff5bafd340 100644 --- a/public/language/vi/social.json +++ b/public/language/vi/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "Đăng nhập với Facebook", "continue-with-facebook": "Tiếp tục với Facebook", "sign-in-with-linkedin": "Đăng nhập với LinkedIn", - "sign-up-with-linkedin": "Đăng ký với LinkedIn" + "sign-up-with-linkedin": "Đăng ký với LinkedIn", + "sign-in-with-wordpress": "Đăng nhập bằng WordPress", + "sign-up-with-wordpress": "Đăng ký với WordPress" } \ No newline at end of file diff --git a/public/language/vi/themes/harmony.json b/public/language/vi/themes/harmony.json index 42be89ca94..aaf7690ac6 100644 --- a/public/language/vi/themes/harmony.json +++ b/public/language/vi/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Chủ Đề Hài Hòa", "skins": "Trang điểm", + "light": "Light", + "dark": "Dark", "collapse": "Thu gọn", "expand": "Mở rộng", "sidebar-toggle": "Chuyển Đổi Thanh Bên", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 0333c6080e..1e1a969616 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -7,13 +7,13 @@ "topic-is-deleted": "Chủ đề này đã bị xóa!", "profile": "Hồ sơ", "posted-by": "Được đăng bởi %1", - "posted-by-guest": "Đăng bởi khách", + "posted-by-guest": "Đăng bởi Khách", "chat": "Trò Chuyện", "notify-me": "Được thông báo khi có trả lời mới trong chủ đề này", "quote": "Trích dẫn", "reply": "Trả lời", "replies-to-this-post": "%1 trả lời", - "one-reply-to-this-post": "1 Phản hồi", + "one-reply-to-this-post": "1 Trả lời", "last-reply-time": "Trả lời cuối cùng", "reply-options": "Tùy chọn trả lời", "reply-as-topic": "Trả lời như chủ đề", @@ -28,8 +28,8 @@ "move": "Di chuyển", "change-owner": "Đổi Chủ Sở Hữu", "manage-editors": "Quản Lý Biên Tập Viên", - "fork": "Tạo bản sao", - "link": "Đường dẫn", + "fork": "Chia nhánh", + "link": "Liên kết", "share": "Chia sẻ", "tools": "Công cụ", "locked": "Đã Khóa", @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 đã tham khảo chủ đề này trên %3", "user-forked-topic-ago": "%1 đã rẽ nhánh chủ đề này %3", "user-forked-topic-on": "%1 đã rẽ nhánh chủ đề này trên %3", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "Bấm vào đây để trở lại bài đọc cuối cùng trong chủ đề này.", "flag-post": "Gắn cờ bài đăng này", "flag-user": "Gắn cờ người dùng này", @@ -103,6 +105,7 @@ "thread-tools.lock": "Khóa Chủ Đề", "thread-tools.unlock": "Mở Khóa Chủ Đề", "thread-tools.move": "Di Chuyển Chủ Đề", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Di Chuyển Bài Viết", "thread-tools.move-all": "Di chuyển tất cả", "thread-tools.change-owner": "Đổi chủ sở hữu", @@ -132,7 +135,8 @@ "pin-modal-help": "Bạn có thể đặt ngày hết hạn cho các chủ đề được ghim tại đây. Ngoài ra, bạn có thể để trống để giữ chủ đề được ghim cho đến khi chủ đề được bỏ ghim theo cách thủ công.", "load-categories": "Đang Tải Chuyên Mục", "confirm-move": "Di chuyển", - "confirm-fork": "Tạo bảo sao", + "confirm-crosspost": "Cross-post", + "confirm-fork": "Chia nhánh", "bookmark": "Dấu trang", "bookmarks": "Dấu trang", "bookmarks.has-no-bookmarks": "Bạn chưa đánh dấu bất kỳ bài viết nào.", @@ -141,6 +145,7 @@ "loading-more-posts": "Tải Thêm Bài Đăng", "move-topic": "Di Chuyển Chủ Đề", "move-topics": "Di Chuyển Chủ Đề", + "crosspost-topic": "Cross-post Topic", "move-post": "Di chuyển bài đăng", "post-moved": "Đã di chuyển bài đăng!", "fork-topic": "Tạo bản sao chủ đề", @@ -154,7 +159,7 @@ "fork-success": "Đã phân nhánh chủ đề thành công! Nhấp vào đây để đi đến chủ đề đã phân nhánh.", "delete-posts-instruction": "Chọn các bài viết bạn muốn xoá/loại bỏ", "merge-topics-instruction": "Nhấn vào các chủ đề bạn muốn gộp hoặc tìm kiếm chúng", - "merge-topic-list-title": "Danh sách các chủ đề sẽ được gộp", + "merge-topic-list-title": "Danh sách các chủ đề được gộp", "merge-options": "Tùy chọn gộp", "merge-select-main-topic": "Chọn chủ đề chính", "merge-new-title-for-topic": "Tiêu đề mới cho chủ đề", @@ -163,6 +168,9 @@ "move-topic-instruction": "Chọn danh mục nhắm đến và sau đó nhấp vào 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", "manage-editors-instruction": "Quản lý những người dùng có thể chỉnh sửa bài đăng này bên dưới.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "Nhập tiêu đề chủ đề của bạn tại đây...", "composer.handle-placeholder": "Nhập tên/xử lý của bạn ở đây", "composer.hide": "Ẩn", @@ -174,6 +182,7 @@ "composer.replying-to": "Đang trả lời %1", "composer.new-topic": "Chủ đề mới", "composer.editing-in": "Đang sửa bài đăng trong %1", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "đang tải lên...", "composer.thumb-url-label": "Dán URL hình mô tả chủ đề", "composer.thumb-title": "Thêm ảnh mô tả cho chủ đề này", @@ -194,9 +203,9 @@ "most-posts": "Nhiều Bài Đăng Nhất", "most-views": "Xem Nhiều Nhất", "stale.title": "Tạo chủ đề mới thay thế?", - "stale.warning": "Chủ đề bạn đang trả lời đã khá cũ. Bạn có muốn tạo chủ đề mới, và liên kết với chủ đề hiện tại trong bài viết trả lời của bạn?", - "stale.create": "Tạo chủ đề mới", - "stale.reply-anyway": "Trả lời chủ đề này", + "stale.warning": "Chủ đề bạn đang trả lời đã khá cũ. Thay vào đó, bạn có muốn tạo một chủ đề mới và tham khảo phần này trong câu trả lời của bạn không?", + "stale.create": "Tạo một chủ đề mới", + "stale.reply-anyway": "Trả lời chủ đề này bằng mọi cách", "link-back": "Trả lời: [%1](%2)", "diffs.title": "Lịch Sử Sửa Bài", "diffs.description": "Bài này có %1 sửa đổi. Bấm vào một trong các bản sửa đổi bên dưới để xem nội dung bài tại thời điểm đó.", @@ -214,7 +223,7 @@ "last-post": "Bài viết cuối cùng", "go-to-my-next-post": "Đi tới bài kế tiếp của tôi", "no-more-next-post": "Bạn không có bài viết nào khác trong chủ đề này", - "open-composer": "Mỏ composer", + "open-composer": "Mở composer", "post-quick-reply": "Trả lời nhanh", "navigator.index": "Bài đăng %1 trên %2", "navigator.unread": "%1 chưa đọc", @@ -224,5 +233,8 @@ "unread-posts-link": "Liên kết bài đăng chưa đọc", "thumb-image": "Ảnh thumbnail chủ đề", "announcers": "Chia sẻ", - "announcers-x": "Chia sẻ (%1)" + "announcers-x": "Chia sẻ (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/vi/user.json b/public/language/vi/user.json index a27c7b3c34..198aeb678e 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -8,7 +8,7 @@ "deleted": "Đã xoá", "username": "Tên Đăng Nhập", "joindate": "Ngày Tham Gia", - "postcount": "Số bài viết", + "postcount": "Số Bài Đăng", "email": "Thư điện tử", "confirm-email": "Xác Nhận Email", "account-info": "Thông Tin Tài Khoản", @@ -105,6 +105,10 @@ "show-email": "Hiện Email Của Tôi", "show-fullname": "Hiển Thị Tên Đầy Đủ Của Tôi", "restrict-chats": "Chỉ cho phép tin nhắn từ người tôi theo dõi", + "disable-incoming-chats": "Tắt tin nhắn trò chuyện đến ", + "chat-allow-list": "Cho phép tin nhắn từ những người dùng theo dõi", + "chat-deny-list": "Từ chối tin nhắn từ những người dùng theo dõi", + "chat-list-add-user": "Thêm người", "digest-label": "Đăng Ký Nhận Bản Tóm Tắt", "digest-description": "Theo dõi email cập nhật của diễn đàn này (thông báo và chủ đề mới) theo lịch trình đã định", "digest-off": "Tắt", @@ -184,7 +188,7 @@ "info.ban-expired": "Hết hạn cấm", "info.banned-permanently": "Bị cấm vĩnh viễn", "info.banned-reason-label": "Lý do", - "info.banned-no-reason": "Không có lí do.", + "info.banned-no-reason": "Không có lý do nào được đưa ra.", "info.mute-history": "Lịch Sử Im Lặng Gần Đây", "info.no-mute-history": "Người dùng này chưa bao giờ bị im lặng", "info.muted-until": "Bị im lặng đến %1", diff --git a/public/language/vi/users.json b/public/language/vi/users.json index bf1c11b467..b3f9fda3a5 100644 --- a/public/language/vi/users.json +++ b/public/language/vi/users.json @@ -15,7 +15,7 @@ "invite": "Mời", "prompt-email": "Thư điện tử:", "groups-to-join": "Nhóm được tham gia khi lời mời được chấp nhận:", - "invitation-email-sent": "Email mời đã được gửi tới %1", + "invitation-email-sent": "Một email mời đã được gửi đến %1", "user-list": "Danh Sách Người Dùng", "recent-topics": "Chủ Đề Gần Đây", "popular-topics": "Chủ Đề Phổ Biến", diff --git a/public/language/vi/world.json b/public/language/vi/world.json index 77804e07aa..7191c7ac5f 100644 --- a/public/language/vi/world.json +++ b/public/language/vi/world.json @@ -1,7 +1,12 @@ { "name": "Thế giới", - "popular": "Chủ đề phổ biến", - "recent": "Tất cả chủ đề", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "Tất cả", "help": "Trợ giúp", "help.title": "Trang này là gì?", @@ -14,5 +19,7 @@ "onboard.title": "Cửa sổ của bạn đến với liên đoàn...", "onboard.what": "Đây là danh mục được cá nhân hóa của bạn chỉ bao gồm nội dung được tìm thấy bên ngoài diễn đàn này. Việc nội dung nào đó có hiển thị trên trang này hay không tùy thuộc vào việc bạn có theo dõi họ hay không hoặc liệu bài đăng đó có được chia sẻ bởi người mà bạn theo dõi hay không.", "onboard.why": "Có rất nhiều điều diễn ra bên ngoài diễn đàn này và không phải tất cả đều phù hợp với sở thích của bạn. Đó là lý do tại sao theo dõi mọi người là cách tốt nhất để báo hiệu rằng bạn muốn biết thêm thông tin từ ai đó.", - "onboard.how": "Trong thời gian chờ đợi, bạn có thể nhấp vào các nút tắt ở trên cùng để xem diễn đàn này biết thêm những gì và bắt đầu khám phá một số nội dung mới!" + "onboard.how": "Trong thời gian chờ đợi, bạn có thể nhấp vào các nút tắt ở trên cùng để xem diễn đàn này biết thêm những gì và bắt đầu khám phá một số nội dung mới!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/language/zh-CN/admin/advanced/cache.json b/public/language/zh-CN/admin/advanced/cache.json index d8b46d5c6c..91b51eaeb2 100644 --- a/public/language/zh-CN/admin/advanced/cache.json +++ b/public/language/zh-CN/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "缓存", - "post-cache": "帖子缓存", - "group-cache": "用户组缓存", - "local-cache": "本地缓存", - "object-cache": "对象缓存", "percent-full": "%1% 容量", "post-cache-size": "帖子缓存大小", "items-in-cache": "缓存中的条目数量" diff --git a/public/language/zh-CN/admin/dashboard.json b/public/language/zh-CN/admin/dashboard.json index 2214fb1dfe..df503c4369 100644 --- a/public/language/zh-CN/admin/dashboard.json +++ b/public/language/zh-CN/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "注册用户页面浏览量", "graphs.page-views-guest": "游客页面浏览量", "graphs.page-views-bot": "爬虫页面浏览量", + "graphs.page-views-ap": "ActivityPub 页面浏览量", "graphs.unique-visitors": "单一访客", "graphs.registered-users": "已注册用户", "graphs.guest-users": "游客", diff --git a/public/language/zh-CN/admin/development/info.json b/public/language/zh-CN/admin/development/info.json index ac389d848e..c6df060106 100644 --- a/public/language/zh-CN/admin/development/info.json +++ b/public/language/zh-CN/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "在线", "git": "git", - "process-memory": "进程内存", + "process-memory": "rss/heap used", "system-memory": "系统内存", "used-memory-process": "进程使用的内存", "used-memory-os": "已使用系统内存", diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index bc9da615d5..cbeb2689ba 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "管理版块", "add-category": "添加版块", + "add-local-category": "添加本地版块", + "add-remote-category": "添加远程版块", + "remove": "移除", + "rename": "重命名", "jump-to": "跳转…", "settings": "版块设置", "edit-category": "编辑版块", "privileges": "权限", "back-to-categories": "回到版块", + "id": "版块 ID", "name": "版块名", "handle": "版块句柄", "handle.help": "您的版块句柄在其他网络中用作该版块的代表,类似于用户名。版块句柄不得与现有的用户名或用户组相匹配。", "description": "版块描述", - "federatedDescription": "“联邦化”说明", - "federatedDescription.help": "当其他网站/应用程序查询时,该文本将附加到版块描述中。", - "federatedDescription.default": "这是一个包含专题讨论的论坛版块。您可以通过提及该版块开始新的讨论。", + "topic-template": "主题模板", + "topic-template.help": "为本版块下新建的主题定义模板。", "bg-color": "背景颜色", "text-color": "图标颜色", "bg-image-size": "背景图片大小", @@ -103,6 +107,11 @@ "alert.create-success": "版块创建成功!", "alert.none-active": "您没有有效的版块。", "alert.create": "创建一个版块", + "alert.add": "添加一个版块", + "alert.add-help": "可以通过指定其句柄将远程版块添加到版块列表中。

注: — 远程版块可能无法反映所有已发布的主题,除非至少有一名本地用户关注或订阅该版块。", + "alert.rename": "重命名远程版块", + "alert.rename-help": "请为该版块输入新名称。留空则恢复原名。", + "alert.confirm-remove": "您确定要删除此版块吗?您可以随时将其重新添加回来。", "alert.confirm-purge": "

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

警告! 版块将被清除!

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

", "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", diff --git a/public/language/zh-CN/admin/manage/custom-reasons.json b/public/language/zh-CN/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/zh-CN/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ No newline at end of file diff --git a/public/language/zh-CN/admin/manage/privileges.json b/public/language/zh-CN/admin/manage/privileges.json index 5c80c8c64a..c65fe192ea 100644 --- a/public/language/zh-CN/admin/manage/privileges.json +++ b/public/language/zh-CN/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "访问主题", "create-topics": "创建主题", "reply-to-topics": "回复主题", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "定时主题", "tag-topics": "标签主题", "edit-posts": "修改回复", diff --git a/public/language/zh-CN/admin/manage/users.json b/public/language/zh-CN/admin/manage/users.json index f330c37f6c..45f46fee09 100644 --- a/public/language/zh-CN/admin/manage/users.json +++ b/public/language/zh-CN/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "删除用户和内容", "download-csv": "下载CSV", "custom-user-fields": "自定义用户字段", + "custom-reasons": "Custom Reasons", "manage-groups": "管理用户组", "set-reputation": "设置声望", "add-group": "添加至群组", @@ -77,9 +78,11 @@ "temp-ban.length": "时长", "temp-ban.reason": "理由(可选) ", + "temp-ban.select-reason": "选择一个原因", "temp-ban.hours": "小时", "temp-ban.days": "天", "temp-ban.explanation": "输入封禁时长。提示,时长为0视为永久封禁。", + "temp-mute.explanation": "输入禁言时长。请注意,0将被视为永久禁言。", "alerts.confirm-ban": "您真的想要永久封禁该用户吗?", "alerts.confirm-ban-multi": "您确定要永久封禁这些用户吗?", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 748ffbbbbe..4effab4baf 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -18,6 +18,28 @@ "probe-timeout": "查询超时(毫秒)", "probe-timeout-help": "(默认值:2000)如果查询在设定的时间内没有收到回复,将直接把用户发送到链接。如果网站响应速度较慢,您希望给予更多时间,则可将此数字调高。", + "rules": "分类", + "rules-intro": "通过 ActivityPub 发现的内容可根据特定规则(如标签)进行自动分类。", + "rules.modal.title": "它的工作原理", + "rules.modal.instructions": "所有传入的内容都会根据这些分类规则进行检查,符合规则的内容会自动移动到指定的版块目录中。

注: 已分类的内容(即位于远程版块中的内容)将不会受这些规则的影响。", + "rules.add": "添加规则", + "rules.help-hashtag": "包含此不区分大小写的标签的主题将匹配。请勿输入 # 符号。", + "rules.help-user": "由输入用户创建的主题将匹配。请输入用户名或完整ID(例如:bob@example.orghttps://example.org/users/bob.", + "rules.type": "类型", + "rules.value": "值", + "rules.cid": "版块", + + "relays": "中继服务", + "relays.intro": "中继服务能提升您 NodeBB 论坛内容的发现效率。订阅中继服务意味着:中继服务接收的内容将转发至此处,而本论坛发布的内容则由中继服务向外同步传播。", + "relays.warning": "注意:中继服务可能接收大量传入流量,并可能增加存储和处理成本。", + "relays.litepub": "NodeBB 遵循 LitePub 风格的中继标准。您在此处输入的 URL 应以 /actor 结尾。", + "relays.add": "添加新中继服务", + "relays.relay": "中继服务", + "relays.state": "状态", + "relays.state-0": "待处理", + "relays.state-1": "仅接收", + "relays.state-2": "已启用", + "server-filtering": "过滤", "count": "该 NodeBB 目前可检测到 %1 台服务器", "server.filter-help": "指定您希望禁止与 NodeBB 联邦化的服务器。或者,您也可以选择性地 允许 与特定服务器联邦化。两者只能选其一。", diff --git a/public/language/zh-CN/admin/settings/chat.json b/public/language/zh-CN/admin/settings/chat.json index d7c8f595a3..6364b5900e 100644 --- a/public/language/zh-CN/admin/settings/chat.json +++ b/public/language/zh-CN/admin/settings/chat.json @@ -5,13 +5,11 @@ "disable-editing": "禁止编辑/删除聊天消息", "disable-editing-help": "管理员和超级管理员不受此限制", "max-length": "聊天信息的最大长度", - "max-length-remote": "远程聊天信息的最长长度", - "max-length-remote-help": "对于本地用户,该值通常设置为高于聊天消息最大值,因为远程消息往往较长(包含 @ 提及等)。", + "max-length-remote": "远程聊天消息的最大长度", + "max-length-remote-help": "该值通常设置得高于本地用户的聊天消息上限,因为远程消息往往更长(包含@提及等内容)", "max-chat-room-name-length": "聊天室名称最大长度", "max-room-size": "聊天室的最多用户数", "delay": "发言频率(毫秒)", - "notification-delay": "聊天信息的通知延迟", - "notification-delay-help": "服务器可能无法承受即时通讯所带来的资源占用,因此在这段时间内发送的其他信息将被整理,并在每个延迟期通知用户一次。设置为 0 则禁用延迟。", - "restrictions.seconds-edit-after": "聊天信息保持可编辑状态的秒数。", - "restrictions.seconds-delete-after": "聊天信息保持可撤回状态的秒数。" + "restrictions.seconds-edit-after": "聊天消息保持可编辑状态的秒数。", + "restrictions.seconds-delete-after": "聊天消息保持可撤回状态的秒数。" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/email.json b/public/language/zh-CN/admin/settings/email.json index 4ce3b4b4ee..bb3e6b732a 100644 --- a/public/language/zh-CN/admin/settings/email.json +++ b/public/language/zh-CN/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "池式连接可防止 NodeBB 为每封邮件创建新的连接。此选项仅适用于启用SMTP传输的情况下。", "smtp-transport.allow-self-signed": "允许自签名证书", "smtp-transport.allow-self-signed-help": "启用此设置可允许您使用自签名或无效的 TLS 证书。", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "编辑电子邮件模板", "template.select": "选择电子邮件模板", "template.revert": "还原为初始模板", + "test-smtp-settings": "Test SMTP Settings", "testing": "电子邮件测试", + "testing.success": "Test Email Sent.", "testing.select": "选择电子邮件模板", "testing.send": "发送测试电子邮件", - "testing.send-help": "测试电子邮件将被发送到当前已登录的用户的电子邮件地址。", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "电子邮件摘要", "subscriptions.disable": "禁用电子邮件摘要", "subscriptions.hour": "摘要小时", diff --git a/public/language/zh-CN/admin/settings/notifications.json b/public/language/zh-CN/admin/settings/notifications.json index 24e65adaf8..dd036f1271 100644 --- a/public/language/zh-CN/admin/settings/notifications.json +++ b/public/language/zh-CN/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "欢迎通知", "welcome-notification-link": "欢迎通知链接", "welcome-notification-uid": "用户欢迎通知 (UID)", - "post-queue-notification-uid": "发布队列用户(UID)" + "post-queue-notification-uid": "发布队列用户(UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/uploads.json b/public/language/zh-CN/admin/settings/uploads.json index 382195d94a..94f89e171a 100644 --- a/public/language/zh-CN/admin/settings/uploads.json +++ b/public/language/zh-CN/admin/settings/uploads.json @@ -5,14 +5,14 @@ "strip-exif-data": "去除 EXIF 数据", "preserve-orphaned-uploads": "当一个帖子被清除后保留上传的文件", "orphanExpiryDays": "保存未使用文件的天数", - "orphanExpiryDays-help": "超过此天数后,无主的上传文件将从文件系统中删除。
设置为 0 或留空表示禁用。", + "orphanExpiryDays-help": "超过此天数后,未使用的文件将从文件系统中删除。
设置为 0 或留空表示禁用。", "private-extensions": "自定义文件扩展名", "private-uploads-extensions-help": "在此处输入以逗号分隔的文件扩展名列表 (例如 pdf,xls,doc )并将其用于自定义。为空则表示允许所有扩展名。", "resize-image-width-threshold": "如果图像宽度超过指定大小,则对图像进行缩放", - "resize-image-width-threshold-help": "(单位:像素,默认值:2000 px,设为 0 则禁用)", + "resize-image-width-threshold-help": "(单位:像素,默认值:2000 px,设置为 0 则禁用)", "resize-image-width": "缩小图片到指定宽度", "resize-image-width-help": "(像素单位,默认 760 px,设置为0以禁用)", - "resize-image-keep-original": "调整大小后保留原始图片", + "resize-image-keep-original": "缩放后保留原始图片", "resize-image-quality": "调整图像大小时使用的质量", "resize-image-quality-help": "使用较低质量的设置来减小调整过大小的图像的文件大小", "max-file-size": "最大文件尺寸(单位 KiB)", @@ -21,7 +21,13 @@ "reject-image-width-help": "宽于此数值大小的图片将会被拒绝", "reject-image-height": "图片最大高度值(单位:像素)", "reject-image-height-help": "高于此数值大小的图片将会被拒绝", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "允许用户上传主题缩略图", + "show-post-uploads-as-thumbnails": "将帖子上传内容显示为缩略图", "topic-thumb-size": "主题缩略图大小", "allowed-file-extensions": "允许的文件扩展名", "allowed-file-extensions-help": "在此处输入以逗号分隔的文件扩展名列表 (例如 pdf,xls,doc )。 为空则表示允许所有扩展名。", diff --git a/public/language/zh-CN/admin/settings/user.json b/public/language/zh-CN/admin/settings/user.json index 60526c93c2..ff2acbaa24 100644 --- a/public/language/zh-CN/admin/settings/user.json +++ b/public/language/zh-CN/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "显示邮箱", "show-fullname": "显示全名", "restrict-chat": "只允许我关注的用户给我发送聊天消息", + "disable-incoming-chats": "停止接收聊天信息", "outgoing-new-tab": "在新标签打开外部链接", "topic-search": "启用主题内搜索", "update-url-with-post-index": "浏览主题时更新 URL 中的帖子索引值", diff --git a/public/language/zh-CN/admin/settings/web-crawler.json b/public/language/zh-CN/admin/settings/web-crawler.json index aca41db4b6..510522f5d3 100644 --- a/public/language/zh-CN/admin/settings/web-crawler.json +++ b/public/language/zh-CN/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "禁用 RSS 订阅", "disable-sitemap-xml": "禁用 Sitemap.xml", "sitemap-topics": "要在 Stemap 中展示的主题数量", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "清除 Sitemap 缓存", "view-sitemap": "查看 Sitemap" } \ No newline at end of file diff --git a/public/language/zh-CN/aria.json b/public/language/zh-CN/aria.json index 9df98227c1..44960de6dc 100644 --- a/public/language/zh-CN/aria.json +++ b/public/language/zh-CN/aria.json @@ -1,9 +1,10 @@ { - "post-sort-option": "帖子分类选项,%1", - "topic-sort-option": "主题分类选项,%1", - "user-avatar-for": "用户头像%1", - "profile-page-for": "用户 %1 的资料页面", - "user-watched-tags": "用户关注标签", + "post-sort-option": "帖子排序选项,%1", + "topic-sort-option": "主题排序选项,%1", + "user-avatar-for": "%1 的用户头像", + "profile-page-for": "用户 %1 的个人资料页面", + "user-watched-tags": "用户关注的标签", "delete-upload-button": "删除上传按钮", - "group-page-link-for": "群组页面链接%1" + "group-page-link-for": "%1 的群组页面链接", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/zh-CN/category.json b/public/language/zh-CN/category.json index 92aa44a5b4..765adb544f 100644 --- a/public/language/zh-CN/category.json +++ b/public/language/zh-CN/category.json @@ -1,12 +1,13 @@ { "category": "版块", "subcategories": "子版块", - "uncategorized": "未分类的", - "uncategorized.description": "不完全符合任何现有类别的主题", - "handle.description": "您可以通过 %1 标签在开放的社交网络上关注这一版块。", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", + "handle.description": "此版块可通过社交网络公开平台使用用户名 %1 进行关注", "new-topic-button": "发表主题", "guest-login-post": "登录以发布", - "no-topics": "此版块还没有任何内容。
赶紧来发帖吧!", + "no-topics": "此版块下尚无主题。
何不尝试发布一个?", + "no-followers": "本网站上没有用户正在关注或订阅此版块。请关注或订阅此版块,以便开始接收更新。", "browsing": "正在浏览", "no-replies": "尚无回复", "no-new-posts": "没有新主题", @@ -16,14 +17,14 @@ "tracking": "跟踪", "not-watching": "未关注", "ignoring": "已忽略", - "watching.description": "有新主题时通知我。
在未读/最近主题中显示。", + "watching.description": "有新主题时通知我。
在未读/最近主题中显示", "tracking.description": "显示未读和最近的主题", "not-watching.description": "不显示未读主题,显示最近主题", "ignoring.description": "不在未读和最近主题显示", - "watching.message": "您关注了此版块和全部子版块的动态。", - "tracking.message": "您关注了此版块和全部子版块的动态。", - "notwatching.message": "您未关注了此版块和全部子版块的动态。", - "ignoring.message": "您未关注此版块和全部子版块的动态。", + "watching.message": "您正在关注此版块及其所有子版块的更新", + "tracking.message": "您正在订阅此版块及其所有子版块的更新", + "notwatching.message": "您未订阅此版块及其所有子版块的更新", + "ignoring.message": "您正在忽略此版块及其所有子版块的更新", "watched-categories": "已关注的版块", "x-more-categories": "还有 %1 个版块" } \ No newline at end of file diff --git a/public/language/zh-CN/email.json b/public/language/zh-CN/email.json index da067df94d..137ea80a8e 100644 --- a/public/language/zh-CN/email.json +++ b/public/language/zh-CN/email.json @@ -6,26 +6,26 @@ "greeting-no-name": "您好", "greeting-with-name": "%1,您好", "email.verify-your-email.subject": "请验证你的电子邮箱", - "email.verify.text1": "您已要求我们更改或确认您的邮件地址", - "email.verify.text2": "为了安全起见,我们只会在通过邮件验证邮件地址所有权以后才会更改存档的邮件地址。假如您没有提出过此请求,您不用进行任何操作。", - "email.verify.text3": "一旦您验证了此电子邮箱地址,我们将会把您当前的电子邮箱地址替换为此电子邮箱地址(%1)。", + "email.verify.text1": "您已要求我们更改或确认您的电子邮件地址", + "email.verify.text2": "出于安全考虑,我们仅在通过邮件确认邮箱所有权后,才会修改或确认系统中登记的邮箱地址。若您未主动提出此请求,则无需采取任何行动。", + "email.verify.text3": "一旦您确认此电子邮件地址,我们将用此地址(%1)替换您当前的电子邮件地址。", "welcome.text1": "感谢您注册 %1 帐户!", "welcome.text2": "在您验证您绑定的邮箱地址之后,您的账户才能激活。", - "welcome.text3": "管理员批准了您的注册申请,您现在可以使用您的用户名和密码进行登录了。", + "welcome.text3": "管理员已批准您的注册申请。您现在可以使用用户名/密码登录。", "welcome.cta": "点击这里确认您的电子邮箱地址", "invitation.text1": "%1 邀请您加入 %2", "invitation.text2": "您的邀请将在 %1 天后过期。", "invitation.cta": "点击这里新建账号", - "reset.text1": "很可能是您忘记了密码,我们收到了重置您帐户密码的请求。 如果不是这个情况,请忽略此邮件。", + "reset.text1": "我们收到重置您密码的请求,可能是您忘记了密码。若非如此,请忽略此邮件。", "reset.text2": "如需继续重置密码,请点击下面的链接:", "reset.cta": "点击这里重置您的密码", "reset.notify.subject": "更改密码成功", "reset.notify.text1": "您在 %1 上的密码已经成功修改。", "reset.notify.text2": "如果您没有授权此操作,请立即联系管理员。", - "digest.unread-rooms": "未读房间", + "digest.unread-rooms": "未读聊天室", "digest.room-name-unreadcount": "%1 (%2 未读)", "digest.latest-topics": "来自 %1 的最新主题", - "digest.top-topics": "来自 %1 的关注主题", + "digest.top-topics": "来自 %1 的热门主题", "digest.popular-topics": "来自 %1 的热门主题", "digest.cta": "点击这里访问 %1", "digest.unsub.info": "根据您的订阅设置,为您发送此摘要。", @@ -37,22 +37,22 @@ "digest.title.week": "您的每周摘要", "digest.title.month": "您的每月摘要", "notif.chat.new-message-from-user": "来自 \"%1\" 的新消息", - "notif.chat.new-message-from-user-in-room": "来自 %1 在房间 %2 中的新消息", + "notif.chat.new-message-from-user-in-room": "来自 %1 的新消息,在聊天室 %2 ", "notif.chat.cta": "点击这里继续会话", "notif.chat.unsub.info": "根据您的订阅设置,为您发送此聊天提醒。", "notif.post.unsub.info": "根据您的订阅设置,为您发送此回帖提醒。", - "notif.post.unsub.one-click": "或者通过点击来取消订阅邮件", + "notif.post.unsub.one-click": "或者,您可以点击此处取消订阅此类邮件", "notif.cta": "点击这里前往论坛", "notif.cta-new-reply": "查看帖子", "notif.cta-new-chat": "查看聊天", "notif.test.short": "测试通知", - "notif.test.long": "这是一个测试的通知邮件。", + "notif.test.long": "这是对通知邮件功能的测试。快来帮忙!", "test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。", "unsub.cta": "点击这里修改这些设置", "unsubscribe": "退订", "unsub.success": "您将不再收到来自%1邮寄名单的邮件", "unsub.failure.title": "无法取消订阅", - "unsub.failure.message": "很不幸,我们不能将您从邮件列表里取消订阅,因为这个链接有问题。不过,您可以到您的用户设置里修改邮件偏好。

(错误:%1)", + "unsub.failure.message": "很遗憾,由于链接出现问题,我们未能成功为您取消邮件订阅。不过您可通过 用户设置 页面调整邮件偏好选项。

(错误:%1)", "banned.subject": "您在 %1 的账户已被封禁", "banned.text1": "您在 %2 的用户 %1 已被封禁。", "banned.text2": "本次封禁将在 %1 结束。", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index 5a43cd9dd4..fc31042886 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -3,6 +3,7 @@ "invalid-json": "无效 JSON", "wrong-parameter-type": "属性 `%1` 要求是类型 %3 的值,却收到了 %2", "required-parameters-missing": "此 API 调用必需参数缺少了:%1", + "reserved-ip-address": "不允许向保留 IP 地址发送网络请求。", "not-logged-in": "您还没有登录。", "account-locked": "您的帐号已被临时锁定", "search-requires-login": "搜索功能仅限会员使用 - 请先登录或者注册。", @@ -38,7 +39,7 @@ "email-not-confirmed": "您需要验证您的邮箱后才能在版块或主题中发布帖子,请点击此处以发送验证邮件。", "email-not-confirmed-chat": "您的电子邮箱尚未确认,无法聊天,请点击这里确认您的电子邮箱。", "email-not-confirmed-email-sent": "您的邮箱尚未验证,请检查您的收件箱以找到验证邮件。在您的邮箱被验证前,您可能不能在某些版块发布帖子或进行聊天。", - "no-email-to-confirm": "您的账号未设置电子邮箱。对于找回账号、聊天以及在版块中发布帖子这几项操作,电子邮箱是必需的。请点击此处输入电子邮箱。", + "no-email-to-confirm": "您的账号尚未设置电子邮箱。电子邮箱是账号找回的必要条件,在某些分类中进行聊天和发帖时也可能需要。请点击此处输入电子邮箱。", "user-doesnt-have-email": "用户“%1”还没有设置邮箱。", "email-confirm-failed": "我们无法确认您的电子邮箱,请重试", "confirm-email-already-sent": "确认邮件已发出,如需重新发送请等待 %1 分钟后再试。", @@ -67,8 +68,8 @@ "no-chat-room": "聊天室不存在", "no-privileges": "您没有权限执行此操作。", "category-disabled": "版块已禁用", - "post-deleted": "Post deleted", - "topic-locked": "Topic locked", + "post-deleted": "帖子已删除", + "topic-locked": "主题已锁定", "post-edit-duration-expired": "您只能在发布后 %1 秒内编辑帖子", "post-edit-duration-expired-minutes": "您只能在发表后 %1 分钟内修改内容", "post-edit-duration-expired-minutes-seconds": "您只能在发表后 %1 分 %2 秒内修改内容", @@ -104,7 +105,7 @@ "still-uploading": "请等待上传完成", "file-too-big": "上传文件的大小限制为 %1 KB - 请缩减文件大小", "guest-upload-disabled": "未登录用户不允许上传", - "cors-error": "由于CORS配置错误,无法上传图片。", + "cors-error": "由于CORS配置错误,无法上传图片", "upload-ratelimit-reached": "您在短时间内上传了过多的文件,请稍后再试", "upload-error-fallback": "无法上传图片 — %1", "scheduling-to-past": "请选择一个未来的日期。", @@ -119,7 +120,7 @@ "cant-mute-other-admins": "您不能禁言其他管理员!", "user-muted-for-hours": "您已被禁言,您在 %1 小时后才能发布内容", "user-muted-for-minutes": "您已被禁言,您在 %1 分钟后才能发布内容", - "cant-make-banned-users-admin": "您不能让被禁止的用户成为管理员。", + "cant-make-banned-users-admin": "您无法将被封禁的用户设为管理员。", "cant-remove-last-admin": "您是唯一的管理员。在删除您的管理员权限前,请添加另一个管理员。", "account-deletion-disabled": "账号删除功能已禁用", "cant-delete-admin": "在删除此账号之前,请先移除其管理权限。", @@ -146,6 +147,7 @@ "post-already-restored": "此帖已经恢复", "topic-already-deleted": "此主题已被删除", "topic-already-restored": "此主题已恢复", + "topic-already-crossposted": "这个主题已经在那里交叉发布了。", "cant-purge-main-post": "无法清除主贴,请直接删除主题", "topic-thumbnails-are-disabled": "主题缩略图已禁用", "invalid-file": "无效文件", @@ -154,6 +156,8 @@ "about-me-too-long": "抱歉,您的关于我不能超过 %1 个字符。", "cant-chat-with-yourself": "您不能和自己聊天!", "chat-restricted": "此用户限制了他的聊天消息。必须他先关注您,您才能和他聊天。", + "chat-allow-list-user-already-added": "该用户已在您的允许列表中", + "chat-deny-list-user-already-added": "该用户已在您的拒绝列表中", "chat-user-blocked": "您已被该用户屏蔽。", "chat-disabled": "聊天系统已关闭", "too-many-messages": "您发送了太多消息,请稍等片刻。", @@ -219,12 +223,13 @@ "no-groups-selected": "没有用户组被选中", "invalid-home-page-route": "无效的首页路径", "invalid-session": "无效的会话", - "invalid-session-text": "您的登录会话似乎不再处于活动状态。请刷新此页面。", + "invalid-session-text": "您的登录会话似乎已失效。请刷新此页面。", "session-mismatch": "会话不匹配", "session-mismatch-text": "您的登录会话似乎与服务器不再匹配。请刷新此页面。", "no-topics-selected": "没有主题被选中!", "cant-move-to-same-topic": "无法将帖子移动到相同的主题中!", "cant-move-topic-to-same-category": "无法将主题移动到相同的版块!", + "cant-move-topic-to-from-remote-categories": "您无法将主题移入或移出远程版块;请考虑采用交叉发布的方式。", "cannot-block-self": "您不能把自己屏蔽!", "cannot-block-privileged": "您不能屏蔽管理员或者全局版主", "cannot-block-guest": "游客无法屏蔽其他用户", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "目前无法连接到服务器。请点击这里重试,或稍后再试", "invalid-plugin-id": "无效插件ID", "plugin-not-whitelisted": "无法安装插件 – 只有被NodeBB包管理器列入白名单的插件才能通过ACP安装。", + "cannot-toggle-system-plugin": "您不能切换系统插件的状态", "plugin-installation-via-acp-disabled": "ACP 安装插件已被禁用", "plugins-set-in-configuration": "您不能修改插件状态因为它们在运行时中被定义(config.json,环境变量或终端选项),请转而修改配置。", "theme-not-set-in-configuration": "在配置中定义活跃的插件时,需要先将新主题加入活跃插件的列表,才能在管理员控制面板中修改主题", @@ -243,9 +249,10 @@ "cant-set-self-as-parent": "无法将自身设置为父版块", "api.master-token-no-uid": "收到一个在请求体中没有对应 `_uid` 的主令牌", "api.400": "您传入的请求某些地方出错了。", - "api.401": "找不到有效的登录会话。请登录后再试。", + "api.401": "未找到有效的登录会话。请登录后重试。", "api.403": "您没有权限使用此调用", "api.404": "无效 API 调用", + "api.413": "The request payload is too large", "api.426": "Write API 的请求需要 HTTPS,请用 HTTPS 重新发送您的请求", "api.429": "您在短时间内发出了过多的请求,请稍后再试", "api.500": "在试图为您的请求提供服务时出现了一个意外的错误。", diff --git a/public/language/zh-CN/global.json b/public/language/zh-CN/global.json index 09545fde3e..af3507b43e 100644 --- a/public/language/zh-CN/global.json +++ b/public/language/zh-CN/global.json @@ -3,14 +3,14 @@ "search": "搜索", "buttons.close": "关闭", "403.title": "禁止访问", - "403.message": "您似乎碰到了一个您没有访问权限的页面。", - "403.login": "请您尝试登录后再试", + "403.message": "您似乎意外访问了一个您无权访问的页面。", + "403.login": "或许您应该先尝试登录?", "404.title": "未找到", - "404.message": "你似乎偶然发现了一个不存在的页面。
回到主页
", + "404.message": "您似乎偶然访问了一个不存在的页面。
请返回主页
", "500.title": "内部错误", "500.message": "哎呀!看来是哪里出错了!", "400.title": "错误的请求", - "400.message": "看起来这个链接是畸形的,请仔细检查并重新尝试。
回到主页
", + "400.message": "该链接格式似乎有误,请重新检查后再次尝试。
返回主页
", "register": "注册", "login": "登录", "please-log-in": "请登录", @@ -46,7 +46,7 @@ "header.notifications": "通知", "header.search": "搜索", "header.profile": "设置", - "header.account": "账户", + "header.account": "账号", "header.navigation": "导航", "header.manage": "管理", "header.drafts": "草稿", @@ -60,7 +60,7 @@ "alert.warning": "警告", "alert.info": "信息", "alert.banned": "已封禁", - "alert.banned.message": "您已被禁止,您当前的访问受到限制。", + "alert.banned.message": "您已被封禁,您当前的访问受到限制。", "alert.unbanned": "已解封", "alert.unbanned.message": "你的封禁已被解除。", "alert.unfollow": "您已取消关注 %1!", @@ -68,8 +68,9 @@ "users": "用户", "topics": "主题", "posts": "帖子", - "x-posts": "%1 个帖子", - "x-topics": "%1 个主题", + "crossposts": "Cross-posts", + "x-posts": "%1 个帖子", + "x-topics": "%1 个主题", "x-reputation": "%1声望", "best": "最佳", "controversial": "有争议的", @@ -82,6 +83,7 @@ "downvoted": "踩", "views": "浏览", "posters": "发布者", + "watching": "关注中", "reputation": "声望", "lastpost": "上一个帖子", "firstpost": "第一个帖子", diff --git a/public/language/zh-CN/groups.json b/public/language/zh-CN/groups.json index 74b4fbb097..696596d8cf 100644 --- a/public/language/zh-CN/groups.json +++ b/public/language/zh-CN/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "所有群组", "groups": "群组", "members": "成员", + "x-members": "%1 member(s)", "view-group": "查看群组", "owner": "群组所有者", "new-group": "创建群组", @@ -23,7 +25,7 @@ "details.members": "成员列表", "details.pending": "待加入成员", "details.invited": "已邀请成员", - "details.has-no-posts": "此用户组的成员尚未发表任何帖子。", + "details.has-no-posts": "此群组的成员尚未发表任何帖子。", "details.latest-posts": "最新帖子", "details.private": "私有", "details.disableJoinRequests": "禁用申请加入群组", diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index 184fc4dec0..8ef99fef8f 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -1,15 +1,15 @@ { - "chat.room-id": "房间 %1", - "chat.chatting-with": "与聊天", + "chat.room-id": "聊天室 %1", + "chat.chatting-with": "与...聊天", "chat.placeholder": "在此处输入聊天信息,拖放图片", "chat.placeholder.mobile": "输入聊天信息", "chat.placeholder.message-room": "消息 #%1", - "chat.scroll-up-alert": "转到最近的信息", + "chat.scroll-up-alert": "转到最近的消息", "chat.usernames-and-x-others": "%1 和 %2 其他人", - "chat.chat-with-usernames": "与聊天", + "chat.chat-with-usernames": "与 %1 聊天", "chat.chat-with-usernames-and-x-others": "与%1 & %2 和其他人聊天", "chat.send": "发送", - "chat.no-active": "暂无聊天", + "chat.no-active": "您当前没有活跃的聊天。", "chat.user-typing-1": "%1 正在输入...", "chat.user-typing-2": "%1%2 正在输入...", "chat.user-typing-3": "%1%2%3 正在输入...", @@ -48,6 +48,7 @@ "chat.add-user": "添加用户", "chat.notification-settings": "通知设置", "chat.default-notification-setting": "默认通知设置", + "chat.join-leave-messages": "加入/退出 消息", "chat.notification-setting-room-default": "默认房间", "chat.notification-setting-none": "无通知", "chat.notification-setting-at-mention-only": "仅@提及", @@ -56,35 +57,35 @@ "chat.add-user-help": "在这里查找更多用户。被选中的用户会被添加到聊天中。新用户不能他们被加入对话前的聊天消息。只有聊天室所有者()可以从聊天室中移除用户。", "chat.confirm-chat-with-dnd-user": "该用户已将其状态设置为 DnD(请勿打扰)。 您仍希望与其聊天吗?", "chat.room-name-optional": "房间名称(可选)", - "chat.rename-room": "重命名房间", - "chat.rename-placeholder": "在这里输入房间名字", - "chat.rename-help": "这里设置的房间名字能够被房间内所有人都看到。", + "chat.rename-room": "重命名聊天室", + "chat.rename-placeholder": "在这里输入聊天室名字", + "chat.rename-help": "这里设置的聊天室名字能够被聊天室内所有人都看到。", "chat.leave": "离开", "chat.leave-room": "离开房间", "chat.leave-prompt": "您确定您要离开聊天室?", "chat.leave-help": "离开此聊天会切断您和此聊天以后的联系。如果您未来重新加入了,您将不能看到您重新加入之前的聊天记录。", "chat.delete": "删除", "chat.delete-room": "删除房间", - "chat.delete-prompt": "您确定要删除此聊天室?", - "chat.in-room": "在此房间", + "chat.delete-prompt": "您确定您要删除此聊天室?", + "chat.in-room": "在此聊天室", "chat.kick": "踢出", "chat.show-ip": "显示 IP", "chat.copy-text": "复制文本", "chat.copy-link": "复制链接", - "chat.owner": "房间所有者", + "chat.owner": "聊天室所有者", "chat.grant-rescind-ownership": "给予/撤销所有权", - "chat.system.user-join": "%1 加入了房间", - "chat.system.user-leave": "%1 离开了房间", - "chat.system.room-rename": "%2 已将房间重命名为 \"%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": "隐藏预览", "composer.help": "帮助", "composer.user-said-in": "%1 在 %2 中说:", - "composer.user-said": "%1 说:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "确定想要取消此帖?", "composer.submit-and-lock": "提交并锁定", - "composer.toggle-dropdown": "标为 Dropdown", + "composer.toggle-dropdown": "切换下拉菜单", "composer.uploading": "正在上传 %1", "composer.formatting.bold": "加粗", "composer.formatting.italic": "倾斜", @@ -105,7 +106,7 @@ "composer.zen-mode": "无干扰模式", "composer.select-category": "选择一个版块", "composer.textarea.placeholder": "在此处输入您的帖子内容,拖放图像", - "composer.post-queue-alert": "Hello👋!
This forum uses a post queue system, since you are a new user your post will be hidden until it is approved by our moderation team.", + "composer.post-queue-alert": "你好👋!
本论坛采用发帖队列系统,由于你是新用户,您的帖子在审核团队批准前将处于隐藏状态。", "composer.schedule-for": "定时主题到", "composer.schedule-date": "日期", "composer.schedule-time": "时间", @@ -113,8 +114,8 @@ "composer.change-schedule-date": "更改日期", "composer.set-schedule-date": "设置日期", "composer.discard-all-drafts": "丢弃所有的草稿", - "composer.no-drafts": "你没有草稿", - "composer.discard-draft-confirm": "你想丢弃这个草案吗?", + "composer.no-drafts": "您没有草稿", + "composer.discard-draft-confirm": "您想丢弃这个草稿吗?", "composer.remote-pid-editing": "编辑远程帖子", "composer.remote-pid-content-immutable": "远程帖子的内容不可编辑。不过,您可以更改主题标题和标签。", "bootbox.ok": "确认", @@ -127,7 +128,7 @@ "cover.saved": "封面照片和位置已保存", "thumbs.modal.title": "管理主题缩略图", "thumbs.modal.no-thumbs": "没有找到缩略图。", - "thumbs.modal.resize-note": "注意:此论坛被配置为缩放主题缩略图到最大值为 %1", + "thumbs.modal.resize-note": "注意:此论坛被配置为缩放主题缩略图到最大值为 %1px", "thumbs.modal.add": "添加缩略图", "thumbs.modal.remove": "移除缩略图", "thumbs.modal.confirm-remove": "您确定您要移除此缩略图吗?" diff --git a/public/language/zh-CN/notifications.json b/public/language/zh-CN/notifications.json index fbc030a867..b361e71e34 100644 --- a/public/language/zh-CN/notifications.json +++ b/public/language/zh-CN/notifications.json @@ -22,7 +22,7 @@ "upvote": "顶", "awards": "奖励", "new-flags": "新举报", - "my-flags": "指派举报给我", + "my-flags": "我的举报", "bans": "封禁", "new-message-from": "来自 %1 的新消息", "new-messages-from": "来自 %2 的 %1 条新消息", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1%2%4发表", "user-posted-in-public-room-triple": "%1, %2%3%5发表", "user-posted-in-public-room-multiple": "%1, %2 和其余 %3 人在 %5发表", - "upvoted-your-post-in": "%1%2 点赞了您的帖子。", - "upvoted-your-post-in-dual": "%1%2%3 赞了您的帖子。", - "upvoted-your-post-in-triple": "%1, %2%3 赞同了您在 %4的帖子。", - "upvoted-your-post-in-multiple": "%1, %2和其余 %3 位 赞同了您在%4的帖子", + "upvoted-your-post-in": "%1 赞了帖子 %2", + "upvoted-your-post-in-dual": "%1%2 赞了帖子 %3", + "upvoted-your-post-in-triple": "%1, %2%3 赞了帖子 %4", + "upvoted-your-post-in-multiple": "%1, %2 和其他 %3 人赞了帖子 %4。", "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-triple": "%1, %2%3%4中标记了一个帖子", - "user-flagged-post-in-multiple": "%1, %2 和其余 %3 人在 %4中标记了一个帖子", + "user-flagged-post-in": "%1 举报了帖子 %2", + "user-flagged-post-in-dual": "%1%2 举报了帖子 %3", + "user-flagged-post-in-triple": "%1, %2%3 举报了帖子 %4", + "user-flagged-post-in-multiple": "%1, %2 和其他 %3 人举报了帖子 %4", "user-flagged-user": "%1 举报了 (%2) 的用户资料", "user-flagged-user-dual": "%1%2 举报了 (%3) 的用户资料", "user-flagged-user-triple": "%1, %2%3 标记了一个用户资料 (%4)", "user-flagged-user-multiple": "%1, %2 和其余 %3 人 标记了一个用户资料 (%4)", - "user-posted-to": "%1 回复了:%2", - "user-posted-to-dual": "%1%2 回复了: %3", - "user-posted-to-triple": "%1, %2%3%4发表回复", - "user-posted-to-multiple": "%1, %2 和其余 %3 人 对%4发表回复", - "user-posted-topic": "%1 发表了新主题:%2", - "user-edited-post": "%1%2 编辑了一个帖子", - "user-posted-topic-with-tag": "%1发表了%2(标签 %3)", - "user-posted-topic-with-tag-dual": "%1 发表了 %2 (标签:%3 和 %4)", - "user-posted-topic-with-tag-triple": "%1 发表了 %2 (标签: %3, %4, 和 %5)", - "user-posted-topic-with-tag-multiple": "%1发表了 %2 (标签: %3)", - "user-posted-topic-in-category": "%1 发表了新主题:%2", + "user-posted-to": "%1%2 进行了回复", + "user-posted-to-dual": "%1%2%3 进行了回复", + "user-posted-to-triple": "%1, %2%3%4 进行了回复", + "user-posted-to-multiple": "%1, %2 和其他 %3 人回复于 %4", + "user-posted-topic": "%1 发布于 %2", + "user-edited-post": "%1 编辑了 %2 中的一个帖子", + "user-posted-topic-with-tag": "%1 发布于 %2 (标签为 %3)", + "user-posted-topic-with-tag-dual": "%1 发布于 %2 (标签为 %3 和 %4)", + "user-posted-topic-with-tag-triple": "%1 发布于 %2 (标签为 %3, %4, 和 %5)", + "user-posted-topic-with-tag-multiple": "%1 发布于 %2 (标签为 %3)", + "user-posted-topic-in-category": "%1%2 发布了 %3", "user-started-following-you": "%1关注了您。", "user-started-following-you-dual": "%1%2 关注了您。", "user-started-following-you-triple": "%1, %2%3 关注了您", @@ -71,11 +71,11 @@ "users-csv-exported": "用户列表 CSV 已导出,点击以下载", "post-queue-accepted": "您先前提交的帖子已通过查验,点击这里查看您的帖子。", "post-queue-rejected": "您先前提交的帖子已被拒绝", - "post-queue-notify": "您先前提交的帖子收到了通知:“%1”", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "队列中的帖子收到通知:\"%1\"", "email-confirmed": "电子邮箱已确认", "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已完全激活。", "email-confirm-error-message": "验证的您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。", - "email-confirm-error-message-already-validated": "您的电子邮件地址已通过验证。", "email-confirm-sent": "确认邮件已发送。", "none": "无", "notification-only": "用通知提醒我", diff --git a/public/language/zh-CN/post-queue.json b/public/language/zh-CN/post-queue.json index a1a5d47414..4beb41d8ff 100644 --- a/public/language/zh-CN/post-queue.json +++ b/public/language/zh-CN/post-queue.json @@ -33,7 +33,7 @@ "reject-all-confirm": "您想要拒绝全部帖子吗?", "reject-selected": "拒绝选中项", "reject-selected-confirm": "您确定要拒绝%1个选择的帖子吗?", - "remove-all": "移动全部", + "remove-all": "移除全部", "remove-all-confirm": "你想删除所有的帖子吗?", "remove-selected": "移除所选内容", "remove-selected-confirm": "你想删除%1的选定帖子吗?", diff --git a/public/language/zh-CN/register.json b/public/language/zh-CN/register.json index 9d03e65295..7edf5c6077 100644 --- a/public/language/zh-CN/register.json +++ b/public/language/zh-CN/register.json @@ -20,7 +20,7 @@ "terms-of-use-error": "您必须同意使用条款", "registration-added-to-queue": "您的注册正在等待批准。一旦通过,管理员会发送邮件通知您。", "registration-queue-average-time": "我们通常的注册批准时间为 %1 小时 %2 分钟。", - "registration-queue-auto-approve-time": "您在此论坛的帐号将会在最迟 %1  小时后被完全激活。", + "registration-queue-auto-approve-time": "您在此论坛的帐号将会在最迟 %1 小时后被完全激活。", "interstitial.intro": "我们需要一些额外信息以更新您的账号。", "interstitial.intro-new": "我们需要一些额外信息以创建您的账号。", "interstitial.errors-found": "请检查输入的信息:", diff --git a/public/language/zh-CN/search.json b/public/language/zh-CN/search.json index 8cd7bc355b..ebe9d2db6a 100644 --- a/public/language/zh-CN/search.json +++ b/public/language/zh-CN/search.json @@ -25,7 +25,7 @@ "all": "所有", "any": "任何", "posted-by": "发表", - "posted-by-usernames": "被发布:%1", + "posted-by-usernames": "发布者:%1", "type-a-username": "输入用户名", "search-child-categories": "搜索子版块", "has-tags": "有标签", @@ -49,20 +49,20 @@ "three-months": "三个月", "six-months": "六个月", "one-year": "一年", - "time-newer-than-86400": "时间:比昨天更早", - "time-older-than-86400": "时间:比昨天更晚", + "time-newer-than-86400": "时间:比昨天更新", + "time-older-than-86400": "时间:比昨天更久远", "time-newer-than-604800": "时间:一周以内", - "time-older-than-604800": "时间:一周前", + "time-older-than-604800": "时间:超过一周", "time-newer-than-1209600": "时间:两周以内", - "time-older-than-1209600": "时间:两周前", + "time-older-than-1209600": "时间:超过两周", "time-newer-than-2592000": "时间:一个月以内", - "time-older-than-2592000": "时间:一个月前", + "time-older-than-2592000": "时间:超过一个月", "time-newer-than-7776000": "时间:三个月以内", - "time-older-than-7776000": "时间:三个月前", + "time-older-than-7776000": "时间:超过三个月", "time-newer-than-15552000": "时间:六个月以内", - "time-older-than-15552000": "时间:六个月前", + "time-older-than-15552000": "时间:超过六个月", "time-newer-than-31104000": "时间:一年以内", - "time-older-than-31104000": "时间:一年前", + "time-older-than-31104000": "时间:超过一年", "sort-by": "排序", "sort": "排序", "last-reply-time": "最后回复时间", diff --git a/public/language/zh-CN/social.json b/public/language/zh-CN/social.json index ff9388001d..2800dfa892 100644 --- a/public/language/zh-CN/social.json +++ b/public/language/zh-CN/social.json @@ -7,6 +7,8 @@ "sign-up-with-google": "通过 Google 注册", "log-in-with-facebook": "通过 Facebook 登录", "continue-with-facebook": "继续使用 Facebook 登录", - "sign-in-with-linkedin": "通过LinkedIn登录", - "sign-up-with-linkedin": "通过LinkedIn注册" + "sign-in-with-linkedin": "通过 LinkedIn 登录", + "sign-up-with-linkedin": "通过 LinkedIn 注册", + "sign-in-with-wordpress": "通过 WordPress 登录", + "sign-up-with-wordpress": "通过 WordPress 注册" } \ No newline at end of file diff --git a/public/language/zh-CN/themes/harmony.json b/public/language/zh-CN/themes/harmony.json index 03c4e9b38c..726fb78fb3 100644 --- a/public/language/zh-CN/themes/harmony.json +++ b/public/language/zh-CN/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony 主题", "skins": "皮肤", + "light": "Light", + "dark": "Dark", "collapse": "折叠", "expand": "展开", "sidebar-toggle": "侧栏滚动", diff --git a/public/language/zh-CN/themes/persona.json b/public/language/zh-CN/themes/persona.json index 18a9f2080f..fe468bb4a6 100644 --- a/public/language/zh-CN/themes/persona.json +++ b/public/language/zh-CN/themes/persona.json @@ -1,6 +1,6 @@ { "settings.title": "主题设置", - "settings.intro": "你可以在这里定制你的主题设置。设置是以每个设备为基础存储的,所以你能够在不同的设备上有不同的设置(手机、平板电脑、桌面等)。", + "settings.intro": "您可以在这里自定义主题设置。设置将按设备单独存储,因此您可以在不同设备(手机、平板、台式机等)上使用不同的设置。", "settings.mobile-menu-side": "移动端导航菜单切换到另一侧", "settings.autoHidingNavbar": "滚动时自动隐藏导航条", "settings.autoHidingNavbar-xs": "非常小的屏幕(如纵向模式的手机)。", diff --git a/public/language/zh-CN/topic.json b/public/language/zh-CN/topic.json index a9830d1883..d307f3f906 100644 --- a/public/language/zh-CN/topic.json +++ b/public/language/zh-CN/topic.json @@ -66,16 +66,18 @@ "user-queued-post-ago": "%1 篇 已排队 待审批的帖子 %3", "user-queued-post-on": "在 %3 中 已排队 %1 篇待审批的帖子", "user-referenced-topic-ago": "%1 被引用 于这个主题 %3", - "user-referenced-topic-on": "%1 在 %3 中 引用了 这个主题", + "user-referenced-topic-on": "%1 在 %3 引用了 此主题", "user-forked-topic-ago": "%1 分支于 这个主题 %3", - "user-forked-topic-on": "%1 这个主题的分支在 %3", + "user-forked-topic-on": "%1 在 %3 上 分支了 这个主题", + "user-crossposted-topic-ago": "%1 将此主题交叉发布至 %2 %3", + "user-crossposted-topic-on": "%1 将此主题交叉发布至 %2 的 %3", "bookmark-instructions": "点击阅读本主题帖中的最新回复", "flag-post": "举报这个帖子", "flag-user": "举报此用户", "already-flagged": "已举报", "view-flag-report": "查看举报报告", "resolve-flag": "解决举报", - "merged-message": "此主题已合并到%2", + "merged-message": "此主题已合并到 %2", "forked-message": "此主题由 %2 分支而来", "deleted-message": "此主题已被删除。只有拥有主题管理权限的用户可以查看。", "following-topic.message": "当有人回复此主题时,您会收到通知。", @@ -103,6 +105,7 @@ "thread-tools.lock": "锁定主题", "thread-tools.unlock": "解锁主题", "thread-tools.move": "移动主题", + "thread-tools.crosspost": "交叉发布主题", "thread-tools.move-posts": "移动帖子", "thread-tools.move-all": "移动全部", "thread-tools.change-owner": "更改所有者", @@ -132,6 +135,7 @@ "pin-modal-help": "您可以在此处选择为置顶主题设置一个失效日期。或者您也可以选择不设置,则该主题将会一直被置顶,直到管理员取消置顶。", "load-categories": "正在载入版块", "confirm-move": "移动", + "confirm-crosspost": "交叉发布", "confirm-fork": "分割", "bookmark": "书签", "bookmarks": "书签", @@ -141,6 +145,7 @@ "loading-more-posts": "正在加载更多帖子", "move-topic": "移动主题", "move-topics": "移动主题", + "crosspost-topic": "交叉发布主题", "move-post": "移动帖子", "post-moved": "帖子已移动!", "fork-topic": "分割主题", @@ -163,6 +168,9 @@ "move-topic-instruction": "选择目标版块然后点击移动", "change-owner-instruction": "点击您想转移给其他用户的帖子", "manage-editors-instruction": "管理可以编辑此帖子的用户", + "crossposts.instructions": "选择一个或多个版块进行交叉发布。主题将可在原始版块及所有交叉发布的版块中访问。", + "crossposts.listing": "本主题已同步发布至以下本地版块:", + "crossposts.none": "该主题未被交叉发布到任何其他版块。", "composer.title-placeholder": "在此输入您主题的标题...", "composer.handle-placeholder": "在这里输入您的姓名/昵称", "composer.hide": "隐藏", @@ -174,6 +182,7 @@ "composer.replying-to": "正在回复 %1", "composer.new-topic": "新主题", "composer.editing-in": "在 %1 中编辑帖子", + "composer.untitled-topic": "无标题主题", "composer.uploading": "正在上传...", "composer.thumb-url-label": "粘贴主题缩略图网址", "composer.thumb-title": "给此主题添加缩略图", @@ -224,5 +233,8 @@ "unread-posts-link": "未读帖子链接", "thumb-image": "主题缩略图", "announcers": "分享", - "announcers-x": "分享 (%1)" + "announcers-x": "分享 (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/zh-CN/user.json b/public/language/zh-CN/user.json index 6aeade77e8..80358d9096 100644 --- a/public/language/zh-CN/user.json +++ b/public/language/zh-CN/user.json @@ -20,8 +20,8 @@ "unmute-account": "解除账号禁言", "delete-account": "删除帐号", "delete-account-as-admin": "删除账号", - "delete-content": "删除账号内容", - "delete-all": "删除账号和内容", + "delete-content": "删除账号 内容", + "delete-all": "删除 账号内容", "delete-account-confirm": "您确定要匿名化您的所有帖子并删除账号吗?
此操作不可撤销,您将无法恢复您的任何数据

请输入您的密码,以确认您要删除这个账号。", "delete-this-account-confirm": "您确定您要删除此账号同时保留其发布的内容吗?
此操作不可逆,帖子将被匿名化,而且您将无法恢复帖子和被删除账号的联系

", "delete-account-content-confirm": "您确定要删除账户内容(帖子/主题/上传)吗?
此操作不可逆,而且您无法恢复任何数据

", @@ -105,6 +105,10 @@ "show-email": "显示我的电子邮箱", "show-fullname": "显示我的全名", "restrict-chats": "只允许我关注的用户给我发送聊天消息", + "disable-incoming-chats": "停止接收聊天信息 ", + "chat-allow-list": "允许以下用户发送聊天信息", + "chat-deny-list": "拒绝来自以下用户的聊天信息", + "chat-list-add-user": "添加用户", "digest-label": "订阅摘要", "digest-description": "订阅此论坛的定期电子邮件更新 (新通知和主题)", "digest-off": "关闭", @@ -200,10 +204,10 @@ "browser-version-on-platform": "%1 %2 在 %3", "consent.title": "您的权利与许可", "consent.lead": "本论坛将会收集与处理您的个人信息。", - "consent.intro": "我们收集这些信息将仅用于个性化您于本社区的体验,和关联您的用户账号与您所发表的帖子。在注册过程中您需要提供一个用户名和邮箱地址,您也可以选择是否提供额外的个人信息,以完善您的用户资料。

在您的用户账号有效期内,我们将保留您的信息。您可以在任何时候通过删除您的账号,以撤回您的许可。您可以在任何时候通过您的权力与许可页面,获取一份您对本论坛的贡献的副本。

如果您有任何疑问,我们鼓励您与本论坛管理团队联系。", - "consent.email-intro": "我们有时可能会向您的注册邮件地址发送电子邮件,以向您提供有关于您的新动态和/或新活动。您可以通过您的用户设置页面自定义(包括直接禁用)社区摘要的发送频率,以及选择性地接收哪些类型的通知。", - "consent.digest-frequency": "本社区默认每 %1 发送一封摘要邮件,除非您在用户设置中明确更改了此项。", - "consent.digest-off": "本社区默认不发送摘要邮件,除非您在用户设置中明确更改了此项。", + "consent.intro": "我们严格使用这些信息来个性化您在本社区的体验,并将您发布的帖子关联至您的用户账号。注册时您需提供用户名和电子邮箱地址,也可选择性提供其他信息以完善本网站的用户资料。

我们将保存这些信息直至您的用户账号终止,您可随时通过注销账号撤回授权。您可随时通过“权利与授权”页面申请获取您在本网站的贡献内容副本。

如有任何疑问或顾虑,欢迎联系本论坛管理团队。", + "consent.email-intro": "我们可能会不定期向您注册的电子邮件地址发送邮件,以便提供更新信息和/或通知您与您相关的新动态。您可通过用户设置页面自定义社区摘要的接收频率(包括完全停用该功能),并选择希望通过邮件接收的通知类型。", + "consent.digest-frequency": "除非在您的用户设置中明确更改,否则本社区默认每 %1 发送一次邮件摘要。", + "consent.digest-off": "除非您在用户设置中明确更改,否则本社区不会发送任何邮件摘要。", "consent.received": "您已许可本网站收集与处理您的个人数据。无需其他额外操作。", "consent.not-received": "您未许可本网站收集与处理您的个人数据。本网站的管理团队可能于任何时候删除您的账号,以符合通用数据保护条例的要求。", "consent.give": "授予许可", diff --git a/public/language/zh-CN/world.json b/public/language/zh-CN/world.json index cc5abacede..524760099f 100644 --- a/public/language/zh-CN/world.json +++ b/public/language/zh-CN/world.json @@ -1,7 +1,12 @@ { "name": "世界", - "popular": "热门主题", - "recent": "全部主题", + "latest": "最新", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "全部", "help": "帮助", "help.title": "这是什么页面?", @@ -14,5 +19,7 @@ "onboard.title": "您通往联邦宇宙的窗口...", "onboard.what": "这是您的个性化版块,只包含本论坛以外的内容。内容是否显示在本页取决于您是否关注他们,或者该帖子是否由您关注的人分享。", "onboard.why": "论坛之外的事情很多,而且并非所有事情都与您的兴趣相关。因此,关注他人是表明您想从某人那里了解更多信息的最佳方式。", - "onboard.how": "在此期间,您可以点击顶部的快捷按钮,了解本论坛的其他内容,并开始发现一些新内容!" + "onboard.how": "在此期间,您可以点击顶部的快捷按钮,了解本论坛的其他内容,并开始发现一些新内容!", + + "category-search": "查找版块..." } \ 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 ecf0948c14..b6746d64fa 100644 --- a/public/language/zh-TW/admin/advanced/cache.json +++ b/public/language/zh-TW/admin/advanced/cache.json @@ -1,9 +1,5 @@ { "cache": "快取", - "post-cache": "貼文快取", - "group-cache": "群組快取", - "local-cache": "本地快取", - "object-cache": "物件快取", "percent-full": "%1% 容量", "post-cache-size": "貼文快取大小", "items-in-cache": "快取中的項目數量" diff --git a/public/language/zh-TW/admin/dashboard.json b/public/language/zh-TW/admin/dashboard.json index 4cc161c595..acf93cadda 100644 --- a/public/language/zh-TW/admin/dashboard.json +++ b/public/language/zh-TW/admin/dashboard.json @@ -75,6 +75,7 @@ "graphs.page-views-registered": "註冊使用者頁面瀏覽量", "graphs.page-views-guest": "訪客頁面瀏覽量", "graphs.page-views-bot": "爬蟲頁面瀏覽量", + "graphs.page-views-ap": "ActivityPub Page Views", "graphs.unique-visitors": "不重複訪客", "graphs.registered-users": "已註冊使用者", "graphs.guest-users": "Guest Users", diff --git a/public/language/zh-TW/admin/development/info.json b/public/language/zh-TW/admin/development/info.json index df3bc6cd12..c7687b77c6 100644 --- a/public/language/zh-TW/admin/development/info.json +++ b/public/language/zh-TW/admin/development/info.json @@ -8,7 +8,7 @@ "nodejs": "nodejs", "online": "在線", "git": "git", - "process-memory": "process memory", + "process-memory": "rss/heap used", "system-memory": "system memory", "used-memory-process": "Used memory by process", "used-memory-os": "Used system memory", diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json index 7a857a1d8c..0989aa7bc7 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -1,18 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "rename": "Rename", "jump-to": "Jump to...", "settings": "版面設定", "edit-category": "Edit Category", "privileges": "權限", "back-to-categories": "Back to categories", + "id": "Category ID", "name": "版面名稱", "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "版面描述", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "背景顏色", "text-color": "圖示顏色", "bg-image-size": "背景圖片大小", @@ -103,6 +107,11 @@ "alert.create-success": "版面建立成功!", "alert.none-active": "您沒有有效的版面。", "alert.create": "建立一個版面", + "alert.add": "Add a Category", + "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", "alert.confirm-purge": "

您確定要清除 “%1” 版面嗎?

警告! 版面將被清除!

清除版塊將刪除所有主題和帖子,並從數據庫中刪除版塊。 如果您想暫時移除版塊,請使用停用版塊。

", "alert.purge-success": "版面已刪除!", "alert.copy-success": "設定已複製!", diff --git a/public/language/zh-TW/admin/manage/custom-reasons.json b/public/language/zh-TW/admin/manage/custom-reasons.json new file mode 100644 index 0000000000..90a2e620af --- /dev/null +++ b/public/language/zh-TW/admin/manage/custom-reasons.json @@ -0,0 +1,16 @@ +{ + "title": "Manage Custom Reasons", + "create-reason": "Create Reason", + "edit-reason": "Edit Reason", + "reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.", + "reason-title": "Title", + "reason-type": "Type", + "reason-body": "Body", + "reason-all": "All", + "reason-ban": "Ban", + "reason-mute": "Mute", + "reason-post-queue": "Post Queue", + "reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.", + "custom-reasons-saved": "Custom reasons saved successfully", + "delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title %1?" +} \ 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 f6fbed0557..f974e70454 100644 --- a/public/language/zh-TW/admin/manage/privileges.json +++ b/public/language/zh-TW/admin/manage/privileges.json @@ -29,6 +29,7 @@ "access-topics": "存取主題", "create-topics": "建立主題", "reply-to-topics": "回覆主題", + "crosspost-topics": "Cross-post Topics", "schedule-topics": "預排的主題", "tag-topics": "新增標籤", "edit-posts": "修改回覆", diff --git a/public/language/zh-TW/admin/manage/users.json b/public/language/zh-TW/admin/manage/users.json index bdb1b535d0..b2b50da964 100644 --- a/public/language/zh-TW/admin/manage/users.json +++ b/public/language/zh-TW/admin/manage/users.json @@ -23,6 +23,7 @@ "purge": "刪除 用戶內容", "download-csv": "下載CSV", "custom-user-fields": "Custom User Fields", + "custom-reasons": "Custom Reasons", "manage-groups": "管理群組", "set-reputation": "設立聲望", "add-group": "新增至群組", @@ -77,9 +78,11 @@ "temp-ban.length": "多久", "temp-ban.reason": "理由(可選)", + "temp-ban.select-reason": "Select a reason", "temp-ban.hours": "小時", "temp-ban.days": "天", "temp-ban.explanation": "輸入停權持續時間。提示,時長為0視為永久停權。", + "temp-mute.explanation": "Enter the length of time for the mute. Note that a time of 0 will be a considered a permanent mute.", "alerts.confirm-ban": "您確定要永久停權該用戶嗎?", "alerts.confirm-ban-multi": "您確定要永久停權這些用戶嗎?", diff --git a/public/language/zh-TW/admin/menu.json b/public/language/zh-TW/admin/menu.json index 7f9ec2e046..87119bb3fa 100644 --- a/public/language/zh-TW/admin/menu.json +++ b/public/language/zh-TW/admin/menu.json @@ -1,10 +1,10 @@ { - "section-dashboard": "Dashboards", - "dashboard/overview": "Overview", - "dashboard/logins": "Logins", - "dashboard/users": "Users", - "dashboard/topics": "Topics", - "dashboard/searches": "Searches", + "section-dashboard": "儀表板", + "dashboard/overview": "概覽", + "dashboard/logins": "登入", + "dashboard/users": "用戶", + "dashboard/topics": "主題", + "dashboard/searches": "搜尋", "section-general": "基本", "section-manage": "管理", @@ -14,7 +14,7 @@ "manage/users": "使用者", "manage/admins-mods": "權限分配", "manage/registration": "註冊申請", - "manage/flagged-content": "Flagged Content", + "manage/flagged-content": "被標記的內容", "manage/post-queue": "貼文隊列", "manage/groups": "群組", "manage/ip-blacklist": "IP 黑名單", @@ -25,7 +25,7 @@ "settings/general": "基本", "settings/homepage": "首頁", "settings/navigation": "導航", - "settings/reputation": "Reputation & Flags", + "settings/reputation": "聲望 & 旗標", "settings/email": "郵件", "settings/user": "使用者", "settings/group": "群組", @@ -37,8 +37,8 @@ "settings/pagination": "分頁", "settings/tags": "標籤", "settings/notifications": "通知", - "settings/api": "API Access", - "settings/activitypub": "Federation (ActivityPub)", + "settings/api": "API 存取", + "settings/activitypub": "聯邦 (ActivityPub)", "settings/sounds": "聲音", "settings/social": "社交", "settings/cookies": "Cookies", @@ -74,13 +74,13 @@ "development/info": "資訊", "rebuild-and-restart-forum": "部署並重啟論壇", - "rebuild-and-restart": "Rebuild & Restart", + "rebuild-and-restart": "重建 & 重啟", "restart-forum": "重啟論壇", - "restart": "Restart", + "restart": "重啟", "logout": "登出", "view-forum": "檢視論壇", - "search.placeholder": "Search settings", + "search.placeholder": "搜尋設定", "search.no-results": "沒有可用結果…", "search.search-forum": "搜索論壇為", "search.keep-typing": "輸入更多以查看結果...", diff --git a/public/language/zh-TW/admin/settings/activitypub.json b/public/language/zh-TW/admin/settings/activitypub.json index 94f9ad7822..dd07276f94 100644 --- a/public/language/zh-TW/admin/settings/activitypub.json +++ b/public/language/zh-TW/admin/settings/activitypub.json @@ -1,25 +1,47 @@ { - "intro-lead": "What is Federation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", - "general": "General", - "pruning": "Content Pruning", - "content-pruning": "Days to keep remote content", + "intro-lead": "什麼是\"聯邦\" ?", + "intro-body": "NodeBB 可以和其他啟用 ActivityPub協定的 NodeBB 主機通訊。也能和使用相同協定的其他 app 或網站做溝通 (例如, Mastodon, Peertube 等等) ", + "general": "準則", + "pruning": "內容修改", + "content-pruning": "遠端內容保存天數", "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", - "user-pruning": "Days to cache remote user accounts", + "user-pruning": "快取遠端用戶帳號的天數", "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", - "enabled": "Enable Federation", + "enabled": "啟用\"聯邦\"", "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", "allowLoopback": "Allow loopback processing", - "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", + "allowLoopback-help": "僅供除錯時有用。沒事別開啟。", - "probe": "Open in App", + "probe": "在 APP 內開啟", "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", - "probe-timeout": "Lookup Timeout (milliseconds)", + "probe-timeout": "查詢時限 (毫秒)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", - "server-filtering": "Filtering", - "count": "This NodeBB is currently aware of %1 server(s)", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", + "rules.add": "Add New Rule", + "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", + "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + + "relays": "Relays", + "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", + "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", + "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", + "relays.add": "Add New Relay", + "relays.relay": "Relay", + "relays.state": "State", + "relays.state-0": "Pending", + "relays.state-1": "Receiving only", + "relays.state-2": "Active", + + "server-filtering": "過濾...", + "count": "本 NodeBB 已發現 %1 台伺服器。", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.", "server.filter-allow-list": "Use this as an Allow List instead" diff --git a/public/language/zh-TW/admin/settings/chat.json b/public/language/zh-TW/admin/settings/chat.json index c712fcfe6f..a210b99985 100644 --- a/public/language/zh-TW/admin/settings/chat.json +++ b/public/language/zh-TW/admin/settings/chat.json @@ -10,8 +10,6 @@ "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "聊天室的最多使用者數", "delay": "Time between chat messages (ms)", - "notification-delay": "Notification delay for chat messages", - "notification-delay-help": "Additional messages sent between this time are collated, and the user is notified once per delay period. Set this to 0 to disable the delay.", "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable.", "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable." } \ 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 a479ee5abc..055a35ef7b 100644 --- a/public/language/zh-TW/admin/settings/email.json +++ b/public/language/zh-TW/admin/settings/email.json @@ -30,14 +30,20 @@ "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", "smtp-transport.allow-self-signed": "Allow self-signed certificates", "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.test-success": "SMTP Test email sent successfully.", "template": "編輯電子郵件樣板", "template.select": "選擇電子郵件樣板", "template.revert": "還原為初始樣板", + "test-smtp-settings": "Test SMTP Settings", "testing": "電子郵件測試", + "testing.success": "Test Email Sent.", "testing.select": "選擇電子郵件樣板", "testing.send": "發送測試電子郵件", - "testing.send-help": "測試電子郵件將被發送到當前已登入的使用者的電郵地址。", + "testing.send-help-plugin": "\"%1\" will be used to send test emails.", + "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.", + "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.", + "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ", "subscriptions": "電子郵件摘要", "subscriptions.disable": "禁用電子郵件摘要", "subscriptions.hour": "摘要小時", diff --git a/public/language/zh-TW/admin/settings/notifications.json b/public/language/zh-TW/admin/settings/notifications.json index 991f4e19d6..9e936e522f 100644 --- a/public/language/zh-TW/admin/settings/notifications.json +++ b/public/language/zh-TW/admin/settings/notifications.json @@ -3,5 +3,7 @@ "welcome-notification": "歡迎通知", "welcome-notification-link": "歡迎通知連結", "welcome-notification-uid": "歡迎通知使用者 (UID)", - "post-queue-notification-uid": "Post Queue User (UID)" + "post-queue-notification-uid": "Post Queue User (UID)", + "notification-delay": "Delay for sending notification emails (seconds)", + "notification-delay-help": "If the user has read the notification within this time, the email will not be sent.
Default: 60 seconds." } \ 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 fc3bddd9ca..2f06729237 100644 --- a/public/language/zh-TW/admin/settings/uploads.json +++ b/public/language/zh-TW/admin/settings/uploads.json @@ -21,7 +21,13 @@ "reject-image-width-help": "寬於此數值大小的圖片將會被拒絕", "reject-image-height": "圖片最大高度值(單位:像素)", "reject-image-height-help": "高於此數值大小的圖片將會被拒絕", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "允許使用者上傳主題縮圖", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "主題縮圖大小", "allowed-file-extensions": "允許的副檔名", "allowed-file-extensions-help": "在此處輸入以逗號分隔的副檔名列表 (例如 pdf,xls,doc )。 為空則表示允許所有副檔名。", diff --git a/public/language/zh-TW/admin/settings/user.json b/public/language/zh-TW/admin/settings/user.json index eb643b244d..1e45394579 100644 --- a/public/language/zh-TW/admin/settings/user.json +++ b/public/language/zh-TW/admin/settings/user.json @@ -64,6 +64,7 @@ "show-email": "顯示郵箱", "show-fullname": "顯示全名", "restrict-chat": "只允許我追隨的使用者給我發送聊天訊息", + "disable-incoming-chats": "Disable incoming chat messages", "outgoing-new-tab": "在新頁籤打開外部連結", "topic-search": "啟用主題內搜尋", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/zh-TW/admin/settings/web-crawler.json b/public/language/zh-TW/admin/settings/web-crawler.json index 04033ccc7c..dca36bb4ba 100644 --- a/public/language/zh-TW/admin/settings/web-crawler.json +++ b/public/language/zh-TW/admin/settings/web-crawler.json @@ -5,6 +5,7 @@ "disable-rss-feeds": "停用 RSS 訂閱", "disable-sitemap-xml": "停用 Sitemap.xml", "sitemap-topics": "要在 Sitemap 中展現的主題數量", + "sitemap-cache-duration-hours": "Sitemap Cache Duration (hours)", "clear-sitemap-cache": "清除 Sitemap 快取", "view-sitemap": "檢視 Sitemap" } \ No newline at end of file diff --git a/public/language/zh-TW/aria.json b/public/language/zh-TW/aria.json index d0388293b0..61fee5591f 100644 --- a/public/language/zh-TW/aria.json +++ b/public/language/zh-TW/aria.json @@ -5,5 +5,6 @@ "profile-page-for": "Profile page for user %1", "user-watched-tags": "關注的標簽", "delete-upload-button": "删除上傳按鈕", - "group-page-link-for": "%1 的群組頁面鏈結" + "group-page-link-for": "%1 的群組頁面鏈結", + "show-crossposts": "Show Cross-posts" } \ No newline at end of file diff --git a/public/language/zh-TW/category.json b/public/language/zh-TW/category.json index 554c819162..377662377e 100644 --- a/public/language/zh-TW/category.json +++ b/public/language/zh-TW/category.json @@ -1,12 +1,13 @@ { "category": "版面", "subcategories": "子版面", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "World", + "uncategorized.description": "Topics from outside of this forum. Views and opinions represented here may not reflect those of this forum and its members.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "發表主題", "guest-login-post": "登入以發表", "no-topics": "此版面還沒有任何內容。
趕緊來貼文吧!", + "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", "browsing": "正在瀏覽", "no-replies": "尚無回覆", "no-new-posts": "沒有新主題", diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index 1415eab378..866bd21d7d 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -3,6 +3,7 @@ "invalid-json": "無效 JSON", "wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead", "required-parameters-missing": "Required parameters were missing from this API call: %1", + "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "您還沒有登入。", "account-locked": "您的帳戶已被暫時鎖定", "search-requires-login": "搜尋功能僅限成員使用 - 請先登入或者註冊。", @@ -146,6 +147,7 @@ "post-already-restored": "此貼文已經恢復", "topic-already-deleted": "此主題已被刪除", "topic-already-restored": "此主題已恢復", + "topic-already-crossposted": "This topic has already been cross-posted there.", "cant-purge-main-post": "無法清除主貼文,請直接刪除主題", "topic-thumbnails-are-disabled": "主題縮圖已停用", "invalid-file": "無效檔案", @@ -154,6 +156,8 @@ "about-me-too-long": "抱歉,您的關於我不能超過 %1 個字元。", "cant-chat-with-yourself": "您不能和自己聊天!", "chat-restricted": "此使用者限制了他的聊天訊息。必須他先追隨您,您才能和他聊天。", + "chat-allow-list-user-already-added": "This user is already in your allow list", + "chat-deny-list-user-already-added": "This user is already in your deny list", "chat-user-blocked": "您已被此用戶封鎖。", "chat-disabled": "聊天系統已關閉", "too-many-messages": "您發送了太多訊息,請稍等片刻。", @@ -225,6 +229,7 @@ "no-topics-selected": "沒有主題被選中!", "cant-move-to-same-topic": "無法將貼文移動到相同的主題中!", "cant-move-topic-to-same-category": "Can't move topic to the same category!", + "cant-move-topic-to-from-remote-categories": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "cannot-block-self": "您不能把自己封鎖!", "cannot-block-privileged": "您不能封鎖管理員或者超級版主", "cannot-block-guest": "訪客無法封鎖其他使用者", @@ -234,6 +239,7 @@ "socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later", "invalid-plugin-id": "無效的插件 ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", @@ -246,6 +252,7 @@ "api.401": "A valid login session was not found. Please log in and try again.", "api.403": "You are not authorised to make this call", "api.404": "Invalid API call", + "api.413": "The request payload is too large", "api.426": "HTTPS is required for requests to the write api, please re-send your request via HTTPS", "api.429": "You have made too many requests, please try again later", "api.500": "An unexpected error was encountered while attempting to service your request.", diff --git a/public/language/zh-TW/global.json b/public/language/zh-TW/global.json index 90f20e9337..a9452ca9b3 100644 --- a/public/language/zh-TW/global.json +++ b/public/language/zh-TW/global.json @@ -68,6 +68,7 @@ "users": "使用者", "topics": "主題", "posts": "貼文", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", @@ -82,6 +83,7 @@ "downvoted": "倒讚", "views": "瀏覽", "posters": "Posters", + "watching": "Watching", "reputation": "聲望", "lastpost": "上一個貼文", "firstpost": "第一個貼文", diff --git a/public/language/zh-TW/groups.json b/public/language/zh-TW/groups.json index edd9618c86..239cf9be97 100644 --- a/public/language/zh-TW/groups.json +++ b/public/language/zh-TW/groups.json @@ -1,7 +1,9 @@ { + "group": "Group", "all-groups": "全部群組", "groups": "群組", "members": "成員", + "x-members": "%1 member(s)", "view-group": "檢視群組", "owner": "群組所有者", "new-group": "新增群組", diff --git a/public/language/zh-TW/modules.json b/public/language/zh-TW/modules.json index a0d0f953d3..e4dfd3036b 100644 --- a/public/language/zh-TW/modules.json +++ b/public/language/zh-TW/modules.json @@ -48,6 +48,7 @@ "chat.add-user": "Add User", "chat.notification-settings": "Notification Settings", "chat.default-notification-setting": "Default Notification Setting", + "chat.join-leave-messages": "Join/Leave Messages", "chat.notification-setting-room-default": "Room Default", "chat.notification-setting-none": "No notifications", "chat.notification-setting-at-mention-only": "@mention only", @@ -81,7 +82,7 @@ "composer.hide-preview": "隱藏預覽", "composer.help": "Help", "composer.user-said-in": "%1 在 %2 中說:", - "composer.user-said": "%1 說:", + "composer.user-said": "%1 [said](%2):", "composer.discard": "確定想要取消此貼文?", "composer.submit-and-lock": "提交並鎖定", "composer.toggle-dropdown": "標為 Dropdown", diff --git a/public/language/zh-TW/notifications.json b/public/language/zh-TW/notifications.json index afdf1dd5b2..d150073fdd 100644 --- a/public/language/zh-TW/notifications.json +++ b/public/language/zh-TW/notifications.json @@ -22,7 +22,7 @@ "upvote": "點讚", "awards": "獎勵", "new-flags": "新舉報", - "my-flags": "指派舉報給我", + "my-flags": "My Flags", "bans": "停權", "new-message-from": "來自 %1 的新訊息", "new-messages-from": "%1 new messages from %2", @@ -32,31 +32,31 @@ "user-posted-in-public-room-dual": "%1 and %2 wrote in %4", "user-posted-in-public-room-triple": "%1, %2 and %3 wrote in %5", "user-posted-in-public-room-multiple": "%1, %2 and %3 others wrote in %5", - "upvoted-your-post-in": "%1%2 點讚了您的貼文。", - "upvoted-your-post-in-dual": "%1%2%3 點讚了您的貼文。", - "upvoted-your-post-in-triple": "%1, %2 and %3 have upvoted your post in %4.", - "upvoted-your-post-in-multiple": "%1, %2 and %3 others have upvoted your post in %4.", + "upvoted-your-post-in": "%1 upvoted your post in %2", + "upvoted-your-post-in-dual": "%1 and %2 upvoted your post in %3", + "upvoted-your-post-in-triple": "%1, %2 and %3 upvoted your post in %4", + "upvoted-your-post-in-multiple": "%1, %2 and %3 others upvoted your post in %4.", "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": "%1 flagged a post in %2", + "user-flagged-post-in-dual": "%1 and %2 flagged a post in %3", "user-flagged-post-in-triple": "%1, %2 and %3 flagged a post in %4", "user-flagged-post-in-multiple": "%1, %2 and %3 others flagged a post in %4", "user-flagged-user": "%1 舉報了 (%2) 的使用者資料", "user-flagged-user-dual": "%1%2 舉報了 (%3) 的使用者資料", "user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)", "user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)", - "user-posted-to": "%1 回覆了:%2", - "user-posted-to-dual": "%1%2 回覆了: %3", - "user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4", - "user-posted-to-multiple": "%1, %2 and %3 others have posted replies to: %4", - "user-posted-topic": "%1 發表了新主題:%2", - "user-edited-post": "%1%2編輯了一則貼文", - "user-posted-topic-with-tag": "%1 發表了 %2 (被標記 %3)", - "user-posted-topic-with-tag-dual": "%1 發表了 %2 (被標記 %3 及 %4)", - "user-posted-topic-with-tag-triple": "%1 發表了 %2 (被標記 %3, %4 及 %5)", - "user-posted-topic-with-tag-multiple": "%1 發表了 %2 (被標記 %3)", - "user-posted-topic-in-category": "%1%2發表了新主題", + "user-posted-to": "%1 posted a reply in %2", + "user-posted-to-dual": "%1 and %2 replied in %3", + "user-posted-to-triple": "%1, %2 and %3 replied in %4", + "user-posted-to-multiple": "%1, %2 and %3 others replied in %4", + "user-posted-topic": "%1 posted %2", + "user-edited-post": "%1 edited a post in %2", + "user-posted-topic-with-tag": "%1 posted %2 (tagged %3)", + "user-posted-topic-with-tag-dual": "%1 posted %2 (tagged %3 and %4)", + "user-posted-topic-with-tag-triple": "%1 posted %2 (tagged %3, %4, and %5)", + "user-posted-topic-with-tag-multiple": "%1 posted %2 (tagged %3)", + "user-posted-topic-in-category": "%1 posted %2 in %3", "user-started-following-you": "%1追隨了您。", "user-started-following-you-dual": "%1%2 追隨了您。", "user-started-following-you-triple": "%1, %2 and %3 started following you.", @@ -71,11 +71,11 @@ "users-csv-exported": "Users csv exported, click to download", "post-queue-accepted": "Your queued post has been accepted. Click here to see your post.", "post-queue-rejected": "Your queued post has been rejected.", - "post-queue-notify": "Queued post received a notification:
\"%1\"", + "post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"", + "post-queue-notify": "Queued post received a notification: \"%1\"", "email-confirmed": "電子信箱已確認", "email-confirmed-message": "感謝您驗證您的電子信箱。您的帳戶現已完全啟用。", "email-confirm-error-message": "驗證的您電子信箱地址時出現了問題。可能是因為驗證碼無效或已過期。", - "email-confirm-error-message-already-validated": "你的電子信箱已驗証。", "email-confirm-sent": "確認郵件已發送。", "none": "不通知", "notification-only": "頁面提醒", diff --git a/public/language/zh-TW/social.json b/public/language/zh-TW/social.json index dc1efd7912..0c2974c36c 100644 --- a/public/language/zh-TW/social.json +++ b/public/language/zh-TW/social.json @@ -8,5 +8,7 @@ "log-in-with-facebook": "以Facebook登入", "continue-with-facebook": "以Facebook繼續使用", "sign-in-with-linkedin": "以 LinkedIn 登入", - "sign-up-with-linkedin": "以 LinkedIn 註冊" + "sign-up-with-linkedin": "以 LinkedIn 註冊", + "sign-in-with-wordpress": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/zh-TW/themes/harmony.json b/public/language/zh-TW/themes/harmony.json index 2b199e254f..eb7ea80bbd 100644 --- a/public/language/zh-TW/themes/harmony.json +++ b/public/language/zh-TW/themes/harmony.json @@ -1,6 +1,8 @@ { "theme-name": "Harmony Theme", "skins": "Skins", + "light": "Light", + "dark": "Dark", "collapse": "Collapse", "expand": "Expand", "sidebar-toggle": "側欄切換", diff --git a/public/language/zh-TW/topic.json b/public/language/zh-TW/topic.json index 927d486876..aaa0ef9d53 100644 --- a/public/language/zh-TW/topic.json +++ b/public/language/zh-TW/topic.json @@ -69,6 +69,8 @@ "user-referenced-topic-on": "%1 引用 了在 %3 的話題", "user-forked-topic-ago": "%1 分支 了話題 %3", "user-forked-topic-on": "%1 分支 了在 %3 的話題", + "user-crossposted-topic-ago": "%1 crossposted this topic to %2 %3", + "user-crossposted-topic-on": "%1 crossposted this topicto %2 on %3", "bookmark-instructions": "點擊閱讀本主題貼文中的最新回覆", "flag-post": "檢舉此貼文", "flag-user": "檢舉此用戶", @@ -103,6 +105,7 @@ "thread-tools.lock": "鎖定主題", "thread-tools.unlock": "解鎖主題", "thread-tools.move": "移動主題", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "移動貼文", "thread-tools.move-all": "移動全部", "thread-tools.change-owner": "更改所有者", @@ -132,6 +135,7 @@ "pin-modal-help": "您可在這裏設定置頂話題的有效日期,也可放著不動它直到被手動將置頂取消。", "load-categories": "正在載入版面", "confirm-move": "移動", + "confirm-crosspost": "Cross-post", "confirm-fork": "分割", "bookmark": "書籤", "bookmarks": "書籤", @@ -141,6 +145,7 @@ "loading-more-posts": "正在載入更多貼文", "move-topic": "移動主題", "move-topics": "移動主題", + "crosspost-topic": "Cross-post Topic", "move-post": "移動貼文", "post-moved": "回覆已移動!", "fork-topic": "分割主題", @@ -163,6 +168,9 @@ "move-topic-instruction": "選擇目標分類後點擊移動", "change-owner-instruction": "點擊您想轉移給其他使用者的貼文", "manage-editors-instruction": "Manage the users who can edit this post below.", + "crossposts.instructions": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", + "crossposts.listing": "This topic has been cross-posted to the following local categories:", + "crossposts.none": "This topic has not been cross-posted to any additional categories", "composer.title-placeholder": "在此輸入您主題的標題...", "composer.handle-placeholder": "在此輸入您的名稱/代稱", "composer.hide": "隱藏", @@ -174,6 +182,7 @@ "composer.replying-to": "正在回覆 %1", "composer.new-topic": "新主題", "composer.editing-in": "編輯在 %1 的貼文", + "composer.untitled-topic": "Untitled Topic", "composer.uploading": "正在上傳...", "composer.thumb-url-label": "添加主題縮圖網址", "composer.thumb-title": "給此主題添加縮圖", @@ -224,5 +233,8 @@ "unread-posts-link": "未讀貼文鏈結", "thumb-image": "主題縮圖", "announcers": "Shares", - "announcers-x": "Shares (%1)" + "announcers-x": "Shares (%1)", + "guest-cta.title": "Hello! It looks like you're interested in this conversation, but you don't have an account yet.", + "guest-cta.message": "Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.", + "guest-cta.closing": "With your input, this post could be even better 💗" } \ No newline at end of file diff --git a/public/language/zh-TW/user.json b/public/language/zh-TW/user.json index 402d3753a0..596f9d6980 100644 --- a/public/language/zh-TW/user.json +++ b/public/language/zh-TW/user.json @@ -105,6 +105,10 @@ "show-email": "顯示我的電子信箱", "show-fullname": "顯示我的全名", "restrict-chats": "只允許我追隨的使用者給我發送聊天訊息", + "disable-incoming-chats": "Disable incoming chat messages ", + "chat-allow-list": "Allow chat messages from the following users", + "chat-deny-list": "Deny chat messages from the following users", + "chat-list-add-user": "Add user", "digest-label": "訂閱摘要", "digest-description": "訂閱此論壇的定期電子郵件更新 (新通知和主題)", "digest-off": "關閉", diff --git a/public/language/zh-TW/world.json b/public/language/zh-TW/world.json index 3753335278..e6694bf507 100644 --- a/public/language/zh-TW/world.json +++ b/public/language/zh-TW/world.json @@ -1,7 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", + "latest": "Latest", + "popular-day": "Popular (Day)", + "popular-week": "Popular (Week)", + "popular-month": "Popular (Month)", + "popular-year": "Popular (Year)", + "popular-alltime": "Popular (All Time)", + "recent": "All", "help": "Help", "help.title": "What is this page?", @@ -14,5 +19,7 @@ "onboard.title": "Your window to the fediverse...", "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!" + "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + + "category-search": "Find a category..." } \ No newline at end of file diff --git a/public/openapi/components/schemas/CategoryObject.yaml b/public/openapi/components/schemas/CategoryObject.yaml index 0b138542b0..fa377cbcdd 100644 --- a/public/openapi/components/schemas/CategoryObject.yaml +++ b/public/openapi/components/schemas/CategoryObject.yaml @@ -8,6 +8,9 @@ CategoryObject: name: type: string description: The category's name/title + nickname: + type: string + description: A nickname for the category. handle: type: string description: | diff --git a/public/openapi/components/schemas/Chats.yaml b/public/openapi/components/schemas/Chats.yaml index dc84aca4ef..3d4339c083 100644 --- a/public/openapi/components/schemas/Chats.yaml +++ b/public/openapi/components/schemas/Chats.yaml @@ -27,6 +27,10 @@ RoomObject: description: Timestamp of when room was created notificationSetting: type: number + description: The notification setting for the room, 0 = no notifications, 1 = only mentions, 2 = all messages + joinLeaveMessages: + type: number + description: Whether join/leave messages are enabled in the room MessageObject: type: object properties: @@ -104,6 +108,10 @@ MessageObject: `icon:text` for the user's auto-generated icon example: "#f44336" + 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" diff --git a/public/openapi/components/schemas/CrosspostObject.yaml b/public/openapi/components/schemas/CrosspostObject.yaml new file mode 100644 index 0000000000..54f36a4f5f --- /dev/null +++ b/public/openapi/components/schemas/CrosspostObject.yaml @@ -0,0 +1,52 @@ +CrosspostObject: + anyOf: + - type: object + properties: + id: + type: string + description: The cross-post ID + cid: + type: object + description: The category id that the topic was cross-posted to + additionalProperties: + oneOf: + - type: string + - type: number + tid: + type: object + description: The topic id that was cross-posted + additionalProperties: + oneOf: + - type: string + - type: number + timestamp: + type: number + uid: + type: object + description: The user id that initiated the cross-post + additionalProperties: + oneOf: + - type: string + - type: number + - type: object + properties: + category: + type: object + properties: + cid: + type: number + name: + type: string + icon: + type: string + bgColor: + type: string + color: + type: string + slug: + type: string +CrosspostsArray: + type: array + description: A list of crosspost objects + items: + $ref: '#/CrosspostObject' \ No newline at end of file diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml index 502ea8044d..80c9a06cd3 100644 --- a/public/openapi/components/schemas/PostObject.yaml +++ b/public/openapi/components/schemas/PostObject.yaml @@ -84,6 +84,8 @@ PostObject: description: A topic identifier title: type: string + generatedTitle: + type: number cid: type: number description: A category identifier @@ -180,6 +182,8 @@ PostDataObject: type: number downvotes: type: number + announces: + type: number bookmarks: type: number deleterUid: @@ -298,6 +302,8 @@ PostDataObject: type: boolean attachments: type: array + uploads: + type: array replies: type: object properties: diff --git a/public/openapi/components/schemas/PostsObject.yaml b/public/openapi/components/schemas/PostsObject.yaml index b43d965888..5a079a08d9 100644 --- a/public/openapi/components/schemas/PostsObject.yaml +++ b/public/openapi/components/schemas/PostsObject.yaml @@ -2,4 +2,15 @@ PostsObject: description: One of the objects in the array returned from `Posts.getPostSummaryByPids` type: array items: - $ref: ./PostObject.yaml#/PostObject \ No newline at end of file + allOf: + - $ref: ./PostObject.yaml#/PostObject + - type: object + description: Optional properties that may or may not be present (except for `pid`, which is always present, and is only here as a hack to pass validation) + properties: + pid: + type: number + description: A post identifier + attachments: + type: array + required: + - pid \ No newline at end of file diff --git a/public/openapi/components/schemas/SettingsObj.yaml b/public/openapi/components/schemas/SettingsObj.yaml index 2ccc8e161c..779d2e2fb4 100644 --- a/public/openapi/components/schemas/SettingsObj.yaml +++ b/public/openapi/components/schemas/SettingsObj.yaml @@ -31,9 +31,25 @@ Settings: followTopicsOnReply: type: boolean description: Automatically be notified of new posts in a topic, when you reply to that topic - restrictChat: + disableIncomingChats: type: boolean description: Do not allow other users to start chats with you (or add you to other chat rooms) + chatAllowList: + type: array + items: + type: string + description: List of uids that can start chats with you + chatDenyList: + type: array + items: + type: string + description: List of uids that are not allowed to start chats with you + chatAllowListUsers: + type: array + description: List of users that can start chats with you + chatDenyListUsers: + type: array + description: List of users that are not allowed to start chats with you topicSearchEnabled: type: boolean description: Enable keyword searching within topics diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml index 3789aa9518..605c32de85 100644 --- a/public/openapi/components/schemas/TopicObject.yaml +++ b/public/openapi/components/schemas/TopicObject.yaml @@ -60,8 +60,6 @@ TopicObject: signature: type: string nullable: true - banned: - type: number status: type: string icon:text: @@ -76,6 +74,10 @@ TopicObject: `icon:text` for the user's auto-generated icon example: "#f44336" + banned: + type: number + banned_until: + type: number banned_until_readable: type: string required: @@ -106,6 +108,9 @@ TopicObject: description: A topic identifier content: type: string + sourceContent: + type: string + nullable: true timestampISO: type: string description: An ISO 8601 formatted date string (complementing `timestamp`) @@ -118,6 +123,10 @@ TopicObject: username: type: string description: A friendly name for a given user account + displayname: + type: string + isLocal: + type: boolean userslug: type: string description: An URL-safe variant of the username (i.e. lower-cased, spaces @@ -207,6 +216,8 @@ TopicObjectSlim: description: A category identifier title: type: string + generatedTitle: + type: number slug: type: string mainPid: @@ -218,6 +229,8 @@ TopicObjectSlim: type: number postercount: type: number + followercount: + type: number scheduled: type: number deleted: diff --git a/public/openapi/components/schemas/UserObject.yaml b/public/openapi/components/schemas/UserObject.yaml index 5dc0da6bf4..61d98b1f80 100644 --- a/public/openapi/components/schemas/UserObject.yaml +++ b/public/openapi/components/schemas/UserObject.yaml @@ -681,6 +681,10 @@ UserObjectACP: 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" diff --git a/public/openapi/components/schemas/admin/relays.yaml b/public/openapi/components/schemas/admin/relays.yaml new file mode 100644 index 0000000000..67bdd09b10 --- /dev/null +++ b/public/openapi/components/schemas/admin/relays.yaml @@ -0,0 +1,18 @@ +RelayObject: + type: object + properties: + url: + type: string + description: The relay actor endpoint + example: https://example.org/actor + state: + type: number + description: "The established state of the relay(0: pending; 1: one way receive; 2: bidirectional)" + enum: [0, 1, 2] + label: + type: string + description: A language key pertaining to the `state` value +RelaysArray: + type: array + items: + $ref: '#/RelayObject' \ No newline at end of file diff --git a/public/openapi/components/schemas/admin/rules.yaml b/public/openapi/components/schemas/admin/rules.yaml new file mode 100644 index 0000000000..282d6eff11 --- /dev/null +++ b/public/openapi/components/schemas/admin/rules.yaml @@ -0,0 +1,23 @@ +RuleObject: + type: object + properties: + rid: + type: string + description: a valid rule ID + example: 4eb506f8-a173-4693-a41b-e23604bc973a + type: + type: string + description: The auto-categorization rule type + example: hashtag + value: + type: string + description: The value that incoming content will be matched against (used alongside `type`) + example: 'example' + cid: + type: number + description: The category ID of a local category + example: 1 +RulesArray: + type: array + items: + $ref: '#/RuleObject' \ No newline at end of file diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml index b43ad81909..d37280d72c 100644 --- a/public/openapi/read.yaml +++ b/public/openapi/read.yaml @@ -66,6 +66,10 @@ tags: - name: other description: Other one-off routes that do not fit in a section of their own paths: + /sping: + $ref: 'read/sping.yaml' + /ping: + $ref: 'read/ping.yaml' /api/: $ref: 'read/index.yaml' /api/admin: @@ -130,6 +134,8 @@ paths: $ref: 'read/admin/manage/users.yaml' /api/admin/manage/users/custom-fields: $ref: 'read/admin/manage/users/custom-fields.yaml' + /api/admin/manage/users/custom-reasons: + $ref: 'read/admin/manage/users/custom-reasons.yaml' /api/admin/manage/registration: $ref: 'read/admin/manage/registration.yaml' /api/admin/manage/admins-mods: diff --git a/public/openapi/read/admin/analytics.yaml b/public/openapi/read/admin/analytics.yaml index 508325aace..67f8ed516c 100644 --- a/public/openapi/read/admin/analytics.yaml +++ b/public/openapi/read/admin/analytics.yaml @@ -53,6 +53,10 @@ get: items: type: number pageviews:guest: + type: array + items: + type: number + pageviews:ap: type: array items: type: number \ No newline at end of file diff --git a/public/openapi/read/admin/config.yaml b/public/openapi/read/admin/config.yaml index 2a72e6d6e1..a58405fb84 100644 --- a/public/openapi/read/admin/config.yaml +++ b/public/openapi/read/admin/config.yaml @@ -87,6 +87,8 @@ get: type: number maximumFileSize: type: number + convertPastedImageTo: + type: string theme:id: type: string theme:src: diff --git a/public/openapi/read/admin/development/info.yaml b/public/openapi/read/admin/development/info.yaml index 1eeb77c1b3..1bf9da12dc 100644 --- a/public/openapi/read/admin/development/info.yaml +++ b/public/openapi/read/admin/development/info.yaml @@ -51,14 +51,17 @@ get: type: number arrayBuffers: type: number - humanReadable: + rssReadable: + type: number + heapUsedReadable: type: number required: - rss - heapTotal - heapUsed - external - - humanReadable + - rssReadable + - heapUsedReadable uptime: type: number uptimeHumanReadable: diff --git a/public/openapi/read/admin/manage/categories.yaml b/public/openapi/read/admin/manage/categories.yaml index b4e6102ac1..c4c48b7a06 100644 --- a/public/openapi/read/admin/manage/categories.yaml +++ b/public/openapi/read/admin/manage/categories.yaml @@ -25,8 +25,14 @@ get: description: A category identifier name: type: string + nickname: + type: string + description: A custom name given to a remote category for de-duplication purposes (not available to local categories.) description: type: string + 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) disabled: type: number icon: @@ -48,6 +54,8 @@ get: type: string order: type: number + isLocal: + type: boolean subCategoriesPerPage: type: number children: diff --git a/public/openapi/read/admin/manage/privileges/cid.yaml b/public/openapi/read/admin/manage/privileges/cid.yaml index d4b0ed43da..c395f39172 100644 --- a/public/openapi/read/admin/manage/privileges/cid.yaml +++ b/public/openapi/read/admin/manage/privileges/cid.yaml @@ -21,19 +21,6 @@ get: privileges: type: object properties: - labels: - type: object - properties: - users: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name - groups: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name labelData: type: array items: diff --git a/public/openapi/read/admin/manage/users/custom-reasons.yaml b/public/openapi/read/admin/manage/users/custom-reasons.yaml new file mode 100644 index 0000000000..ea609e1709 --- /dev/null +++ b/public/openapi/read/admin/manage/users/custom-reasons.yaml @@ -0,0 +1,28 @@ +get: + tags: + - admin + summary: Manage ban reasons for users + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + reasons: + type: array + items: + type: object + properties: + key: + type: string + title: + type: string + body: + type: string + parsedBody: + type: string + description: "body parsed with filter:parse.raw hook" + - $ref: ../../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/read/admin/settings/activitypub.yaml b/public/openapi/read/admin/settings/activitypub.yaml index b0871999b6..48a0415fef 100644 --- a/public/openapi/read/admin/settings/activitypub.yaml +++ b/public/openapi/read/admin/settings/activitypub.yaml @@ -16,4 +16,8 @@ get: instanceCount: type: number description: The number of ActivityPub-enabled instances that this forum knows about. + rules: + $ref: ../../../components/schemas/admin/rules.yaml#/RulesArray + relays: + $ref: ../../../components/schemas/admin/relays.yaml#/RelaysArray - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/read/admin/settings/email.yaml b/public/openapi/read/admin/settings/email.yaml index 235d6cc833..60e1de451c 100644 --- a/public/openapi/read/admin/settings/email.yaml +++ b/public/openapi/read/admin/settings/email.yaml @@ -13,6 +13,10 @@ get: properties: title: type: string + emailerPlugin: + type: string + smtpEnabled: + type: boolean emails: type: array items: diff --git a/public/openapi/read/ap.yaml b/public/openapi/read/ap.yaml index 0704cc6dab..89bee103c5 100644 --- a/public/openapi/read/ap.yaml +++ b/public/openapi/read/ap.yaml @@ -14,8 +14,8 @@ get: name: resource schema: type: string - description: A URL to query for potential ActivityPub resource - example: 'https://example.org/ap' + description: A URL-encoded address to query for potential ActivityPub resource + example: 'https://try.nodebb.org/uid/1' responses: "200": description: Sent if the `/api` prefix is used. The `X-Redirect` header is sent with the redirection target. @@ -24,7 +24,7 @@ get: schema: type: string "307": - description: Redirect the user to the local representation or original URL. + description: Redirect the user to the local representation or /outgoing interstitial page for original URL. headers: Location: schema: diff --git a/public/openapi/read/category/category_id.yaml b/public/openapi/read/category/category_id.yaml index 28502d30f7..273e4c41e6 100644 --- a/public/openapi/read/category/category_id.yaml +++ b/public/openapi/read/category/category_id.yaml @@ -61,6 +61,9 @@ get: type: boolean isIgnored: type: boolean + hasFollowers: + type: boolean + nullable: true title: type: string selectCategoryLabel: diff --git a/public/openapi/read/config.yaml b/public/openapi/read/config.yaml index d6e012bdae..16d2043667 100644 --- a/public/openapi/read/config.yaml +++ b/public/openapi/read/config.yaml @@ -87,6 +87,8 @@ get: type: number maximumFileSize: type: number + convertPastedImageTo: + type: string theme:id: type: string theme:src: diff --git a/public/openapi/read/confirm/code.yaml b/public/openapi/read/confirm/code.yaml index 9677cb1a66..9d55b016c1 100644 --- a/public/openapi/read/confirm/code.yaml +++ b/public/openapi/read/confirm/code.yaml @@ -24,9 +24,6 @@ get: error: type: string description: Translation key for client-side localisation - alreadyValidated: - type: boolean - description: set to true if the email was already validated required: - title - $ref: ../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/read/ping.yaml b/public/openapi/read/ping.yaml new file mode 100644 index 0000000000..2000de8306 --- /dev/null +++ b/public/openapi/read/ping.yaml @@ -0,0 +1,13 @@ +get: + tags: + - ping + summary: Check if NodeBB is up + description: This route returns "200" and 200 status code if NodeBB is up and running. + responses: + "200": + description: "" + content: + text/plain: + schema: + type: string + example: "200" diff --git a/public/openapi/read/popular.yaml b/public/openapi/read/popular.yaml index 67c7d5030f..fe6a0a6480 100644 --- a/public/openapi/read/popular.yaml +++ b/public/openapi/read/popular.yaml @@ -60,6 +60,8 @@ get: type: string feeds:disableRSS: type: number + reputation:disabled: + type: number rssFeedUrl: type: string title: diff --git a/public/openapi/read/post-queue.yaml b/public/openapi/read/post-queue.yaml index 0ecb95500c..9bd93903c4 100644 --- a/public/openapi/read/post-queue.yaml +++ b/public/openapi/read/post-queue.yaml @@ -1,7 +1,7 @@ get: tags: - admin - summary: Get flag data + summary: Get post queue responses: "200": description: "" @@ -42,6 +42,8 @@ get: description: A user identifier type: type: string + canEdit: + type: boolean data: type: object properties: diff --git a/public/openapi/read/recent.yaml b/public/openapi/read/recent.yaml index 74d3d91a27..848a306b79 100644 --- a/public/openapi/read/recent.yaml +++ b/public/openapi/read/recent.yaml @@ -58,6 +58,8 @@ get: type: string feeds:disableRSS: type: number + reputation:disabled: + type: number rssFeedUrl: type: string title: diff --git a/public/openapi/read/sping.yaml b/public/openapi/read/sping.yaml new file mode 100644 index 0000000000..9ef9642622 --- /dev/null +++ b/public/openapi/read/sping.yaml @@ -0,0 +1,13 @@ +get: + tags: + - ping + summary: Check if NodeBB is up + description: This route returns "healthy" and 200 status code if NodeBB is up and running. + responses: + "200": + description: "" + content: + text/plain: + schema: + type: string + example: "healthy" diff --git a/public/openapi/read/top.yaml b/public/openapi/read/top.yaml index 8594ca9f14..6f0cbc9bc1 100644 --- a/public/openapi/read/top.yaml +++ b/public/openapi/read/top.yaml @@ -71,6 +71,8 @@ get: type: string feeds:disableRSS: type: number + reputation:disabled: + type: number rssFeedUrl: type: string title: diff --git a/public/openapi/read/topic/topic_id.yaml b/public/openapi/read/topic/topic_id.yaml index 302d39dbcc..81b3e9531f 100644 --- a/public/openapi/read/topic/topic_id.yaml +++ b/public/openapi/read/topic/topic_id.yaml @@ -212,6 +212,10 @@ get: isLocal: type: boolean description: Whether the user belongs to the local installation or not. + crossposts: + type: array + items: + $ref: ../../components/schemas/CrosspostObject.yaml#/CrosspostObject - type: object description: Optional properties that may or may not be present (except for `tid`, which is always present, and is only here as a hack to pass validation) properties: diff --git a/public/openapi/read/unread.yaml b/public/openapi/read/unread.yaml index e916231d65..6ae0e8500e 100644 --- a/public/openapi/read/unread.yaml +++ b/public/openapi/read/unread.yaml @@ -19,6 +19,8 @@ get: type: boolean showTopicTools: type: boolean + reputation:disabled: + type: number nextStart: type: number topics: @@ -107,6 +109,8 @@ get: `icon:text` for the user's auto-generated icon example: "#f44336" + banned_until: + type: number banned_until_readable: type: string required: @@ -138,6 +142,9 @@ get: description: A topic identifier content: type: string + sourceContent: + type: string + nullable: true timestampISO: type: string description: An ISO 8601 formatted date string (complementing `timestamp`) @@ -150,6 +157,10 @@ get: username: type: string description: A friendly name for a given user account + displayname: + type: string + isLocal: + type: boolean userslug: type: string description: An URL-safe variant of the username (i.e. lower-cased, spaces diff --git a/public/openapi/read/user/userslug/categories.yaml b/public/openapi/read/user/userslug/categories.yaml index f25a168f91..17454ffa09 100644 --- a/public/openapi/read/user/userslug/categories.yaml +++ b/public/openapi/read/user/userslug/categories.yaml @@ -42,6 +42,8 @@ get: type: string bgColor: type: string + backgroundImage: + type: string descriptionParsed: type: string depth: diff --git a/public/openapi/read/user/userslug/chats/roomid.yaml b/public/openapi/read/user/userslug/chats/roomid.yaml index 5c5fd1c296..448f350a42 100644 --- a/public/openapi/read/user/userslug/chats/roomid.yaml +++ b/public/openapi/read/user/userslug/chats/roomid.yaml @@ -56,6 +56,8 @@ get: type: array notificationOptionsIcon: type: string + joinLeaveMessages: + type: number messages: type: array items: @@ -122,6 +124,8 @@ get: `icon:text` for the user's auto-generated icon example: "#f44336" + banned_until: + type: number banned_until_readable: type: string deleted: @@ -360,6 +364,8 @@ get: type: string notificationSetting: type: number + joinLeaveMessages: + type: number publicRooms: type: array items: diff --git a/public/openapi/read/world.yaml b/public/openapi/read/world.yaml index 2b29fbc247..d4bf00a658 100644 --- a/public/openapi/read/world.yaml +++ b/public/openapi/read/world.yaml @@ -24,48 +24,14 @@ get: type: array items: type: string - topicCount: - type: number - topics: - type: array - items: - $ref: ../components/schemas/TopicObject.yaml#/TopicObject - # tids: - # type: array - # items: - # type: number - # canPost: - # type: boolean - # showSelect: - # type: boolean - # showTopicTools: - # type: boolean - # allCategoriesUrl: - # type: string - # selectedCategory: - # type: object - # properties: - # icon: - # type: string - # name: - # type: string - # bgColor: - # type: string - # nullable: true - # selectedCids: - # type: array - # items: - # type: number - selectedTag: - type: object - properties: - label: - type: string - nullable: true - selectedTags: - type: array - items: - type: string + posts: + $ref: ../components/schemas/PostsObject.yaml#/PostsObject + showThumbs: + type: boolean + showTopicTools: + type: boolean + showSelect: + type: boolean isWatched: type: boolean isTracked: @@ -74,92 +40,17 @@ get: type: boolean isIgnored: type: boolean - feeds:disableRSS: - type: number - rssFeedUrl: - type: string - reputation:disabled: - type: number + hasFollowers: + type: boolean + nullable: true title: type: string - privileges: - type: object - properties: - topics:create: - type: boolean - topics:read: - type: boolean - topics:tag: - type: boolean - topics:schedule: - type: boolean - read: - type: boolean - posts:view_deleted: - type: boolean - cid: - type: string - uid: - type: number - description: A user identifier - editable: - type: boolean - view_deleted: - type: boolean - isAdminOrMod: - type: boolean - # filters: - # type: array - # items: - # type: object - # properties: - # name: - # type: string - # url: - # type: string - # selected: - # type: boolean - # filter: - # type: string - # icon: - # type: string - # selectedFilter: - # type: object - # properties: - # name: - # type: string - # url: - # type: string - # selected: - # type: boolean - # filter: - # type: string - # icon: - # 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 + topicCount: + type: number + categories: + type: array + items: + $ref: ../components/schemas/CategoryObject.yaml#/CategoryObject - $ref: ../components/schemas/Pagination.yaml#/Pagination - $ref: ../components/schemas/Breadcrumbs.yaml#/Breadcrumbs - $ref: ../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index f7012e77a8..0741c799ed 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -168,6 +168,8 @@ paths: $ref: 'write/topics/tid/bump.yaml' /topics/{tid}/move: $ref: 'write/topics/tid/move.yaml' + /topics/{tid}/crossposts: + $ref: 'write/topics/tid/crossposts.yaml' /tags/{tag}/follow: $ref: 'write/tags/tag/follow.yaml' /posts/{pid}: @@ -202,6 +204,14 @@ paths: $ref: 'write/posts/pid/diffs/timestamp.yaml' /posts/{pid}/replies: $ref: 'write/posts/pid/replies.yaml' + /posts/queue/{id}: + $ref: 'write/posts/queue/id.yaml' + /posts/queue/{id}/notify: + $ref: 'write/posts/queue/notify.yaml' + /posts/{pid}/owner: + $ref: 'write/posts/pid/owner.yaml' + /posts/owner: + $ref: 'write/posts/owner.yaml' /chats/: $ref: 'write/chats.yaml' /chats/unread: @@ -266,6 +276,16 @@ paths: $ref: 'write/admin/chats/roomId.yaml' /admin/groups: $ref: 'write/admin/groups.yaml' + /admin/activitypub/rules: + $ref: 'write/admin/activitypub/rules.yaml' + /admin/activitypub/rules/{rid}: + $ref: 'write/admin/activitypub/rules/rid.yaml' + /admin/activitypub/rules/order: + $ref: 'write/admin/activitypub/rules/order.yaml' + /admin/activitypub/relays: + $ref: 'write/admin/activitypub/relays.yaml' + /admin/activitypub/relays/{url}: + $ref: 'write/admin/activitypub/relays/url.yaml' /files/: $ref: 'write/files.yaml' /files/folder: diff --git a/public/openapi/write/admin/activitypub/relays.yaml b/public/openapi/write/admin/activitypub/relays.yaml new file mode 100644 index 0000000000..0f58c7580d --- /dev/null +++ b/public/openapi/write/admin/activitypub/relays.yaml @@ -0,0 +1,28 @@ +post: + tags: + - admin + summary: add relay + description: This operation establishes a connection to a remote relay for content discovery purposes + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + url: + type: string + description: The relay actor endpoint + example: https://example.org/actor + responses: + '200': + description: rule successfully created + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + $ref: ../../../components/schemas/admin/relays.yaml#/RelaysArray diff --git a/public/openapi/write/admin/activitypub/relays/url.yaml b/public/openapi/write/admin/activitypub/relays/url.yaml new file mode 100644 index 0000000000..4f4b182ffb --- /dev/null +++ b/public/openapi/write/admin/activitypub/relays/url.yaml @@ -0,0 +1,25 @@ +delete: + tags: + - admin + summary: remove relay + description: This operation removes a pre-established relay connection + parameters: + - in: path + name: url + schema: + type: string + required: true + description: The relay actor endpoint, URL encoded. + example: https%3A%2F%2Fexample.org%2Factor + responses: + '200': + description: rule successfully deleted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../../components/schemas/Status.yaml#/Status + response: + $ref: ../../../../components/schemas/admin/relays.yaml#/RelaysArray diff --git a/public/openapi/write/admin/activitypub/rules.yaml b/public/openapi/write/admin/activitypub/rules.yaml new file mode 100644 index 0000000000..8a74425101 --- /dev/null +++ b/public/openapi/write/admin/activitypub/rules.yaml @@ -0,0 +1,36 @@ +post: + tags: + - admin + summary: create auto-categorization rule + description: This operation creates a new auto-categorization rule that is applied to new remote content received via ActivityPub. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + type: + type: string + description: The auto-categorization rule type + example: hashtag + value: + type: string + description: The value that incoming content will be matched against (used alongside `type`) + example: 'example' + cid: + type: number + description: The category ID of a local category + example: 1 + responses: + '200': + description: rule successfully created + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + $ref: ../../../components/schemas/admin/rules.yaml#/RulesArray diff --git a/public/openapi/write/admin/activitypub/rules/order.yaml b/public/openapi/write/admin/activitypub/rules/order.yaml new file mode 100644 index 0000000000..ef459958f1 --- /dev/null +++ b/public/openapi/write/admin/activitypub/rules/order.yaml @@ -0,0 +1,30 @@ +put: + tags: + - admin + summary: re-order auto-categorization rules + description: This operation updates the order of some or all auto-categorization rules. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + rids: + type: array + description: A list of rule IDs in the preferred order. Any omitted IDs will remain in the last-known order, which may conflict with the new ordering. + items: + type: string + example: ["be1aee05-2d56-484e-9d83-6212d0f8a033", "81b20d59-85af-44e7-9e52-dadf6ff3c7fb"] + responses: + '200': + description: rules successfully re-ordered + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../../components/schemas/Status.yaml#/Status + response: + $ref: ../../../../components/schemas/admin/rules.yaml#/RulesArray diff --git a/public/openapi/write/admin/activitypub/rules/rid.yaml b/public/openapi/write/admin/activitypub/rules/rid.yaml new file mode 100644 index 0000000000..08243c16a2 --- /dev/null +++ b/public/openapi/write/admin/activitypub/rules/rid.yaml @@ -0,0 +1,25 @@ +delete: + tags: + - admin + summary: delete auto-categorization rule + description: This operation deletes a previously set-up auto-categorization rule + parameters: + - in: path + name: rid + schema: + type: string + required: true + description: a valid rule ID + example: 4eb506f8-a173-4693-a41b-e23604bc973a + responses: + '200': + description: rule successfully deleted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../../components/schemas/Status.yaml#/Status + response: + $ref: ../../../../components/schemas/admin/rules.yaml#/RulesArray diff --git a/public/openapi/write/categories/cid/moderator/uid.yaml b/public/openapi/write/categories/cid/moderator/uid.yaml index 78ca52ca96..bbba82c7c6 100644 --- a/public/openapi/write/categories/cid/moderator/uid.yaml +++ b/public/openapi/write/categories/cid/moderator/uid.yaml @@ -31,19 +31,6 @@ put: response: type: object properties: - labels: - type: object - properties: - users: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name - groups: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name labelData: type: array items: @@ -92,6 +79,10 @@ put: type: number description: A Boolean representing whether a user is banned or not example: 0 + 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" @@ -99,7 +90,6 @@ put: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false types: type: object @@ -116,7 +106,6 @@ put: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false types: type: object diff --git a/public/openapi/write/categories/cid/privileges.yaml b/public/openapi/write/categories/cid/privileges.yaml index 6ed7c6fbf8..4760e03f5c 100644 --- a/public/openapi/write/categories/cid/privileges.yaml +++ b/public/openapi/write/categories/cid/privileges.yaml @@ -24,19 +24,6 @@ get: response: type: object properties: - labels: - type: object - properties: - users: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name - groups: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name labelData: type: array items: @@ -60,7 +47,6 @@ get: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false isPrivate: type: boolean @@ -78,7 +64,6 @@ get: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false types: type: object diff --git a/public/openapi/write/categories/cid/privileges/privilege.yaml b/public/openapi/write/categories/cid/privileges/privilege.yaml index d6985f27f3..9c7cba8882 100644 --- a/public/openapi/write/categories/cid/privileges/privilege.yaml +++ b/public/openapi/write/categories/cid/privileges/privilege.yaml @@ -41,19 +41,6 @@ put: response: type: object properties: - labels: - type: object - properties: - users: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name - groups: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name labelData: type: array items: @@ -106,7 +93,6 @@ put: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false groups: type: array @@ -120,7 +106,6 @@ put: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false types: type: object @@ -191,19 +176,6 @@ delete: response: type: object properties: - labels: - type: object - properties: - users: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name - groups: - type: array - items: - type: string - description: Language key of the privilege name's user-friendly name labelData: type: array items: @@ -256,7 +228,6 @@ delete: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false groups: type: array @@ -270,7 +241,6 @@ delete: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false types: type: object diff --git a/public/openapi/write/categories/cid/topics.yaml b/public/openapi/write/categories/cid/topics.yaml index a14664b20c..fd36d8e417 100644 --- a/public/openapi/write/categories/cid/topics.yaml +++ b/public/openapi/write/categories/cid/topics.yaml @@ -71,5 +71,4 @@ get: privileges: type: object additionalProperties: - type: boolean description: A set of privileges with either true or false \ No newline at end of file diff --git a/public/openapi/write/chats/roomId/messages/mid.yaml b/public/openapi/write/chats/roomId/messages/mid.yaml index 5053f1546d..c899627802 100644 --- a/public/openapi/write/chats/roomId/messages/mid.yaml +++ b/public/openapi/write/chats/roomId/messages/mid.yaml @@ -49,7 +49,7 @@ put: type: number required: true description: a valid message id - example: 5 + example: 2 requestBody: required: true content: @@ -92,7 +92,7 @@ delete: type: number required: true description: a valid message id - example: 5 + example: 2 responses: '200': description: Message successfully deleted @@ -125,7 +125,7 @@ post: type: number required: true description: a valid message id - example: 5 + example: 2 responses: '200': description: message successfully restored diff --git a/public/openapi/write/chats/roomId/messages/mid/ip.yaml b/public/openapi/write/chats/roomId/messages/mid/ip.yaml index 0d2a82cba9..2c2af8fb1b 100644 --- a/public/openapi/write/chats/roomId/messages/mid/ip.yaml +++ b/public/openapi/write/chats/roomId/messages/mid/ip.yaml @@ -17,7 +17,7 @@ get: type: string required: true description: a valid chat message id - example: 5 + example: 2 responses: '200': description: Chat message ip address retrieved diff --git a/public/openapi/write/posts/owner.yaml b/public/openapi/write/posts/owner.yaml new file mode 100644 index 0000000000..98283f8e96 --- /dev/null +++ b/public/openapi/write/posts/owner.yaml @@ -0,0 +1,39 @@ +post: + tags: + - posts + summary: Change owner of one or more posts + description: Change the owner of the posts identified by pids to the user uid. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - pids + - uid + properties: + pids: + type: array + items: + type: integer + description: Array of post IDs to change owner for + example: [2] + uid: + type: integer + description: Target user id + example: 1 + responses: + '200': + description: Owner changed successfully + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../components/schemas/Status.yaml#/Status + response: + type: object + '404': + description: Post not found diff --git a/public/openapi/write/posts/pid/diffs.yaml b/public/openapi/write/posts/pid/diffs.yaml index ea76f7ea66..ca8dd91ac7 100644 --- a/public/openapi/write/posts/pid/diffs.yaml +++ b/public/openapi/write/posts/pid/diffs.yaml @@ -24,6 +24,10 @@ get: response: type: object properties: + uid: + type: number + pid: + type: number timestamps: type: array items: @@ -37,6 +41,8 @@ get: type: string username: type: string + uid: + type: number editable: type: boolean deletable: diff --git a/public/openapi/write/posts/pid/owner.yaml b/public/openapi/write/posts/pid/owner.yaml new file mode 100644 index 0000000000..0e1a701faa --- /dev/null +++ b/public/openapi/write/posts/pid/owner.yaml @@ -0,0 +1,39 @@ +put: + summary: Change owner of a post + description: Change the owner (uid) of a post identified by pid. + tags: + - posts + parameters: + - name: pid + in: path + description: Post id + required: true + schema: + type: integer + example: 2 + requestBody: + description: New owner payload + required: true + content: + application/json: + schema: + type: object + required: + - uid + properties: + uid: + type: integer + description: User id of the new owner + example: 2 + responses: + '200': + description: Owner changed successfully + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object diff --git a/public/openapi/write/posts/queue/id.yaml b/public/openapi/write/posts/queue/id.yaml new file mode 100644 index 0000000000..00bc01d303 --- /dev/null +++ b/public/openapi/write/posts/queue/id.yaml @@ -0,0 +1,92 @@ +post: + summary: Accept a queued post + tags: + - QueuedPosts + parameters: + - in: path + name: id + schema: + type: string + required: true + description: a valid queued post id + example: 2 + responses: + '200': + description: post successfully accepted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + type: + type: string + pid: + type: number + tid: + type: number + '400': + description: Bad request, invalid post id +delete: + summary: Remove a queued post + tags: + - QueuedPosts + parameters: + - name: id + in: path + required: true + schema: + type: string + example: 'topic-12345' + responses: + '200': + description: Post removed successfully + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + '400': + description: Bad request, invalid post id +put: + summary: Edit a queued post + tags: + - QueuedPosts + parameters: + - name: id + in: path + required: true + schema: + type: string + example: 'topic-12345' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + example: This is a test reply + cid: + type: number + description: Category ID to which the post belongs + title: + type: string + description: Updated Post Title + responses: + '200': + description: Post edited successfully + '400': + description: Bad request, invalid post id + + diff --git a/public/openapi/write/posts/queue/notify.yaml b/public/openapi/write/posts/queue/notify.yaml new file mode 100644 index 0000000000..8569d9b232 --- /dev/null +++ b/public/openapi/write/posts/queue/notify.yaml @@ -0,0 +1,36 @@ +post: + summary: Notify the owner of a queued post + tags: + - QueuedPosts + parameters: + - in: path + name: id + schema: + type: string + required: true + description: a valid queued post id + example: 2 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: body of the notification message + responses: + '200': + description: post successfully accepted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + '400': + description: Bad request, invalid post id \ No newline at end of file diff --git a/public/openapi/write/topics/tid/crossposts.yaml b/public/openapi/write/topics/tid/crossposts.yaml new file mode 100644 index 0000000000..f292292e0a --- /dev/null +++ b/public/openapi/write/topics/tid/crossposts.yaml @@ -0,0 +1,104 @@ +get: + tags: + - topics + summary: get topic crossposts + description: This operation retrieves a list of crossposts for the requested topic + parameters: + - in: path + name: tid + schema: + type: string + required: true + description: a valid topic id + example: 1 + responses: + '200': + description: Topic crossposts retrieved + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + crossposts: + $ref: ../../../components/schemas/CrosspostObject.yaml#/CrosspostsArray +post: + tags: + - topics + summary: crosspost a topic + description: This operation crossposts a topic to another category. + parameters: + - in: path + name: tid + schema: + type: string + required: true + description: a valid topic id + example: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + cid: + type: number + example: 1 + responses: + '200': + description: Topic successfully crossposted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + crossposts: + $ref: ../../../components/schemas/CrosspostObject.yaml#/CrosspostsArray +delete: + tags: + - topics + summary: uncrossposts a topic + description: This operation uncrossposts a topic from a category. + parameters: + - in: path + name: tid + schema: + type: string + required: true + description: a valid topic id + example: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + cid: + type: number + example: 1 + responses: + '200': + description: Topic successfully uncrossposted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + crossposts: + $ref: ../../../components/schemas/CrosspostObject.yaml#/CrosspostsArray \ No newline at end of file diff --git a/public/openapi/write/topics/tid/thumbs.yaml b/public/openapi/write/topics/tid/thumbs.yaml index 3817d2a5a3..5d28264266 100644 --- a/public/openapi/write/topics/tid/thumbs.yaml +++ b/public/openapi/write/topics/tid/thumbs.yaml @@ -83,55 +83,6 @@ post: type: string name: type: string -put: - tags: - - topics - summary: migrate topic thumbnail - description: This operation migrates a thumbnails from a topic or draft, to another tid or draft. - parameters: - - in: path - name: tid - schema: - type: string - required: true - description: a valid topic id or draft uuid - example: 1 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - tid: - type: string - description: a valid topic id or draft uuid - example: '1' - responses: - '200': - description: Topic thumbnails migrated - content: - application/json: - schema: - type: object - properties: - status: - $ref: ../../../components/schemas/Status.yaml#/Status - response: - type: array - description: A list of the topic thumbnails in the destination topic - items: - type: object - properties: - id: - type: string - name: - type: string - path: - type: string - url: - type: string - description: Path to a topic thumbnail delete: tags: - topics diff --git a/public/openapi/write/topics/tid/thumbs/order.yaml b/public/openapi/write/topics/tid/thumbs/order.yaml index a0f1602bc8..a8acefbf0a 100644 --- a/public/openapi/write/topics/tid/thumbs/order.yaml +++ b/public/openapi/write/topics/tid/thumbs/order.yaml @@ -2,14 +2,14 @@ put: tags: - topics summary: reorder topic thumbnail - description: This operation sets the order for a topic thumbnail. It can handle either topics (if a valid `tid` is passed in), or drafts. A 404 is returned if the topic or draft does not actually contain that thumbnail path. Paths passed in should **not** contain the path to the uploads folder (`config.upload_url` on client side) + description: This operation sets the order for a topic thumbnail. A 404 is returned if the topic does not contain path. Paths passed in should **not** contain the path to the uploads folder (`config.upload_url` on client side) parameters: - in: path name: tid schema: type: string required: true - description: a valid topic id or draft uuid + description: a valid topic id example: 2 requestBody: required: true diff --git a/public/scss/admin/fonts.scss b/public/scss/admin/fonts.scss index ba9988154d..d13da2cc7b 100644 --- a/public/scss/admin/fonts.scss +++ b/public/scss/admin/fonts.scss @@ -1,20 +1,24 @@ -@use "@fontsource/inter/scss/mixins" as Inter; -@use "@fontsource/poppins/scss/mixins" as Poppins; +@use "pkg:@fontsource-utils/scss" as fontsource; +@use "pkg:@fontsource/inter/scss" as inter; +@use "pkg:@fontsource/poppins/scss" as poppins; $weights: $font-weight-light, $font-weight-normal, $font-weight-semibold, $font-weight-bold; $subsets: (latin, latin-ext); -@include Inter.faces( - $weights: $weights, - $subsets: $subsets, - $display: fallback, - $directory: "./plugins/core/inter" +@include fontsource.faces( + $metadata: inter.$metadata, + $subsets: $subsets, + $weights: $weights, + $styles: all, + $directory: "./plugins/core/inter" ); -@include Poppins.faces( - $weights: $weights, - $subsets: $subsets, - $display: fallback, - $directory: "./plugins/core/poppins" + +@include fontsource.faces( + $metadata: poppins.$metadata, + $subsets: $subsets, + $weights: $weights, + $styles: all, + $directory: "./plugins/core/poppins" ); .ff-base { font-family: $font-family-base !important; } diff --git a/public/scss/generics.scss b/public/scss/generics.scss index 808d427ba5..f2c50ee44e 100644 --- a/public/scss/generics.scss +++ b/public/scss/generics.scss @@ -23,11 +23,13 @@ display: block; } } -.dropdown-left { - .dropdown-menu { --bs-position: start; } +html[data-dir="ltr"] { + .dropdown-left .dropdown-menu { --bs-position: start; } + .dropdown-right .dropdown-menu { --bs-position: end; } } -.dropdown-right { - .dropdown-menu { --bs-position: end; } +html[data-dir="rtl"] { + .dropdown-left .dropdown-menu { --bs-position: end; } + .dropdown-right .dropdown-menu { --bs-position: start; } } .category-dropdown-menu { @@ -143,6 +145,38 @@ blockquote { } } +.text-contain { + min-width: 0; + word-break: break-word; + overflow-wrap: anywhere; + > * { + max-width: 100%; + white-space: pre-wrap; + word-break: break-word; + overflow-wrap: anywhere; + } +} + +.hidden-blockquote { blockquote { display: none; } } +.hidden-pre { pre { display: none; } } + +$hidden-elements-for-first-last-child: br, hr; + +@each $element in $hidden-elements-for-first-last-child { + .hidden-first-child-#{$element} { + > #{$element}:first-child { + display: none; + } + } + .hidden-last-child-#{$element} { + > #{$element}:last-child { + display: none; + } + } +} + +.trim-last-divider > *:last-child hr { display: none; } + // some classes that are used commonly in themes from bs3 .hidden, .hide { display: none!important; diff --git a/public/scss/jquery-ui.scss b/public/scss/jquery-ui.scss index ea14c5ca97..32348f21fc 100644 --- a/public/scss/jquery-ui.scss +++ b/public/scss/jquery-ui.scss @@ -1,7 +1,6 @@ @import 'jquery-ui/themes/base/core'; @import 'jquery-ui/themes/base/menu'; @import 'jquery-ui/themes/base/button'; -@import 'jquery-ui/themes/base/datepicker'; @import 'jquery-ui/themes/base/autocomplete'; @import 'jquery-ui/themes/base/resizable'; @import 'jquery-ui/themes/base/selectable'; diff --git a/public/scss/mixins.scss b/public/scss/mixins.scss index c38a4d13b3..4c75a264c5 100644 --- a/public/scss/mixins.scss +++ b/public/scss/mixins.scss @@ -111,6 +111,28 @@ } } +// Use on elements with `line-clamp-*` classes +@mixin clamp-fade($lines) { + position: relative; + cursor: pointer; + + &.line-clamp-#{$lines}::before { + content: ''; + position: absolute; + top: calc(1.5em * ($lines - 2)); + display: block; + width: 100%; + height: 3em; + background: linear-gradient(180deg, transparent 0, $body-bg 100%); + } +} + +@for $i from 1 through 6 { + .clamp-fade-#{$i} { + @include clamp-fade($i); + } +} + @each $color, $value in $grays { .border-gray-#{$color} { border-color: $value !important; diff --git a/public/scss/modules/bottom-sheet.scss b/public/scss/modules/bottom-sheet.scss index 339e000a8b..94d5fa1508 100644 --- a/public/scss/modules/bottom-sheet.scss +++ b/public/scss/modules/bottom-sheet.scss @@ -45,6 +45,7 @@ width: auto; } + // this does not look to be used anymore .dropdown-backdrop { background-color: rgba(0, 0, 0, .3); } diff --git a/public/scss/skins.scss b/public/scss/skins.scss index 4940bbbe08..4a330c521f 100644 --- a/public/scss/skins.scss +++ b/public/scss/skins.scss @@ -1,6 +1,13 @@ // fixes for global skin issues +// brite text-secondary is white :/ +.skin-brite { + .text-secondary { + color: var(--bs-secondary-color) !important; + } +} + // fix minty buttons -.skin-minty .btn{ +.skin-minty .btn { color: initial!important; } \ No newline at end of file diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index e0d4e49b7f..c9bbd2bc5b 100644 --- a/public/src/admin/admin.js +++ b/public/src/admin/admin.js @@ -9,43 +9,12 @@ require('../../scripts-admin'); app.onDomReady(); (function () { - let logoutTimer = 0; - let logoutMessage; - function startLogoutTimer() { - if (app.config.adminReloginDuration <= 0) { - return; - } - if (logoutTimer) { - clearTimeout(logoutTimer); - } - // pre-translate language string gh#9046 - if (!logoutMessage) { - require(['translator'], function (translator) { - translator.translate('[[login:logged-out-due-to-inactivity]]', function (translated) { - logoutMessage = translated; - }); - }); - } - - logoutTimer = setTimeout(function () { - require(['bootbox'], function (bootbox) { - bootbox.alert({ - closeButton: false, - message: logoutMessage, - callback: function () { - window.location.reload(); - }, - }); - }); - }, 3600000); - } - - require(['hooks', 'admin/settings'], (hooks, Settings) => { + require(['hooks', 'admin/settings', 'admin/modules/relogin-timer'], (hooks, Settings, reloginTimer) => { hooks.on('action:ajaxify.end', (data) => { updatePageTitle(data.url); setupRestartLinks(); showCorrectNavTab(); - startLogoutTimer(); + reloginTimer.start(app.config.adminReloginDuration); $('[data-bs-toggle="tooltip"]').tooltip({ animation: false, @@ -59,6 +28,7 @@ app.onDomReady(); Settings.populateTOC(); } }); + hooks.on('action:ajaxify.start', function () { require(['bootstrap'], function (boostrap) { const offcanvas = boostrap.Offcanvas.getInstance('#offcanvas'); @@ -147,23 +117,17 @@ app.onDomReady(); fallback = $(this).text(); }); - let mainTitle; let pageTitle; if (/admin\/plugins\//.test(url)) { - mainTitle = fallback; - pageTitle = '[[admin/menu:section-plugins]] > ' + mainTitle; + pageTitle = '[[admin/menu:section-plugins]] > ' + fallback; } else { const matches = url.match(/admin\/(.+?)\/(.+?)$/); if (matches) { - mainTitle = '[[admin/menu:' + matches[1] + '/' + matches[2] + ']]'; + const mainTitle = '[[admin/menu:' + matches[1] + '/' + matches[2] + ']]'; pageTitle = '[[admin/menu:section-' + (matches[1] === 'development' ? 'advanced' : matches[1]) + ']]' + (matches[2] ? (' > ' + mainTitle) : ''); - if (matches[2] === 'settings') { - mainTitle = translator.compile('admin/menu:settings.page-title', mainTitle); - } } else { - mainTitle = '[[admin/menu:section-dashboard]]'; pageTitle = '[[admin/menu:section-dashboard]]'; } } diff --git a/public/src/admin/advanced/cache.js b/public/src/admin/advanced/cache.js index 4b77cfb42b..ce11ea6581 100644 --- a/public/src/admin/advanced/cache.js +++ b/public/src/admin/advanced/cache.js @@ -27,6 +27,46 @@ define('admin/advanced/cache', ['alerts'], function (alerts) { } }); }); + + $(document).on('click', '#cache-table th', function () { + const table = $(this).closest('table'); + const tbody = table.find('tbody'); + const columnIndex = $(this).index(); + + const rows = tbody.find('tr').toArray(); + + // Toggle sort direction + const ascending = !!$(this).data('asc'); + $(this).data('asc', !ascending); + + // Remove sort indicators from all headers + table.find('th i').addClass('invisible'); + + $(this).find('i').removeClass('invisible') + .toggleClass('fa-sort-up', ascending) + .toggleClass('fa-sort-down', !ascending); + + rows.sort(function (a, b) { + const A = $(a).children().eq(columnIndex).attr('data-sort-value').trim(); + const B = $(b).children().eq(columnIndex).attr('data-sort-value').trim(); + // Remove thousands separators + const cleanA = A.replace(/,/g, ''); + const cleanB = B.replace(/,/g, ''); + + const numA = parseFloat(cleanA); + const numB = parseFloat(cleanB); + + if (!isNaN(numA) && !isNaN(numB)) { + return ascending ? numA - numB : numB - numA; + } + + return ascending ? + A.localeCompare(B) : + B.localeCompare(A); + }); + + tbody.append(rows); + }); }; return Cache; }); diff --git a/public/src/admin/dashboard.js b/public/src/admin/dashboard.js index a2b624c5a8..0b5d110656 100644 --- a/public/src/admin/dashboard.js +++ b/public/src/admin/dashboard.js @@ -159,27 +159,33 @@ function setupGraphs(callback) { Chart.defaults.plugins.tooltip.enabled = false; } - const t = translator.Translator.create(); - Promise.all([ - t.translateKey('admin/dashboard:graphs.page-views', []), - t.translateKey('admin/dashboard:graphs.page-views-registered', []), - t.translateKey('admin/dashboard:graphs.page-views-guest', []), - t.translateKey('admin/dashboard:graphs.page-views-bot', []), - t.translateKey('admin/dashboard:graphs.unique-visitors', []), - t.translateKey('admin/dashboard:graphs.registered-users', []), - t.translateKey('admin/dashboard:graphs.guest-users', []), - t.translateKey('admin/dashboard:on-categories', []), - t.translateKey('admin/dashboard:reading-posts', []), - t.translateKey('admin/dashboard:browsing-topics', []), - t.translateKey('admin/dashboard:recent', []), - t.translateKey('admin/dashboard:unread', []), - ]).then(function (translations) { + const keys = [ + '[[admin/dashboard:graphs.page-views]]', + '[[admin/dashboard:graphs.page-views-registered]]', + '[[admin/dashboard:graphs.page-views-guest]]', + '[[admin/dashboard:graphs.page-views-bot]]', + '[[admin/dashboard:graphs.page-views-ap]]', + '[[admin/dashboard:graphs.unique-visitors]]', + '[[admin/dashboard:graphs.registered-users]]', + '[[admin/dashboard:graphs.guest-users]]', + '[[admin/dashboard:on-categories]]', + '[[admin/dashboard:reading-posts]]', + '[[admin/dashboard:browsing-topics]]', + '[[admin/dashboard:recent]]', + '[[admin/dashboard:unread]]', + ]; + const graphLabels = {}; + translator.translateKeys(keys, config.acpLang).then(function (translations) { + keys.forEach(function (key, index) { + graphLabels[key.split(':')[1].slice(0, -2)] = translations[index]; + }); + const tension = 0.25; const data = { labels: trafficLabels, datasets: [ { - label: translations[0], + label: graphLabels['graphs.page-views'], fill: 'origin', tension: tension, backgroundColor: 'rgba(220,220,220,0.2)', @@ -191,7 +197,7 @@ function setupGraphs(callback) { data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }, { - label: translations[1], + label: graphLabels['graphs.page-views-registered'], fill: 'origin', tension: tension, backgroundColor: '#ab464233', @@ -203,7 +209,7 @@ function setupGraphs(callback) { data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }, { - label: translations[2], + label: graphLabels['graphs.page-views-guest'], fill: 'origin', tension: tension, backgroundColor: '#ba8baf33', @@ -215,7 +221,7 @@ function setupGraphs(callback) { data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }, { - label: translations[3], + label: graphLabels['graphs.page-views-bot'], fill: 'origin', tension: tension, backgroundColor: '#f7ca8833', @@ -227,7 +233,19 @@ function setupGraphs(callback) { data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }, { - label: translations[4], + label: graphLabels['graphs.page-views-ap'], + fill: 'origin', + tension: tension, + backgroundColor: 'rgba(151,187,205,0.2)', + borderColor: 'rgba(110, 187, 132, 1)', + pointBackgroundColor: 'rgba(110, 187, 132, 1)', + pointHoverBackgroundColor: 'rgba(110, 187, 132, 1)', + pointBorderColor: '#fff', + pointHoverBorderColor: 'rgba(110, 187, 132, 1)', + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }, + { + label: graphLabels['graphs.unique-visitors'], fill: 'origin', tension: tension, backgroundColor: 'rgba(151,187,205,0.2)', @@ -247,7 +265,8 @@ function setupGraphs(callback) { data.datasets[1].yAxisID = 'left-y-axis'; data.datasets[2].yAxisID = 'left-y-axis'; data.datasets[3].yAxisID = 'left-y-axis'; - data.datasets[4].yAxisID = 'right-y-axis'; + data.datasets[4].yAxisID = 'left-y-axis'; + data.datasets[5].yAxisID = 'right-y-axis'; graphs.traffic = new Chart(trafficCtx, { type: 'line', @@ -260,7 +279,7 @@ function setupGraphs(callback) { type: 'linear', title: { display: true, - text: translations[0], + text: graphLabels['graphs.page-views'], }, beginAtZero: true, }, @@ -269,7 +288,7 @@ function setupGraphs(callback) { type: 'linear', title: { display: true, - text: translations[4], + text: graphLabels['graphs.unique-visitors'], }, beginAtZero: true, }, @@ -297,7 +316,7 @@ function setupGraphs(callback) { graphs.registered = new Chart(registeredCtx, { type: 'doughnut', data: { - labels: translations.slice(5, 7), + labels: [graphLabels['graphs.registered-users'], graphLabels['graphs.guest-users']], datasets: [{ data: [1, 1], backgroundColor: ['#F7464A', '#46BFBD'], @@ -310,7 +329,9 @@ function setupGraphs(callback) { graphs.presence = new Chart(presenceCtx, { type: 'doughnut', data: { - labels: translations.slice(7, 12), + labels: [ + graphLabels['on-categories'], graphLabels['reading-posts'], graphLabels['browsing-topics'], graphLabels['recent'], graphLabels['unread'], + ], datasets: [{ data: [1, 1, 1, 1, 1], backgroundColor: ['#F7464A', '#46BFBD', '#FDB45C', '#949FB1', '#9FB194'], @@ -446,7 +467,8 @@ function updateTrafficGraph(units, until, amount) { graphs.traffic.data.datasets[1].data = data.pageviewsRegistered; graphs.traffic.data.datasets[2].data = data.pageviewsGuest; graphs.traffic.data.datasets[3].data = data.pageviewsBot; - graphs.traffic.data.datasets[4].data = data.uniqueVisitors; + graphs.traffic.data.datasets[4].data = data.appageviews; + graphs.traffic.data.datasets[5].data = data.uniqueVisitors; graphs.traffic.data.labels = graphs.traffic.data.xLabels; graphs.traffic.update(); diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js index a70a6d17f0..5e73d9622f 100644 --- a/public/src/admin/extend/plugins.js +++ b/public/src/admin/extend/plugins.js @@ -24,7 +24,10 @@ define('admin/extend/plugins', [ pluginID = pluginEl.attr('data-plugin-id'); const btn = $(this); - const pluginData = ajaxify.data.installed[pluginEl.attr('data-plugin-index')]; + const pluginData = ajaxify.data.installed.find(plugin => plugin.id === pluginID); + if (!pluginData) { + return; + } function toggleActivate() { socket.emit('admin.plugins.toggleActive', pluginID, function (err, status) { diff --git a/public/src/admin/extend/rewards.js b/public/src/admin/extend/rewards.js index 805b217193..25119c3a27 100644 --- a/public/src/admin/extend/rewards.js +++ b/public/src/admin/extend/rewards.js @@ -84,14 +84,10 @@ define('admin/extend/rewards', [ let inputs; let html = ''; - for (const reward in available) { - if (available.hasOwnProperty(reward)) { - if (available[reward].rid === el.attr('data-selected')) { - inputs = available[reward].inputs; - parent.attr('data-rid', available[reward].rid); - break; - } - } + const selectedReward = available.find(reward => reward.rid === el.attr('data-selected')); + if (selectedReward) { + inputs = selectedReward.inputs; + parent.attr('data-rid', selectedReward.rid); } if (!inputs) { @@ -122,10 +118,8 @@ define('admin/extend/rewards', [ const div = $(this).find('.inputs'); const rewards = active[i].rewards; - for (const reward in rewards) { - if (rewards.hasOwnProperty(reward)) { - div.find('[name="' + reward + '"]').val(rewards[reward]); - } + for (const [reward, value] of Object.entries(rewards)) { + div.find('[name="' + reward + '"]').val(value); } }); } diff --git a/public/src/admin/extend/widgets.js b/public/src/admin/extend/widgets.js index 1238ee772b..73ba9a046a 100644 --- a/public/src/admin/extend/widgets.js +++ b/public/src/admin/extend/widgets.js @@ -7,7 +7,6 @@ define('admin/extend/widgets', [ 'jquery-ui/widgets/sortable', 'jquery-ui/widgets/draggable', 'jquery-ui/widgets/droppable', - 'jquery-ui/widgets/datepicker', ], function (bootbox, alerts) { const Widgets = {}; @@ -74,7 +73,6 @@ define('admin/extend/widgets', [ $('#widgets .widget-area').sortable({ update: function (event, ui) { - createDatePicker(ui.item); appendToggle(ui.item); }, start: function () { @@ -117,23 +115,21 @@ define('admin/extend/widgets', [ area.find('.widget-panel[data-widget]').each(function () { const widgetData = {}; const data = $(this).find('form').serializeArray(); - - for (const d in data) { - if (data.hasOwnProperty(d)) { - if (data[d].name) { - if (widgetData[data[d].name]) { - if (!Array.isArray(widgetData[data[d].name])) { - widgetData[data[d].name] = [ - widgetData[data[d].name], - ]; - } - widgetData[data[d].name].push(data[d].value); - } else { - widgetData[data[d].name] = data[d].value; + data.forEach((widgetField) => { + const { name, value } = widgetField; + if (name) { + if (widgetData[name]) { + if (!Array.isArray(widgetData[name])) { + widgetData[name] = [ + widgetData[name], + ]; } + widgetData[name].push(value); + } else { + widgetData[name] = value; } } - } + }); widgets.push({ widget: $(this).attr('data-widget'), @@ -180,15 +176,6 @@ define('admin/extend/widgets', [ }); } - function createDatePicker(el) { - const currentYear = new Date().getFullYear(); - el.find('.date-selector').datepicker({ - changeMonth: true, - changeYear: true, - yearRange: currentYear + ':' + (currentYear + 100), - }); - } - function appendToggle(el) { if (!el.hasClass('block')) { el.addClass('block').css('width', '').css('height', '') @@ -245,7 +232,6 @@ define('admin/extend/widgets', [ widgetArea.append(populateWidget(widgetEl, widgetData.data)); appendToggle(widgetEl); - createDatePicker(widgetEl); } } diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 7342d0c1de..5478beab39 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -27,6 +27,7 @@ define('admin/manage/categories', [ Categories.render(ajaxify.data.categoriesTree); $('button[data-action="create"]').on('click', Categories.throwCreateModal); + $('button[data-action="add"]').on('click', Categories.throwAddModal); // Enable/Disable toggle events $('.categories').on('click', '.category-tools [data-action="toggle"]', function () { @@ -68,7 +69,7 @@ define('admin/manage/categories', [ if (val && cid) { const modified = {}; modified[cid] = { order: Math.max(1, parseInt(val, 10)) }; - api.put('/categories/' + cid, modified[cid]).then(function () { + api.put('/categories/' + encodeURIComponent(cid), modified[cid]).then(function () { ajaxify.refresh(); }).catch(alerts.error); } else { @@ -80,6 +81,22 @@ define('admin/manage/categories', [ }); }); + $('.categories').on('click', 'a[data-action]', function () { + const action = this.getAttribute('data-action'); + + switch (action) { + case 'remove': { + Categories.remove.call(this); + break; + } + + case 'rename': { + Categories.rename.call(this); + break; + } + } + }); + $('#toggle-collapse-all').on('click', function () { const $this = $(this); const isCollapsed = parseInt($this.attr('data-collapsed'), 10) === 1; @@ -151,6 +168,53 @@ define('admin/manage/categories', [ }); }; + Categories.throwAddModal = function () { + Benchpress.render('admin/partials/categories/add', {}).then(function (html) { + const modal = bootbox.dialog({ + title: '[[admin/manage/categories:alert.add]]', + message: html, + buttons: { + save: { + label: '[[global:save]]', + className: 'btn-primary', + callback: submit, + }, + }, + }); + + function submit() { + const formData = modal.find('form').serializeObject(); + api.post('/api/admin/manage/categories', formData).then(() => { + ajaxify.refresh(); + modal.modal('hide'); + }).catch(alerts.error); + return false; + } + + modal.find('form').on('submit', submit); + }); + }; + + Categories.remove = function () { + bootbox.confirm('[[admin/manage/categories:alert.confirm-remove]]', (ok) => { + if (ok) { + const cid = this.getAttribute('data-cid'); + api.del(`/api/admin/manage/categories/${encodeURIComponent(cid)}`).then(ajaxify.refresh); + } + }); + }; + + Categories.rename = function () { + bootbox.prompt({ + title: '[[admin/manage/categories:alert.rename]]', + message: '

[[admin/manage/categories:alert.rename-help]]

', + callback: (name) => { + const cid = this.getAttribute('data-cid'); + api.post(`/api/admin/manage/categories/${encodeURIComponent(cid)}/name`, { name }).then(ajaxify.refresh); + }, + }); + }; + Categories.create = function (payload) { api.post('/categories', payload, function (err, data) { if (err) { @@ -187,7 +251,7 @@ define('admin/manage/categories', [ Categories.toggle = function (cids, disabled) { const listEl = document.querySelector('.categories [data-cid="0"]'); - Promise.all(cids.map(cid => api.put('/categories/' + cid, { + Promise.all(cids.map(cid => api.put('/categories/' + encodeURIComponent(cid), { disabled: disabled ? 1 : 0, }).then(() => { const categoryEl = listEl.querySelector(`li[data-cid="${cid}"]`); @@ -239,7 +303,7 @@ define('admin/manage/categories', [ } newCategoryId = -1; - api.put('/categories/' + cid, modified[cid]).catch(alerts.error); + api.put('/categories/' + encodeURIComponent(cid), modified[cid]).catch(alerts.error); } } diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index 1e91b17c0b..c856f0b487 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -88,7 +88,7 @@ define('admin/manage/group', [ bootbox.confirm('[[admin/manage/groups:alerts.confirm-delete]]', function (confirm) { if (confirm) { api.del(`/groups/${slugify(ajaxify.data.group.name)}`, {}).then(() => { - ajaxify.go('/admin/managegroups'); + ajaxify.go('/admin/manage/groups'); }).catch(alerts.error); } }); diff --git a/public/src/admin/manage/groups.js b/public/src/admin/manage/groups.js index ba3d11f258..a12881af26 100644 --- a/public/src/admin/manage/groups.js +++ b/public/src/admin/manage/groups.js @@ -40,7 +40,6 @@ define('admin/manage/groups', [ const createModal = $('#create-modal'); const createGroupName = $('#create-group-name'); const createModalGo = $('#create-modal-go'); - const createModalError = $('#create-modal-error'); createGroupName.trigger('focus'); createModal.on('keypress', function (e) { @@ -61,18 +60,12 @@ define('admin/manage/groups', [ }; api.post('/groups', submitObj).then((response) => { - createModalError.addClass('hide'); createGroupName.val(''); createModal.on('hidden.bs.modal', function () { ajaxify.go('admin/manage/groups/' + response.name); }); createModal.modal('hide'); - }).catch((err) => { - if (!utils.hasLanguageKey(err.status.message)) { - err.status.message = '[[admin/manage/groups:alerts.create-failure]]'; - } - createModalError.translateHtml(err.status.message).removeClass('hide'); - }); + }).catch(alerts.error); }); }); }); diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js index 5da36cb0d3..f5f3b689c1 100644 --- a/public/src/admin/manage/privileges.js +++ b/public/src/admin/manage/privileges.js @@ -30,6 +30,7 @@ define('admin/manage/privileges', [ Privileges.refreshPrivilegeTable(); ajaxify.updateHistory('admin/manage/privileges/' + (cid || '')); }, + localOnly: true, localCategories: ajaxify.data.categories, privilege: 'find', showLinks: true, diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 657cfb3231..bbf5ed1a00 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -246,7 +246,7 @@ define('admin/manage/users', [ bootbox.confirm((uids.length > 1 ? '[[admin/manage/users:alerts.confirm-ban-multi]]' : '[[admin/manage/users:alerts.confirm-ban]]'), function (confirm) { if (confirm) { Promise.all(uids.map(function (uid) { - return api.put('/users/' + uid + '/ban'); + return api.put('/users/' + encodeURIComponent(uid) + '/ban'); })).then(() => { onSuccess('[[admin/manage/users:alerts.ban-success]]', '.ban', true); }).catch(alerts.error); @@ -254,47 +254,52 @@ define('admin/manage/users', [ }); }); - $('.ban-user-temporary').on('click', function () { + $('.ban-user-temporary').on('click', async function () { const uids = getSelectedUids(); if (!uids.length) { alerts.error('[[error:no-users-selected]]'); return false; // specifically to keep the menu open } + const reasons = await socket.emit('user.getCustomReasons', { type: 'ban' }); + const html = await app.parseAndTranslate('modals/temporary-ban', { reasons }); + const modal = bootbox.dialog({ + title: '[[user:ban-account]]', + message: html, + show: true, + onEscape: true, + buttons: { + close: { + label: '[[global:close]]', + className: 'btn-link', + }, + submit: { + label: '[[admin/manage/users:alerts.button-ban-x, ' + uids.length + ']]', + callback: function () { + const formData = modal.find('form').serializeArray().reduce(function (data, cur) { + data[cur.name] = cur.value; + return data; + }, {}); + const until = formData.length > 0 ? ( + Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1)) + ) : 0; - Benchpress.render('modals/temporary-ban', {}).then(function (html) { - const modal = bootbox.dialog({ - title: '[[user:ban-account]]', - message: html, - show: true, - onEscape: true, - buttons: { - close: { - label: '[[global:close]]', - className: 'btn-link', - }, - submit: { - label: '[[admin/manage/users:alerts.button-ban-x, ' + uids.length + ']]', - callback: function () { - const formData = modal.find('form').serializeArray().reduce(function (data, cur) { - data[cur.name] = cur.value; - return data; - }, {}); - const until = formData.length > 0 ? ( - Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1)) - ) : 0; - - Promise.all(uids.map(function (uid) { - return api.put('/users/' + uid + '/ban', { - until: until, - reason: formData.reason, - }); - })).then(() => { - onSuccess('[[admin/manage/users:alerts.ban-success]]', '.ban', true); - }).catch(alerts.error); - }, + Promise.all(uids.map(function (uid) { + return api.put('/users/' + encodeURIComponent(uid) + '/ban', { + until: until, + reason: formData.reason, + }); + })).then(() => { + onSuccess('[[admin/manage/users:alerts.ban-success]]', '.ban', true); + }).catch(alerts.error); }, }, - }); + }, + }); + modal.find('[data-key]').on('click', function () { + const reason = reasons.find(r => String(r.key) === $(this).attr('data-key')); + if (reason && reason.body) { + modal.find('[name="reason"]').val(translator.unescape(reason.body)); + } }); }); @@ -326,7 +331,7 @@ define('admin/manage/users', [ Promise.all(uids.map(function (uid) { - return api.del('/users/' + uid + '/ban', { + return api.del('/users/' + encodeURIComponent(uid) + '/ban', { reason: formData.reason || '', }); })).then(() => { diff --git a/public/src/admin/manage/users/custom-reasons.js b/public/src/admin/manage/users/custom-reasons.js new file mode 100644 index 0000000000..923f601cae --- /dev/null +++ b/public/src/admin/manage/users/custom-reasons.js @@ -0,0 +1,97 @@ +define('admin/manage/user/custom-reasons', [ + 'benchpress', 'bootbox', 'alerts', 'translator', 'jquery-ui/widgets/sortable', +], function (benchpress, bootbox, alerts, translator) { + const manageCustomReasons = {}; + + manageCustomReasons.init = function () { + const table = $('table'); + + $('#new').on('click', () => showModal()); + + table.on('click', '[data-action="edit"]', function () { + const row = $(this).parents('[data-key]'); + showModal(getDataFromEl(row)); + }); + + table.on('click', '[data-action="delete"]', function () { + const row = $(this).parents('[data-key]'); + const title = row.attr('data-title'); + bootbox.confirm(`[[admin/manage/custom-reasons:delete-reason-confirm-x, "${title}"]]`, function (ok) { + if (!ok) { + return; + } + row.remove(); + }); + }); + + $('tbody').sortable({ + handle: '[component="sort/handle"]', + axis: 'y', + zIndex: 9999, + }); + + $('#save').on('click', () => { + const reasons = []; + $('tbody tr[data-key]').each((index, el) => { + reasons.push(getDataFromEl($(el))); + }); + socket.emit('admin.user.saveCustomReasons', reasons, function (err) { + if (err) { + return alerts.error(err); + } + alerts.success('[[admin/manage/custom-reasons:custom-reasons-saved]]'); + }); + }); + }; + + function getDataFromEl(el) { + return { + key: el.attr('data-key'), + title: el.attr('data-title'), + type: el.attr('data-type'), + body: el.attr('data-body'), + }; + } + + async function showModal(reason = null) { + const html = await benchpress.render('admin/partials/manage-custom-reasons-modal', reason); + const modal = bootbox.dialog({ + message: html, + onEscape: true, + title: reason ? + '[[admin/manage/custom-reasons:edit-reason]]' : + '[[admin/manage/custom-reasons:create-reason]]', + buttons: { + submit: { + label: '[[global:save]]', + callback: async function () { + const formData = modal.find('form').serializeObject(); + formData.key = reason ? reason.key : Date.now(); + formData.body = translator.escape(formData.body); + formData.parsedBody = translator.escape(await socket.emit('admin.parseRaw', formData.body)); + + app.parseAndTranslate('admin/manage/users/custom-reasons', 'reasons', { + reasons: [formData], + }, (html) => { + if (reason) { + const oldKey = reason.key; + $(`tbody [data-key="${oldKey}"]`).replaceWith(html); + } else { + $('tbody').append(html); + } + }); + }, + }, + }, + }); + // bootbox translates message we want the translation keys to be preseved. + if (reason && reason.body) { + modal.find('[name="body"]').val(reason.body); + } + } + + + return manageCustomReasons; +}); + + diff --git a/public/src/admin/modules/relogin-timer.js b/public/src/admin/modules/relogin-timer.js new file mode 100644 index 0000000000..f8504b51a2 --- /dev/null +++ b/public/src/admin/modules/relogin-timer.js @@ -0,0 +1,36 @@ +import { translate } from 'translator'; +import { alert as bootboxAlert } from 'bootbox'; + +let logoutTimer = 0; +let logoutMessage; + +export function start(adminReloginDuration) { + clearTimer(); + if (adminReloginDuration <= 0) { + return; + } + + // pre-translate language string gh#9046 + if (!logoutMessage) { + translate('[[login:logged-out-due-to-inactivity]]', function (translated) { + logoutMessage = translated; + }); + } + + const timeoutMs = adminReloginDuration * 60000; + logoutTimer = setTimeout(function () { + bootboxAlert({ + closeButton: false, + message: logoutMessage, + callback: function () { + window.location.reload(); + }, + }); + }, timeoutMs); +} + +function clearTimer() { + if (logoutTimer) { + clearTimeout(logoutTimer); + } +} \ No newline at end of file diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index 0721d8c209..29ede2f502 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -2,8 +2,8 @@ define('admin/settings', [ - 'uploader', 'mousetrap', 'hooks', 'alerts', 'settings', 'bootstrap', -], function (uploader, mousetrap, hooks, alerts, settings, bootstrap) { + 'uploader', 'mousetrap', 'hooks', 'alerts', 'settings', 'bootstrap', 'admin/modules/relogin-timer', +], function (uploader, mousetrap, hooks, alerts, settings, bootstrap, reloginTimer) { const Settings = {}; Settings.populateTOC = function () { @@ -25,10 +25,13 @@ define('admin/settings', [ }); const offset = mainHader.outerHeight(true); // https://stackoverflow.com/a/11814275/583363 - tocList.find('a').on('click', function (event) { - event.preventDefault(); + tocList.find('a').on('click', function () { const href = $(this).attr('href'); - $(href)[0].scrollIntoView(); + const $target = $(href); + if (!$target.length) { + return; + } + $target.get(0).scrollIntoView(true); window.location.hash = href; scrollBy(0, -offset); setTimeout(() => { @@ -215,9 +218,10 @@ define('admin/settings', [ return callback(err); } - for (const field in data) { - if (data.hasOwnProperty(field)) { - app.config[field] = data[field]; + for (const [field, value] of Object.entries(data)) { + app.config[field] = value; + if (field === 'adminReloginDuration') { + reloginTimer.start(parseInt(value, 10)); } } diff --git a/public/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js new file mode 100644 index 0000000000..9351f6e6c9 --- /dev/null +++ b/public/src/admin/settings/activitypub.js @@ -0,0 +1,193 @@ +'use strict'; + +define('admin/settings/activitypub', [ + 'benchpress', + 'bootbox', + 'categorySelector', + 'api', + 'alerts', + 'translator', + + 'jquery-ui/widgets/sortable', +], function (Benchpress, bootbox, categorySelector, api, alerts, translator) { + const ActivityPub = {}; + + ActivityPub.init = function () { + setupRules(); + setupRelays(); + }; + + ActivityPub.throwRulesModal = function () { + Benchpress.render('admin/partials/activitypub/rules', {}).then(function (html) { + const submit = function () { + const formEl = modal.find('form').get(0); + const payload = Object.fromEntries(new FormData(formEl)); + + api.post('/admin/activitypub/rules', payload).then(async (data) => { + const html = await Benchpress.render('admin/settings/activitypub', { rules: data }, 'rules'); + const tbodyEl = document.querySelector('#rules tbody'); + if (tbodyEl) { + tbodyEl.innerHTML = html; + } + }).catch(alerts.error); + }; + const modal = bootbox.dialog({ + title: '[[admin/settings/activitypub:rules.add]]', + message: html, + buttons: { + save: { + label: '[[global:save]]', + className: 'btn-primary', + callback: submit, + }, + }, + }); + + modal.on('shown.bs.modal', function () { + modal.find('input').focus(); + }); + + + // help text + const updateHelp = async (key, el) => { + const text = await translator.translate(`[[admin/settings/activitypub:rules.help-${key}]]`); + el.innerHTML = text; + }; + const helpTextEl = modal.get(0).querySelector('#help-text'); + const typeEl = modal.get(0).querySelector('#type'); + updateHelp(modal.get(0).querySelector('#type option').value, helpTextEl); + if (typeEl && helpTextEl) { + typeEl.addEventListener('change', function () { + updateHelp(this.value, helpTextEl); + }); + } + + // category switcher + categorySelector.init(modal.find('[component="category-selector"]'), { + onSelect: function (selectedCategory) { + modal.find('[name="cid"]').val(selectedCategory.cid); + }, + cacheList: false, + showLinks: true, + template: 'admin/partials/category/selector-dropdown-right', + }); + }); + }; + + ActivityPub.throwRelaysModal = function () { + Benchpress.render('admin/partials/activitypub/relays', {}).then(function (html) { + const submit = function () { + const formEl = modal.find('form').get(0); + const payload = Object.fromEntries(new FormData(formEl)); + + api.post('/admin/activitypub/relays', payload).then(async (data) => { + const html = await app.parseAndTranslate('admin/settings/activitypub', 'relays', { relays: data }); + const tbodyEl = document.querySelector('#relays tbody'); + if (tbodyEl) { + $(tbodyEl).html(html); + } + }).catch(alerts.error); + }; + const modal = bootbox.dialog({ + title: '[[admin/settings/activitypub:relays.add]]', + message: html, + buttons: { + save: { + label: '[[global:save]]', + className: 'btn-primary', + callback: submit, + }, + }, + }); + + modal.on('shown.bs.modal', function () { + modal.find('input').focus(); + }); + }); + }; + + function setupRules() { + const rulesEl = document.getElementById('rules'); + if (!rulesEl) { + return; + } + + rulesEl.addEventListener('click', (e) => { + const subselector = e.target.closest('[data-action]'); + if (subselector) { + const action = subselector.getAttribute('data-action'); + switch (action) { + case 'rules.add': { + ActivityPub.throwRulesModal(); + break; + } + + case 'rules.delete': { + const rid = subselector.closest('tr').getAttribute('data-rid'); + api.del(`/admin/activitypub/rules/${rid}`, {}).then(async (data) => { + const html = await Benchpress.render('admin/settings/activitypub', { rules: data }, 'rules'); + const tbodyEl = document.querySelector('#rules tbody'); + if (tbodyEl) { + tbodyEl.innerHTML = html; + } + }).catch(alerts.error); + } + } + } + }); + + const tbodyEl = $(rulesEl).find('tbody'); + tbodyEl.sortable({ + handle: '.drag-handle', + helper: fixWidthHelper, + placeholder: 'ui-state-highlight', + axis: 'y', + update: function () { + const rids = []; + tbodyEl.find('tr').each(function () { + rids.push($(this).data('rid')); + }); + + api.put('/admin/activitypub/rules/order', { rids }).catch(alerts.error); + }, + }); + + function fixWidthHelper(e, ui) { + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + } + } + + function setupRelays() { + const relaysEl = document.getElementById('relays'); + if (relaysEl) { + relaysEl.addEventListener('click', (e) => { + const subselector = e.target.closest('[data-action]'); + if (subselector) { + const action = subselector.getAttribute('data-action'); + switch (action) { + case 'relays.add': { + ActivityPub.throwRelaysModal(); + break; + } + + case 'relays.remove': { + const url = subselector.closest('tr').getAttribute('data-url'); + api.del(`/admin/activitypub/relays/${encodeURIComponent(url)}`, {}).then(async (data) => { + const html = await app.parseAndTranslate('admin/settings/activitypub', 'relays', { relays: data }); + const tbodyEl = document.querySelector('#relays tbody'); + if (tbodyEl) { + $(tbodyEl).html(html); + } + }).catch(alerts.error); + } + } + } + }); + } + } + + return ActivityPub; +}); diff --git a/public/src/admin/settings/cookies.js b/public/src/admin/settings/cookies.js index 757c466d74..4773b53b87 100644 --- a/public/src/admin/settings/cookies.js +++ b/public/src/admin/settings/cookies.js @@ -1,9 +1,9 @@ 'use strict'; define('admin/settings/cookies', ['alerts'], function (alerts) { - const Module = {}; + const Cookies = {}; - Module.init = function () { + Cookies.init = function () { $('#delete-all-sessions').on('click', function () { socket.emit('admin.deleteAllSessions', function (err) { if (err) { @@ -15,5 +15,5 @@ define('admin/settings/cookies', ['alerts'], function (alerts) { }); }; - return Module; + return Cookies; }); diff --git a/public/src/admin/settings/email.js b/public/src/admin/settings/email.js index d514f2a482..937e17b906 100644 --- a/public/src/admin/settings/email.js +++ b/public/src/admin/settings/email.js @@ -2,10 +2,11 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function (ace, alerts) { - const module = {}; + const Email = {}; let emailEditor; - module.init = function () { + Email.init = function () { + configureSmtpTester(); configureEmailTester(); configureEmailEditor(); handleDigestHourChange(); @@ -26,6 +27,29 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function socket.emit('admin.user.restartJobs'); } + function configureSmtpTester() { + $('[data-action="email.smtp.test"]').on('click', function () { + const smtpOptions = {}; + $('[data-field^="email:smtp"]').each(function (index, el) { + const $el = $(el); + const key = $el.attr('data-field'); + if ($el.is(':checkbox')) { + smtpOptions[key] = $el.is(':checked'); + } else { + smtpOptions[key] = $el.val(); + } + }); + + socket.emit('admin.email.testSmtp', { smtp: smtpOptions }, function (err) { + if (err) { + console.error(err.message); + return alerts.error(err); + } + alerts.success('[[admin/settings/email:smtp-transport.test-success]]'); + }); + }); + } + function configureEmailTester() { $('button[data-action="email.test"]').off('click').on('click', function () { socket.emit('admin.email.test', { template: $('#test-email').val() }, function (err) { @@ -33,7 +57,7 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function console.error(err.message); return alerts.error(err); } - alerts.success('Test Email Sent'); + alerts.success('[[admin/settings/email:testing.success]]'); }); return false; }); @@ -135,5 +159,5 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function }); } - return module; + return Email; }); diff --git a/public/src/admin/settings/general.js b/public/src/admin/settings/general.js index 019cbcdeca..62523ef060 100644 --- a/public/src/admin/settings/general.js +++ b/public/src/admin/settings/general.js @@ -2,9 +2,9 @@ define('admin/settings/general', ['admin/settings'], function () { - const Module = {}; + const General = {}; - Module.init = function () { + General.init = function () { $('button[data-action="removeLogo"]').on('click', function () { $('input[data-field="brand:logo"]').val(''); }); @@ -34,5 +34,5 @@ define('admin/settings/general', ['admin/settings'], function () { } } - return Module; + return General; }); diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 34a9cc1116..679273da14 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -198,9 +198,15 @@ ajaxify.widgets = { render: render }; ajaxify.currentPage = url.split(/[?#]/)[0]; ajaxify.requestedPage = null; if (window.history && window.history.pushState) { + const prependSlash = url && !url.startsWith('?') && !url.startsWith('#'); + const { relative_path } = config; + const historyUrl = prependSlash ? + (relative_path + '/' + url) : + relative_path + (url || (relative_path ? '' : '/')); + window.history[!quiet ? 'pushState' : 'replaceState']({ url: url, - }, url, config.relative_path + '/' + url); + }, '', historyUrl); } }; @@ -292,73 +298,54 @@ ajaxify.widgets = { render: render }; ajaxify.updateTitle = updateTitle; function updateTags() { - const metaWhitelist = ['title', 'description', /og:.+/, /article:.+/, 'robots'].map(function (val) { - return new RegExp(val); - }); + const metaWhitelist = ['title', 'description', /og:.+/, /article:.+/, 'robots'].map(val => new RegExp(val)); const linkWhitelist = ['canonical', 'alternate', 'up']; // Delete the old meta tags - Array.prototype.slice - .call(document.querySelectorAll('head meta')) - .filter(function (el) { - const name = el.getAttribute('property') || el.getAttribute('name'); - return metaWhitelist.some(function (exp) { - return !!exp.test(name); - }); - }) - .forEach(function (el) { - document.head.removeChild(el); - }); + document.querySelectorAll('head meta').forEach(el => { + const name = el.getAttribute('property') || el.getAttribute('name') || ''; + if (metaWhitelist.some(exp => exp.test(name))) { + el.remove(); + } + }); // Add new meta tags - ajaxify.data._header.tags.meta - .filter(function (tagObj) { - const name = tagObj.name || tagObj.property; - return metaWhitelist.some(function (exp) { - return !!exp.test(name); - }); - }).forEach(async function (tagObj) { + ajaxify.data._header.tags.meta.forEach(async (tagObj) => { + const name = tagObj.name || tagObj.property; + if (metaWhitelist.some(exp => exp.test(name))) { if (tagObj.content) { tagObj.content = await translator.translate(tagObj.content); } const metaEl = document.createElement('meta'); - Object.keys(tagObj).forEach(function (prop) { - metaEl.setAttribute(prop, tagObj[prop]); - }); + Object.keys(tagObj).forEach(prop => metaEl.setAttribute(prop, tagObj[prop])); document.head.appendChild(metaEl); - }); - + } + }); // Delete the old link tags - Array.prototype.slice - .call(document.querySelectorAll('head link')) - .filter(function (el) { - const name = el.getAttribute('rel'); - return linkWhitelist.some(function (item) { - return item === name; - }); - }) - .forEach(function (el) { - document.head.removeChild(el); - }); + document.querySelectorAll('head link').forEach(el => { + const name = el.getAttribute('rel'); + if (linkWhitelist.some(item => item === name)) { + el.remove(); + } + }); // Add new link tags - ajaxify.data._header.tags.link - .filter(function (tagObj) { - return linkWhitelist.some(function (item) { - return item === tagObj.rel; - }); - }) - .forEach(function (tagObj) { + ajaxify.data._header.tags.link.forEach(async (tagObj) => { + if (linkWhitelist.some(item => item === tagObj.rel)) { const linkEl = document.createElement('link'); - Object.keys(tagObj).forEach(function (prop) { - linkEl.setAttribute(prop, tagObj[prop]); - }); + Object.keys(tagObj).forEach(prop => linkEl.setAttribute(prop, tagObj[prop])); document.head.appendChild(linkEl); - }); + } + }); } ajaxify.end = function (url, tpl_url) { + // Cancel reconnectAction if there was one pending, since ajaxify was a success + if (ajaxify.reconnectAction) { + $(window).off('action:reconnected', ajaxify.reconnectAction); + } + // Scroll back to top of page if (!ajaxify.isCold()) { window.scrollTo(0, 0); @@ -396,8 +383,11 @@ ajaxify.widgets = { render: render }; }; ajaxify.removeRelativePath = function (url) { - if (url.startsWith(config.relative_path.slice(1))) { - url = url.slice(config.relative_path.length); + if (config.relative_path && url.startsWith(config.relative_path.slice(1))) { + url = url.slice(config.relative_path.length - 1); + if (url.startsWith('/')) { + url = url.slice(1); + } } return url; }; @@ -557,10 +547,11 @@ ajaxify.widgets = { render: render }; $(document).ready(function () { window.addEventListener('popstate', (ev) => { if (ev !== null && ev.state) { - if (ev.state.url === null && ev.state.returnPath !== undefined) { + const { returnPath } = ev.state; + if (ev.state.url === null && returnPath !== undefined) { window.history.replaceState({ - url: ev.state.returnPath, - }, ev.state.returnPath, config.relative_path + '/' + ev.state.returnPath); + url: returnPath, + }, '', returnPath); } else if (ev.state.url !== undefined) { ajaxify.handleTransientElements(); ajaxify.go(ev.state.url, function () { diff --git a/public/src/app.js b/public/src/app.js index 1ba3a4093c..a2aec0ce2a 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -192,7 +192,6 @@ if (document.readyState === 'loading') { const pageParams = utils.params(); function queryMatch(search) { const mySearchParams = new URLSearchParams(search); - // eslint-disable-next-line no-restricted-syntax for (const [key, value] of mySearchParams) { if (pageParams[key] === value) { return true; @@ -319,7 +318,6 @@ if (document.readyState === 'loading') { return callback(); } require([ - 'jquery-ui/widgets/datepicker', 'jquery-ui/widgets/autocomplete', 'jquery-ui/widgets/sortable', 'jquery-ui/widgets/resizable', diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 1d6f1c1652..2e220fb080 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -2,8 +2,8 @@ define('forum/account/settings', [ - 'forum/account/header', 'components', 'api', 'alerts', 'hooks', -], function (header, components, api, alerts, hooks) { + 'forum/account/header', 'components', 'api', 'alerts', 'hooks', 'autocomplete', +], function (header, components, api, alerts, hooks, autocomplete) { const AccountSettings = {}; let savedSkin = ''; // If page skin is changed but not saved, switch the skin back @@ -45,6 +45,8 @@ define('forum/account/settings', [ toggleCustomRoute(); components.get('user/sessions').find('.timeago').timeago(); + + handleChatAllowDenyList(); }; function loadSettings() { @@ -53,6 +55,9 @@ define('forum/account/settings', [ $('.account').find('input, textarea, select').each(function (id, input) { input = $(input); const setting = input.attr('data-property'); + if (!setting) { + return; + } if (input.is('select')) { settings[setting] = input.val(); return; @@ -68,6 +73,13 @@ define('forum/account/settings', [ } }); + const chatAllowList = $('[component="chat/allow/list/user"][data-uid]') + .map((i, el) => $(el).data('uid')).get(); + const chatDenyList = $('[component="chat/deny/list/user"][data-uid]') + .map((i, el) => $(el).data('uid')).get(); + settings.chatAllowList = JSON.stringify(chatAllowList); + settings.chatDenyList = JSON.stringify(chatDenyList); + return settings; } @@ -75,24 +87,22 @@ define('forum/account/settings', [ api.put(`/users/${ajaxify.data.uid}/settings`, { settings }).then((newSettings) => { alerts.success('[[success:settings-saved]]'); let languageChanged = false; - for (const key in newSettings) { - if (newSettings.hasOwnProperty(key)) { - if (key === 'userLang' && config.userLang !== newSettings.userLang) { - languageChanged = true; - } - if (key === 'bootswatchSkin') { - savedSkin = newSettings.bootswatchSkin; - config.bootswatchSkin = savedSkin === 'noskin' ? '' : savedSkin; - } else if (config.hasOwnProperty(key)) { - config[key] = newSettings[key]; - } + for (const [key, value] of Object.entries(newSettings)) { + if (key === 'userLang' && config.userLang !== newSettings.userLang) { + languageChanged = true; + } + if (key === 'bootswatchSkin') { + savedSkin = newSettings.bootswatchSkin; + config.bootswatchSkin = savedSkin === 'noskin' ? '' : savedSkin; + } else if (config.hasOwnProperty(key)) { + config[key] = value; } } if (languageChanged && parseInt(app.user.uid, 10) === parseInt(ajaxify.data.theirid, 10)) { window.location.reload(); } - }); + }).catch(alerts.error); } function toggleCustomRoute() { @@ -161,5 +171,56 @@ define('forum/account/settings', [ reskin(skin); }; + function handleChatAllowDenyList() { + autocomplete.user($('#chatAllowListAdd'), async function (ev, selected) { + const { user } = selected.item; + if (!user || String(user.uid) === String(app.user.uid)) { + return; + } + if ($(`[component="chat/allow/list/user"][data-uid="${user.uid}"]`).length) { + return alerts.error('[[error:chat-allow-list-user-already-added]]'); + } + const html = await app.parseAndTranslate('account/settings', 'settings.chatAllowListUsers', { + settings: { chatAllowListUsers: [selected.item.user] }, + }); + + $('[component="chat/allow/list"]').append(html); + $('#chatAllowListAdd').val(''); + toggleNoUsersElement(); + }); + + autocomplete.user($('#chatDenyListAdd'), async function (ev, selected) { + const { user } = selected.item; + if (!user || String(user.uid) === String(app.user.uid)) { + return; + } + if ($(`[component="chat/deny/list/user"][data-uid="${user.uid}"]`).length) { + return alerts.error('[[error:chat-deny-list-user-already-added]]'); + } + const html = await app.parseAndTranslate('account/settings', 'settings.chatDenyListUsers', { + settings: { chatDenyListUsers: [selected.item.user] }, + }); + + $('[component="chat/deny/list"]').append(html); + $('#chatDenyListAdd').val(''); + toggleNoUsersElement(); + }); + + $('[component="chat/allow/list"]').on('click', '[component="chat/allow/delete"]', function () { + $(this).parent().remove(); + toggleNoUsersElement(); + }); + + $('[component="chat/deny/list"]').on('click', '[component="chat/deny/delete"]', function () { + $(this).parent().remove(); + toggleNoUsersElement(); + }); + + function toggleNoUsersElement() { + $('[component="chat/allow/list/no-users"]').toggleClass('hidden', !!$('[component="chat/allow/list/user"]').length); + $('[component="chat/deny/list/no-users"]').toggleClass('hidden', !!$('[component="chat/deny/list/user"]').length); + } + } + return AccountSettings; }); diff --git a/public/src/client/category.js b/public/src/client/category.js index a2a5d579ad..9ae2152b41 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -2,7 +2,6 @@ define('forum/category', [ 'forum/infinitescroll', - 'share', 'navigator', 'topicList', 'sort', @@ -11,7 +10,7 @@ define('forum/category', [ 'alerts', 'api', 'clipboard', -], function (infinitescroll, share, navigator, topicList, sort, categorySelector, hooks, alerts, api, clipboard) { +], function (infinitescroll, navigator, topicList, sort, categorySelector, hooks, alerts, api, clipboard) { const Category = {}; $(window).on('action:ajaxify.start', function (ev, data) { @@ -41,6 +40,8 @@ define('forum/category', [ handleLoadMoreSubcategories(); + handleDescription(); + categorySelector.init($('[component="category-selector"]'), { privilege: 'find', parentCid: ajaxify.data.cid, @@ -70,7 +71,7 @@ define('forum/category', [ const $this = $(this); const state = $this.attr('data-state'); - api.put(`/categories/${cid}/watch`, { state }, (err) => { + api.put(`/categories/${encodeURIComponent(cid)}/watch`, { state }, (err) => { if (err) { return alerts.error(err); } @@ -113,12 +114,24 @@ define('forum/category', [ }); } + function handleDescription() { + const fadeEl = document.querySelector('.description.clamp-fade-4'); + if (!fadeEl) { + return; + } + + fadeEl.addEventListener('click', () => { + const state = fadeEl.classList.contains('line-clamp-4'); + fadeEl.classList.toggle('line-clamp-4', !state); + }); + } + Category.toTop = function () { navigator.scrollTop(0); }; Category.toBottom = async () => { - const { count } = await api.get(`/categories/${ajaxify.data.category.cid}/count`); + const { count } = await api.get(`/categories/${encodeURIComponent(ajaxify.data.category.cid)}/count`); navigator.scrollBottom(count - 1); }; @@ -127,7 +140,7 @@ define('forum/category', [ hooks.fire('action:topics.loading'); const params = utils.params(); - infinitescroll.loadMore(`/categories/${ajaxify.data.cid}/topics`, { + infinitescroll.loadMore(`/categories/${encodeURIComponent(ajaxify.data.cid)}/topics`, { after: after, direction: direction, query: params, diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index b372d7a2f6..58a257f293 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -12,8 +12,8 @@ define('forum/category/tools', [ ], function (topicSelect, threadTools, components, api, bootbox, alerts) { const CategoryTools = {}; - CategoryTools.init = function () { - topicSelect.init(updateDropdownOptions); + CategoryTools.init = function (containerEl) { + topicSelect.init(updateDropdownOptions, containerEl); handlePinnedTopicSort(); @@ -284,8 +284,20 @@ define('forum/category/tools', [ topic.find('[component="topic/locked"]').toggleClass('hidden', !data.isLocked); } - function onTopicMoved(data) { - getTopicEl(data.tid).remove(); + async function onTopicMoved(data) { + if (ajaxify.data.template.category || String(data.toCid) === '-1') { + getTopicEl(data.tid).remove(); + } else { + const category = await api.get(`/categories/${data.toCid}`); + const html = await app.parseAndTranslate('partials/topics_list', { + topics: [{ + ...data, + category, + }], + }); + const categoryLabelSelector = `[component="category/topic"][data-tid="${data.tid}"] [component="topic/category"]`; + $(categoryLabelSelector).replaceWith(html.find(categoryLabelSelector)); + } } function onTopicPurged(data) { diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 4181bd79cb..a4a4ed7288 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -11,6 +11,7 @@ define('forum/chats', [ 'forum/chats/user-list', 'forum/chats/message-search', 'forum/chats/pinned-messages', + 'forum/chats/events', 'autocomplete', 'hooks', 'bootbox', @@ -21,15 +22,14 @@ define('forum/chats', [ ], function ( components, mousetrap, recentChats, create, manage, messages, userList, messageSearch, pinnedMessages, - autocomplete, hooks, bootbox, alerts, chatModule, api, - uploadHelpers + events, autocomplete, hooks, bootbox, alerts, chatModule, + api, uploadHelpers ) { const Chats = { - initialised: false, activeAutocomplete: {}, + newMessage: false, }; - let newMessage = false; let chatNavWrapper = null; $(window).on('action:ajaxify.start', function () { @@ -54,10 +54,9 @@ define('forum/chats', [ socket.emit('modules.chats.enterPublic', ajaxify.data.publicRooms.map(r => r.roomId)); const env = utils.findBootstrapEnvironment(); chatNavWrapper = $('[component="chat/nav-wrapper"]'); - if (!Chats.initialised) { - Chats.addSocketListeners(); - Chats.addGlobalEventListeners(); - } + + Chats.addSocketListeners(); + Chats.addGlobalEventListeners(); recentChats.init(); @@ -68,7 +67,6 @@ define('forum/chats', [ Chats.addHotkeys(); } - Chats.initialised = true; const chatContentEl = $('[component="chat/message/content"]'); messages.wrapImagesInLinks(chatContentEl); if (ajaxify.data.scrollToIndex) { @@ -297,7 +295,7 @@ define('forum/chats', [ let loading = false; let previousScrollTop = el.scrollTop(); let currentScrollTop = previousScrollTop; - el.off('scroll').on('scroll', utils.debounce(function () { + el.off('scroll').on('scroll', utils.debounce(async function () { if (parseInt(el.attr('data-ignore-next-scroll'), 10) === 1) { el.removeAttr('data-ignore-next-scroll'); previousScrollTop = el.scrollTop(); @@ -323,41 +321,13 @@ define('forum/chats', [ if ((scrollPercent < top && direction === -1) || (scrollPercent > bottom && direction === 1)) { loading = true; - const msgEls = el.children('[data-mid]').not('.new'); - const afterEl = direction > 0 ? msgEls.last() : msgEls.first(); - const start = parseInt(afterEl.attr('data-index'), 10) || 0; - - api.get(`/chats/${roomId}/messages`, { uid, start, direction }).then((data) => { - let messageData = data.messages; - if (!messageData) { - loading = false; - return; - } - messageData = messageData.filter(function (chatMsg) { - const msgOnDom = el.find('[component="chat/message"][data-mid="' + chatMsg.messageId + '"]'); - msgOnDom.removeClass('new'); - return !msgOnDom.length; - }); - if (!messageData.length) { - loading = false; - return; - } - messages.parseMessage(messageData, function (html) { - el.attr('data-ignore-next-scroll', 1); - if (direction > 0) { - html.insertAfter(afterEl); - messages.onMessagesAddedToDom(html); - } else { - const currentScrollTop = el.scrollTop(); - const previousHeight = el[0].scrollHeight; - el.prepend(html); - messages.onMessagesAddedToDom(html); - el.scrollTop((el[0].scrollHeight - previousHeight) + currentScrollTop); - } - - loading = false; - }); - }).catch(alerts.error); + try { + await messages.loadMoreMessages(el, uid, roomId, direction); + } catch (err) { + alerts.error(err); + } finally { + loading = false; + } } }, 100)); }; @@ -675,95 +645,39 @@ define('forum/chats', [ }; Chats.addGlobalEventListeners = function () { - $(window).on('mousemove keypress click', function () { - if (newMessage && ajaxify.data.roomId) { - api.del(`/chats/${ajaxify.data.roomId}/state`, {}); - newMessage = false; - } - }); + $(window).off('mousemove keypress click', onUserInteraction) + .on('mousemove keypress click', onUserInteraction); }; + function onUserInteraction() { + if (Chats.newMessage && ajaxify.data.roomId) { + // mark current room read on user interaction + api.del(`/chats/${ajaxify.data.roomId}/state`, {}); + Chats.newMessage = false; + } + } + Chats.addSocketListeners = function () { - socket.on('event:chats.receive', function (data) { - if (chatModule.isFromBlockedUser(data.fromUid)) { - return; - } - if (parseInt(data.roomId, 10) === parseInt(ajaxify.data.roomId, 10)) { - data.self = parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0; - if (!newMessage) { - newMessage = data.self === 0; - } - data.message.self = data.self; - data.message.timestamp = Math.min(Date.now(), data.message.timestamp); - data.message.timestampISO = utils.toISOString(data.message.timestamp); - messages.appendChatMessage($('[component="chat/message/content"]'), data.message); - - Chats.updateTeaser(data.roomId, { - content: utils.stripHTMLTags(utils.decodeHTMLEntities(data.message.content)), - user: data.message.fromUser, - timestampISO: data.message.timestampISO, - }); - } - }); - - socket.on('event:chats.public.unread', function (data) { - if ( - chatModule.isFromBlockedUser(data.fromUid) || - chatModule.isLookingAtRoom(data.roomId) || - app.user.uid === parseInt(data.fromUid, 10) - ) { - return; - } - Chats.markChatPageElUnread(data); - Chats.increasePublicRoomUnreadCount(chatNavWrapper.find('[data-roomid=' + data.roomId + ']')); - }); - - socket.on('event:user_status_change', function (data) { - app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status); - }); + events.init(); messages.addSocketListeners(); - - socket.on('event:chats.roomRename', function (data) { - const roomEl = components.get('chat/recent/room', data.roomId); - if (roomEl.length) { - const titleEl = roomEl.find('[component="chat/room/title"]'); - ajaxify.data.roomName = data.newName; - titleEl.translateText(data.newName ? data.newName : ajaxify.data.usernames); - } - const titleEl = $(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"] [component="chat/header/title"]`); - if (titleEl.length) { - titleEl.html( - data.newName ? - ` ${data.newName}` : - ajaxify.data.chatWithMessage - ); - } - }); - - socket.on('event:chats.mark', ({ roomId, state }) => { - const roomEls = $(`[component="chat/recent"] [data-roomid="${roomId}"], [component="chat/list"] [data-roomid="${roomId}"], [component="chat/public"] [data-roomid="${roomId}"]`); - roomEls.each((idx, el) => { - const roomEl = $(el); - chatModule.markChatElUnread(roomEl, state === 1); - if (state === 0) { - Chats.updatePublicRoomUnreadCount(roomEl, 0); - } - }); - }); - - socket.on('event:chats.typing', async (data) => { - if (data.uid === app.user.uid || chatModule.isFromBlockedUser(data.uid)) { - return; - } - chatModule.updateTypingUserList($(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"]`), data); - }); }; Chats.updateTeaser = async function (roomId, teaser) { - if (!ajaxify.data.template.chats || !app.user.userslug) { + if (!ajaxify.data.template.chats || ajaxify.data.public || !app.user.userslug) { return; } + + function moveChatAndHrToTop(roomEl) { + const hr = roomEl.next('hr'); + const recentChats = components.get('chat/recent'); + const chatCount = recentChats.find('[data-roomid]').length; + recentChats.prepend(roomEl); + if (hr.length || chatCount > 1) { + roomEl.after(hr.length ? hr : `
`); + } + } + const roomEl = chatNavWrapper.find(`[data-roomid="${roomId}"]`); if (roomEl.length) { const html = await app.parseAndTranslate('partials/chats/room-teaser', { @@ -771,16 +685,16 @@ define('forum/chats', [ }); roomEl.find('[component="chat/room/teaser"]').html(html[0].outerHTML); roomEl.find('.timeago').timeago(); + moveChatAndHrToTop(roomEl); } else { const { rooms } = await api.get(`/chats`, { start: 0, perPage: 2 }); const room = rooms.find(r => parseInt(r.roomId, 10) === parseInt(roomId, 10)); if (room) { - const recentEl = components.get('chat/recent'); const html = await app.parseAndTranslate('chats', 'rooms', { rooms: [room], showBottomHr: true, }); - recentEl.prepend(html); + moveChatAndHrToTop(html); } } }; diff --git a/public/src/client/chats/events.js b/public/src/client/chats/events.js new file mode 100644 index 0000000000..774aa8c2c1 --- /dev/null +++ b/public/src/client/chats/events.js @@ -0,0 +1,117 @@ + +'use strict'; + +define('forum/chats/events', [ + 'forum/chats/messages', + 'chat', + 'components', +], function (messages, chatModule, components) { + const Events = {}; + + const events = { + 'event:chats.receive': chatsReceive, + 'event:chats.public.unread': publicChatUnread, + 'event:user_status_change': onUserStatusChange, + 'event:chats.roomRename': onRoomRename, + 'event:chats.mark': markChatState, + 'event:chats.typing': onChatTyping, + }; + let chatNavWrapper = null; + + let Chats = null; + + Events.init = async function () { + Chats = await app.require('forum/chats'); + chatNavWrapper = $('[component="chat/nav-wrapper"]'); + Events.removeListeners(); + for (const [eventName, handler] of Object.entries(events)) { + socket.on(eventName, handler); + } + }; + + Events.removeListeners = function () { + for (const [eventName, handler] of Object.entries(events)) { + socket.removeListener(eventName, handler); + } + }; + + function chatsReceive(data) { + if (chatModule.isFromBlockedUser(data.fromUid)) { + return; + } + data.self = parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0; + data.message.self = data.self; + data.message.timestamp = Math.min(Date.now(), data.message.timestamp); + data.message.timestampISO = utils.toISOString(data.message.timestamp); + const isMessageForCurrentRoom = parseInt(data.roomId, 10) === parseInt(ajaxify.data.roomId, 10); + + if (isMessageForCurrentRoom) { + if (!Chats.newMessage) { + Chats.newMessage = data.self === 0; + } + + messages.appendChatMessage($('[component="chat/message/content"]'), data.message); + } + Chats.updateTeaser(data.roomId, { + content: utils.stripHTMLTags(utils.decodeHTMLEntities(data.message.content)), + user: data.message.fromUser, + timestampISO: data.message.timestampISO, + }); + } + + function publicChatUnread(data) { + if ( + !ajaxify.data.template.chats || + chatModule.isFromBlockedUser(data.fromUid) || + chatModule.isLookingAtRoom(data.roomId) || + app.user.uid === parseInt(data.fromUid, 10) + ) { + return; + } + Chats.markChatPageElUnread(data); + Chats.increasePublicRoomUnreadCount(chatNavWrapper.find('[data-roomid=' + data.roomId + ']')); + } + + function onUserStatusChange(data) { + app.updateUserStatus( + $(`.chats-list [data-uid="${data.uid}"] [component="user/status"]`), data.status + ); + } + + function onRoomRename(data) { + const roomEl = components.get('chat/recent/room', data.roomId); + if (roomEl.length) { + const titleEl = roomEl.find('[component="chat/room/title"]'); + ajaxify.data.roomName = data.newName; + titleEl.translateText(data.newName ? data.newName : ajaxify.data.usernames); + } + const titleEl = $(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"] [component="chat/header/title"]`); + if (titleEl.length) { + titleEl.html( + data.newName ? + ` ${data.newName}` : + ajaxify.data.chatWithMessage + ); + } + } + + function markChatState({ roomId, state }) { + const roomEls = $(`[component="chat/recent"] [data-roomid="${roomId}"], [component="chat/list"] [data-roomid="${roomId}"], [component="chat/public"] [data-roomid="${roomId}"]`); + roomEls.each((idx, el) => { + const roomEl = $(el); + chatModule.markChatElUnread(roomEl, state === 1); + if (state === 0) { + Chats.updatePublicRoomUnreadCount(roomEl, 0); + } + }); + } + + function onChatTyping(data) { + if (data.uid === app.user.uid || chatModule.isFromBlockedUser(data.uid)) { + return; + } + chatModule.updateTypingUserList($(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"]`), data); + } + + return Events; +}); diff --git a/public/src/client/chats/manage.js b/public/src/client/chats/manage.js index 2ab92c4295..f2e5cbc04c 100644 --- a/public/src/client/chats/manage.js +++ b/public/src/client/chats/manage.js @@ -75,12 +75,16 @@ define('forum/chats/manage', [ modal.find('[component="chat/manage/save"]').on('click', () => { const notifSettingEl = modal.find('[component="chat/room/notification/setting"]'); + const joinLeaveMessagesEl = modal.find('[component="chat/room/join-leave-messages"]'); + api.put(`/chats/${roomId}`, { groups: modal.find('[component="chat/room/groups"]').val(), notificationSetting: notifSettingEl.val(), + joinLeaveMessages: joinLeaveMessagesEl.is(':checked') ? 1 : 0, }).then((payload) => { ajaxify.data.groups = payload.groups; ajaxify.data.notificationSetting = payload.notificationSetting; + ajaxify.data.joinLeaveMessages = payload.joinLeaveMessages; const roomDefaultOption = payload.notificationOptions[0]; $('[component="chat/notification/setting"] [data-icon]').first().attr( 'data-icon', roomDefaultOption.icon diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index d351a263f9..062e19ef2f 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -79,7 +79,7 @@ define('forum/chats/messages', [ }); } - messages.appendChatMessage = function (chatContentEl, data) { + messages.appendChatMessage = async function (chatContentEl, data) { const lastMsgEl = chatContentEl.find('.chat-message').last(); const lastSpeaker = parseInt(lastMsgEl.attr('data-uid'), 10); const lasttimestamp = parseInt(lastMsgEl.attr('data-timestamp'), 10); @@ -89,9 +89,8 @@ define('forum/chats/messages', [ data.index = parseInt(lastMsgEl.attr('data-index'), 10) + 1; } - messages.parseMessage(data, function (html) { - onMessagesParsed(chatContentEl, html, data); - }); + const html = await messages.parseMessage(data); + onMessagesParsed(chatContentEl, html, data); }; function onMessagesParsed(chatContentEl, html, msgData) { @@ -125,16 +124,15 @@ define('forum/chats/messages', [ hooks.fire('action:chat.onMessagesAddedToDom', { messageEls }); }; - messages.parseMessage = function (data, callback) { + messages.parseMessage = function (data) { const tplData = { messages: data, isAdminOrGlobalMod: app.user.isAdmin || app.user.isGlobalMod, }; if (Array.isArray(data)) { - app.parseAndTranslate('partials/chats/messages', tplData).then(callback); - } else { - app.parseAndTranslate('partials/chats/' + (data.system ? 'system-message' : 'message'), tplData).then(callback); + return app.parseAndTranslate('partials/chats/messages', tplData); } + return app.parseAndTranslate('partials/chats/' + (data.system ? 'system-message' : 'message'), tplData); }; messages.isAtBottom = function (containerEl, threshold) { @@ -273,35 +271,37 @@ define('forum/chats/messages', [ socket.removeListener('event:chats.restore', onChatMessageRestored); socket.on('event:chats.restore', onChatMessageRestored); + + socket.removeListener('connect', onChatReconnect); + socket.on('connect', onChatReconnect); }; - function onChatMessageEdited(data) { - data.messages.forEach(function (message) { + async function onChatMessageEdited(data) { + await Promise.all(data.messages.map(async (message) => { const self = parseInt(message.fromuid, 10) === parseInt(app.user.uid, 10); message.self = self ? 1 : 0; - messages.parseMessage(message, function (html) { - const msgEl = components.get('chat/message', message.mid); - if (msgEl.length) { - const componentsToReplace = [ - '[component="chat/message/body"]', - '[component="chat/message/edited"]', - ]; - componentsToReplace.forEach((cmp) => { - msgEl.find(cmp).replaceWith(html.find(cmp)); - }); - messages.onMessagesAddedToDom(components.get('chat/message', message.mid)); - } - const parentEl = $(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`); - if (parentEl.length) { - parentEl.find('[component="chat/message/parent/content"]').html( - html.find('[component="chat/message/body"]').html() - ); - messages.onMessagesAddedToDom( - $(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`) - ); - } - }); - }); + const html = await messages.parseMessage(message); + const msgEl = components.get('chat/message', message.mid); + if (msgEl.length) { + const componentsToReplace = [ + '[component="chat/message/body"]', + '[component="chat/message/edited"]', + ]; + componentsToReplace.forEach((cmp) => { + msgEl.find(cmp).replaceWith(html.find(cmp)); + }); + messages.onMessagesAddedToDom(components.get('chat/message', message.mid)); + } + const parentEl = $(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`); + if (parentEl.length) { + parentEl.find('[component="chat/message/parent/content"]').html( + html.find('[component="chat/message/body"]').html() + ); + messages.onMessagesAddedToDom( + $(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`) + ); + } + })); } function onChatMessageDeleted(messageId) { @@ -341,6 +341,53 @@ define('forum/chats/messages', [ } } + function onChatReconnect() { + $('[component="chat/message/content"]').each(function () { + const chatContentEl = $(this); + const roomId = chatContentEl.attr('data-roomid'); + const uid = (ajaxify.template.chats && ajaxify.data.uid) || app.user.uid; + const isAtBottom = messages.isAtBottom(chatContentEl); + messages.loadMoreMessages(chatContentEl, uid, roomId, 1) + .then(() => { + if (isAtBottom) { + messages.scrollToBottomAfterImageLoad(chatContentEl); + } + }).catch(alerts.error); + }); + } + + messages.loadMoreMessages = async function (chatContentEl, uid, roomId, direction) { + const msgEls = chatContentEl.children('[data-mid]').not('.new'); + const afterEl = direction > 0 ? msgEls.last() : msgEls.first(); + const start = parseInt(afterEl.attr('data-index'), 10) || 0; + + const data = await api.get(`/chats/${roomId}/messages`, { uid, start, direction }); + let messageData = data.messages; + if (!messageData) { + return; + } + messageData = messageData.filter(function (chatMsg) { + const msgOnDom = chatContentEl.find(`[component="chat/message"][data-mid="${chatMsg.messageId}"]`); + msgOnDom.removeClass('new'); + return !msgOnDom.length; + }); + if (!messageData.length) { + return; + } + const html = await messages.parseMessage(messageData); + chatContentEl.attr('data-ignore-next-scroll', 1); + if (direction > 0) { + html.insertAfter(afterEl); + messages.onMessagesAddedToDom(html); + } else { + const currentScrollTop = chatContentEl.scrollTop(); + const previousHeight = chatContentEl[0].scrollHeight; + chatContentEl.prepend(html); + messages.onMessagesAddedToDom(html); + chatContentEl.scrollTop((chatContentEl[0].scrollHeight - previousHeight) + currentScrollTop); + } + }; + messages.delete = function (messageId, roomId) { bootbox.confirm('[[modules:chat.delete-message-confirm]]', function (ok) { if (!ok) { diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js index 110ef1f334..8d13124280 100644 --- a/public/src/client/flags/list.js +++ b/public/src/client/flags/list.js @@ -103,10 +103,8 @@ export function enableFilterForm() { }); } else { // Persona; parse ajaxify data to set form values to reflect current filters - for (const filter in ajaxify.data.filters) { - if (ajaxify.data.filters.hasOwnProperty(filter)) { - $filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]); - } + for (const [filter, value] of Object.entries(ajaxify.data.filters)) { + $filtersEl.find('[name="' + filter + '"]').val(value); } $filtersEl.find('[name="sort"]').val(ajaxify.data.sort); diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 8dde2e3eab..9ed57cd6ed 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -81,6 +81,7 @@ define('forum/groups/details', [ case 'toggleOwnership': api[isOwner ? 'del' : 'put'](`/groups/${ajaxify.data.group.slug}/ownership/${uid}`, {}).then(() => { ownerFlagEl.toggleClass('invisible'); + userRow.attr('data-isowner', isOwner ? '0' : '1'); }).catch(alerts.error); break; diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index bd13996044..a68b87bf9a 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -8,13 +8,16 @@ define('forum/groups/list', [ Groups.init = function () { // Group creation $('button[data-action="new"]').on('click', function () { - bootbox.prompt('[[groups:new-group.group-name]]', function (name) { - if (name && name.length) { - api.post('/groups', { - name: name, - }).then((res) => { + const modal = bootbox.prompt('[[groups:new-group.group-name]]', function (name) { + if (name === '') { + return false; + } + if (name && name.trim().length) { + api.post('/groups', { name }).then((res) => { + modal.modal('hide'); ajaxify.go('groups/' + res.slug); }).catch(alerts.error); + return false; } }); }); @@ -42,19 +45,17 @@ define('forum/groups/list', [ return false; }; - function renderSearchResults(data) { - app.parseAndTranslate('partials/paginator', { - pagination: data.pagination, - }).then(function (html) { - $('.pagination-container').replaceWith(html); - }); - - const groupsEl = $('#groups-list'); - app.parseAndTranslate('partials/groups/list', { - groups: data.groups, - }).then(function (html) { - groupsEl.empty().append(html); - }); + async function renderSearchResults(data) { + const [paginationHtml, groupsHtml] = await Promise.all([ + app.parseAndTranslate('partials/paginator', { + pagination: data.pagination, + }), + app.parseAndTranslate('partials/groups/list', { + groups: data.groups, + }), + ]); + $('.pagination-container').replaceWith(paginationHtml); + $('#groups-list').empty().append(groupsHtml); } return Groups; diff --git a/public/src/client/header.js b/public/src/client/header.js index f0ef69cf42..6b228f4a55 100644 --- a/public/src/client/header.js +++ b/public/src/client/header.js @@ -6,9 +6,9 @@ define('forum/header', [ 'forum/header/chat', 'alerts', ], function (unread, notifications, chat, alerts) { - const module = {}; + const header = {}; - module.prepareDOM = function () { + header.prepareDOM = function () { if (app.user.uid > 0) { unread.initUnreadTopics(); } @@ -44,12 +44,24 @@ define('forum/header', [ return; } - $('#header-menu #main-nav').tooltip({ - selector: '.nav-item', - placement: 'bottom', - trigger: 'hover', - container: 'body', + // only affects persona, harmony uses a different structure in harmony.js + const tooltipEls = $('#header-menu .nav-item[title]'); + + tooltipEls.tooltip({ + trigger: 'manual', animation: false, + placement: 'bottom', + }); + + tooltipEls.on('mouseenter', function (ev) { + const target = $(ev.target); + const isDropdown = target.hasClass('dropdown-menu') || !!target.parents('.dropdown-menu').length; + if (!isDropdown) { + $(this).tooltip('show'); + } + }); + tooltipEls.on('click mouseleave', function () { + $(this).tooltip('hide'); }); } @@ -62,5 +74,5 @@ define('forum/header', [ }); } - return module; + return header; }); diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js index eaf3e97720..74c69d21dd 100644 --- a/public/src/client/infinitescroll.js +++ b/public/src/client/infinitescroll.js @@ -21,7 +21,7 @@ define('forum/infinitescroll', ['hooks', 'alerts', 'api'], function (hooks, aler previousScrollTop = $(window).scrollTop(); $(window).off('scroll', startScrollTimeout).on('scroll', startScrollTimeout); if ($body.height() <= $(window).height() && ( - !ajaxify.data.hasOwnProperty('pageCount') || ajaxify.data.pageCount > 1 + ajaxify.data.pagination && ajaxify.data.pagination.pageCount > 1 )) { callback(1); } diff --git a/public/src/client/notifications.js b/public/src/client/notifications.js index d42b23d51e..2b82174ff4 100644 --- a/public/src/client/notifications.js +++ b/public/src/client/notifications.js @@ -13,7 +13,7 @@ define('forum/notifications', ['components', 'notifications'], function (compone notifications.handleUnreadButton(listEl); components.get('notifications/mark_all').on('click', function () { - notifications.markAllRead(function () { + notifications.markAllRead(ajaxify.data.selectedFilter.filter, function () { components.get('notifications/item').removeClass('unread'); }); }); diff --git a/public/src/client/post-queue.js b/public/src/client/post-queue.js index ff5fa931d7..debd6d3066 100644 --- a/public/src/client/post-queue.js +++ b/public/src/client/post-queue.js @@ -2,11 +2,11 @@ define('forum/post-queue', [ - 'categoryFilter', 'categorySelector', 'api', 'alerts', 'bootbox', - 'accounts/moderate', 'accounts/delete', + 'categoryFilter', 'categorySelector', 'api', 'alerts', 'translator', + 'bootbox', 'accounts/moderate', 'accounts/delete', ], function ( - categoryFilter, categorySelector, api, alerts, bootbox, - AccountModerate, AccountsDelete + categoryFilter, categorySelector, api, alerts, translator, + bootbox, AccountModerate, AccountsDelete ) { const PostQueue = {}; @@ -69,14 +69,10 @@ define('forum/post-queue', [ const id = textarea.parents('[data-id]').attr('data-id'); const titleEdit = triggerClass === '[data-action="editTitle"]'; - socket.emit('posts.editQueuedContent', { - id: id, + api.put(`/posts/queue/${id}`, { title: titleEdit ? textarea.val() : undefined, content: titleEdit ? undefined : textarea.val(), - }, function (err, data) { - if (err) { - return alerts.error(err); - } + }).then((data) => { if (titleEdit) { preview.find('.title-text').text(data.postData.title); } else { @@ -85,7 +81,7 @@ define('forum/post-queue', [ textarea.parent().addClass('hidden'); preview.removeClass('hidden'); - }); + }).catch(alerts.error); }); } @@ -96,8 +92,7 @@ define('forum/post-queue', [ onSubmit: function (selectedCategory) { Promise.all([ api.get(`/categories/${selectedCategory.cid}`, {}), - socket.emit('posts.editQueuedContent', { - id: id, + api.put(`/posts/queue/${id}`, { cid: selectedCategory.cid, }), ]).then(function (result) { @@ -163,67 +158,128 @@ define('forum/post-queue', [ AccountsDelete.purge(uid, ajaxify.go.bind(null, 'post-queue')); break; - default: - handleQueueActions.call(e.target); + case 'accept': + handleAccept(subselector); break; + + case 'reject': + handleReject(subselector); + break; + + case 'notify': + handleNotify(subselector); + break; + + default: + throw new Error(`Unknown action: ${action}`); } } }); } } - async function handleQueueActions() { - // accept, reject, notify - function getMessage() { - return new Promise((resolve) => { - const modal = bootbox.dialog({ - title: '[[post-queue:notify-user]]', - message: '', - buttons: { - OK: { - label: '[[modules:bootbox.send]]', - callback: function () { - const val = modal.find('textarea').val(); - if (val) { - resolve(val); - } - }, - }, - }, - }); - }); - } - - const parent = $(this).parents('[data-id]'); - const action = $(this).attr('data-action'); + function handleAccept(btn) { + const parent = $(btn).parents('[data-id]'); const id = parent.attr('data-id'); - const listContainer = parent.get(0).parentNode; + doAction('accept', id).then(() => removePostQueueElement(parent)).catch(alerts.error); + } - if ((!['accept', 'reject', 'notify'].includes(action)) || - (action === 'reject' && !await confirmReject(ajaxify.data.canAccept ? '[[post-queue:confirm-reject]]' : '[[post-queue:confirm-remove]]'))) { + async function handleReject(btn) { + const parent = $(btn).parents('[data-id]'); + const id = parent.attr('data-id'); + const translationString = ajaxify.data.canAccept ? + '[[post-queue:confirm-reject]]' : + '[[post-queue:confirm-remove]]'; + + const message = await getMessage(translationString); + if (message === false) { + return; + } + doAction('reject', id, message).then(() => removePostQueueElement(parent)).catch(alerts.error); + } + + function removePostQueueElement(parent) { + const listContainer = parent.get(0).parentNode; + parent.remove(); + if (listContainer.childElementCount === 0) { + if (ajaxify.data.singlePost) { + ajaxify.go('/post-queue' + window.location.search); + } else { + ajaxify.refresh(); + } + } + } + + async function handleNotify(btn) { + const parent = $(btn).parents('[data-id]'); + const id = parent.attr('data-id'); + const message = await getMessage('[[post-queue:notify-user]]'); + if (message === false) { return; } - socket.emit('posts.' + action, { - id: id, - message: action === 'notify' ? await getMessage() : undefined, - }, function (err) { - if (err) { - return alerts.error(err); - } - if (action === 'accept' || action === 'reject') { - parent.remove(); - } + doAction('notify', id, message).catch(alerts.error); + } - if (listContainer.childElementCount === 0) { - if (ajaxify.data.singlePost) { - ajaxify.go('/post-queue' + window.location.search); - } else { - ajaxify.refresh(); + async function getMessage(title) { + const reasons = await socket.emit('user.getCustomReasons', { type: 'post-queue' }); + const html = await app.parseAndTranslate('partials/custom-reason', { reasons }); + + return new Promise((resolve) => { + let resolved = false; + const done = (value) => { + if (resolved) { + return; } - } + resolved = true; + resolve(value); + }; + + const modal = bootbox.dialog({ + title: title, + message: `
${html.html()}
`, + show: true, + onEscape: true, + buttons: { + close: { + label: '[[global:close]]', + className: 'btn-link', + callback: function () { + done(false); + }, + }, + submit: { + label: '[[modules:bootbox.confirm]]', + callback: function () { + done(modal.find('[name="reason"]').val()); + }, + }, + }, + }); + + modal.on('hidden.bs.modal', () => { + done(false); + }); + modal.find('[data-key]').on('click', function () { + const reason = reasons.find(r => String(r.key) === $(this).attr('data-key')); + if (reason && reason.body) { + modal.find('[name="reason"]').val(translator.unescape(reason.body)); + } + }); }); - return false; + } + + async function doAction(action, id, message = '') { + const actionsMap = { + accept: () => api.post(`/posts/queue/${id}`, {}), + reject: () => api.del(`/posts/queue/${id}`, { message }), + notify: () => api.post(`/posts/queue/${id}/notify`, { message }), + }; + if (actionsMap[action]) { + const result = actionsMap[action](); + return (result instanceof Promise ? result : Promise.resolve(result)); + } + throw new Error(`Unknown action: ${action}`); } function handleBulkActions() { @@ -244,7 +300,7 @@ define('forum/post-queue', [ return; } const action = bulkAction.split('-')[0]; - const promises = ids.map(id => socket.emit('posts.' + action, { id: id })); + const promises = ids.map(id => doAction(action, id)); Promise.allSettled(promises).then(function (results) { const fulfilled = results.filter(res => res.status === 'fulfilled').length; diff --git a/public/src/client/register.js b/public/src/client/register.js index f989901e7b..1cd0570ded 100644 --- a/public/src/client/register.js +++ b/public/src/client/register.js @@ -121,12 +121,13 @@ define('forum/register', [ username_notify.text(''); const usernameInput = $('#username'); const userslug = slugify(username); - if (username.length < ajaxify.data.minimumUsernameLength || userslug.length < ajaxify.data.minimumUsernameLength) { - showError(usernameInput, username_notify, '[[error:username-too-short]]'); - } else if (username.length > ajaxify.data.maximumUsernameLength) { - showError(usernameInput, username_notify, '[[error:username-too-long]]'); - } else if (!utils.isUserNameValid(username) || !userslug) { + const { minimumUsernameLength, maximumUsernameLength } = ajaxify.data; + if (!utils.isUserNameValid(username) || !utils.isSlugValid(userslug)) { showError(usernameInput, username_notify, '[[error:invalid-username]]'); + } else if (username.length < minimumUsernameLength || userslug.length < minimumUsernameLength) { + showError(usernameInput, username_notify, '[[error:username-too-short]]'); + } else if (username.length > maximumUsernameLength) { + showError(usernameInput, username_notify, '[[error:username-too-long]]'); } else { Promise.allSettled([ api.head(`/users/bySlug/${userslug}`, {}), diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 1919638f01..b54185bf80 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -69,6 +69,7 @@ define('forum/topic', [ setupQuickReply(); handleBookmark(tid); handleThumbs(); + addCrosspostsHandler(); $(window).on('scroll', utils.debounce(updateTopicTitle, 250)); @@ -202,10 +203,16 @@ define('forum/topic', [ $('[component="topic/thumb/select"]').removeClass('border-primary'); $(this).addClass('border-primary'); $('[component="topic/thumb/current"]') - .attr('src', $(this).attr('src')); + .attr('src', $(this).find('img').attr('src')); }); } }); + + $('[component="topic/thumb/list/expand"]').on('click', function () { + const btn = $(this); + btn.parents('[component="topic/thumb/list"]').removeClass('thumbs-collapsed'); + btn.remove(); + }); } function addBlockQuoteHandler() { @@ -307,66 +314,113 @@ define('forum/topic', [ if (!ajaxify.data.showPostPreviewsOnHover || utils.isMobile()) { return; } - let timeoutId = 0; + let renderTimeout = 0; let destroyed = false; + let link = null; + const postCache = {}; function destroyTooltip() { - clearTimeout(timeoutId); + clearTimeout(renderTimeout); + renderTimeout = 0; $('#post-tooltip').remove(); destroyed = true; } + + function onClickOutside(ev) { + // If the click is outside the tooltip, destroy it + if (!$(ev.target).closest('#post-tooltip').length) { + destroyTooltip(); + } + } + $(window).one('action:ajaxify.start', destroyTooltip); - $('[component="topic"]').on('mouseenter', 'a[component="post/parent"], [component="post/content"] a, [component="topic/event"] a', async function () { - const link = $(this); + + $('[component="topic"]').on('mouseenter', 'a[component="post/parent"], [component="post/parent/content"] a,[component="post/content"] a, [component="topic/event"] a', async function () { + link = $(this); + link.removeAttr('over-tooltip'); + link.one('mouseleave', function () { + clearTimeout(renderTimeout); + renderTimeout = 0; + setTimeout(() => { + if (!link.attr('over-tooltip') && !renderTimeout) { + destroyTooltip(); + } + }, 100); + }); + clearTimeout(renderTimeout); destroyed = false; - async function renderPost(pid) { - const postData = postCache[pid] || await api.get(`/posts/${encodeURIComponent(pid)}/summary`); - $('#post-tooltip').remove(); - if (postData && ajaxify.data.template.topic) { - postCache[pid] = postData; - const tooltip = await app.parseAndTranslate('partials/topic/post-preview', { post: postData }); - if (destroyed) { - return; + renderTimeout = setTimeout(async () => { + async function renderPost(pid) { + const postData = postCache[pid] || await api.get(`/posts/${encodeURIComponent(pid)}/summary`); + $('#post-tooltip').remove(); + if (postData && ajaxify.data.template.topic) { + postCache[pid] = postData; + const tooltip = await app.parseAndTranslate('partials/topic/post-preview', { post: postData }); + if (destroyed) { + return; + } + tooltip.hide().find('.timeago').timeago(); + tooltip.appendTo($('body')).fadeIn(300); + const postContent = link.parents('[component="topic"]').find('[component="post/content"]').first(); + const postRect = postContent.offset(); + const postWidth = postContent.width(); + const { top } = link.get(0).getBoundingClientRect(); + const dropup = top > window.innerHeight / 2; + tooltip.on('mouseenter', function () { + link.attr('over-tooltip', 1); + }); + tooltip.one('mouseleave', destroyTooltip); + $(window).off('click', onClickOutside).one('click', onClickOutside); + const css = { + left: postRect.left, + width: postWidth, + }; + if (dropup) { + css.bottom = window.innerHeight - top - window.scrollY + 5; + } else { + css.top = top + window.scrollY + 30; + } + tooltip.css(css); } - tooltip.hide().find('.timeago').timeago(); - tooltip.appendTo($('body')).fadeIn(300); - const postContent = link.parents('[component="topic"]').find('[component="post/content"]').first(); - const postRect = postContent.offset(); - const postWidth = postContent.width(); - const linkRect = link.offset(); - tooltip.css({ - top: linkRect.top + 30, - left: postRect.left, - width: postWidth, - }); - } - } - - const href = link.attr('href'); - const location = utils.urlToLocation(href); - const pathname = location.pathname; - const validHref = href && href !== '#' && window.location.hostname === location.hostname; - $('#post-tooltip').remove(); - const postMatch = validHref && pathname && pathname.match(/\/post\/([\d]+|(?:[\w_.~!$&'()*+,;=:@-]|%[\dA-F]{2})+)/); - const topicMatch = validHref && pathname && pathname.match(/\/topic\/([\da-z-]+)/); - if (postMatch) { - const pid = postMatch[1]; - if (encodeURIComponent(link.parents('[component="post"]').attr('data-pid')) === encodeURIComponent(pid)) { - return; // dont render self post } - timeoutId = setTimeout(async () => { + const href = link.attr('href'); + const location = utils.urlToLocation(href); + const pathname = location.pathname; + const validHref = href && href !== '#' && window.location.hostname === location.hostname; + $('#post-tooltip').remove(); + const postMatch = validHref && pathname && pathname.match(/\/post\/([\d]+|(?:[\w_.~!$&'()*+,;=:@-]|%[\dA-F]{2})+)/); + const topicMatch = validHref && pathname && pathname.match(/\/topic\/([\da-z-]+)/); + if (postMatch) { + const pid = postMatch[1]; + if (encodeURIComponent(link.parents('[component="post"]').attr('data-pid')) === encodeURIComponent(pid)) { + return; // dont render self post + } renderPost(pid); - }, 300); - } else if (topicMatch) { - timeoutId = setTimeout(async () => { + } else if (topicMatch) { const tid = topicMatch[1]; const topicData = await api.get('/topics/' + tid, {}); renderPost(topicData.mainPid); - }, 300); - } - }).on('mouseleave', '[component="post"] a, [component="topic/event"] a', destroyTooltip); + } + }, 300); + }); + } + + function addCrosspostsHandler() { + const anchorEl = document.getElementById('show-crossposts'); + if (anchorEl) { + anchorEl.addEventListener('click', async () => { + const { crossposts } = ajaxify.data; + const html = await app.parseAndTranslate('modals/crossposts', { crossposts }); + bootbox.dialog({ + onEscape: true, + backdrop: true, + title: '[[global:crossposts]]', + message: html, + }); + }); + } } function setupQuickReply() { diff --git a/public/src/client/topic/change-owner.js b/public/src/client/topic/change-owner.js index b0b4e5be6c..9be031b62a 100644 --- a/public/src/client/topic/change-owner.js +++ b/public/src/client/topic/change-owner.js @@ -5,7 +5,8 @@ define('forum/topic/change-owner', [ 'postSelect', 'autocomplete', 'alerts', -], function (postSelect, autocomplete, alerts) { + 'api', +], function (postSelect, autocomplete, alerts, api) { const ChangeOwner = {}; let modal; @@ -69,14 +70,12 @@ define('forum/topic/change-owner', [ if (!toUid) { return; } - socket.emit('posts.changeOwner', { pids: postSelect.pids, toUid: toUid }, function (err) { - if (err) { - return alerts.error(err); - } + + api.post('/posts/owner', { pids: postSelect.pids, uid: toUid}).then(() => { ajaxify.go(`/post/${postSelect.pids[0]}`); closeModal(); - }); + }).catch(alerts.error); } function closeModal() { diff --git a/public/src/client/topic/crosspost.js b/public/src/client/topic/crosspost.js new file mode 100644 index 0000000000..473450f3d1 --- /dev/null +++ b/public/src/client/topic/crosspost.js @@ -0,0 +1,153 @@ +'use strict'; + + +define('forum/topic/crosspost', [ + 'categoryFilter', 'alerts', 'hooks', 'api', 'components', +], function (categoryFilter, alerts, hooks, api, components) { + const Crosspost = {}; + let modal; + let selectedCids; + + Crosspost.init = function (tid) { + if (modal) { + return; + } + Crosspost.tid = tid; + Crosspost.cid = ajaxify.data.cid; + Crosspost.current = ajaxify.data.crossposts; + + showModal(); + }; + + function showModal() { + const selectedCategory = (() => { + const multiple = { + icon: 'fa-plus', + name: '[[unread:multiple-categories-selected]]', + bgColor: '#ddd', + }; + if (ajaxify.data.cid > 0) { + return ajaxify.data.crossposts.length ? multiple : ajaxify.data.category; + } + + switch (ajaxify.data.crossposts.length) { + case 0: + return undefined; + + case 1: + return ajaxify.data.crossposts[0].category; + + default: + return multiple; + } + })(); + app.parseAndTranslate('modals/crosspost-topic', { selectedCategory }, function (html) { + modal = html; + $('body').append(modal); + + const dropdownEl = modal.find('[component="category-selector"]'); + dropdownEl.addClass('dropup'); + + const selectedCids = [...ajaxify.data.crossposts.map(c => c.cid)]; + if (ajaxify.data.cid > 0) { + selectedCids.unshift(ajaxify.data.cid); + } + categoryFilter.init($('[component="category/dropdown"]'), { + onHidden: onCategoriesSelected, + hideAll: true, + hideUncategorized: true, + localOnly: true, + selectedCids: Array.from(new Set(selectedCids)), + }); + + modal.find('#crosspost_thread_commit').on('click', onCommitClicked); + modal.find('#crosspost_topic_cancel').on('click', closeCrosspostModal); + }); + } + + function onCategoriesSelected(data) { + selectedCids = data.selectedCids.filter(cid => utils.isNumber(cid) && cid > 0); + if (data.changed) { + modal.find('#crosspost_thread_commit').prop('disabled', false); + } + } + + function onCommitClicked() { + const commitEl = modal.find('#crosspost_thread_commit'); + + if (!commitEl.prop('disabled')) { + commitEl.prop('disabled', true); + const data = { + tid: Crosspost.tid, + cids: selectedCids, + }; + + crosspost(data); + } + } + + function crosspost(data) { + hooks.fire('action:topic.crosspost', data); + + const cids = data.cids.map((cid) => parseInt(cid, 10)); + if (!cids.includes(Crosspost.cid)) { + cids.unshift(Crosspost.cid); + } + const current = [Crosspost.cid, ...Crosspost.current.map(x => parseInt(x.cid, 10))]; + const add = cids.filter(cid => !current.includes(cid)); + const remove = current.filter(cid => !cids.includes(cid)); + + const queries = [ + ...add.map((cid) => { return api.post(`/topics/${data.tid}/crossposts`, { cid }); }), + ...remove.map((cid) => { return api.del(`/topics/${data.tid}/crossposts`, { cid }); }), + ]; + + Promise.all(queries).then(async () => { + const statsEl = components.get('topic/stats'); + updateSpinner('progress'); + const { crossposts } = await api.get(`/topics/${data.tid}/crossposts`); + ajaxify.data.crossposts = crossposts; + const html = await app.parseAndTranslate('partials/topic/stats', ajaxify.data); + statsEl.html(html); + updateSpinner('success'); + }).catch((e) => { + updateSpinner('error'); + alerts.error(e); + }); + } + + const spinnerClasses = new Map(Object.entries({ + 'initial': ['d-none'], + 'progress': ['fa-spinner', 'text-secondary', 'fa-spin'], + 'error': ['fa-times', 'text-error'], + 'success': ['fa-check', 'text-success'], + })); + function updateSpinner(state) { + if (modal) { + const spinnerEl = document.getElementById('crosspost_topic_spinner'); + const remove = [ + ...spinnerClasses.get('initial'), + ...spinnerClasses.get('progress'), + ...spinnerClasses.get('error'), + ...spinnerClasses.get('success'), + ]; + spinnerEl.classList.remove(...remove); + spinnerEl.classList.add(...spinnerClasses.get(state)); + + if (state !== 'initial') { + setTimeout(() => { + updateSpinner('initial'); + }, 2500); + } + } + } + + function closeCrosspostModal() { + if (modal) { + modal.remove(); + modal = null; + } + } + + return Crosspost; +}); diff --git a/public/src/client/topic/delete-posts.js b/public/src/client/topic/delete-posts.js index 58b4058f09..8e01a839e0 100644 --- a/public/src/client/topic/delete-posts.js +++ b/public/src/client/topic/delete-posts.js @@ -31,6 +31,8 @@ define('forum/topic/delete-posts', [ postSelect.init(function () { checkButtonEnable(); showPostsSelected(); + }, { + allowMainPostSelect: true, }); showPostsSelected(); diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index 5b45deeb54..10c5d54cf2 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -50,18 +50,14 @@ define('forum/topic/events', [ Events.init = function () { Events.removeListeners(); - for (const eventName in events) { - if (events.hasOwnProperty(eventName)) { - socket.on(eventName, events[eventName]); - } + for (const [eventName, handler] of Object.entries(events)) { + socket.on(eventName, handler); } }; Events.removeListeners = function () { - for (const eventName in events) { - if (events.hasOwnProperty(eventName)) { - socket.removeListener(eventName, events[eventName]); - } + for (const [eventName, handler] of Object.entries(events)) { + socket.removeListener(eventName, handler); } }; @@ -166,7 +162,7 @@ define('forum/topic/events', [ translator.unescape(data.post.content) ); parentEl.find('img:not(.not-responsive)').addClass('img-fluid'); - parentEl.find('[component="post/parent/content]" img:not(.emoji)').each(function () { + parentEl.find('[component="post/parent/content"] img:not(.emoji)').each(function () { images.wrapImageInLink($(this)); }); } @@ -180,6 +176,12 @@ define('forum/topic/events', [ }); } + if (data.topic.thumbsupdated) { + require(['topicThumbs'], function (topicThumbs) { + topicThumbs.updateTopicThumbs(data.topic.tid); + }); + } + postTools.removeMenu(components.get('post', 'pid', data.post.pid)); } diff --git a/public/src/client/topic/move.js b/public/src/client/topic/move.js index ba4f055e68..08ce3e9d5c 100644 --- a/public/src/client/topic/move.js +++ b/public/src/client/topic/move.js @@ -2,8 +2,8 @@ define('forum/topic/move', [ - 'categorySelector', 'alerts', 'hooks', -], function (categorySelector, alerts, hooks) { + 'categorySelector', 'alerts', 'hooks', 'api', +], function (categorySelector, alerts, hooks, api) { const Move = {}; let modal; let selectedCategory; @@ -34,6 +34,7 @@ define('forum/topic/move', [ categorySelector.init(dropdownEl, { onSelect: onCategorySelected, privilege: 'moderate', + localOnly: true, }); modal.find('#move_thread_commit').on('click', onCommitClicked); @@ -88,15 +89,26 @@ define('forum/topic/move', [ function moveTopics(data) { hooks.fire('action:topic.move', data); - socket.emit(!data.tids ? 'topics.moveAll' : 'topics.move', data, function (err) { - if (err) { - return alerts.error(err); - } + if (data.tids) { + data.tids.forEach((tid) => { + api.put(`/topics/${tid}/move`, { cid: data.cid }).then(() => { + if (typeof data.onComplete === 'function') { + data.onComplete(); + } + }).catch(alerts.error); + }); + } else { + socket.emit('topics.moveAll', data, function (err) { + if (err) { + return alerts.error(err); + } + + if (typeof data.onComplete === 'function') { + data.onComplete(); + } + }); + } - if (typeof data.onComplete === 'function') { - data.onComplete(); - } - }); } function closeMoveModal() { diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 42f10093a0..28538b972f 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -397,7 +397,7 @@ define('forum/topic/postTools', [ return; } const post = button.parents('[data-pid]'); - if (post.length) { + if (post.length && !post.hasClass('self-post')) { require(['slugify'], function (slugify) { slug = slugify(post.attr('data-username'), true); if (!slug) { @@ -453,7 +453,7 @@ define('forum/topic/postTools', [ require(['chat'], function (chat) { chat.newChat(post.attr('data-uid')); }); - button.parents('.btn-group').find('.dropdown-toggle').click(); + button.parents('.dropdown').find('.dropdown-toggle').click(); return false; } diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index a83a9d86b5..7e1a001ada 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -116,6 +116,12 @@ define('forum/topic/threadTools', [ return false; }); + topicContainer.on('click', '[component="topic/crosspost"]', () => { + require(['forum/topic/crosspost'], (crosspost) => { + crosspost.init(tid, ajaxify.data.cid); + }); + }); + topicContainer.on('click', '[component="topic/delete/posts"]', function () { require(['forum/topic/delete-posts'], function (deletePosts) { deletePosts.init(); @@ -225,7 +231,7 @@ define('forum/topic/threadTools', [ return; } dropdownMenu.html(helpers.generatePlaceholderWave([8, 8, 8])); - const data = await socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid }); + const data = await socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid }).catch(alerts.error); const html = await app.parseAndTranslate('partials/topic/topic-menu-list', data); $(dropdownMenu).attr('data-loaded', 'true').html(html); hooks.fire('action:topic.tools.load', { diff --git a/public/src/client/unread.js b/public/src/client/unread.js index 65d5e75d6d..240d426a53 100644 --- a/public/src/client/unread.js +++ b/public/src/client/unread.js @@ -61,6 +61,17 @@ define('forum/unread', [ doneRemovingTids(tids); }); } + + // Generate list of default categories based on topic list + let defaultCategories = ajaxify.data.topics.reduce((map, topic) => { + const { category } = topic; + let { cid } = category; + cid = utils.isNumber(cid) ? parseInt(cid, 10) : cid; + map.set(cid, category); + return map; + }, new Map()); + defaultCategories = Array.from(defaultCategories.values()); + const selector = categorySelector.init($('[component="category-selector"]'), { onSelect: function (category) { selector.selectCategory(0); @@ -68,7 +79,7 @@ define('forum/unread', [ markAllRead(); } else if (category.cid === 'selected') { markSelectedRead(); - } else if (parseInt(category.cid, 10) > 0) { + } else if (category.cid) { markCategoryRead(category.cid); } }, @@ -85,6 +96,7 @@ define('forum/unread', [ icon: '', }, ], + defaultCategories, }); } diff --git a/public/src/client/world.js b/public/src/client/world.js index 34a7c86d0f..adf6925e57 100644 --- a/public/src/client/world.js +++ b/public/src/client/world.js @@ -1,46 +1,158 @@ 'use strict'; -define('forum/world', ['topicList', 'sort', 'hooks', 'alerts', 'api', 'bootbox'], function (topicList, sort, hooks, alerts, api, bootbox) { +define('forum/world', [ + 'forum/infinitescroll', 'search', 'sort', 'hooks', + 'alerts', 'api', 'bootbox', 'helpers', 'forum/category/tools', + 'translator', +], function (infinitescroll, search, sort, hooks, alerts, api, bootbox, helpers, categoryTools, translator) { const World = {}; + $(window).on('action:ajaxify.start', function () { + categoryTools.removeListeners(); + }); + World.init = function () { app.enterRoom('world'); - topicList.init('world'); + categoryTools.init($('#world-feed')); sort.handleSort('categoryTopicSort', 'world'); - handleIgnoreWatch(-1); + handleButtons(); handleHelp(); + // Add label to sort + const sortLabelEl = document.getElementById('sort-label'); + const sortOptionsEl = document.getElementById('sort-options'); + if (sortLabelEl && sortOptionsEl) { + const params = new URLSearchParams(window.location.search); + if (params.get('sort') === 'popular') { + translator.translate(`[[world:popular-${params.get('term')}]]`, function (translated) { + sortLabelEl.innerText = translated; + }); + } else { + translator.translate('[[world:latest]]', function (translated) { + sortLabelEl.innerText = translated; + }); + } + } + + search.enableQuickSearch({ + searchElements: { + inputEl: $('[component="category-search"]'), + resultEl: $('.world .quick-search-container'), + }, + searchOptions: { + in: 'categories', + }, + dropdown: { + maxWidth: '400px', + maxHeight: '350px', + }, + hideOnNoMatches: false, + }); + + if (!config.usePagination) { + infinitescroll.init((direction) => { + const posts = Array.from(document.querySelectorAll('[component="category/topic"]')); + const afterEl = direction > 0 ? posts.pop() : posts.shift(); + const after = (parseInt(afterEl.getAttribute('data-index'), 10) || 0) + (direction > 0 ? 1 : 0); + if (after < config.topicsPerPage) { + return; + } + + loadTopicsAfter(after, direction, (payload) => { + app.parseAndTranslate(ajaxify.data.template.name, 'posts', payload, function (html) { + const listEl = document.getElementById('world-feed'); + $(listEl).append(html); + html.find('.timeago').timeago(); + }); + }); + }); + } + + ajaxify.data.categories.forEach(function (category) { + handleIgnoreWatch(category.cid); + }); + hooks.fire('action:topics.loaded', { topics: ajaxify.data.topics }); hooks.fire('action:category.loaded', { cid: ajaxify.data.cid }); }; - function handleIgnoreWatch(cid) { - $('[component="category/watching"], [component="category/tracking"], [component="category/ignoring"], [component="category/notwatching"]').on('click', function () { - const $this = $(this); - const state = $this.attr('data-state'); + function calculateNextPage(after, direction) { + return Math.floor(after / config.topicsPerPage) + (direction > 0 ? 1 : 0); + } - api.put(`/categories/${cid}/watch`, { state }, (err) => { + function loadTopicsAfter(after, direction, callback) { + callback = callback || function () {}; + const query = utils.params(); + query.page = calculateNextPage(after, direction); + infinitescroll.loadMoreXhr(query, callback); + } + + function handleButtons() { + const feedEl = $('#world-feed'); + + feedEl.on('click', '[data-action="bookmark"]', function () { + const $this = $(this); + const isBookmarked = $this.attr('data-bookmarked') === 'true'; + const pid = $this.attr('data-pid'); + const bookmarkCount = parseInt($this.attr('data-bookmarks'), 10); + const method = isBookmarked ? 'del' : 'put'; + + api[method](`/posts/${pid}/bookmark`, undefined, function (err) { + if (err) { + return alerts.error(err); + } + const type = isBookmarked ? 'unbookmark' : 'bookmark'; + const newBookmarkCount = bookmarkCount + (isBookmarked ? -1 : 1); + $this.find('[component="bookmark-count"]').text( + helpers.humanReadableNumber(newBookmarkCount) + ); + $this.attr('data-bookmarks', newBookmarkCount); + $this.attr('data-bookmarked', isBookmarked ? 'false' : 'true'); + $this.find('i').toggleClass('fa text-primary', !isBookmarked) + .toggleClass('fa-regular text-muted', isBookmarked); + hooks.fire(`action:post.${type}`, { pid: pid }); + }); + }); + + feedEl.on('click', '[data-action="upvote"]', function () { + const $this = $(this); + const isUpvoted = $this.attr('data-upvoted') === 'true'; + const pid = $this.attr('data-pid'); + const upvoteCount = parseInt($this.attr('data-upvotes'), 10); + const method = isUpvoted ? 'del' : 'put'; + const delta = 1; + api[method](`/posts/${pid}/vote`, { delta }, function (err) { if (err) { return alerts.error(err); } - $('[component="category/watching/menu"]').toggleClass('hidden', state !== 'watching'); - $('[component="category/watching/check"]').toggleClass('fa-check', state === 'watching'); + const newUpvoteCount = upvoteCount + (isUpvoted ? -1 : 1); + $this.find('[component="upvote-count"]').text( + helpers.humanReadableNumber(newUpvoteCount) + ); + $this.attr('data-upvotes', newUpvoteCount); + $this.attr('data-upvoted', isUpvoted ? 'false' : 'true'); + $this.find('i').toggleClass('fa text-danger', !isUpvoted) + .toggleClass('fa-regular text-muted', isUpvoted); - $('[component="category/tracking/menu"]').toggleClass('hidden', state !== 'tracking'); - $('[component="category/tracking/check"]').toggleClass('fa-check', state === 'tracking'); - - $('[component="category/notwatching/menu"]').toggleClass('hidden', state !== 'notwatching'); - $('[component="category/notwatching/check"]').toggleClass('fa-check', state === 'notwatching'); - - $('[component="category/ignoring/menu"]').toggleClass('hidden', state !== 'ignoring'); - $('[component="category/ignoring/check"]').toggleClass('fa-check', state === 'ignoring'); - - alerts.success('[[category:' + state + '.message]]'); + hooks.fire('action:post.toggleVote', { + pid: pid, + delta: delta, + unvote: method === 'del', + }); }); }); + + feedEl.on('click', '[data-action="reply"]', function () { + const $this = $(this); + const isMain = $this.attr('data-is-main') === 'true'; + app.newReply({ + tid: $this.attr('data-tid'), + pid: !isMain ? $this.attr('data-pid') : undefined, + }).catch(alerts.error); + }); } function handleHelp() { @@ -66,5 +178,36 @@ define('forum/world', ['topicList', 'sort', 'hooks', 'alerts', 'api', 'bootbox'] }); } + function handleIgnoreWatch(cid) { + const category = $('[data-cid="' + cid + '"]'); + category.find( + '[component="category/watching"], [component="category/tracking"], [component="category/ignoring"], [component="category/notwatching"]' + ).on('click', async (e) => { + const state = e.currentTarget.getAttribute('data-state'); + const { uid } = ajaxify.data; + + const { modified } = await api.put(`/categories/${encodeURIComponent(cid)}/watch`, { state, uid }); + updateDropdowns(modified, state); + alerts.success('[[category:' + state + '.message]]'); + }); + } + + function updateDropdowns(modified_cids, state) { + modified_cids.forEach(function (cid) { + const category = $('[data-cid="' + cid + '"]'); + category.find('[component="category/watching/menu"]').toggleClass('hidden', state !== 'watching'); + category.find('[component="category/watching/check"]').toggleClass('fa-check', state === 'watching'); + + category.find('[component="category/tracking/menu"]').toggleClass('hidden', state !== 'tracking'); + category.find('[component="category/tracking/check"]').toggleClass('fa-check', state === 'tracking'); + + category.find('[component="category/notwatching/menu"]').toggleClass('hidden', state !== 'notwatching'); + category.find('[component="category/notwatching/check"]').toggleClass('fa-check', state === 'notwatching'); + + category.find('[component="category/ignoring/menu"]').toggleClass('hidden', state !== 'ignoring'); + category.find('[component="category/ignoring/check"]').toggleClass('fa-check', state === 'ignoring'); + }); + } + return World; }); diff --git a/public/src/modules/accounts/moderate.js b/public/src/modules/accounts/moderate.js index 9b1308a8e1..084376140e 100644 --- a/public/src/modules/accounts/moderate.js +++ b/public/src/modules/accounts/moderate.js @@ -1,11 +1,11 @@ 'use strict'; define('forum/account/moderate', [ - 'benchpress', 'api', 'bootbox', 'alerts', -], function (Benchpress, api, bootbox, alerts) { + 'translator', +], function (api, bootbox, alerts, translator) { const AccountModerate = {}; AccountModerate.banAccount = function (theirid, onSuccess) { @@ -14,11 +14,12 @@ define('forum/account/moderate', [ throwModal({ tpl: 'modals/temporary-ban', title: '[[user:ban-account]]', + type: 'ban', onSubmit: function (formData) { const until = formData.length > 0 ? ( Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1)) ) : 0; - api.put('/users/' + theirid + '/ban', { + api.put('/users/' + encodeURIComponent(theirid) + '/ban', { until: until, reason: formData.reason || '', }).then(() => { @@ -36,8 +37,9 @@ define('forum/account/moderate', [ throwModal({ tpl: 'modals/unban', title: '[[user:unban-account]]', + type: 'ban', onSubmit: function (formData) { - api.del('/users/' + theirid + '/ban', { + api.del('/users/' + encodeURIComponent(theirid) + '/ban', { reason: formData.reason || '', }).then(() => { ajaxify.refresh(); @@ -51,6 +53,7 @@ define('forum/account/moderate', [ throwModal({ tpl: 'modals/temporary-mute', title: '[[user:mute-account]]', + type: 'mute', onSubmit: function (formData) { const until = formData.length > 0 ? ( Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1)) @@ -83,31 +86,37 @@ define('forum/account/moderate', [ }); }; - function throwModal(options) { - Benchpress.render(options.tpl, {}).then(function (html) { - const modal = bootbox.dialog({ - title: options.title, - message: html, - show: true, - onEscape: true, - buttons: { - close: { - label: '[[global:close]]', - className: 'btn-link', - }, - submit: { - label: options.title, - callback: function () { - const formData = modal.find('form').serializeArray().reduce(function (data, cur) { - data[cur.name] = cur.value; - return data; - }, {}); + async function throwModal(options) { + const reasons = await socket.emit('user.getCustomReasons', { type: options.type || '' }); + const html = await app.parseAndTranslate(options.tpl, { reasons }); + const modal = bootbox.dialog({ + title: options.title, + message: html, + show: true, + onEscape: true, + buttons: { + close: { + label: '[[global:close]]', + className: 'btn-link', + }, + submit: { + label: options.title, + callback: function () { + const formData = modal.find('form').serializeArray().reduce(function (data, cur) { + data[cur.name] = cur.value; + return data; + }, {}); - options.onSubmit(formData); - }, + options.onSubmit(formData); }, }, - }); + }, + }); + modal.find('[data-key]').on('click', function () { + const reason = reasons.find(r => String(r.key) === $(this).attr('data-key')); + if (reason && reason.body) { + modal.find('[name="reason"]').val(translator.unescape(reason.body)); + } }); } diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js index 13e4e56427..24ca324392 100644 --- a/public/src/modules/autocomplete.js +++ b/public/src/modules/autocomplete.js @@ -55,6 +55,7 @@ define('autocomplete', [ slug: user.userslug, username: user.username, userslug: user.userslug, + displayname: user.displayname, picture: user.picture, banned: user.banned, 'icon:text': user['icon:text'], @@ -137,7 +138,7 @@ define('autocomplete', [ if (!targetEl) { return; } - var editor; + let editor; if (targetEl.nodeName === 'TEXTAREA' || targetEl.nodeName === 'INPUT') { editor = new TextareaEditor(targetEl); } else if (targetEl.nodeName === 'DIV' && targetEl.getAttribute('contenteditable') === 'true') { @@ -149,7 +150,7 @@ define('autocomplete', [ // yuku-t/textcomplete inherits directionality from target element itself targetEl.setAttribute('dir', document.querySelector('html').getAttribute('data-dir')); - var textcomplete = new Textcomplete(editor, strategies, { + const textcomplete = new Textcomplete(editor, strategies, { dropdown: options, }); textcomplete.on('rendered', function () { diff --git a/public/src/modules/categorySearch.js b/public/src/modules/categorySearch.js index 8fa7a9f184..8542131b37 100644 --- a/public/src/modules/categorySearch.js +++ b/public/src/modules/categorySearch.js @@ -4,7 +4,7 @@ define('categorySearch', ['alerts', 'bootstrap', 'api'], function (alerts, boots const categorySearch = {}; categorySearch.init = function (el, options) { - let categoriesList = null; + let categoriesList = options.defaultCategories || null; options = options || {}; options.privilege = options.privilege || 'topics:read'; options.states = options.states || ['watching', 'tracking', 'notwatching', 'ignoring']; @@ -13,6 +13,9 @@ define('categorySearch', ['alerts', 'bootstrap', 'api'], function (alerts, boots let localCategories = []; if (Array.isArray(options.localCategories)) { localCategories = options.localCategories.map(c => ({ ...c })); + if (categoriesList) { + categoriesList = [...localCategories, ...categoriesList]; + } } options.selectedCids = options.selectedCids || ajaxify.data.selectedCids || []; @@ -73,6 +76,8 @@ define('categorySearch', ['alerts', 'bootstrap', 'api'], function (alerts, boots privilege: options.privilege, states: options.states, showLinks: options.showLinks, + localOnly: options.localOnly, + hideUncategorized: options.hideUncategorized, }, function (err, { categories }) { if (err) { return alerts.error(err); @@ -90,6 +95,7 @@ define('categorySearch', ['alerts', 'bootstrap', 'api'], function (alerts, boots categoryItems: categories.slice(0, 200), selectedCategory: ajaxify.data.selectedCategory, allCategoriesUrl: ajaxify.data.allCategoriesUrl, + hideAll: options.hideAll, }, function (html) { el.find('[component="category/list"]') .html(html.find('[component="category/list"]').html()); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 426bd8c6e9..81b9ce9d9c 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -3,17 +3,17 @@ define('chat', [ 'components', 'taskbar', 'translator', 'hooks', 'bootbox', 'alerts', 'api', 'scrollStop', ], function (components, taskbar, translator, hooks, bootbox, alerts, api, scrollStop) { - const module = {}; + const Chat = {}; - module.openChat = function (roomId, uid) { + Chat.openChat = function (roomId, uid) { if (!app.user.uid) { return alerts.error('[[error:not-logged-in]]'); } function loadAndCenter(chatModal) { - module.load(chatModal.attr('data-uuid')); - module.center(chatModal); - module.focusInput(chatModal); + Chat.load(chatModal.attr('data-uuid')); + Chat.center(chatModal); + Chat.focusInput(chatModal); } hooks.fire('filter:chat.openChat', { modal: true, @@ -23,8 +23,8 @@ define('chat', [ if (!hookData.modal) { return ajaxify.go(`/chats/${roomId}`); } - if (module.modalExists(roomId)) { - loadAndCenter(module.getModal(roomId)); + if (Chat.modalExists(roomId)) { + loadAndCenter(Chat.getModal(roomId)); } else { api.get(`/chats/${roomId}`, { uid: uid || app.user.uid, @@ -34,19 +34,19 @@ define('chat', [ }); roomData.uid = uid || app.user.uid; roomData.isSelf = true; - module.createModal(roomData, loadAndCenter); + Chat.createModal(roomData, loadAndCenter); }).catch(alerts.error); } }); }; - module.newChat = function (touid, callback) { + Chat.newChat = function (touid, callback) { function createChat() { api.post(`/chats`, { uids: [touid], }).then(({ roomId }) => { if (!ajaxify.data.template.chats) { - module.openChat(roomId); + Chat.openChat(roomId); } else { ajaxify.go('chats/' + roomId); } @@ -82,7 +82,7 @@ define('chat', [ }).catch(alerts.error); }; - module.loadChatsDropdown = function (chatsListEl) { + Chat.loadChatsDropdown = function (chatsListEl) { api.get('/chats', { uid: app.user.uid, after: 0, @@ -114,7 +114,7 @@ define('chat', [ } const roomId = $(this).attr('data-roomid'); if (!ajaxify.currentPage.match(/^chats\//)) { - module.openChat(roomId); + Chat.openChat(roomId); } else { ajaxify.go('user/' + app.user.userslug + '/chats/' + roomId); } @@ -129,7 +129,7 @@ define('chat', [ const roomId = el.getAttribute('data-roomid'); await api.del(`/chats/${roomId}/state`); if (ajaxify.data.template.chats) { - module.markChatElUnread($(el), false); + Chat.markChatElUnread($(el), false); } })); }); @@ -145,32 +145,32 @@ define('chat', [ e.stopPropagation(); const chatEl = e.target.closest('[data-roomid]'); - module.toggleReadState(chatEl); + Chat.toggleReadState(chatEl); } - module.toggleReadState = function (chatEl) { + Chat.toggleReadState = function (chatEl) { const state = !chatEl.classList.contains('unread'); // this is the new state const roomId = chatEl.getAttribute('data-roomid'); api[state ? 'put' : 'del'](`/chats/${roomId}/state`, {}).catch((err) => { alerts.error(err); // Revert on failure - module.markChatElUnread($(chatEl), !state); + Chat.markChatElUnread($(chatEl), !state); }); // Immediate feedback - module.markChatElUnread($(chatEl), state); + Chat.markChatElUnread($(chatEl), state); }; - module.isFromBlockedUser = function (fromUid) { + Chat.isFromBlockedUser = function (fromUid) { return app.user.blocks.includes(parseInt(fromUid, 10)); }; - module.isLookingAtRoom = function (roomId) { + Chat.isLookingAtRoom = function (roomId) { return ajaxify.data.template.chats && parseInt(ajaxify.data.roomId, 10) === parseInt(roomId, 10); }; - module.markChatElUnread = function (roomEl, unread) { + Chat.markChatElUnread = function (roomEl, unread) { if (roomEl.length > 0) { roomEl.toggleClass('unread', unread); const markEl = roomEl.find('.mark-read'); @@ -181,12 +181,12 @@ define('chat', [ } }; - module.onChatMessageReceived = function (data) { + Chat.onChatMessageReceived = function (data) { if (app.user.blocks.includes(parseInt(data.fromUid, 10))) { return; } - if (module.modalExists(data.roomId)) { - const modal = module.getModal(data.roomId); + if (Chat.modalExists(data.roomId)) { + const modal = Chat.getModal(data.roomId); const newMessage = parseInt(modal.attr('new-message'), 10) === 1; data.self = parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0; if (!newMessage) { @@ -200,7 +200,7 @@ define('chat', [ }; function addMessageToModal(data) { - const modal = module.getModal(data.roomId); + const modal = Chat.getModal(data.roomId); const username = data.message.fromUser.username; const isSelf = data.self === 1; require(['forum/chats/messages'], function (ChatsMessages) { @@ -215,7 +215,7 @@ define('chat', [ ChatsMessages.scrollToBottomAfterImageLoad(modal.find('[component="chat/message/content"]')); } } else if (!ajaxify.data.template.chats) { - module.toggleNew(modal.attr('data-uuid'), true, true); + Chat.toggleNew(modal.attr('data-uuid'), true, true); } if (!isSelf && (!modal.is(':visible') || !app.isFocused)) { @@ -229,8 +229,8 @@ define('chat', [ }); } - module.onRoomRename = function (data) { - const modal = module.getModal(data.roomId); + Chat.onRoomRename = function (data) { + const modal = Chat.getModal(data.roomId); const titleEl = modal.find('[component="chat/room/name"]'); const icon = titleEl.attr('data-icon'); if (titleEl.length) { @@ -250,17 +250,17 @@ define('chat', [ })); }; - module.onUserTyping = function (data) { - if (data.uid === app.user.uid || module.isFromBlockedUser(data.uid)) { + Chat.onUserTyping = function (data) { + if (data.uid === app.user.uid || Chat.isFromBlockedUser(data.uid)) { return; } - const modal = module.getModal(data.roomId); + const modal = Chat.getModal(data.roomId); if (modal.length) { - module.updateTypingUserList(modal, data); + Chat.updateTypingUserList(modal, data); } }; - module.updateTypingUserList = async function (container, { uid, username, typing }) { + Chat.updateTypingUserList = async function (container, { uid, username, typing }) { const typingEl = container.find(`[component="chat/composer/typing"]`); const typingUsersList = typingEl.find('[component="chat/composer/typing/users"]'); const userEl = typingUsersList.find(`[data-uid="${uid}"]`); @@ -288,15 +288,15 @@ define('chat', [ typingTextEl.toggleClass('hidden', !usernames.length); }; - module.getModal = function (roomId) { + Chat.getModal = function (roomId) { return $('#chat-modal-' + roomId); }; - module.modalExists = function (roomId) { + Chat.modalExists = function (roomId) { return $('#chat-modal-' + roomId).length !== 0; }; - module.initWidget = function (roomId, chatModal) { + Chat.initWidget = function (roomId, chatModal) { require(['forum/chats', 'forum/chats/messages'], function (Chats, ChatsMessages) { socket.emit('modules.chats.enter', roomId); api.del(`/chats/${roomId}/state`, {}); @@ -345,15 +345,15 @@ define('chat', [ }); }; - module.createModal = function (data, callback) { + Chat.createModal = function (data, callback) { callback = callback || function () {}; require([ 'forum/chats', 'forum/chats/messages', 'forum/chats/message-search', ], function (Chats, ChatsMessages, messageSearch) { app.parseAndTranslate('chat', data, function (chatModal) { const roomId = data.roomId; - if (module.modalExists(roomId)) { - return callback(module.getModal(data.roomId)); + if (Chat.modalExists(roomId)) { + return callback(Chat.getModal(data.roomId)); } const uuid = utils.generateUUID(); let dragged = false; @@ -369,12 +369,12 @@ define('chat', [ scrollStop.apply(chatModal.find('[component="chat/messages"] .chat-content')); - module.center(chatModal); + Chat.center(chatModal); makeModalResizeableDraggable(chatModal, uuid); chatModal.find('#chat-close-btn').on('click', function () { - module.close(uuid); + Chat.close(uuid); }); function gotoChats() { @@ -384,14 +384,14 @@ define('chat', [ }); ajaxify.go(`user/${app.user.userslug}/chats/${roomId}`); - module.close(uuid); + Chat.close(uuid); } chatModal.find('.modal-header').on('dblclick', gotoChats); chatModal.find('button[data-action="maximize"]').on('click', gotoChats); chatModal.find('button[data-action="minimize"]').on('click', function () { const uuid = chatModal.attr('data-uuid'); - module.minimize(uuid); + Chat.minimize(uuid); }); chatModal.on('mouseup', function () { @@ -475,7 +475,7 @@ define('chat', [ return; } - chatModal.find('.modal-body').css('height', module.calculateChatListHeight(chatModal)); + chatModal.find('.modal-body').css('height', Chat.calculateChatListHeight(chatModal)); }); chatModal.draggable({ @@ -484,7 +484,7 @@ define('chat', [ chatModal.css({ bottom: 'auto', right: 'auto' }); }, stop: function () { - module.focusInput(chatModal); + Chat.focusInput(chatModal); }, distance: 10, handle: '.modal-header', @@ -492,20 +492,20 @@ define('chat', [ }); } - module.focusInput = function (chatModal) { + Chat.focusInput = function (chatModal) { setTimeout(function () { chatModal.find('[component="chat/input"]').focus(); }, 20); }; - module.close = function (uuid) { + Chat.close = function (uuid) { const chatModal = $('.chat-modal[data-uuid="' + uuid + '"]'); chatModal.remove(); chatModal.data('modal', null); taskbar.discard('chat', uuid); if (chatModal.attr('data-mobile')) { - module.disableMobileBehaviour(chatModal); + Chat.disableMobileBehaviour(chatModal); } const roomId = chatModal.attr('data-roomid'); require(['forum/chats'], function (chats) { @@ -518,7 +518,7 @@ define('chat', [ }); }; - module.center = function (chatModal) { + Chat.center = function (chatModal) { const center = chatModal.attr('data-center'); if (!center || center === 'false') { return; @@ -537,30 +537,30 @@ define('chat', [ return chatModal; }; - module.load = function (uuid) { + Chat.load = function (uuid) { require(['forum/chats/messages'], function (ChatsMessages) { const chatModal = $('.chat-modal[data-uuid="' + uuid + '"]'); chatModal.removeClass('hide'); taskbar.updateActive(uuid); ChatsMessages.scrollToBottomAfterImageLoad(chatModal.find('.chat-content')); - module.focusInput(chatModal); + Chat.focusInput(chatModal); const roomId = chatModal.attr('data-roomid'); api.del(`/chats/${roomId}/state`, {}); socket.emit('modules.chats.enter', roomId); const env = utils.findBootstrapEnvironment(); if (env === 'xs' || env === 'sm') { - module.enableMobileBehaviour(chatModal); + Chat.enableMobileBehaviour(chatModal); } }); }; - module.enableMobileBehaviour = function (modalEl) { + Chat.enableMobileBehaviour = function (modalEl) { app.toggleNavbar(false); modalEl.attr('data-mobile', '1'); const messagesEl = modalEl.find('.modal-body'); - messagesEl.css('height', module.calculateChatListHeight(modalEl)); + messagesEl.css('height', Chat.calculateChatListHeight(modalEl)); function resize() { - messagesEl.css('height', module.calculateChatListHeight(modalEl)); + messagesEl.css('height', Chat.calculateChatListHeight(modalEl)); require(['forum/chats/messages'], function (ChatsMessages) { ChatsMessages.scrollToBottom(modalEl.find('.chat-content')); }); @@ -568,21 +568,21 @@ define('chat', [ $(window).on('resize', resize); $(window).one('action:ajaxify.start', function () { - module.close(modalEl.attr('data-uuid')); + Chat.close(modalEl.attr('data-uuid')); $(window).off('resize', resize); }); }; - module.disableMobileBehaviour = function () { + Chat.disableMobileBehaviour = function () { app.toggleNavbar(true); }; - module.calculateChatListHeight = function (modalEl) { + Chat.calculateChatListHeight = function (modalEl) { // Formula: modal height minus header height. Simple(tm). return modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight(); }; - module.minimize = function (uuid) { + Chat.minimize = function (uuid) { const chatModal = $('.chat-modal[data-uuid="' + uuid + '"]'); chatModal.addClass('hide'); taskbar.minimize('chat', uuid); @@ -592,7 +592,7 @@ define('chat', [ }); }; - module.toggleNew = taskbar.toggleNew; + Chat.toggleNew = taskbar.toggleNew; - return module; + return Chat; }); diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index 454fb441c2..6a09a32869 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -25,6 +25,8 @@ module.exports = function (utils, Benchpress, relative_path) { userAgentIcons, buildAvatar, increment, + lessthan, + greaterthan, generateWroteReplied, generateRepliedTo, generateWrote, @@ -33,7 +35,9 @@ module.exports = function (utils, Benchpress, relative_path) { shouldHideReplyContainer, humanReadableNumber, formattedNumber, + isNumber, txEscape, + uploadBasename, generatePlaceholderWave, register, __escape: identity, @@ -73,10 +77,12 @@ module.exports = function (utils, Benchpress, relative_path) { } function buildLinkTag(tag) { - const attributes = ['link', 'rel', 'as', 'type', 'href', 'sizes', 'title', 'crossorigin']; - const [link, rel, as, type, href, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); + const attributes = [ + 'link', 'rel', 'as', 'type', 'href', 'hreflang', 'sizes', 'title', 'crossorigin', + ]; + const [link, rel, as, type, href, hreflang, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); - return '\n\t'; + return '\n\t'; } function stringify(obj) { @@ -107,7 +113,7 @@ module.exports = function (utils, Benchpress, relative_path) { } const href = tag === 'a' ? `href="${relative_path}/category/${category.slug}"` : ''; - return `<${tag} ${href} class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> + return `<${tag} component="topic/category" ${href} class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> ${category.icon && category.icon !== 'fa-nbb-none' ? `` : ''} ${category.name} `; @@ -180,14 +186,12 @@ module.exports = function (utils, Benchpress, relative_path) { function spawnPrivilegeStates(cid, member, privileges, types) { const states = []; - for (const priv in privileges) { - if (privileges.hasOwnProperty(priv)) { - states.push({ - name: priv, - state: privileges[priv], - type: types[priv], - }); - } + for (const [priv, state] of Object.entries(privileges)) { + states.push({ + name: priv, + state: state, + type: types[priv], + }); } return states.map(function (priv) { const guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login', 'groups:group:create']; @@ -329,6 +333,14 @@ module.exports = function (utils, Benchpress, relative_path) { return String(value + parseInt(inc, 10)); } + function lessthan(a, b) { + return parseInt(a, 10) < parseInt(b, 10); + } + + function greaterthan(a, b) { + return parseInt(a, 10) > parseInt(b, 10); + } + function generateWroteReplied(post, timeagoCutoff) { if (post.toPid) { return generateRepliedTo(post, timeagoCutoff); @@ -362,11 +374,7 @@ module.exports = function (utils, Benchpress, relative_path) { } function shouldHideReplyContainer(post) { - if (post.replies.count <= 0 || post.replies.hasSingleImmediateReply) { - return true; - } - - return false; + return post.replies.count <= 0 || post.replies.hasSingleImmediateReply; } function humanReadableNumber(number, toFixed = 1) { @@ -377,10 +385,20 @@ module.exports = function (utils, Benchpress, relative_path) { return utils.addCommas(number); } + function isNumber(value) { + return utils.isNumber(value); + } + function txEscape(text) { return String(text).replace(/%/g, '%').replace(/,/g, ','); } + function uploadBasename(str, sep = '/') { + const hasTimestampPrefix = /^\d+-/; + const name = str.substr(str.lastIndexOf(sep) + 1); + return hasTimestampPrefix.test(name) ? name.slice(14) : name; + } + function generatePlaceholderWave(items) { const html = items.map((i) => { if (i === 'divider') { diff --git a/public/src/modules/iconSelect.js b/public/src/modules/iconSelect.js index 674e8caf41..409ddb059b 100644 --- a/public/src/modules/iconSelect.js +++ b/public/src/modules/iconSelect.js @@ -255,7 +255,6 @@ define('iconSelect', ['benchpress', 'bootbox'], function (Benchpress, bootbox) { className: 'btn-default', callback: function () { el.removeClass(selected.icon); - // eslint-disable-next-line no-restricted-syntax for (const style of selected.styles) { el.removeClass(style); } @@ -272,11 +271,9 @@ define('iconSelect', ['benchpress', 'bootbox'], function (Benchpress, bootbox) { const newIcon = cleanFAClass(iconClass); if (newIcon.icon) { el.removeClass(selected.icon).addClass(newIcon.icon); - // eslint-disable-next-line no-restricted-syntax for (const style of selected.styles || []) { el.removeClass(style); } - // eslint-disable-next-line no-restricted-syntax for (const style of newIcon.styles || []) { el.addClass(style); } @@ -391,7 +388,6 @@ define('iconSelect', ['benchpress', 'bootbox'], function (Benchpress, bootbox) { function cleanFAClass(classList) { const styles = []; let icon; - // eslint-disable-next-line no-restricted-syntax for (const className of classList) { if (className.startsWith('fa-') && !excludedClassRegex.test(className)) { if (styleRegex.test(className)) { diff --git a/public/src/modules/messages.js b/public/src/modules/messages.js index 0539c119e3..33a78fb24c 100644 --- a/public/src/modules/messages.js +++ b/public/src/modules/messages.js @@ -26,20 +26,17 @@ define('messages', ['bootbox', 'translator', 'storage', 'alerts', 'hooks'], func storage.setItem('email-confirm-dismiss', 1); }, }; - + function hideAlertAndGotoEditEmail() { + alerts.remove('email_confirm'); + ajaxify.go('/me/edit/email'); + } if (!app.user.email && !app.user.isEmailConfirmSent) { msg.message = '[[error:no-email-to-confirm]]'; - msg.clickfn = function () { - alerts.remove('email_confirm'); - ajaxify.go('user/' + app.user.userslug + '/edit/email'); - }; + msg.clickfn = hideAlertAndGotoEditEmail; alerts.alert(msg); } else if (!app.user['email:confirmed'] && !app.user.isEmailConfirmSent) { msg.message = message || '[[error:email-not-confirmed]]'; - msg.clickfn = function () { - alerts.remove('email_confirm'); - ajaxify.go('/me/edit/email'); - }; + msg.clickfn = hideAlertAndGotoEditEmail; alerts.alert(msg); } else if (!app.user['email:confirmed'] && app.user.isEmailConfirmSent) { msg.message = '[[error:email-not-confirmed-email-sent]]'; @@ -74,6 +71,7 @@ define('messages', ['bootbox', 'translator', 'storage', 'alerts', 'hooks'], func function showQueryStringMessages() { const params = utils.params({ full: true }); + const originalQs = params.toString(); showWelcomeMessage = params.has('loggedin'); registerMessage = params.get('register'); @@ -102,7 +100,9 @@ define('messages', ['bootbox', 'translator', 'storage', 'alerts', 'hooks'], func } const qs = params.toString(); - ajaxify.updateHistory(ajaxify.currentPage + (qs ? `?${qs}` : '') + document.location.hash, true); + if (qs !== originalQs) { + ajaxify.updateHistory(ajaxify.currentPage + (qs ? `?${qs}` : '') + document.location.hash, true); + } } messages.showInvalidSession = function () { diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 8c557985c3..766bc02d9c 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -441,7 +441,13 @@ define('navigator', [ function generateUrl(index) { const pathname = window.location.pathname.replace(config.relative_path, ''); const parts = pathname.split('/'); - return parts[1] + '/' + parts[2] + '/' + parts[3] + (index ? '/' + index : ''); + const newUrl = parts[1] + '/' + parts[2] + '/' + parts[3] + (index ? '/' + index : ''); + const data = { + newUrl, + index, + }; + hooks.fire('action:navigator.generateUrl', data); + return data.newUrl; } navigator.getCount = () => count; @@ -721,7 +727,7 @@ define('navigator', [ } } - let scrollTop = 0; + let scrollTop; if (postHeight < viewportHeight - navbarHeight - topicHeaderHeight) { scrollTop = scrollTo.offset().top - (viewportHeight / 2) + (postHeight / 2); } else { diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 891ca17fe1..f4806eafdf 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -59,7 +59,9 @@ define('notifications', [ const nid = notifEl.attr('data-nid'); markNotification(nid, true); }); - components.get('notifications').on('click', '.mark-all-read', Notifications.markAllRead); + components.get('notifications').on('click', '.mark-all-read', () => { + Notifications.markAllRead(); + }); Notifications.handleUnreadButton(notifList); @@ -105,7 +107,7 @@ define('notifications', [ }); if (!unreadNotifs[notifData.nid]) { - unreadNotifs[notifData.nid] = true; + unreadNotifs[notifData.nid] = notifData; } }; @@ -165,12 +167,21 @@ define('notifications', [ } }; - Notifications.markAllRead = function () { - socket.emit('notifications.markAllRead', function (err) { + Notifications.markAllRead = function (filter = '') { + socket.emit('notifications.markAllRead', { filter }, function (err) { if (err) { alerts.error(err); } - unreadNotifs = {}; + if (filter) { + Object.keys(unreadNotifs).forEach(nid => { + if (unreadNotifs[nid].type === filter) { + delete unreadNotifs[nid]; + } + }); + } else { + unreadNotifs = {}; + } + const notifEls = $('[component="notifications/list"] [data-nid]'); notifEls.removeClass('unread'); notifEls.find('.mark-read .unread').addClass('hidden'); diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index 6b0e9f8b6d..ce69c6b6c3 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -1,9 +1,9 @@ 'use strict'; define('pictureCropper', ['alerts'], function (alerts) { - const module = {}; + const PictureCropper = {}; - module.show = function (data, callback) { + PictureCropper.show = function (data, callback) { const fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false; app.parseAndTranslate('modals/upload-file', { showHelp: data.hasOwnProperty('showHelp') && data.showHelp !== undefined ? data.showHelp : true, @@ -32,7 +32,7 @@ define('pictureCropper', ['alerts'], function (alerts) { }); }; - module.handleImageCrop = function (data, callback) { + PictureCropper.handleImageCrop = function (data, callback) { $('#crop-picture-modal').remove(); app.parseAndTranslate('modals/crop_picture', { url: utils.escapeHTML(data.url), @@ -227,7 +227,7 @@ define('pictureCropper', ['alerts'], function (alerts) { data.uploadModal.modal('hide'); - module.handleImageCrop({ + PictureCropper.handleImageCrop({ url: imageUrl, imageType: file.type, socketMethod: data.socketMethod, @@ -245,5 +245,5 @@ define('pictureCropper', ['alerts'], function (alerts) { } } - return module; + return PictureCropper; }); diff --git a/public/src/modules/quickreply.js b/public/src/modules/quickreply.js index b0d5b1a672..3293bb3275 100644 --- a/public/src/modules/quickreply.js +++ b/public/src/modules/quickreply.js @@ -19,8 +19,11 @@ define('quickreply', [ strategies: [], options: { style: { - 'z-index': 100, + 'z-index': 20000, + 'max-height': '250px', + overflow: 'auto', }, + className: 'dropdown-menu textcomplete-dropdown ghost-scrollbar', }, }; @@ -60,7 +63,7 @@ define('quickreply', [ return; } - const replyMsg = components.get('topic/quickreply/text').val(); + const replyMsg = element.val(); const replyData = { tid: ajaxify.data.tid, handle: undefined, @@ -74,9 +77,11 @@ define('quickreply', [ } ready = false; + element.val(''); api.post(`/topics/${ajaxify.data.tid}`, replyData, function (err, data) { ready = true; if (err) { + element.val(replyMsg); return alerts.error(err); } if (data && data.queued) { @@ -91,7 +96,7 @@ define('quickreply', [ }); } - components.get('topic/quickreply/text').val(''); + element.val(''); storage.removeItem(qrDraftId); QuickReply._autocomplete.hide(); hooks.fire('action:quickreply.success', { data }); diff --git a/public/src/modules/scrollStop.js b/public/src/modules/scrollStop.js index 497a099f71..dd3b934530 100644 --- a/public/src/modules/scrollStop.js +++ b/public/src/modules/scrollStop.js @@ -10,9 +10,9 @@ */ define('scrollStop', function () { - const Module = {}; + const ScrollStop = {}; - Module.apply = function (element) { + ScrollStop.apply = function (element) { $(element).on('mousewheel', function (e) { const scrollTop = this.scrollTop; const scrollHeight = this.scrollHeight; @@ -27,5 +27,5 @@ define('scrollStop', function () { }); }; - return Module; + return ScrollStop; }); diff --git a/public/src/modules/search.js b/public/src/modules/search.js index abf3343ac0..0c8a6d4f72 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -132,34 +132,56 @@ define('search', [ options.searchOptions.searchOnly = 1; Search.api(options.searchOptions, function (data) { quickSearchResults.find('.loading-indicator').addClass('hidden'); - if (!data.posts || (options.hideOnNoMatches && !data.posts.length)) { - return quickSearchResults.addClass('hidden').find('.quick-search-results-container').html(''); - } - data.posts.forEach(function (p) { - const text = $('
' + p.content + '
').text(); - const query = inputEl.val().toLowerCase().replace(/^in:topic-\d+/, ''); - const start = Math.max(0, text.toLowerCase().indexOf(query) - 40); - p.snippet = utils.escapeHTML((start > 0 ? '...' : '') + - text.slice(start, start + 80) + - (text.length - start > 80 ? '...' : '')); - }); - data.dropdown = { maxWidth: '400px', maxHeight: '500px', ...options.dropdown }; - app.parseAndTranslate('partials/quick-search-results', data, function (html) { - if (html.length) { - html.find('.timeago').timeago(); + + if (options.searchOptions.in === 'categories') { + if (!data.categories || (options.hideOnNoMatches && !data.categories.length)) { + return quickSearchResults.addClass('hidden').find('.quick-search-results-container').html(''); } - quickSearchResults.toggleClass('hidden', !html.length || !inputEl.is(':focus')) - .find('.quick-search-results-container') - .html(html.length ? html : ''); - const highlightEls = quickSearchResults.find( - '.quick-search-results .quick-search-title, .quick-search-results .snippet' - ); - Search.highlightMatches(options.searchOptions.term, highlightEls); - hooks.fire('action:search.quick.complete', { - data: data, - options: options, + + data.dropdown = { maxWidth: '400px', maxHeight: '500px', ...options.dropdown }; + app.parseAndTranslate('partials/quick-category-search-results', data, (html) => { + if (html.length) { + html.find('.timeago').timeago(); + } + quickSearchResults.toggleClass('hidden', !html.length || !inputEl.is(':focus')) + .find('.quick-search-results-container') + .html(html.length ? html : ''); + + hooks.fire('action:search.quick.complete', { + data: data, + options: options, + }); }); - }); + } else { + if (!data.posts || (options.hideOnNoMatches && !data.posts.length)) { + return quickSearchResults.addClass('hidden').find('.quick-search-results-container').html(''); + } + data.posts.forEach(function (p) { + const text = $('
' + p.content + '
').text(); + const query = inputEl.val().toLowerCase().replace(/^in:topic-\d+/, ''); + const start = Math.max(0, text.toLowerCase().indexOf(query) - 40); + p.snippet = utils.escapeHTML((start > 0 ? '...' : '') + + text.slice(start, start + 80) + + (text.length - start > 80 ? '...' : '')); + }); + data.dropdown = { maxWidth: '400px', maxHeight: '500px', ...options.dropdown }; + app.parseAndTranslate('partials/quick-search-results', data, function (html) { + if (html.length) { + html.find('.timeago').timeago(); + } + quickSearchResults.toggleClass('hidden', !html.length || !inputEl.is(':focus')) + .find('.quick-search-results-container') + .html(html.length ? html : ''); + const highlightEls = quickSearchResults.find( + '.quick-search-results .quick-search-title, .quick-search-results .snippet' + ); + Search.highlightMatches(options.searchOptions.term, highlightEls); + hooks.fire('action:search.quick.complete', { + data: data, + options: options, + }); + }); + } }); } @@ -267,13 +289,7 @@ define('search', [ function createQueryString(data) { const searchIn = data.in || 'titles'; - let term = data.term.replace(/^[ ?#]*/, ''); - try { - term = encodeURIComponent(term); - } catch (err) { - console.error(err); - return alerts.error('[[error:invalid-search-term]]'); - } + const term = data.term.replace(/^[ ?#]*/, ''); const query = { ...data, diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 148f72d182..bb54ba7436 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -2,12 +2,9 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { - let Settings; let onReady = []; let waitingJobs = 0; - let helper; - /** Returns the hook of given name that matches the given type or element. @param type The type of the element to get the matching hook for, or the element itself. @@ -29,7 +26,7 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { return null; } - helper = { + const helper = { /** @returns Object A deep clone of the given object. */ @@ -48,10 +45,8 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { */ createElement: function (tagName, data, text) { const element = document.createElement(tagName); - for (const k in data) { - if (data.hasOwnProperty(k)) { - element.setAttribute(k, data[k]); - } + for (const [k, val] of Object.entries(data)) { + element.setAttribute(k, val); } if (text) { element.appendChild(document.createTextNode(text)); @@ -331,7 +326,7 @@ define('settings', ['hooks', 'alerts'], function (hooks, alerts) { }, }; - Settings = { + const Settings = { helper: helper, plugins: {}, cfg: {}, diff --git a/public/src/modules/settings/array.js b/public/src/modules/settings/array.js index 161d817b40..2867011115 100644 --- a/public/src/modules/settings/array.js +++ b/public/src/modules/settings/array.js @@ -45,16 +45,13 @@ define('settings/array', function () { element.attr('data-parent', '_' + key); delete attributes['data-type']; delete attributes.tagName; - for (const name in attributes) { - if (attributes.hasOwnProperty(name)) { - const val = attributes[name]; - if (name.search('data-') === 0) { - element.data(name.substring(5), val); - } else if (name.search('prop-') === 0) { - element.prop(name.substring(5), val); - } else { - element.attr(name, val); - } + for (const [name, val] of Object.entries(attributes)) { + if (name.search('data-') === 0) { + element.data(name.substring(5), val); + } else if (name.search('prop-') === 0) { + element.prop(name.substring(5), val); + } else { + element.attr(name, val); } } helper.fillField(element, value); diff --git a/public/src/modules/settings/object.js b/public/src/modules/settings/object.js index 4d0a57645b..9a94950055 100644 --- a/public/src/modules/settings/object.js +++ b/public/src/modules/settings/object.js @@ -25,16 +25,13 @@ define('settings/object', function () { element.attr('data-prop', prop); delete attributes['data-type']; delete attributes.tagName; - for (const name in attributes) { - if (attributes.hasOwnProperty(name)) { - const val = attributes[name]; - if (name.search('data-') === 0) { - element.data(name.substring(5), val); - } else if (name.search('prop-') === 0) { - element.prop(name.substring(5), val); - } else { - element.attr(name, val); - } + for (const [name, val] of Object.entries(attributes)) { + if (name.search('data-') === 0) { + element.data(name.substring(5), val); + } else if (name.search('prop-') === 0) { + element.prop(name.substring(5), val); + } else { + element.attr(name, val); } } helper.fillField(element, value); @@ -62,9 +59,7 @@ define('settings/object', function () { const properties = element.data('attributes') || element.data('properties'); const key = element.data('key') || element.data('parent'); let separator = element.data('split') || ', '; - let propertyIndex; - let propertyName; - let attributes; + separator = (function () { try { return $(separator); @@ -77,27 +72,26 @@ define('settings/object', function () { if (typeof value !== 'object') { value = {}; } + if (Array.isArray(properties)) { - for (propertyIndex in properties) { - if (properties.hasOwnProperty(propertyIndex)) { - attributes = properties[propertyIndex]; - if (typeof attributes !== 'object') { - attributes = {}; - } - propertyName = attributes['data-prop'] || attributes['data-property'] || propertyIndex; - if (value[propertyName] === undefined && attributes['data-new'] !== undefined) { - value[propertyName] = attributes['data-new']; - } - addObjectPropertyElement( - element, - key, - attributes, - propertyName, - value[propertyName], - separator.clone(), - function (el) { element.append(el); } - ); + for (const [propertyIndex, attr] of Object.entries(properties)) { + let attributes = attr; + if (typeof attr !== 'object') { + attributes = {}; } + const propertyName = attributes['data-prop'] || attributes['data-property'] || propertyIndex; + if (value[propertyName] === undefined && attributes['data-new'] !== undefined) { + value[propertyName] = attributes['data-new']; + } + addObjectPropertyElement( + element, + key, + attributes, + propertyName, + value[propertyName], + separator.clone(), + function (el) { element.append(el); } + ); } } }, diff --git a/public/src/modules/settings/sorted-list.js b/public/src/modules/settings/sorted-list.js index 2ff9bbc82f..707a679c02 100644 --- a/public/src/modules/settings/sorted-list.js +++ b/public/src/modules/settings/sorted-list.js @@ -64,7 +64,6 @@ define('settings/sorted-list', [ })); // todo: parse() needs to be refactored to return the html, so multiple calls can be parallelized - // eslint-disable-next-line no-restricted-syntax for (const { itemUUID, item } of items) { // eslint-disable-next-line no-await-in-loop const element = await parse($container, itemUUID, item); diff --git a/public/src/modules/share.js b/public/src/modules/share.js index 9a1aa4b56a..1f2752fd76 100644 --- a/public/src/modules/share.js +++ b/public/src/modules/share.js @@ -2,10 +2,10 @@ define('share', ['hooks'], function (hooks) { - const module = {}; + const share = {}; const baseUrl = window.location.protocol + '//' + window.location.host; - module.addShareHandlers = function (name) { + share.addShareHandlers = function (name) { function openShare(url, urlToPost, width, height) { window.open(url, '_blank', 'width=' + width + ',height=' + height + ',scrollbars=no,status=no'); hooks.fire('action:share.open', { @@ -76,5 +76,5 @@ define('share', ['hooks'], function (hooks) { return baseUrl + config.relative_path + path; } - return module; + return share; }); diff --git a/public/src/modules/slugify.js b/public/src/modules/slugify.js index 159b356e47..7912d2576c 100644 --- a/public/src/modules/slugify.js +++ b/public/src/modules/slugify.js @@ -1,16 +1,15 @@ 'use strict'; -/* global XRegExp */ (function (factory) { if (typeof define === 'function' && define.amd) { - define('slugify', ['xregexp'], factory); + define('slugify', factory); } else if (typeof exports === 'object') { - module.exports = factory(require('xregexp')); + module.exports = factory(); } else { - window.slugify = factory(XRegExp); + window.slugify = factory(); } -}(function (XRegExp) { - const invalidUnicodeChars = XRegExp('[^\\p{L}\\s\\d\\-_@.]', 'g'); +}(function () { + const invalidUnicodeChars = /[^\p{L}\s\d\-_@.]/gu; const invalidLatinChars = /[^\w\s\d\-_@.]/g; const trimRegex = /^\s+|\s+$/g; const collapseWhitespace = /\s+/g; @@ -28,13 +27,16 @@ if (isLatin.test(str)) { str = str.replace(invalidLatinChars, '-'); } else { - str = XRegExp.replace(str, invalidUnicodeChars, '-'); + str = str.replace(invalidUnicodeChars, '-'); } str = !preserveCase ? str.toLocaleLowerCase() : str; str = str.replace(collapseWhitespace, '-'); str = str.replace(collapseDash, '-'); str = str.replace(trimTrailingDash, ''); str = str.replace(trimLeadingDash, ''); + if (str === '.' || str === '..') { + return ''; + } return str; }; })); diff --git a/public/src/modules/sort.js b/public/src/modules/sort.js index 0482cff837..17a1b437e7 100644 --- a/public/src/modules/sort.js +++ b/public/src/modules/sort.js @@ -2,9 +2,9 @@ define('sort', ['components'], function (components) { - const module = {}; + const Sort = {}; - module.handleSort = function (field, gotoOnSave) { + Sort.handleSort = function (field, gotoOnSave) { const threadSort = components.get('thread/sort'); threadSort.find('i').removeClass('fa-check'); const currentSort = utils.params().sort || config[field]; @@ -22,5 +22,5 @@ define('sort', ['components'], function (components) { }); }; - return module; + return Sort; }); diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index 6396bb9e8e..4af7eee9c5 100644 --- a/public/src/modules/topicList.js +++ b/public/src/modules/topicList.js @@ -53,7 +53,7 @@ define('topicList', [ handleBack.init(function (after, handleBackCallback) { loadTopicsCallback(after, 1, function (data, loadCallback) { - onTopicsLoaded(templateName, data.topics, ajaxify.data.showSelect, 1, function () { + onTopicsLoaded(templateName, data, ajaxify.data.showSelect, 1, function () { handleBackCallback(); loadCallback(); }); @@ -166,7 +166,7 @@ define('topicList', [ } loadTopicsCallback(after, direction, function (data, done) { - onTopicsLoaded(templateName, data.topics, ajaxify.data.showSelect, direction, done); + onTopicsLoaded(templateName, data, ajaxify.data.showSelect, direction, done); }); }; @@ -187,7 +187,8 @@ define('topicList', [ }); } - function onTopicsLoaded(templateName, topics, showSelect, direction, callback) { + function onTopicsLoaded(templateName, data, showSelect, direction, callback) { + let { topics } = data; if (!topics || !topics.length) { $('#load-more-btn').hide(); return callback(); @@ -212,11 +213,15 @@ define('topicList', [ const tplData = { topics: topics, showSelect: showSelect, + 'reputation:disabled': data['reputation:disabled'], template: { name: templateName, + [templateName]: true, }, }; - tplData.template[templateName] = true; + if (ajaxify.data.cid) { + tplData.cid = ajaxify.data.cid; + } hooks.fire('action:topics.loading', { topics: topics, after: after, before: before }); diff --git a/public/src/modules/topicSelect.js b/public/src/modules/topicSelect.js index 1dd231b8c9..50d38c4ba7 100644 --- a/public/src/modules/topicSelect.js +++ b/public/src/modules/topicSelect.js @@ -7,8 +7,8 @@ define('topicSelect', ['components'], function (components) { let topicsContainer; - TopicSelect.init = function (onSelect) { - topicsContainer = $('[component="category"]'); + TopicSelect.init = function (onSelect, containerEl) { + topicsContainer = containerEl || $('[component="category"]'); topicsContainer.on('selectstart', '[component="topic/select"]', function (ev) { ev.preventDefault(); }); diff --git a/public/src/modules/topicThumbs.js b/public/src/modules/topicThumbs.js index 70c13218d3..340d856ded 100644 --- a/public/src/modules/topicThumbs.js +++ b/public/src/modules/topicThumbs.js @@ -7,23 +7,27 @@ define('topicThumbs', [ Thumbs.get = id => api.get(`/topics/${id}/thumbs`, { thumbsOnly: 1 }); - Thumbs.getByPid = pid => api.get(`/posts/${encodeURIComponent(pid)}`, {}).then(post => Thumbs.get(post.tid)); - Thumbs.delete = (id, path) => api.del(`/topics/${id}/thumbs`, { path: path, }); + Thumbs.updateTopicThumbs = async (tid) => { + const thumbs = await Thumbs.get(tid); + const html = await app.parseAndTranslate('partials/topic/thumbs', { thumbs }); + $('[component="topic/thumb/list"]').html(html); + }; + Thumbs.deleteAll = (id) => { Thumbs.get(id).then((thumbs) => { Promise.all(thumbs.map(thumb => Thumbs.delete(id, thumb.url))); }); }; - Thumbs.upload = id => new Promise((resolve) => { + Thumbs.upload = () => new Promise((resolve) => { uploader.show({ title: '[[topic:composer.thumb-title]]', method: 'put', - route: config.relative_path + `/api/v3/topics/${id}/thumbs`, + route: config.relative_path + `/api/topic/thumb/upload`, }, function (url) { resolve(url); }); @@ -32,24 +36,16 @@ define('topicThumbs', [ Thumbs.modal = {}; Thumbs.modal.open = function (payload) { - const { id, pid } = payload; + const { id, postData } = payload; let { modal } = payload; - let numThumbs; + const thumbs = postData.thumbs || []; return new Promise((resolve) => { - Promise.all([ - Thumbs.get(id), - pid ? Thumbs.getByPid(pid) : [], - ]).then(results => new Promise((resolve) => { - const thumbs = results.reduce((memo, cur) => memo.concat(cur)); - numThumbs = thumbs.length; - - resolve(thumbs); - })).then(thumbs => Benchpress.render('modals/topic-thumbs', { thumbs })).then((html) => { + Benchpress.render('modals/topic-thumbs', { thumbs }).then((html) => { if (modal) { translator.translate(html, function (translated) { modal.find('.bootbox-body').html(translated); - Thumbs.modal.handleSort({ modal, numThumbs }); + Thumbs.modal.handleSort({ modal, thumbs }); }); } else { modal = bootbox.dialog({ @@ -62,7 +58,11 @@ define('topicThumbs', [ label: ' [[modules:thumbs.modal.add]]', className: 'btn-success', callback: () => { - Thumbs.upload(id).then(() => { + Thumbs.upload().then((thumbUrl) => { + postData.thumbs.push( + thumbUrl.replace(new RegExp(`^${config.upload_url}`), '') + ); + Thumbs.modal.open({ ...payload, modal }); require(['composer'], (composer) => { composer.updateThumbCount(id, $(`[component="composer"][data-uuid="${id}"]`)); @@ -79,7 +79,7 @@ define('topicThumbs', [ }, }); Thumbs.modal.handleDelete({ ...payload, modal }); - Thumbs.modal.handleSort({ modal, numThumbs }); + Thumbs.modal.handleSort({ modal, thumbs }); } }); }); @@ -94,42 +94,42 @@ define('topicThumbs', [ if (!ok) { return; } - - const id = ev.target.closest('[data-id]').getAttribute('data-id'); const path = ev.target.closest('[data-path]').getAttribute('data-path'); - api.del(`/topics/${id}/thumbs`, { - path: path, - }).then(() => { + const postData = payload.postData; + if (postData && postData.thumbs && postData.thumbs.includes(path)) { + postData.thumbs = postData.thumbs.filter(thumb => thumb !== path); Thumbs.modal.open(payload); require(['composer'], (composer) => { composer.updateThumbCount(uuid, $(`[component="composer"][data-uuid="${uuid}"]`)); }); - }).catch(alerts.error); + } }); } }); }; - Thumbs.modal.handleSort = ({ modal, numThumbs }) => { - if (numThumbs > 1) { + Thumbs.modal.handleSort = ({ modal, thumbs }) => { + if (thumbs.length > 1) { const selectorEl = modal.find('.topic-thumbs-modal'); selectorEl.sortable({ - items: '[data-id]', + items: '[data-path]', + }); + selectorEl.on('sortupdate', function () { + if (!thumbs) return; + const newOrder = []; + selectorEl.find('[data-path]').each(function () { + const path = $(this).attr('data-path'); + const thumb = thumbs.find(t => t === path); + if (thumb) { + newOrder.push(thumb); + } + }); + // Mutate thumbs array in place + thumbs.length = 0; + Array.prototype.push.apply(thumbs, newOrder); }); - selectorEl.on('sortupdate', Thumbs.modal.handleSortChange); } }; - Thumbs.modal.handleSortChange = (ev, ui) => { - const items = ui.item.get(0).parentNode.querySelectorAll('[data-id]'); - Array.from(items).forEach((el, order) => { - const id = el.getAttribute('data-id'); - let path = el.getAttribute('data-path'); - path = path.replace(new RegExp(`^${config.upload_url}`), ''); - - api.put(`/topics/${id}/thumbs/order`, { path, order }).catch(alerts.error); - }); - }; - return Thumbs; }); diff --git a/public/src/modules/translator.common.js b/public/src/modules/translator.common.js index c69a9cc265..8f78c5c49e 100644 --- a/public/src/modules/translator.common.js +++ b/public/src/modules/translator.common.js @@ -464,7 +464,7 @@ module.exports = function (utils, load, warn) { */ Translator.escape = function escape(text) { return typeof text === 'string' ? - text.replace(/\[\[/g, '[[').replace(/\]\]/g, ']]') : + text.replace(/\[\[([a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+)\]\]/g, '[[$1]]') : text; }; @@ -475,7 +475,7 @@ module.exports = function (utils, load, warn) { */ Translator.unescape = function unescape(text) { return typeof text === 'string' ? - text.replace(/]]/g, ']]').replace(/[[/g, '[[') : + text.replace(/[[([a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+)]]/g, '[[$1]]') : text; }; diff --git a/public/src/modules/uploadHelpers.js b/public/src/modules/uploadHelpers.js index 49ed9fd9fb..0fbcd9a9f6 100644 --- a/public/src/modules/uploadHelpers.js +++ b/public/src/modules/uploadHelpers.js @@ -88,7 +88,7 @@ define('uploadHelpers', ['alerts'], function (alerts) { let formData; if (window.FormData) { formData = new FormData(); - for (var i = 0; i < files.length; ++i) { + for (let i = 0; i < files.length; ++i) { formData.append('files[]', files[i], files[i].name); } } @@ -123,25 +123,43 @@ define('uploadHelpers', ['alerts'], function (alerts) { uploadHelpers.handlePaste = function (options) { const container = options.container; - container.on('paste', function (event) { + container.on('paste', async function (event) { const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items; const files = []; const fileNames = []; - let formData = null; - if (window.FormData) { - formData = new FormData(); - } - [].forEach.call(items, function (item) { - const file = item.getAsFile(); - if (file) { - const fileName = utils.generateUUID() + '-' + file.name; - if (formData) { - formData.append('files[]', file, fileName); - } - files.push(file); - fileNames.push(fileName); + const formData = window.FormData ? new FormData() : null; + + function addFile(file, fileName) { + files.push(file); + fileNames.push(fileName); + if (formData) { + formData.append('files[]', file, fileName); } - }); + } + const { convertPastedImageTo } = config; + for (const item of items) { + const file = item.getAsFile(); + if (!file) continue; + try { + if (convertPastedImageTo && file.type.match(/image./) && file.type !== convertPastedImageTo) { + // eslint-disable-next-line no-await-in-loop + const convertedBlob = await convertImage(file, convertPastedImageTo, 0.9); + const ext = convertedBlob.type.split('/')[1]; + const fileName = `${utils.generateUUID()}-image.${ext}`; + + const convertedFile = new File([convertedBlob], fileName, { + type: convertedBlob.type, + }); + addFile(convertedFile, fileName); + } else { + const fileName = utils.generateUUID() + '-' + file.name; + addFile(file, fileName); + } + } catch (err) { + alerts.error(err); + console.error(err); + } + } if (files.length) { options.callback({ @@ -181,7 +199,7 @@ define('uploadHelpers', ['alerts'], function (alerts) { '[[error:parse-error]]'; if (xhr && xhr.status === 413) { - errorMsg = xhr.statusText || 'Request Entity Too Large'; + errorMsg = '[[error:api.413]]'; } alerts.error(errorMsg); alerts.remove(alert_id); @@ -197,7 +215,7 @@ define('uploadHelpers', ['alerts'], function (alerts) { success: function (res) { const uploads = res.response.images; if (uploads && uploads.length) { - for (var i = 0; i < uploads.length; ++i) { + for (let i = 0; i < uploads.length; ++i) { uploads[i].filename = files[i].name; uploads[i].isImage = /image./.test(files[i].type); } @@ -217,5 +235,34 @@ define('uploadHelpers', ['alerts'], function (alerts) { options.uploadForm.submit(); }; + function convertImage(file, mime, quality = 0.9) { + return new Promise((resolve, reject) => { + const img = new Image(); + const reader = new FileReader(); + + reader.onload = e => { + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + + canvas.toBlob(blob => { + if (!blob) return reject(new Error('Conversion failed')); + resolve(blob); + }, mime, quality); + }; + + img.onerror = reject; + img.src = e.target.result; + }; + + reader.onerror = reject; + reader.readAsDataURL(file); + }); + } + return uploadHelpers; }); diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js index 5bcaed9324..c137a1606d 100644 --- a/public/src/modules/uploader.js +++ b/public/src/modules/uploader.js @@ -2,9 +2,9 @@ define('uploader', ['jquery-form'], function () { - const module = {}; + const Uploader = {}; - module.show = function (data, callback) { + Uploader.show = function (data, callback) { const fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false; app.parseAndTranslate('modals/upload-file', { showHelp: data.hasOwnProperty('showHelp') && data.showHelp !== undefined ? data.showHelp : true, @@ -35,7 +35,7 @@ define('uploader', ['jquery-form'], function () { }); }; - module.hideAlerts = function (modal) { + Uploader.hideAlerts = function (modal) { $(modal).find('#alert-status, #alert-success, #alert-error, #upload-progress-box').addClass('hide'); }; @@ -53,11 +53,11 @@ define('uploader', ['jquery-form'], function () { return showAlert(uploadModal, 'error', '[[error:file-too-big, ' + fileSize + ']]'); } - module.ajaxSubmit(uploadModal, callback); + Uploader.ajaxSubmit(uploadModal, callback); } function showAlert(uploadModal, type, message) { - module.hideAlerts(uploadModal); + Uploader.hideAlerts(uploadModal); if (type === 'error') { uploadModal.find('#fileUploadSubmitBtn').removeClass('disabled'); } @@ -65,7 +65,7 @@ define('uploader', ['jquery-form'], function () { uploadModal.find('#alert-' + type).translateText(message).removeClass('hide'); } - module.ajaxSubmit = function (uploadModal, callback) { + Uploader.ajaxSubmit = function (uploadModal, callback) { const uploadForm = uploadModal.find('#uploadForm'); uploadForm.ajaxSubmit({ headers: { @@ -96,7 +96,7 @@ define('uploader', ['jquery-form'], function () { showAlert(uploadModal, 'success', '[[uploads:upload-success]]'); setTimeout(function () { - module.hideAlerts(uploadModal); + Uploader.hideAlerts(uploadModal); uploadModal.modal('hide'); }, 750); }, @@ -122,5 +122,5 @@ define('uploader', ['jquery-form'], function () { return true; } - return module; + return Uploader; }); diff --git a/public/src/utils.common.js b/public/src/utils.common.js index 2e916e7768..7dd3a3c0e0 100644 --- a/public/src/utils.common.js +++ b/public/src/utils.common.js @@ -16,6 +16,8 @@ function replaceChar(c) { } const escapeChars = /[&<>"'`=]/g; +const invisibleChars = /[\u200B-\u200F\u202A-\u202E\u2066-\u2069\u3164\uFEFF]/; + const HTMLEntities = Object.freeze({ amp: '&', gt: '>', @@ -300,7 +302,9 @@ const utils = { const pattern = (tags || ['']).join('|'); return String(str).replace(new RegExp('<(\\/)?(' + (pattern || '[^\\s>]+') + ')(\\s+[^<>]*?)?\\s*(\\/)?>', 'gi'), ''); }, - + stripBidiControls: function (input) { + return input.replace(/[\u202A-\u202E\u2066-\u2069]/gi, ''); + }, cleanUpTag: function (tag, maxLength) { if (typeof tag !== 'string' || !tag.length) { return ''; @@ -308,7 +312,7 @@ const utils = { tag = tag.trim().toLowerCase(); // see https://github.com/NodeBB/NodeBB/issues/4378 - tag = tag.replace(/\u202E/gi, ''); + tag = utils.stripBidiControls(tag); tag = tag.replace(/[,/#!$^*;:{}=_`<>'"~()?|]/g, ''); tag = tag.slice(0, maxLength || 15).trim(); const matches = tag.match(/^[.-]*(.+?)[.-]*$/); @@ -317,7 +321,12 @@ const utils = { } return tag; }, - + createFieldChecker: function (fields = []) { + const allFields = !fields.length; + return function hasField(field) { + return allFields || fields.includes(field); + }; + }, removePunctuation: function (str) { return str.replace(/[.,-/#!$%^&*;:{}=\-_`<>'"~()?]/g, ''); }, @@ -327,7 +336,17 @@ const utils = { }, isUserNameValid: function (name) { - return (name && name !== '' && (/^['" \-+.*[\]0-9\u00BF-\u1FFF\u2C00-\uD7FF\w]+$/.test(name))); + if (!name || name === '') return false; + if (name.trim().length === 0) return false; + if (invisibleChars.test(name)) return false; + return (/^['" \-+.*[\]0-9\u00BF-\u1FFF\u2C00-\uD7FF\w]+$/.test(name)); + }, + + isSlugValid: function (slug) { + if (!slug || slug === '' || slug === '.' || slug === '..') return false; + if (slug.trim().length === 0) return false; + if (invisibleChars.test(slug)) return false; + return true; }, isPasswordValid: function (password) { @@ -507,9 +526,15 @@ const utils = { return str.toString().replace(escapeChars, replaceChar); }, - isAndroidBrowser: function () { + isAndroidBrowser: function (nua) { + if (!nua) { + if (typeof navigator !== 'undefined' && navigator.userAgent) { + nua = navigator.userAgent; + } else { + return false; + } + } // http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser - const nua = navigator.userAgent; return ((nua.indexOf('Mozilla/5.0') > -1 && nua.indexOf('Android ') > -1 && nua.indexOf('AppleWebKit') > -1) && !(nua.indexOf('Chrome') > -1)); }, @@ -564,10 +589,7 @@ const utils = { params: function (options = {}) { let url; if (options.url && !options.url.startsWith('http')) { - // relative path passed in - options.url = options.url.replace(new RegExp(`/?${config.relative_path.slice(1)}/`, 'g'), ''); - url = new URL(document.location); - url.pathname = options.url; + url = new URL(options.url, 'http://dummybase'); } else { url = new URL(options.url || document.location); } @@ -631,7 +653,6 @@ const utils = { try { str = JSON.parse(str); - // eslint-disable-next-line no-unused-vars } catch (err) { /* empty */ } return str; diff --git a/renovate.json b/renovate.json index 7cbecd9e95..16656afbcc 100644 --- a/renovate.json +++ b/renovate.json @@ -2,7 +2,7 @@ "extends": [ "config:recommended" ], - "baseBranches": [ + "baseBranchPatterns": [ "develop" ], "labels": [ @@ -14,8 +14,7 @@ "dependencies" ], "rangeStrategy": "pin", - "matchPackageNames": [ - ] + "matchPackageNames": [] }, { "matchDepTypes": [ diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 370fb70c31..90fadfe628 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -7,11 +7,13 @@ const _ = require('lodash'); const db = require('../database'); const meta = require('../meta'); const batch = require('../batch'); +const categories = require('../categories'); const user = require('../user'); const utils = require('../utils'); const TTLCache = require('../cache/ttl'); const failedWebfingerCache = TTLCache({ + name: 'ap-failed-webfinger-cache', max: 5000, ttl: 1000 * 60 * 10, // 10 minutes }); @@ -19,16 +21,18 @@ const failedWebfingerCache = TTLCache({ const activitypub = module.parent.exports; const Actors = module.exports; +Actors._followerCache = TTLCache({ + name: 'ap-follower-cache', + max: 5000, + ttl: 1000 * 60 * 60, // 1 hour +}); -Actors.assert = async (ids, options = {}) => { +Actors.qualify = async (ids, options = {}) => { /** - * Ensures that the passed in ids or webfinger handles are stored in database. - * Options: - * - update: boolean, forces re-fetch/process of the resolved id - * Return one of: - * - An array of newly processed ids - * - false: if input incorrect (or webfinger handle cannot resolve) - * - true: no new IDs processed; all passed-in IDs present. + * Sanity-checks, cache handling, webfinger translations, so that only + * an array of actor uris are handled by assert/assertGroup. + * + * This method is only called by assert/assertGroup (at least in core.) */ // Handle single values @@ -46,8 +50,10 @@ Actors.assert = async (ids, options = {}) => { // Filter out uids if passed in ids = ids.filter(id => !utils.isNumber(id)); + // Filter out constants + ids = ids.filter(id => !activitypub._constants.acceptablePublicAddresses.includes(id)); + // Translate webfinger handles to uris - const hostMap = new Map(); ids = (await Promise.all(ids.map(async (id) => { const originalId = id; if (activitypub.helpers.isWebfinger(id)) { @@ -57,7 +63,6 @@ Actors.assert = async (ids, options = {}) => { } ({ actorUri: id } = await activitypub.helpers.query(id)); - hostMap.set(id, host); } // ensure the final id is a valid URI if (!id || !activitypub.helpers.isUri(id)) { @@ -77,18 +82,44 @@ Actors.assert = async (ids, options = {}) => { ids = ids.filter(uri => uri !== 'loopback' && new URL(uri).host !== nconf.get('url_parsed').host); } + // Separate those who need migration from user to category + const migrate = new Set(); + if (options.qualifyGroup) { + const exists = await db.exists(ids.map(id => `userRemote:${id}`)); + ids.forEach((id, idx) => { + if (exists[idx]) { + migrate.add(id); + } + }); + } + // Only assert those who haven't been seen recently (configurable), unless update flag passed in (force refresh) if (!options.update) { const upperBound = Date.now() - (1000 * 60 * 60 * 24 * meta.config.activitypubUserPruneDays); const lastCrawled = await db.sortedSetScores('usersRemote:lastCrawled', ids.map(id => ((typeof id === 'object' && id.hasOwnProperty('id')) ? id.id : id))); ids = ids.filter((id, idx) => { const timestamp = lastCrawled[idx]; - return !timestamp || timestamp < upperBound; + return migrate.has(id) || !timestamp || timestamp < upperBound; }); } - if (!ids.length) { - return true; + return ids; +}; + +Actors.assert = async (ids, options = {}) => { + /** + * Ensures that the passed in ids or webfinger handles are stored in database. + * Options: + * - update: boolean, forces re-fetch/process of the resolved id + * Return one of: + * - An array of newly processed ids + * - false: if input incorrect (or webfinger handle cannot resolve) + * - true: no new IDs processed; all passed-in IDs present. + */ + + ids = await Actors.qualify(ids, options); + if (!ids || !ids.length) { + return ids; } activitypub.helpers.log(`[activitypub/actors] Asserting ${ids.length} actor(s)`); @@ -98,16 +129,30 @@ Actors.assert = async (ids, options = {}) => { const urlMap = new Map(); const followersUrlMap = new Map(); const pubKeysMap = new Map(); + const categories = new Set(); let actors = await Promise.all(ids.map(async (id) => { try { activitypub.helpers.log(`[activitypub/actors] Processing ${id}`); const actor = (typeof id === 'object' && id.hasOwnProperty('id')) ? id : await activitypub.get('uid', 0, id, { cache: process.env.CI === 'true' }); + // webfinger backreference check + const { hostname: domain } = new URL(id); + const { actorUri: canonicalId } = await activitypub.helpers.query(`${actor.preferredUsername}@${domain}`); + if (id !== canonicalId) { + return null; + } + let typeOk = false; if (Array.isArray(actor.type)) { typeOk = actor.type.some(type => activitypub._constants.acceptableActorTypes.has(type)); + if (!typeOk && actor.type.some(type => activitypub._constants.acceptableGroupTypes.has(type))) { + categories.add(actor.id); + } } else { typeOk = activitypub._constants.acceptableActorTypes.has(actor.type); + if (!typeOk && activitypub._constants.acceptableGroupTypes.has(actor.type)) { + categories.add(actor.id); + } } if ( @@ -129,7 +174,6 @@ Actors.assert = async (ids, options = {}) => { // no action required activitypub.helpers.log(`[activitypub/actor.assert] Unable to retrieve follower counts for ${actor.id}`); } - // Save url for backreference const url = Array.isArray(actor.url) ? actor.url.shift() : actor.url; if (url && url !== actor.id) { @@ -161,9 +205,12 @@ Actors.assert = async (ids, options = {}) => { } })); actors = actors.filter(Boolean); // remove unresolvable actors + if (!actors.length && !categories.size) { + return []; + } // Build userData object for storage - const profiles = (await activitypub.mocks.profile(actors, hostMap)).filter(Boolean); + const profiles = (await activitypub.mocks.profile(actors)).filter(Boolean); const now = Date.now(); const bulkSet = profiles.reduce((memo, profile) => { @@ -219,29 +266,213 @@ Actors.assert = async (ids, options = {}) => { db.setObject('handle:uid', queries.handleAdd), ]); + // Handle any actors that should be asserted as a group instead + if (categories.size) { + const assertion = await Actors.assertGroup(Array.from(categories), options); + if (assertion === false) { + return false; + } else if (Array.isArray(assertion)) { + return [...actors, ...assertion]; + } + + // otherwise, assertGroup returned true and output can be safely ignored. + } + return actors; }; -Actors.getLocalFollowers = async (id) => { - const response = { +Actors.assertGroup = async (ids, options = {}) => { + /** + * Ensures that the passed in ids or webfinger handles are stored in database. + * Options: + * - update: boolean, forces re-fetch/process of the resolved id + * Return one of: + * - An array of newly processed ids + * - false: if input incorrect (or webfinger handle cannot resolve) + * - true: no new IDs processed; all passed-in IDs present. + */ + + ids = await Actors.qualify(ids, { + qualifyGroup: true, + ...options, + }); + if (!ids) { + return ids; + } + + activitypub.helpers.log(`[activitypub/actors] Asserting ${ids.length} group(s)`); + + // NOTE: MAKE SURE EVERY DB ADDITION HAS A CORRESPONDING REMOVAL IN ACTORS.REMOVEGROUP! + + const urlMap = new Map(); + const followersUrlMap = new Map(); + const pubKeysMap = new Map(); + let groups = await Promise.all(ids.map(async (id) => { + try { + activitypub.helpers.log(`[activitypub/actors] Processing group ${id}`); + const actor = (typeof id === 'object' && id.hasOwnProperty('id')) ? id : await activitypub.get('uid', 0, id, { cache: process.env.CI === 'true' }); + + // webfinger backreference check + const { hostname: domain } = new URL(id); + const { actorUri: canonicalId } = await activitypub.helpers.query(`${actor.preferredUsername}@${domain}`); + if (id !== canonicalId) { + return null; + } + + const typeOk = Array.isArray(actor.type) ? + actor.type.some(type => activitypub._constants.acceptableGroupTypes.has(type)) : + activitypub._constants.acceptableGroupTypes.has(actor.type); + + if ( + !typeOk || + !activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop)) + ) { + return null; + } + + // Save url for backreference + const url = Array.isArray(actor.url) ? actor.url.shift() : actor.url; + if (url && url !== actor.id) { + urlMap.set(url, actor.id); + } + + // Save followers url for backreference + if (actor.hasOwnProperty('followers') && activitypub.helpers.isUri(actor.followers)) { + followersUrlMap.set(actor.followers, actor.id); + } + + // Public keys + pubKeysMap.set(actor.id, actor.publicKey); + + return actor; + } catch (e) { + if (e.code === 'ap_get_410') { + const exists = await categories.exists(id); + if (exists) { + await categories.purge(id, 0); + } + } + + return null; + } + })); + groups = groups.filter(Boolean); // remove unresolvable actors + + // Build categoryData object for storage + const categoryObjs = (await activitypub.mocks.category(groups)).filter(Boolean); + const now = Date.now(); + + const bulkSet = categoryObjs.reduce((memo, category) => { + const key = `categoryRemote:${category.cid}`; + memo.push([key, category], [`${key}:keys`, pubKeysMap.get(category.cid)]); + return memo; + }, []); + if (urlMap.size) { + bulkSet.push(['remoteUrl:cid', Object.fromEntries(urlMap)]); + } + if (followersUrlMap.size) { + bulkSet.push(['followersUrl:cid', Object.fromEntries(followersUrlMap)]); + } + + const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', categoryObjs.map(p => p.cid)); + const cidsForCurrent = categoryObjs.map((p, idx) => (exists[idx] ? p.cid : 0)); + const current = await categories.getCategoriesFields(cidsForCurrent, ['slug']); + const queries = categoryObjs.reduce((memo, profile, idx) => { + const { slug, name } = current[idx]; + + if (options.update || slug !== profile.slug) { + if (cidsForCurrent[idx] !== 0 && slug) { + // memo.searchRemove.push(['ap.preferredUsername:sorted', `${slug.toLowerCase()}:${profile.uid}`]); + memo.handleRemove.push(slug.toLowerCase()); + } + + memo.searchAdd.push(['categories:name', 0, `${profile.slug.slice(0, 200).toLowerCase()}:${profile.cid}`]); + memo.handleAdd[profile.slug.toLowerCase()] = profile.cid; + } + + if (options.update || (profile.name && name !== profile.name)) { + if (name && cidsForCurrent[idx] !== 0) { + memo.searchRemove.push(['categories:name', `${name.toLowerCase()}:${profile.cid}`]); + } + + memo.searchAdd.push(['categories:name', 0, `${profile.name.toLowerCase()}:${profile.cid}`]); + } + + return memo; + }, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} }); + + // Removals + await Promise.all([ + db.sortedSetRemoveBulk(queries.searchRemove), + db.deleteObjectFields('handle:cid', queries.handleRemove), + ]); + + // Privilege mask + const [masksAdd, masksRemove] = categoryObjs.reduce(([add, remove], category) => { + (category?._activitypub?.postingRestrictedToMods ? add : remove).push(`cid:${category.cid}:privilegeMask`); + return [add, remove]; + }, [[], []]); + + await Promise.all([ + db.setObjectBulk(bulkSet), + db.sortedSetAdd('usersRemote:lastCrawled', groups.map(() => now), groups.map(p => p.id)), + db.sortedSetAddBulk(queries.searchAdd), + db.setObject('handle:cid', queries.handleAdd), + db.setsAdd(masksAdd, 'topics:create'), + db.setsRemove(masksRemove, 'topics:create'), + ]); + + return categoryObjs; +}; + +Actors.getFollowers = async (id) => { + /** + * Returns followers by local or remote id. Pass in a... + * - Remote id: returns local uids/cids that follow + * - Local id: returns remote uids that follow + */ + let response = Actors._followerCache.get(id); + if (response) { + return response; + } + + response = { uids: new Set(), cids: new Set(), }; - if (!activitypub.helpers.isUri(id)) { - return response; + const [isUser, isCategory] = await Promise.all([ + user.exists(id), + categories.exists(id), + ]); + + if (isUser) { + const members = await db.getSortedSetMembers(`followersRemote:${id}`); + + members.forEach((id) => { + if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { + response.cids.add(parseInt(id.slice(4), 10)); + } else { + response.uids.add(utils.isNumber(id) ? parseInt(id, 10) : id); + } + }); + } else if (isCategory) { + // Internally, users are different, they follow via watch state instead + // Possibly refactor to store in followersRemote:${id} too?? + const members = await db.getSortedSetRangeByScore(`cid:${id}:uid:watch:state`, 0, -1, categories.watchStates.tracking, categories.watchStates.watching); + members.forEach((uid) => { + response.uids.add(uid); + }); + + const cids = await db.getSortedSetMembers(`followersRemote:${id}`); + cids.forEach((id) => { + if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { + response.cids.add(parseInt(id.slice(4), 10)); + } + }); } - const members = await db.getSortedSetMembers(`followersRemote:${id}`); - - members.forEach((id) => { - if (utils.isNumber(id)) { - response.uids.add(parseInt(id, 10)); - } else if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { - response.cids.add(parseInt(id.slice(4), 10)); - } - }); - + Actors._followerCache.set(id, response); return response; }; @@ -310,38 +541,107 @@ Actors.remove = async (id) => { ]); }; +Actors.removeGroup = async (id) => { + /** + * Remove ActivityPub related metadata pertaining to a remote id + * + * Note: don't call this directly! It is called as part of categories.purge + */ + const exists = await db.isSortedSetMember('usersRemote:lastCrawled', id); + if (!exists) { + return false; + } + + let { slug, name, url, followersUrl } = await categories.getCategoryFields(id, ['slug', 'name', 'url', 'followersUrl']); + slug = slug.toLowerCase(); + + const bulkRemove = [ + ['categories:name', `${slug}:${id}`], + ]; + if (name) { + bulkRemove.push(['categories:name', `${name.toLowerCase()}:${id}`]); + } + + await Promise.all([ + db.sortedSetRemoveBulk(bulkRemove), + db.deleteObjectField('handle:cid', slug), + db.deleteObjectField('followersUrl:cid', followersUrl), + db.deleteObjectField('remoteUrl:cid', url), + db.delete(`categoryRemote:${id}:keys`), + ]); + + await Promise.all([ + db.delete(`categoryRemote:${id}`), + db.sortedSetRemove('usersRemote:lastCrawled', id), + ]); +}; + Actors.prune = async () => { /** * Clear out remote user accounts that do not have content on the forum anywhere */ - winston.info('[actors/prune] Started scheduled pruning of remote user accounts'); + activitypub.helpers.log('[actors/prune] Started scheduled pruning of remote user accounts and categories'); const days = parseInt(meta.config.activitypubUserPruneDays, 10); const timestamp = Date.now() - (1000 * 60 * 60 * 24 * days); - const uids = await db.getSortedSetRangeByScore('usersRemote:lastCrawled', 0, 500, '-inf', timestamp); - if (!uids.length) { - winston.info('[actors/prune] No remote users to prune, all done.'); - return; + const ids = await db.getSortedSetRangeByScore('usersRemote:lastCrawled', 0, 500, '-inf', timestamp); + if (!ids.length) { + activitypub.helpers.log('[actors/prune] No remote actors to prune, all done.'); + return { + counts: { + deleted: 0, + missing: 0, + preserved: 0, + }, + preserved: new Set(), + }; } - winston.info(`[actors/prune] Found ${uids.length} remote users last crawled more than ${days} days ago`); + activitypub.helpers.log(`[actors/prune] Found ${ids.length} remote actors last crawled more than ${days} days ago`); let deletionCount = 0; let deletionCountNonExisting = 0; let notDeletedDueToLocalContent = 0; - const notDeletedUids = []; - await batch.processArray(uids, async (uids) => { - const exists = await db.exists(uids.map(uid => `userRemote:${uid}`)); + const preservedIds = []; + const cleanupUids = []; - const uidsThatExist = uids.filter((uid, idx) => exists[idx]); - const uidsThatDontExist = uids.filter((uid, idx) => !exists[idx]); - - const [postCounts, roomCounts, followCounts] = await Promise.all([ - db.sortedSetsCard(uidsThatExist.map(uid => `uid:${uid}:posts`)), - db.sortedSetsCard(uidsThatExist.map(uid => `uid:${uid}:chat:rooms`)), - Actors.getLocalFollowCounts(uidsThatExist), + await batch.processArray(ids, async (ids) => { + const exists = await Promise.all([ + db.exists(ids.map(id => `userRemote:${id}`)), + db.exists(ids.map(id => `categoryRemote:${id}`)), ]); - await Promise.all(uidsThatExist.map(async (uid, idx) => { + let uids = new Set(); + let cids = new Set(); + const missing = new Set(); + ids.forEach((id, idx) => { + switch (true) { + case exists[0][idx]: { + uids.add(id); + break; + } + + case exists[1][idx]: { + cids.add(id); + break; + } + + default: { + missing.add(id); + break; + } + } + }); + uids = Array.from(uids); + cids = Array.from(cids); + + // Remote users + const [postCounts, roomCounts, followCounts] = await Promise.all([ + db.sortedSetsCard(uids.map(uid => `uid:${uid}:posts`)), + db.sortedSetsCard(uids.map(uid => `uid:${uid}:chat:rooms`)), + Actors.getLocalFollowCounts(uids), + ]); + + await Promise.all(uids.map(async (uid, idx) => { const { followers, following } = followCounts[idx]; const postCount = postCounts[idx]; const roomCount = roomCounts[idx]; @@ -349,25 +649,62 @@ Actors.prune = async () => { try { await user.deleteAccount(uid); deletionCount += 1; + } catch (err) { + winston.error(`Failed to delete user with uid ${uid}: ${err.stack}`); + if (err.message === '[[error:no-user]]') { + cleanupUids.push(uid); + } + } + } else { + notDeletedDueToLocalContent += 1; + preservedIds.push(uid); + } + })); + + if (cleanupUids.length) { + await Promise.all([ + db.sortedSetRemove('usersRemote:lastCrawled', cleanupUids), + db.deleteAll(cleanupUids.map(uid => `userRemote:${uid}`)), + ]); + winston.info(`[actors/prune] Cleaned up ${cleanupUids.length} remote users that were not found in the database.`); + } + + // Remote categories + let counts = await categories.getCategoriesFields(cids, ['topic_count']); + counts = counts.map(count => count.topic_count); + await Promise.all(cids.map(async (cid, idx) => { + const topicCount = counts[idx]; + if (topicCount === 0) { + try { + await categories.purge(cid, 0); + deletionCount += 1; } catch (err) { winston.error(err.stack); } } else { notDeletedDueToLocalContent += 1; - notDeletedUids.push(uid); + preservedIds.push(cid); } })); - deletionCountNonExisting += uidsThatDontExist.length; - await db.sortedSetRemove('usersRemote:lastCrawled', uidsThatDontExist); + deletionCountNonExisting += missing.size; + await db.sortedSetRemove('usersRemote:lastCrawled', Array.from(missing)); // update timestamp in usersRemote:lastCrawled so we don't try to delete users // with content over and over const now = Date.now(); - await db.sortedSetAdd('usersRemote:lastCrawled', notDeletedUids.map(() => now), notDeletedUids); + await db.sortedSetAdd('usersRemote:lastCrawled', preservedIds.map(() => now), preservedIds); }, { batch: 50, interval: 1000, }); - winston.info(`[actors/prune] ${deletionCount} remote users pruned. ${deletionCountNonExisting} does not exist. ${notDeletedDueToLocalContent} not deleted due to local content`); + activitypub.helpers.log(`[actors/prune] ${deletionCount} remote users pruned. ${deletionCountNonExisting} did not exist. ${notDeletedDueToLocalContent} not deleted due to local content`); + return { + counts: { + deleted: deletionCount, + missing: deletionCountNonExisting, + preserved: notDeletedDueToLocalContent, + }, + preserved: new Set(preservedIds), + }; }; diff --git a/src/activitypub/contexts.js b/src/activitypub/contexts.js index 0748e42ece..8335ae7cc4 100644 --- a/src/activitypub/contexts.js +++ b/src/activitypub/contexts.js @@ -81,6 +81,10 @@ Contexts.getItems = async (uid, id, options) => { } if (items) { + if (options.returnRootId) { + return items.pop(); + } + items = await Promise.all(items .map(async item => (activitypub.helpers.isUri(item) ? parseString(uid, item) : parseItem(uid, item)))); items = items.filter(Boolean); @@ -113,19 +117,6 @@ Contexts.getItems = async (uid, id, options) => { return chain; } - // Handle special case where originating object is not actually part of the context collection - const inputId = activitypub.helpers.isUri(options.input) ? options.input : options.input.id; - const inCollection = Array.from(chain).map(p => p.pid).includes(inputId); - if (!inCollection) { - const item = activitypub.helpers.isUri(options.input) ? - await parseString(uid, options.input) : - await parseItem(uid, options.input); - - if (item) { - chain.add(item); - } - } - return chain; }; @@ -133,7 +124,7 @@ async function parseString(uid, item) { const { type, id } = await activitypub.helpers.resolveLocalId(item); const pid = type === 'post' && id ? id : item; const postData = await posts.getPostData(pid); - if (postData) { + if (postData && postData.pid) { // Already cached return postData; } @@ -152,14 +143,6 @@ async function parseString(uid, item) { } async function parseItem(uid, item) { - const { type, id } = await activitypub.helpers.resolveLocalId(item.id); - const pid = type === 'post' && id ? id : item.id; - const postData = await posts.getPostData(pid); - if (postData) { - // Already cached - return postData; - } - // Handle activity wrapper if (item.type === 'Create') { item = item.object; @@ -171,6 +154,14 @@ async function parseItem(uid, item) { return null; } + const { type, id } = await activitypub.helpers.resolveLocalId(item.id); + const pid = type === 'post' && id ? id : item.id; + const postData = await posts.getPostData(pid); + if (postData && postData.pid) { + // Already cached + return postData; + } + activitypub.helpers.log(`[activitypub/context] Parsing ${pid}`); return await activitypub.mocks.post(item); } diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index 1e2d96441e..262010b7da 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -3,6 +3,8 @@ const nconf = require('nconf'); const posts = require('../posts'); +const topics = require('../topics'); +const utils = require('../utils'); const activitypub = module.parent.exports; const Feps = module.exports; @@ -12,43 +14,67 @@ Feps.announce = async function announce(id, activity) { if (String(id).startsWith(nconf.get('url'))) { ({ id: localId } = await activitypub.helpers.resolveLocalId(id)); } - const cid = await posts.getCidByPid(localId || id); - if (cid === -1) { + + /** + * Re-broadcasting occurs on + * - local cids (for all tids), and + * - local tids (posted to remote cids) only + */ + const tid = await posts.getPostField(localId || id, 'tid'); + const cid = await topics.getTopicField(tid, 'cid'); + const localCid = utils.isNumber(cid) && cid > 0; + const addressed = activitypub.helpers.addressed(cid, activity); + const shouldAnnounce = localCid || (utils.isNumber(tid) && !addressed); + if (!shouldAnnounce) { // inverse conditionals can kiss my ass. return; } - const followers = await activitypub.notes.getCategoryFollowers(cid); - if (!followers.length) { + let relays = await activitypub.relays.list(); + relays = relays.reduce((memo, { state, url }) => { + if (state === 2) { + memo.push(url); + } + return memo; + }, []); + const followers = localCid ? await activitypub.notes.getCategoryFollowers(cid) : [cid]; + const targets = relays.concat(followers); + if (!targets.length) { return; } const { actor } = activity; - if (actor && !actor.startsWith(nconf.get('url'))) { - followers.unshift(actor); + if (localCid && actor && !actor.startsWith(nconf.get('url'))) { + targets.unshift(actor); } const now = Date.now(); + const to = [localCid ? `${nconf.get('url')}/category/${cid}/followers` : cid]; + const cc = [activitypub._constants.publicAddress]; + if (localCid) { + cc.unshift(actor); + } + if (activity.type === 'Create') { const isMain = await posts.isMain(localId || id); if (isMain) { - activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing plain object (${activity.id}) to followers of cid ${cid}`); - await activitypub.send('cid', cid, followers, { - id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now}`, + activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing plain object (${activity.id}) to followers of cid ${cid} and ${relays.length} relays`); + await activitypub.send('cid', localCid ? cid : 0, targets, { + id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${localCid ? `cid/${cid}` : 'uid/0'}`, type: 'Announce', - actor: `${nconf.get('url')}/category/${cid}`, - to: [`${nconf.get('url')}/category/${cid}/followers`], - cc: [actor, activitypub._constants.publicAddress], + actor: localCid ? `${nconf.get('url')}/category/${cid}` : `${nconf.get('url')}/actor`, + to, + cc, object: activity.object, }); } } - activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing ${activity.type} (${activity.id}) to followers of cid ${cid}`); - await activitypub.send('cid', cid, followers, { - id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now + 1}`, + activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing ${activity.type} (${activity.id}) to followers of cid ${cid} and ${relays.length} relays`); + await activitypub.send('cid', localCid ? cid : 0, targets, { + id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now}`, type: 'Announce', - actor: `${nconf.get('url')}/category/${cid}`, - to: [`${nconf.get('url')}/category/${cid}/followers`], - cc: [actor, activitypub._constants.publicAddress], + actor: localCid ? `${nconf.get('url')}/category/${cid}` : `${nconf.get('url')}/actor`, + to, + cc, object: activity, }); }; diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 637d4b4afd..9a6577af41 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -5,7 +5,6 @@ const process = require('process'); const nconf = require('nconf'); const winston = require('winston'); const validator = require('validator'); -// const cheerio = require('cheerio'); const crypto = require('crypto'); const meta = require('../meta'); @@ -21,6 +20,7 @@ const activitypub = require('.'); const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/; const webfingerCache = ttl({ + name: 'ap-webfinger-cache', max: 5000, ttl: 1000 * 60 * 60 * 24, // 24 hours }); @@ -28,6 +28,8 @@ const sha256 = payload => crypto.createHash('sha256').update(payload).digest('he const Helpers = module.exports; +Helpers._webfingerCache = webfingerCache; // exported for tests + Helpers._test = (method, args) => { // because I am lazy and I probably wrote some variant of this below code 1000 times already setTimeout(async () => { @@ -63,10 +65,11 @@ Helpers.isUri = (value) => { }); }; -Helpers.assertAccept = accept => (accept && accept.split(',').some((value) => { - const parts = value.split(';').map(v => v.trim()); - return activitypub._constants.acceptableTypes.includes(value || parts[0]); -})); +Helpers.assertAccept = (accept) => { + if (!accept) return false; + const normalized = accept.split(',').map(s => s.trim().replace(/\s*;\s*/g, ';')).join(','); + return activitypub._constants.acceptableTypes.some(type => normalized.includes(type)); +}; Helpers.isWebfinger = (value) => { // N.B. returns normalized handle, so truthy check! @@ -106,7 +109,12 @@ Helpers.query = async (id) => { let response; let body; try { - ({ response, body } = await request.get(`https://${hostname}/.well-known/webfinger?${query}`)); + ({ response, body } = await request.get(`https://${hostname}/.well-known/webfinger?${query}`, { + headers: { + accept: 'application/jrd+json', + }, + timeout: 5000, + })); } catch (e) { return false; } @@ -122,9 +130,12 @@ Helpers.query = async (id) => { ({ href: actorUri } = actorUri); } - const { subject, publicKey } = body; + let { subject, publicKey } = body; + // Fix missing scheme + if (!subject.startsWith('acct:') && !subject.startsWith('did:')) { + subject = `acct:${subject}`; + } const payload = { subject, username, hostname, actorUri, publicKey }; - const claimedId = new URL(subject).pathname; webfingerCache.set(claimedId, payload); if (claimedId !== id) { @@ -186,6 +197,9 @@ Helpers.resolveLocalId = async (input) => { case 'message': return { type: 'message', id: value, ...activityData }; + + case 'actor': + return { type: 'application', id: null }; } return { type: null, id: null, ...activityData }; @@ -211,7 +225,7 @@ Helpers.resolveActor = (type, id) => { case 'category': case 'cid': { - return `${nconf.get('url')}/category/${id}`; + return `${nconf.get('url')}${id > 0 ? `/category/${id}` : '/actor'}`; } default: @@ -439,14 +453,20 @@ Helpers.remoteAnchorToLocalProfile = async (content, isMarkdown = false) => { return content; }; -// eslint-disable-next-line max-len -Helpers.makeSet = (object, properties) => new Set(properties.reduce((memo, property) => memo.concat(Array.isArray(object[property]) ? object[property] : [object[property]]), [])); +Helpers.makeSet = (object, properties) => new Set(properties.reduce((memo, property) => + memo.concat(object[property] ? + Array.isArray(object[property]) ? + object[property] : + [object[property]] : + []), [])); -Helpers.generateCollection = async ({ set, method, page, perPage, url }) => { +Helpers.generateCollection = async ({ set, method, count, page, perPage, url }) => { if (!method) { - method = db.getSortedSetRange; + method = db.getSortedSetRange.bind(null, set); + } else if (set) { + method = method.bind(null, set); } - const count = await db.sortedSetCard(set); + count = count || await db.sortedSetCard(set); const pageCount = Math.max(1, Math.ceil(count / perPage)); let items = []; let paginate = true; @@ -464,7 +484,7 @@ Helpers.generateCollection = async ({ set, method, page, perPage, url }) => { const start = Math.max(0, ((page - 1) * perPage) - 1); const stop = Math.max(0, start + perPage - 1); - items = await method(set, start, stop); + items = await method.call(null, start, stop); } const object = { @@ -480,8 +500,6 @@ Helpers.generateCollection = async ({ set, method, page, perPage, url }) => { object.next = page < pageCount ? `${url}?page=${page + 1}` : null; object.prev = page > 1 ? `${url}?page=${page - 1}` : null; } - } else { - object.orderedItems = []; } if (paginate) { @@ -508,3 +526,58 @@ Helpers.generateDigest = (set) => { return result.toString('hex'); }); }; + +Helpers.addressed = (id, activity) => { + // Returns Boolean for if id is found in addressing fields (to, cc, etc.) + if (!id || !activity || typeof activity !== 'object') { + return false; + } + + const combined = new Set([ + ...(activity.to || []), + ...(activity.cc || []), + ...(activity.bto || []), + ...(activity.bcc || []), + ...(activity.audience || []), + ]); + + return combined.has(id); +}; + +Helpers.renderEmoji = (text, tags, strip = false) => { + if (!text || !tags) { + return text; + } + + tags = Array.isArray(tags) ? tags : [tags]; + let result = text; + + tags.forEach((tag) => { + const isEmoji = tag.type === 'Emoji'; + const hasUrl = tag.icon && tag.icon.url; + const isImage = !tag.icon?.mediaType || tag.icon.mediaType.startsWith('image/'); + + if (isEmoji && (strip || (hasUrl && isImage))) { + let { name } = tag; + + if (!name.startsWith(':')) { + name = `:${name}`; + } + if (!name.endsWith(':')) { + name = `${name}:`; + } + + const imgTag = strip ? + '' : + ``; + + let index = result.indexOf(name); + while (index !== -1) { + result = result.substring(0, index) + imgTag + result.substring(index + name.length); + index = result.indexOf(name, index + imgTag.length); + } + } + }); + + return result; +}; diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index cfd262985b..ea0e99de35 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -13,6 +13,7 @@ const notifications = require('../notifications'); const messaging = require('../messaging'); const flags = require('../flags'); const api = require('../api'); +const utils = require('../utils'); const activitypub = require('.'); const socketHelpers = require('../socket.io/helpers'); @@ -32,16 +33,21 @@ function reject(type, object, target, senderType = 'uid', id = 0) { }).catch(err => winston.error(err.stack)); } +function publiclyAddressed(recipients) { + return activitypub._constants.acceptablePublicAddresses.some(address => recipients.includes(address)); +} + inbox.create = async (req) => { const { object, actor } = req.body; // Alternative logic for non-public objects - const isPublic = [...(object.to || []), ...(object.cc || [])].includes(activitypub._constants.publicAddress); + const isPublic = publiclyAddressed([...(object.to || []), ...(object.cc || [])]); if (!isPublic) { return await activitypub.notes.assertPrivate(object); } - const { cids } = await activitypub.actors.getLocalFollowers(actor); + // Category sync, remove when cross-posting available + const { cids } = await activitypub.actors.getFollowers(actor); let cid = null; if (cids.size > 0) { cid = Array.from(cids)[0]; @@ -49,7 +55,7 @@ inbox.create = async (req) => { const asserted = await activitypub.notes.assert(0, object, { cid }); if (asserted) { - activitypub.feps.announce(object.id, req.body); + await activitypub.feps.announce(object.id, req.body); // api.activitypub.add(req, { pid: object.id }); } }; @@ -73,9 +79,81 @@ inbox.add = async (req) => { } }; +inbox.remove = async (req) => { + const { actor, object, target } = req.body; + + const isContext = activitypub._constants.acceptable.contextTypes.has(object.type); + if (!isContext) { + return; // don't know how to handle other types + } + + const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); + const fromCid = target || object.audience; + const exists = await posts.exists(mainPid); + if (!exists || !fromCid) { + return; // post not cached; do nothing. + } + + // Ensure that cid is same-origin as the actor + const tid = await posts.getPostField(mainPid, 'tid'); + const cid = await topics.getTopicField(tid, 'cid'); + if (utils.isNumber(cid) || cid !== fromCid) { + // remote removal of topic in local cid, or resolved cid does not match + return; + } + const actorHostname = new URL(actor).hostname; + const cidHostname = new URL(cid).hostname; + if (actorHostname !== cidHostname) { + throw new Error('[[error:activitypub.origin-mismatch]]'); + } + + activitypub.helpers.log(`[activitypub/inbox/remove] Removing topic ${tid} from ${cid}`); + await topics.tools.move(tid, { + cid: -1, + uid: 'system', + }); +}; + +inbox.move = async (req) => { + const { actor, object, origin, target } = req.body; + + const isContext = activitypub._constants.acceptable.contextTypes.has(object.type); + if (!isContext) { + return; // don't know how to handle other types + } + + const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); + const fromCid = origin; + const toCid = target || object.audience; + const exists = await posts.exists(mainPid); + if (!exists || !toCid) { + return; // post not cached; do nothing. + } + + // Ensure that cid is same-origin as the actor + const tid = await posts.getPostField(mainPid, 'tid'); + const cid = await topics.getTopicField(tid, 'cid'); + if (utils.isNumber(cid)) { + // remote removal of topic in local cid, or resolved cid does not match + return; + } + const actorHostname = new URL(actor).hostname; + const toCidHostname = new URL(toCid).hostname; + const fromCidHostname = new URL(fromCid).hostname; + if (actorHostname !== toCidHostname || actorHostname !== fromCidHostname) { + throw new Error('[[error:activitypub.origin-mismatch]]'); + } + + activitypub.helpers.log(`[activitypub/inbox/remove] Moving topic ${tid} from ${fromCid} to ${toCid}`); + await topics.tools.move(tid, { + cid: toCid, + uid: 'system', + }); +}; + inbox.update = async (req) => { const { actor, object } = req.body; - const isPublic = [...(object.to || []), ...(object.cc || [])].includes(activitypub._constants.publicAddress); + const isPublic = publiclyAddressed([...(object.to || []), ...(object.cc || [])]); // Origin checking const actorHostname = new URL(actor).hostname; @@ -84,8 +162,8 @@ inbox.update = async (req) => { throw new Error('[[error:activitypub.origin-mismatch]]'); } - switch (object.type) { - case 'Note': { + switch (true) { + case activitypub._constants.acceptedPostTypes.includes(object.type): { const [isNote, isMessage] = await Promise.all([ posts.exists(object.id), messaging.messageExists(object.id), @@ -94,7 +172,14 @@ inbox.update = async (req) => { try { switch (true) { case isNote: { + const cid = await posts.getCidByPid(object.id); + const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid); + if (!allowed) { + throw new Error('[[error:no-privileges]]'); + } + const postData = await activitypub.mocks.post(object); + postData.tags = await activitypub.notes._normalizeTags(postData._activitypub.tag, postData.cid); await posts.edit(postData); const isDeleted = await posts.getPostField(object.id, 'deleted'); if (isDeleted) { @@ -117,7 +202,7 @@ inbox.update = async (req) => { return await activitypub.notes.assertPrivate(object); } - const { cids } = await activitypub.actors.getLocalFollowers(actor); + const { cids } = await activitypub.actors.getFollowers(actor); let cid = null; if (cids.size > 0) { cid = Array.from(cids)[0]; @@ -136,16 +221,12 @@ inbox.update = async (req) => { break; } - case 'Application': // falls through - case 'Group': // falls through - case 'Organization': // falls through - case 'Service': // falls through - case 'Person': { + case activitypub._constants.acceptableActorTypes.has(object.type): { await activitypub.actors.assert(object.id, { update: true }); break; } - case 'Tombstone': { + case object.type === 'Tombstone': { const [isNote, isMessage/* , isActor */] = await Promise.all([ posts.exists(object.id), messaging.messageExists(object.id), @@ -180,18 +261,18 @@ inbox.delete = async (req) => { throw new Error('[[error:invalid-pid]]'); } } - const pid = object.id || object; + const id = object.id || object; let type = object.type || undefined; // Deletes don't have their objects resolved automatically let method = 'purge'; try { if (!type) { - ({ type } = await activitypub.get('uid', 0, pid)); + ({ type } = await activitypub.get('uid', 0, id)); } if (type === 'Tombstone') { - method = 'delete'; + method = 'delete'; // soft delete } } catch (e) { // probably 410/404 @@ -200,21 +281,41 @@ inbox.delete = async (req) => { // Deletions must be made by an actor of the same origin const actorHostname = new URL(actor).hostname; - const objectHostname = new URL(pid).hostname; + const objectHostname = new URL(id).hostname; if (actorHostname !== objectHostname) { - throw new Error('[[error:activitypub.origin-mismatch]]'); + return reject('Delete', object, actor); } - const [isNote/* , isActor */] = await Promise.all([ - posts.exists(pid), + const [isNote, isContext/* , isActor */] = await Promise.all([ + posts.exists(id), + activitypub.contexts.getItems(0, id, { returnRootId: true }), // ⚠️ unreliable, needs better logic (Contexts.is?) // db.isSortedSetMember('usersRemote:lastCrawled', object.id), ]); switch (true) { case isNote: { - const uid = await posts.getPostField(pid, 'uid'); - await activitypub.feps.announce(pid, req.body); - await api.posts[method]({ uid }, { pid }); + const cid = await posts.getCidByPid(id); + const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid); + if (!allowed) { + return reject('Delete', object, actor); + } + + const uid = await posts.getPostField(id, 'uid'); + await activitypub.feps.announce(id, req.body); + await api.posts[method]({ uid }, { pid: id }); + break; + } + + case !!isContext: { + const pid = isContext; + const exists = await posts.exists(pid); + if (!exists) { + activitypub.helpers.log(`[activitypub/inbox.delete] Context main pid (${pid}) not found locally. Doing nothing.`); + return; + } + const { tid, uid } = await posts.getPostFields(pid, ['tid', 'uid']); + activitypub.helpers.log(`[activitypub/inbox.delete] Deleting tid ${tid}.`); + await api.topics[method]({ uid }, { tids: [tid] }); break; } @@ -224,7 +325,7 @@ inbox.delete = async (req) => { // } default: { - activitypub.helpers.log(`[activitypub/inbox.delete] Object (${pid}) does not exist locally. Doing nothing.`); + activitypub.helpers.log(`[activitypub/inbox.delete] Object (${id}) does not exist locally. Doing nothing.`); break; } } @@ -240,19 +341,40 @@ inbox.like = async (req) => { const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid); if (!allowed) { - winston.verbose(`[activitypub/inbox.like] ${id} not allowed to be upvoted.`); + activitypub.helpers.log(`[activitypub/inbox.like] ${id} not allowed to be upvoted.`); return reject('Like', object, actor); } - winston.verbose(`[activitypub/inbox/like] id ${id} via ${actor}`); + activitypub.helpers.log(`[activitypub/inbox/like] id ${id} via ${actor}`); const result = await posts.upvote(id, actor); - activitypub.feps.announce(object.id, req.body); + await activitypub.feps.announce(object.id, req.body); socketHelpers.upvote(result, 'notifications:upvoted-your-post-in'); }; +inbox.dislike = async (req) => { + const { actor, object } = req.body; + const { type, id } = await activitypub.helpers.resolveLocalId(object.id); + + if (type !== 'post' || !(await posts.exists(id))) { + return reject('Dislike', object, actor); + } + + const allowed = await privileges.posts.can('posts:downvote', id, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/inbox.like] ${id} not allowed to be downvoted.`); + return reject('Dislike', object, actor); + } + + activitypub.helpers.log(`[activitypub/inbox/dislike] id ${id} via ${actor}`); + + await posts.downvote(id, actor); + await activitypub.feps.announce(object.id, req.body); +}; + inbox.announce = async (req) => { - const { actor, object, published, to, cc } = req.body; + let { actor, object, published, to, cc } = req.body; + activitypub.helpers.log(`[activitypub/inbox/announce] Parsing Announce(${object.type}) from ${actor}`); let timestamp = new Date(published); timestamp = timestamp.toString() !== 'Invalid Date' ? timestamp.getTime() : Date.now(); @@ -264,67 +386,112 @@ inbox.announce = async (req) => { let tid; let pid; - const { cids } = await activitypub.actors.getLocalFollowers(actor); + // Category sync, remove when cross-posting available + const { cids } = await activitypub.actors.getFollowers(actor); + const syncedCids = Array.from(cids); + + // 1b12 announce let cid = null; - if (cids.size > 0) { - cid = Array.from(cids)[0]; + const categoryActor = await categories.exists(actor); + if (categoryActor) { + cid = actor; } - if (String(object.id).startsWith(nconf.get('url'))) { // Local object - const { type, id } = await activitypub.helpers.resolveLocalId(object.id); - if (type !== 'post' || !(await posts.exists(id))) { - throw new Error('[[error:activitypub.invalid-id]]'); + // Received via relay? + const fromRelay = await activitypub.relays.is(actor); + + switch(true) { + case object.type === 'Like': { + const id = object.object.id || object.object; + const { id: localId } = await activitypub.helpers.resolveLocalId(id); + const exists = await posts.exists(localId || id); + if (exists) { + try { + await activitypub.actors.assert(object.actor); + const result = await posts.upvote(localId || id, object.actor); + if (localId) { + socketHelpers.upvote(result, 'notifications:upvoted-your-post-in'); + } + } catch (e) { + // vote denied due to local limitations (frequency, privilege, etc.); noop. + } + } + + break; } - pid = id; - tid = await posts.getPostField(id, 'tid'); + case object.type === 'Update': { + req.body = object; + await inbox.update(req); + break; + } - socketHelpers.sendNotificationToPostOwner(pid, actor, 'announce', 'notifications:activitypub.announce'); - } else { // Remote object - // Follower check - if (!cid) { - const { followers } = await activitypub.actors.getLocalFollowCounts(actor); - if (!followers) { - winston.verbose(`[activitypub/inbox.announce] Rejecting ${object.id} via ${actor} due to no followers`); - reject('Announce', object, actor); - return; + case object.type === 'Create': { + object = object.object; + // falls through + } + + // Announce(Object) + case activitypub._constants.acceptedPostTypes.includes(object.type): { + if (String(object.id).startsWith(nconf.get('url'))) { // Local object + const { type, id } = await activitypub.helpers.resolveLocalId(object.id); + if (type !== 'post' || !(await posts.exists(id))) { + reject('Announce', object, actor); + return; + } + + pid = id; + tid = await posts.getPostField(id, 'tid'); + + socketHelpers.sendNotificationToPostOwner(pid, actor, 'announce', 'notifications:activitypub.announce'); + } else { // Remote object + // Follower check + if (!fromRelay && !cid && !syncedCids.length) { + const { followers } = await activitypub.actors.getLocalFollowCounts(actor); + if (!followers) { + winston.verbose(`[activitypub/inbox.announce] Rejecting ${object.id} via ${actor} due to no followers`); + reject('Announce', object, actor); + return; + } + } + + pid = object.id; + pid = await activitypub.resolveId(0, pid); // in case wrong id is passed-in; unlikely, but still. + if (!pid) { + return; + } + + const assertion = await activitypub.notes.assert(0, pid, { cid, skipChecks: true }); + if (!assertion) { + return; + } + + ({ tid } = assertion); + await activitypub.notes.updateLocalRecipients(pid, { to, cc }); + await activitypub.notes.syncUserInboxes(tid); + + if (syncedCids) { + await Promise.all(syncedCids.map(async (cid) => { + await topics.crossposts.add(tid, cid, 0); + })); + } + } + + if (!cid) { // Topic events from actors followed by users only + await activitypub.notes.announce.add(pid, actor, timestamp); } } - - // Handle case where Announce(Create(Note-ish)) is received - if (object.type === 'Create' && activitypub._constants.acceptedPostTypes.includes(object.object.type)) { - pid = object.object.id; - } else { - pid = object.id; - } - - pid = await activitypub.resolveId(0, pid); // in case wrong id is passed-in; unlikely, but still. - if (!pid) { - return; - } - - const assertion = await activitypub.notes.assert(0, pid, { cid }); - if (!assertion) { - return; - } - - ({ tid } = assertion); - await activitypub.notes.updateLocalRecipients(pid, { to, cc }); - await activitypub.notes.syncUserInboxes(tid); - } - - winston.verbose(`[activitypub/inbox/announce] Parsing id ${pid}`); - - if (!cid) { // Topic events from actors followed by users only - await activitypub.notes.announce.add(pid, actor, timestamp); } }; inbox.follow = async (req) => { const { actor, object, id: followId } = req.body; + // Sanity checks const { type, id } = await helpers.resolveLocalId(object.id); - if (!['category', 'user'].includes(type)) { + if (type === 'application') { + return activitypub.relays.handshake(req.body); + } else if (!['category', 'user'].includes(type)) { throw new Error('[[error:activitypub.invalid-id]]'); } @@ -355,6 +522,7 @@ inbox.follow = async (req) => { const followerRemoteCount = await db.sortedSetCard(`followersRemote:${id}`); await user.setUserField(id, 'followerRemoteCount', followerRemoteCount); + activitypub.actors._followerCache.del(id); await user.onFollow(actor, id); activitypub.send('uid', id, actor, { @@ -409,7 +577,9 @@ inbox.accept = async (req) => { const { type } = object; const { type: localType, id } = await helpers.resolveLocalId(object.actor); - if (!['user', 'category'].includes(localType)) { + if (object.id === `${nconf.get('url')}/actor`) { + return activitypub.relays.handshake(req.body); + } else if (!['user', 'category'].includes(localType)) { throw new Error('[[error:invalid-data]]'); } @@ -444,6 +614,8 @@ inbox.accept = async (req) => { db.sortedSetAdd(`followersRemote:${actor}`, timestamp, `cid|${id}`), // for notes assertion checking ]); } + + activitypub.actors._followerCache.del(actor); } }; @@ -481,6 +653,7 @@ inbox.undo = async (req) => { const followerRemoteCount = await db.sortedSetCard(`followerRemote:${id}`); await user.setUserField(id, 'followerRemoteCount', followerRemoteCount); notifications.rescind(`follow:${id}:uid:${actor}`); + activitypub.actors._followerCache.del(id); break; } @@ -501,7 +674,8 @@ inbox.undo = async (req) => { case 'Like': { const exists = await posts.exists(id); if (localType !== 'post' || !exists) { - throw new Error('[[error:invalid-pid]]'); + reject('Like', object, actor); + break; } const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid); @@ -571,6 +745,8 @@ inbox.reject = async (req) => { const queueId = `${type}:${id}:${hostname}`; // stop retrying rejected requests - clearTimeout(activitypub.retryQueue.get(queueId)); - activitypub.retryQueue.delete(queueId); + await Promise.all([ + db.sortedSetRemove('ap:retry:queue', queueId), + db.delete(`ap:retry:queue:${queueId}`), + ]); }; diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 96fcbeac47..85491b27b1 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -3,44 +3,51 @@ const nconf = require('nconf'); const winston = require('winston'); const { createHash, createSign, createVerify, getHashes } = require('crypto'); -const { CronJob } = require('cron'); const request = require('../request'); const db = require('../database'); const meta = require('../meta'); +const categories = require('../categories'); const posts = require('../posts'); const messaging = require('../messaging'); const user = require('../user'); const utils = require('../utils'); const ttl = require('../cache/ttl'); -const lru = require('../cache/lru'); const batch = require('../batch'); -const pubsub = require('../pubsub'); const analytics = require('../analytics'); +const crypto = require('crypto'); const requestCache = ttl({ + name: 'ap-request-cache', max: 5000, ttl: 1000 * 60 * 5, // 5 minutes }); const probeCache = ttl({ + name: 'ap-probe-cache', max: 500, ttl: 1000 * 60 * 60, // 1 hour }); +const probeRateLimit = ttl({ + name: 'ap-probe-rate-limit-cache', + ttl: 1000 * 3, // 3 seconds +}); const ActivityPub = module.exports; ActivityPub._constants = Object.freeze({ uid: -2, publicAddress: 'https://www.w3.org/ns/activitystreams#Public', + acceptablePublicAddresses: ['https://www.w3.org/ns/activitystreams#Public', 'as:Public', 'Public'], acceptableTypes: [ 'application/activity+json', - 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"', ], acceptedPostTypes: [ 'Note', 'Page', 'Article', 'Question', 'Video', ], - acceptableActorTypes: new Set(['Application', 'Group', 'Organization', 'Person', 'Service']), - requiredActorProps: ['inbox', 'outbox'], + acceptableActorTypes: new Set(['Application', 'Organization', 'Person', 'Service']), + acceptableGroupTypes: new Set(['Group']), + requiredActorProps: ['inbox'], acceptedProtocols: ['https', ...(process.env.CI === 'true' ? ['http'] : [])], acceptable: { customFields: new Set(['PropertyValue', 'Link', 'Note']), @@ -58,32 +65,10 @@ ActivityPub.contexts = require('./contexts'); ActivityPub.actors = require('./actors'); ActivityPub.instances = require('./instances'); ActivityPub.feps = require('./feps'); - -ActivityPub.startJobs = () => { - ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); - new CronJob('0 0 * * *', async () => { - if (!meta.config.activitypubEnabled) { - return; - } - try { - await ActivityPub.notes.prune(); - await db.sortedSetsRemoveRangeByScore(['activities:datetime'], '-inf', Date.now() - 604800000); - } catch (err) { - winston.error(err.stack); - } - }, null, true, null, null, false); // change last argument to true for debugging - - new CronJob('*/30 * * * *', async () => { - if (!meta.config.activitypubEnabled) { - return; - } - try { - await ActivityPub.actors.prune(); - } catch (err) { - winston.error(err.stack); - } - }, null, true, null, null, false); // change last argument to true for debugging -}; +ActivityPub.rules = require('./rules'); +ActivityPub.relays = require('./relays'); +ActivityPub.out = require('./out'); +ActivityPub.jobs = require('./jobs'); ActivityPub.resolveId = async (uid, id) => { try { @@ -107,24 +92,61 @@ ActivityPub.resolveInboxes = async (ids) => { if (!meta.config.activitypubAllowLoopback) { ids = ids.filter((id) => { - const { hostname } = new URL(id); - return hostname !== nconf.get('url_parsed').hostname; + try { + const { hostname } = new URL(id); + return hostname !== nconf.get('url_parsed').hostname; + } catch (err) { + winston.error(`[activitypub/resolveInboxes] Invalid id: ${id}`); + return false; + } }); } await ActivityPub.actors.assert(ids); + + // Remove non-asserted targets + const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', ids); + ids = ids.filter((_, idx) => exists[idx]); + await batch.processArray(ids, async (currentIds) => { - const usersData = await user.getUsersFields(currentIds, ['inbox', 'sharedInbox']); - usersData.forEach((u) => { - if (u && (u.sharedInbox || u.inbox)) { - inboxes.add(u.sharedInbox || u.inbox); + const isCategory = await db.exists(currentIds.map(id => `categoryRemote:${id}`)); + const [cids, uids] = currentIds.reduce(([cids, uids], id, idx) => { + const array = isCategory[idx] ? cids : uids; + array.push(id); + return [cids, uids]; + }, [[], []]); + const categoryData = await categories.getCategoriesFields(cids, ['inbox', 'sharedInbox']); + const userData = await user.getUsersFields(uids, ['inbox', 'sharedInbox']); + currentIds.forEach((id) => { + if (cids.includes(id)) { + const data = categoryData[cids.indexOf(id)]; + inboxes.add(data.sharedInbox || data.inbox); + } else if (uids.includes(id)) { + const data = userData[uids.indexOf(id)]; + inboxes.add(data.sharedInbox || data.inbox); } }); }, { batch: 500, }); - return Array.from(inboxes); + let inboxArr = Array.from(inboxes); + + // Filter out blocked instances + const blocked = []; + inboxArr = inboxArr.filter((inbox) => { + const { hostname } = new URL(inbox); + const allowed = ActivityPub.instances.isAllowed(hostname); + if (!allowed) { + blocked.push(inbox); + } + return allowed; + }); + if (blocked.length) { + ActivityPub.helpers.log(`[activitypub/resolveInboxes] Not delivering to blocked instances: ${blocked.join(', ')}`); + } + + return inboxArr; }; ActivityPub.getPublicKey = async (type, id) => { @@ -157,7 +179,7 @@ ActivityPub.getPrivateKey = async (type, id) => { if (type === 'uid') { keyId = `${nconf.get('url')}${id > 0 ? `/uid/${id}` : '/actor'}#key`; } else { - keyId = `${nconf.get('url')}/category/${id}#key`; + keyId = `${nconf.get('url')}${id > 0 ? `/category/${id}` : '/actor'}#key`; } return { key: privateKey, keyId }; @@ -213,49 +235,49 @@ ActivityPub.verify = async (req) => { return false; } - // Break the signature apart - let { keyId, headers, signature, algorithm, created, expires } = req.headers.signature.split(',').reduce((memo, cur) => { - const split = cur.split('="'); - const key = split.shift(); - const value = split.join('="'); - memo[key] = value.slice(0, -1); - return memo; - }, {}); - - const acceptableHashes = getHashes(); - if (algorithm === 'hs2019' || !acceptableHashes.includes(algorithm)) { - algorithm = 'sha256'; - } - - // Re-construct signature string - const signed_string = headers.split(' ').reduce((memo, cur) => { - switch (cur) { - case '(request-target)': { - memo.push(`${cur}: ${String(req.method).toLowerCase()} ${req.baseUrl}${req.path}`); - break; - } - - case '(created)': { - memo.push(`${cur}: ${created}`); - break; - } - - case '(expires)': { - memo.push(`${cur}: ${expires}`); - break; - } - - default: { - memo.push(`${cur}: ${req.headers[cur]}`); - break; - } - } - - return memo; - }, []).join('\n'); - // Verify the signature string via public key try { + // Break the signature apart + let { keyId, headers, signature, algorithm, created, expires } = req.headers.signature.split(',').reduce((memo, cur) => { + const split = cur.split('="'); + const key = split.shift(); + const value = split.join('="'); + memo[key] = value.slice(0, -1); + return memo; + }, {}); + + const acceptableHashes = getHashes(); + if (algorithm === 'hs2019' || !acceptableHashes.includes(algorithm)) { + algorithm = 'sha256'; + } + + // Re-construct signature string + const signed_string = headers.split(' ').reduce((memo, cur) => { + switch (cur) { + case '(request-target)': { + memo.push(`${cur}: ${String(req.method).toLowerCase()} ${req.baseUrl}${req.path}`); + break; + } + + case '(created)': { + memo.push(`${cur}: ${created}`); + break; + } + + case '(expires)': { + memo.push(`${cur}: ${expires}`); + break; + } + + default: { + memo.push(`${cur}: ${req.headers[cur]}`); + break; + } + } + + return memo; + }, []).join('\n'); + // Retrieve public key from remote instance ActivityPub.helpers.log(`[activitypub/verify] Retrieving pubkey for ${keyId}`); const { publicKeyPem } = await ActivityPub.fetchPublicKey(keyId); @@ -277,6 +299,15 @@ ActivityPub.get = async (type, id, uri, options) => { throw new Error('[[error:activitypub.not-enabled]]'); } + const { hostname } = new URL(uri); + const allowed = ActivityPub.instances.isAllowed(hostname); + if (!allowed) { + ActivityPub.helpers.log(`[activitypub/get] Not retrieving ${uri}, domain is blocked.`); + const e = new Error(`[[error:activitypub.get-failed]]`); + e.code = `ap_get_domain_blocked`; + throw e; + } + options = { cache: true, ...options, @@ -313,31 +344,21 @@ ActivityPub.get = async (type, id, uri, options) => { requestCache.set(cacheKey, body); return body; - } catch (e) { - if (String(e.code).startsWith('ap_get_')) { - throw e; + } catch (err) { + if (String(err.code).startsWith('ap_get_')) { + throw err; } // Handle things like non-json body, etc. - const { cause } = e; - throw new Error(`[[error:activitypub.get-failed]]`, { cause }); + throw new Error(`[[error:activitypub.get-failed]]`, { cause: err }); } }; -ActivityPub.retryQueue = lru({ name: 'activitypub-retry-queue', max: 4000, ttl: 1000 * 60 * 60 * 24 * 60 }); - -// handle clearing retry queue from another member of the cluster -pubsub.on(`activitypub-retry-queue:lruCache:del`, (keys) => { - if (Array.isArray(keys)) { - keys.forEach(key => clearTimeout(ActivityPub.retryQueue.get(key))); - } -}); - -async function sendMessage(uri, id, type, payload, attempts = 1) { - const keyData = await ActivityPub.getPrivateKey(type, id); - const headers = await ActivityPub.sign(keyData, uri, payload); - +ActivityPub._sendMessage = async function (uri, id, type, payload) { try { + const keyData = await ActivityPub.getPrivateKey(type, id); + const headers = await ActivityPub.sign(keyData, uri, payload); + const { response, body } = await request.post(uri, { headers: { ...headers, @@ -349,27 +370,17 @@ async function sendMessage(uri, id, type, payload, attempts = 1) { if (String(response.statusCode).startsWith('2')) { ActivityPub.helpers.log(`[activitypub/send] Successfully sent ${payload.type} to ${uri}`); - } else { - if (typeof body === 'object') { - throw new Error(JSON.stringify(body)); - } - throw new Error(String(body)); + return true; } + if (typeof body === 'object') { + throw new Error(JSON.stringify(body)); + } + throw new Error(String(body)); } catch (e) { ActivityPub.helpers.log(`[activitypub/send] Could not send ${payload.type} to ${uri}; error: ${e.message}`); - // add to retry queue - if (attempts < 12) { // stop attempting after ~2 months - const timeout = (4 ** attempts) * 1000; // exponential backoff - const queueId = `${payload.type}:${payload.id}:${new URL(uri).hostname}`; - const timeoutId = setTimeout(() => sendMessage(uri, id, type, payload, attempts + 1), timeout); - ActivityPub.retryQueue.set(queueId, timeoutId); - - ActivityPub.helpers.log(`[activitypub/send] Added ${payload.type} to ${uri} to retry queue for ${timeout}ms`); - } else { - winston.warn(`[activitypub/send] Max attempts reached for ${payload.type} to ${uri}; giving up on sending`); - } + return false; } -} +}; ActivityPub.send = async (type, id, targets, payload) => { if (!meta.config.activitypubEnabled) { @@ -378,14 +389,15 @@ ActivityPub.send = async (type, id, targets, payload) => { ActivityPub.helpers.log(`[activitypub/send] ${payload.id}`); - if (process.env.hasOwnProperty('CI')) { - ActivityPub._sent.set(payload.id, payload); - } - if (!Array.isArray(targets)) { targets = [targets]; } + if (process.env.hasOwnProperty('CI')) { + ActivityPub._sent.set(payload.id, { payload, targets }); + return; + } + const inboxes = await ActivityPub.resolveInboxes(targets); const actor = ActivityPub.helpers.resolveActor(type, id); @@ -396,15 +408,39 @@ ActivityPub.send = async (type, id, targets, payload) => { ...payload, }; - // Runs in background... potentially a better queue is required... later. - batch.processArray( - inboxes, - async inboxBatch => Promise.all(inboxBatch.map(async uri => sendMessage(uri, id, type, payload))), - { - batch: 50, - interval: 100, - }, - ); + const oneMinute = 1000 * 60; + batch.processArray(inboxes, async (inboxBatch) => { + const retryQueueAdd = []; + const retryQueuedSet = []; + + await Promise.all(inboxBatch.map(async (uri) => { + const ok = await ActivityPub._sendMessage(uri, id, type, payload); + if (!ok) { + const queueId = crypto.createHash('sha256').update(`${type}:${id}:${uri}`).digest('hex'); + const nextTryOn = Date.now() + oneMinute; + retryQueueAdd.push(['ap:retry:queue', nextTryOn, queueId]); + retryQueuedSet.push([`ap:retry:queue:${queueId}`, { + queueId, + uri, + id, + type, + attempts: 1, + timestamp: nextTryOn, + payload: JSON.stringify(payload), + }]); + } + })); + + if (retryQueueAdd.length) { + await Promise.all([ + db.sortedSetAddBulk(retryQueueAdd), + db.setObjectBulk(retryQueuedSet), + ]); + } + }, { + batch: 50, + interval: 100, + }).catch(err => winston.error(err.stack)); }; ActivityPub.record = async ({ id, type, actor }) => { @@ -413,27 +449,31 @@ ActivityPub.record = async ({ id, type, actor }) => { await Promise.all([ db.sortedSetAdd(`activities:datetime`, now, id), - db.sortedSetAdd('domains:lastSeen', now, hostname), + ActivityPub.instances.log(hostname), analytics.increment(['activities', `activities:byType:${type}`, `activities:byHost:${hostname}`]), ]); }; -ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { +ActivityPub.buildRecipients = async function (object, options) { /** * - Builds a list of targets for activitypub.send to consume * - Extends to and cc since the activity can be addressed more widely * - Optional parameters: - * - `cid`: includes followers of the passed-in cid (local only) + * - `cid`: includes followers of the passed-in cid (local only, can also be an array) * - `uid`: includes followers of the passed-in uid (local only) * - `pid`: includes post announcers and all topic participants + * - `targets`: boolean; whether to calculate targets (default: true) */ let { to, cc } = object; to = new Set(to); cc = new Set(cc); + let { pid, uid, cid } = options; + options.targets = options.targets ?? true; + let followers = []; if (uid) { - followers = await db.getSortedSetMembers(`followersRemote:${uid}`); + ({ uids: followers } = await ActivityPub.actors.getFollowers(uid)); const followersUrl = `${nconf.get('url')}/uid/${uid}/followers`; if (!to.has(followersUrl)) { cc.add(followersUrl); @@ -441,23 +481,39 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { } if (cid) { - const cidFollowers = await ActivityPub.notes.getCategoryFollowers(cid); - followers = followers.concat(cidFollowers); - const followersUrl = `${nconf.get('url')}/category/${cid}/followers`; - if (!to.has(followersUrl)) { - cc.add(followersUrl); - } + cid = Array.isArray(cid) ? cid : [cid]; + await Promise.all(cid.map(async (cid) => { + const cidFollowers = await ActivityPub.notes.getCategoryFollowers(cid); + followers = followers.concat(cidFollowers); + const followersUrl = `${nconf.get('url')}/category/${cid}/followers`; + if (!to.has(followersUrl)) { + cc.add(followersUrl); + } + })); } - const targets = new Set([...followers, ...to, ...cc]); + let targets = new Set(); + if (options.targets) { + targets = new Set([...followers, ...to, ...cc]); - // Remove any ids that aren't asserted actors - const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', [...targets]); - Array.from(targets).forEach((uri, idx) => { - if (!exists[idx]) { - targets.delete(uri); + // Remove local uris, public addresses, and any ids that aren't asserted actors + targets.forEach((address) => { + if (address.startsWith(nconf.get('url'))) { + targets.delete(address); + } + }); + ActivityPub._constants.acceptablePublicAddresses.forEach((address) => { + targets.delete(address); + }); + if (targets.size) { + const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', [...targets]); + Array.from(targets).forEach((uri, idx) => { + if (!exists[idx]) { + targets.delete(uri); + } + }); } - }); + } // Topic posters, post announcers and their followers if (pid) { @@ -480,6 +536,62 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { }; }; +ActivityPub.checkHeader = async (url, timeout) => { + timeout = timeout || meta.config.activitypubProbeTimeout || 2000; + + try { + const { hostname } = new URL(url); + const { response } = await request.head(url, { + timeout, + }); + const { headers } = response; + + // headers.link = + if (headers && headers.link) { + // Multiple link headers could be combined + const links = headers.link.split(','); + let apLink = false; + + links.forEach((link) => { + let parts = link.split(';'); + const url = parts.shift().match(/<(.+)>/)[1]; + if (!url || apLink) { + return; + } + + parts = parts + .map(p => p.trim()) + .reduce((memo, cur) => { + cur = cur.split('='); + if (cur.length < 2) { + cur.push(''); + } + memo[cur[0]] = cur[1].slice(1, -1); + return memo; + }, {}); + + if (parts.rel === 'alternate' && parts.type === 'application/activity+json') { + apLink = url; + } + }); + + if (apLink) { + const { hostname: compare } = new URL(apLink); + if (hostname !== compare) { + apLink = false; + } + } + + return apLink; + } + + return false; + } catch (e) { + ActivityPub.helpers.log(`[activitypub/checkHeader] Failed on ${url}: ${e.message}`); + return false; + } +}; + ActivityPub.probe = async ({ uid, url }) => { /** * Checks whether a passed-in id or URL is an ActivityPub object and can be mapped to a local representation @@ -487,6 +599,13 @@ ActivityPub.probe = async ({ uid, url }) => { * - Returns a relative path if already available, true if not, and false otherwise. */ + // Disable on config setting; restrict lookups to HTTPS-enabled URLs only + const { activitypubProbe } = meta.config; + const { protocol, host } = new URL(url); + if (!activitypubProbe || protocol !== 'https:' || host === nconf.get('url_parsed').host) { + return false; + } + // Known resources const [isNote, isMessage, isActor, isActorUrl] = await Promise.all([ posts.exists(url), @@ -522,42 +641,35 @@ ActivityPub.probe = async ({ uid, url }) => { } } + // Guests not allowed to use expensive logic path + if (!uid) { + return false; + } + + // One request allowed every 3 seconds (configured at top) + const limited = probeRateLimit.get(uid); + if (limited) { + return false; + } + // Cached result if (probeCache.has(url)) { return probeCache.get(url); } // Opportunistic HEAD - async function checkHeader(timeout) { - const { response } = await request.head(url, { - timeout, - }); - const { headers } = response; - if (headers && headers.link) { - let parts = headers.link.split(';'); - parts.shift(); - parts = parts - .map(p => p.trim()) - .reduce((memo, cur) => { - cur = cur.split('='); - memo[cur[0]] = cur[1].slice(1, -1); - return memo; - }, {}); - - if (parts.rel === 'alternate' && parts.type === 'application/activity+json') { - probeCache.set(url, true); - return true; - } - } - - return false; - } try { - return await checkHeader(meta.config.activitypubProbeTimeout || 2000); + probeRateLimit.set(uid, true); + const probe = await ActivityPub.checkHeader(url).then((result) => { + probeCache.set(url, result); + return !!result; + }); + + return !!probe; } catch (e) { if (e.name === 'TimeoutError') { // Return early but retry for caching purposes - checkHeader(1000 * 60).then((result) => { + ActivityPub.checkHeader(url, 1000 * 60).then((result) => { probeCache.set(url, result); }).catch(err => ActivityPub.helpers.log(err.stack)); return false; diff --git a/src/activitypub/instances.js b/src/activitypub/instances.js index 16765ea553..64502c0998 100644 --- a/src/activitypub/instances.js +++ b/src/activitypub/instances.js @@ -11,7 +11,7 @@ Instances.log = async (domain) => { Instances.getCount = async () => db.sortedSetCard('instances:lastSeen'); -Instances.isAllowed = async (domain) => { +Instances.isAllowed = (domain) => { let { activitypubFilter: type, activitypubFilterList: list } = meta.config; list = new Set(String(list).split('\n')); // eslint-disable-next-line no-bitwise diff --git a/src/activitypub/jobs.js b/src/activitypub/jobs.js new file mode 100644 index 0000000000..57c2e51221 --- /dev/null +++ b/src/activitypub/jobs.js @@ -0,0 +1,114 @@ +'use strict'; + +const winston = require('winston'); +const { CronJob } = require('cron'); + +const db = require('../database'); +const meta = require('../meta'); +const topics = require('../topics'); +const utils = require('../utils'); +const activitypub = module.parent.exports; + +const Jobs = module.exports; + +Jobs.start = () => { + activitypub.helpers.log('[activitypub/jobs] Registering jobs.'); + async function tryCronJob(method) { + if (!meta.config.activitypubEnabled) { + return; + } + try { + await method(); + } catch (err) { + winston.error(err.stack); + } + } + new CronJob('0 0 * * *', async () => { + await tryCronJob(async () => { + await activitypub.notes.prune(); + await db.sortedSetsRemoveRangeByScore(['activities:datetime'], '-inf', Date.now() - 604800000); + }); + }, null, true, null, null, false); // change last argument to true for debugging + + new CronJob('*/30 * * * *', async () => { + await tryCronJob(activitypub.actors.prune); + }, null, true, null, null, false); // change last argument to true for debugging + + new CronJob('0 * * * * *', async () => { + await tryCronJob(retryFailedMessages); + }, null, true, null, null, false); // change last argument to true for debugging + + new CronJob('15 * * * *', async () => { + await tryCronJob(backfill); + }, null, true, null, null, false); // change last argument to true for debugging +}; + +async function retryFailedMessages() { + const queueIds = await db.getSortedSetRangeByScore('ap:retry:queue', 0, 50, '-inf', Date.now()); + const queuedData = (await db.getObjects(queueIds.map(id => `ap:retry:queue:${id}`))); + + const retryQueueAdd = []; + const retryQueuedSet = []; + const queueIdsToRemove = []; + + const oneMinute = 1000 * 60; + await Promise.all(queuedData.map(async (data, index) => { + const queueId = queueIds[index]; + if (!data) { + queueIdsToRemove.push(queueId); + return; + } + + const { uri, id, type, attempts, payload } = data; + if (!uri || !id || !type || !payload || attempts > 10) { + queueIdsToRemove.push(queueId); + return; + } + let payloadObj; + try { + payloadObj = JSON.parse(payload); + } catch (err) { + queueIdsToRemove.push(queueId); + return; + } + const ok = await activitypub._sendMessage(uri, id, type, payloadObj); + if (ok) { + queueIdsToRemove.push(queueId); + } else { + const nextAttempt = (parseInt(attempts, 10) || 0) + 1; + const timeout = (2 ** nextAttempt) * oneMinute; // exponential backoff + const nextTryOn = Date.now() + timeout; + retryQueueAdd.push(['ap:retry:queue', nextTryOn, queueId]); + retryQueuedSet.push([`ap:retry:queue:${queueId}`, { + attempts: nextAttempt, + timestamp: nextTryOn, + }]); + } + })); + + await Promise.all([ + db.sortedSetAddBulk(retryQueueAdd), + db.setObjectBulk(retryQueuedSet), + db.sortedSetRemove('ap:retry:queue', queueIdsToRemove), + db.deleteAll(queueIdsToRemove.map(id => `ap:retry:queue:${id}`)), + ]); +} + +async function backfill() { + const start = 0; + const stop = meta.config.topicsPerPage - 1; + const sorted = await topics.getSortedTopics({ + term: 'day', + sort: 'posts', + uid: 0, + start, + stop, + }); + + // Remote mainPids only + const pids = sorted.topics + .map(({ mainPid }) => mainPid) + .filter(pid => !utils.isNumber(pid)); + + await activitypub.notes.backfill(pids); +} diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index b503887f1b..5d423e5625 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -5,6 +5,7 @@ const mime = require('mime'); const path = require('path'); const validator = require('validator'); const sanitize = require('sanitize-html'); +const tokenizer = require('sbd'); const db = require('../database'); const user = require('../user'); @@ -12,6 +13,7 @@ const categories = require('../categories'); const posts = require('../posts'); const topics = require('../topics'); const messaging = require('../messaging'); +const privileges = require('../privileges'); const plugins = require('../plugins'); const slugify = require('../slugify'); const translator = require('../translator'); @@ -19,8 +21,6 @@ const utils = require('../utils'); const accountHelpers = require('../controllers/accounts/helpers'); -const isEmojiShortcode = /^:[\w]+:$/; - const activitypub = module.parent.exports; const Mocks = module.exports; @@ -32,6 +32,7 @@ const sanitizeConfig = { allowedTags: sanitize.defaults.allowedTags.concat(['img', 'picture', 'source']), allowedClasses: { '*': [], + 'p': ['quote-inline'], }, allowedAttributes: { a: ['href', 'rel'], @@ -42,7 +43,7 @@ const sanitizeConfig = { Mocks._normalize = async (object) => { // Normalized incoming AP objects into expected types for easier mocking - let { attributedTo, url, image, content, source } = object; + let { type, attributedTo, url, image, mediaType, content, source, attachment, cc } = object; switch (true) { // non-string attributedTo handling case Array.isArray(attributedTo): { @@ -52,6 +53,10 @@ Mocks._normalize = async (object) => { } else if (typeof cur === 'object') { if (cur.type === 'Person' && cur.id) { valid.push(cur.id); + } else if (cur.type === 'Group' && cur.id) { + // Add any groups found to cc where it is expected + cc = Array.isArray(cc) ? cc : [cc]; + cc.push(cur.id); } } @@ -70,6 +75,9 @@ Mocks._normalize = async (object) => { if (sourceContent) { content = null; sourceContent = await activitypub.helpers.remoteAnchorToLocalProfile(sourceContent, true); + } else if (mediaType === 'text/markdown') { + sourceContent = await activitypub.helpers.remoteAnchorToLocalProfile(content, true); + content = null; } else if (content && content.length) { content = sanitize(content, sanitizeConfig); content = await activitypub.helpers.remoteAnchorToLocalProfile(content); @@ -77,7 +85,7 @@ Mocks._normalize = async (object) => { content = 'This post did not contain any content.'; } - switch (true) { + switch (true) { // image handling case image && image.hasOwnProperty('url') && !!image.url: { image = image.url; break; @@ -94,7 +102,8 @@ Mocks._normalize = async (object) => { } if (image) { const parsed = new URL(image); - if (!mime.getType(parsed.pathname).startsWith('image/')) { + const type = mime.getType(parsed.pathname); + if (!type || !type.startsWith('image/')) { activitypub.helpers.log(`[activitypub/mocks.post] Received image not identified as image due to MIME type: ${image}`); image = null; } @@ -102,6 +111,30 @@ Mocks._normalize = async (object) => { if (url) { // Handle url array if (Array.isArray(url)) { + // Special handling for Video type (from PeerTube specifically) + if (type === 'Video') { + const stream = url.reduce((memo, { type, mediaType, tag }) => { + if (!memo) { + if (type === 'Link' && mediaType === 'application/x-mpegURL') { + memo = tag.reduce((memo, { type, mediaType, href, width, height }) => { + if (!memo && (type === 'Link' && mediaType === 'video/mp4')) { + memo = { mediaType, href, width, height }; + } + + return memo; + }, null); + } + } + + return memo; + }, null); + + if (stream) { + attachment = attachment || []; + attachment.push(stream); + } + } + url = url.reduce((valid, cur) => { if (typeof cur === 'string') { valid.push(cur); @@ -121,15 +154,17 @@ Mocks._normalize = async (object) => { return { ...object, + cc, attributedTo, content, sourceContent, image, url, + attachment, }; }; -Mocks.profile = async (actors, hostMap) => { +Mocks.profile = async (actors) => { // Should only ever be called by activitypub.actors.assert const profiles = await Promise.all(actors.map(async (actor) => { if (!actor) { @@ -137,7 +172,7 @@ Mocks.profile = async (actors, hostMap) => { } const uid = actor.id; - let hostname = hostMap.get(uid); + let hostname; let { url, preferredUsername, published, icon, image, name, summary, followers, inbox, endpoints, tag, @@ -145,12 +180,10 @@ Mocks.profile = async (actors, hostMap) => { preferredUsername = slugify(preferredUsername || name); const { followers: followerCount, following: followingCount } = await activitypub.actors.getLocalFollowCounts(uid); - if (!hostname) { // if not available via webfinger, infer from id - try { - ({ hostname } = new URL(actor.id)); - } catch (e) { - return null; - } + try { + ({ hostname } = new URL(actor.id)); + } catch (e) { + return null; } let picture; @@ -160,17 +193,7 @@ Mocks.profile = async (actors, hostMap) => { const iconBackgrounds = await user.getIconBackgrounds(); let bgColor = Array.prototype.reduce.call(preferredUsername, (cur, next) => cur + next.charCodeAt(), 0); bgColor = iconBackgrounds[bgColor % iconBackgrounds.length]; - - // Replace emoji in summary - if (tag && Array.isArray(tag)) { - tag - .filter(tag => tag.type === 'Emoji' && - isEmojiShortcode.test(tag.name) && - tag.icon && tag.icon.mediaType && tag.icon.mediaType.startsWith('image/')) - .forEach((tag) => { - summary = summary.replace(new RegExp(tag.name, 'g'), ``); - }); - } + summary = activitypub.helpers.renderEmoji(summary || '', tag); // Add custom fields into user hash const customFields = actor.attachment && Array.isArray(actor.attachment) && actor.attachment.length ? @@ -218,7 +241,7 @@ Mocks.profile = async (actors, hostMap) => { uploadedpicture: undefined, 'cover:url': !image || typeof image === 'string' ? image : image.url, 'cover:position': '50% 50%', - aboutme: summary, + aboutme: posts.sanitize(summary), followerCount, followingCount, @@ -235,6 +258,76 @@ Mocks.profile = async (actors, hostMap) => { return profiles; }; +Mocks.category = async (actors) => { + const categories = await Promise.all(actors.map(async (actor) => { + if (!actor) { + return null; + } + + const cid = actor.id; + let hostname; + let { + url, preferredUsername, icon, /* image, */ + name, summary, followers, inbox, endpoints, tag, + postingRestrictedToMods, + } = actor; + preferredUsername = slugify(preferredUsername || name); + /* + const { + followers: followerCount, + following: followingCount + } = await activitypub.actors.getLocalFollowCounts(uid); + */ + + try { + ({ hostname } = new URL(actor.id)); + } catch (e) { + return null; + } + + // No support for category avatars yet ;( + // let picture; + // if (image) { + // picture = typeof image === 'string' ? image : image.url; + // } + const iconBackgrounds = await user.getIconBackgrounds(); + let bgColor = Array.prototype.reduce.call(preferredUsername, (cur, next) => cur + next.charCodeAt(), 0); + bgColor = iconBackgrounds[bgColor % iconBackgrounds.length]; + + const backgroundImage = !icon || typeof icon === 'string' ? icon : icon.url; + + const payload = { + cid, + name, + handle: `${preferredUsername}@${hostname}`, + slug: `${preferredUsername}@${hostname}`, + description: summary, + descriptionParsed: posts.sanitize(activitypub.helpers.renderEmoji(summary || '', tag)), + icon: backgroundImage ? 'fa-nbb-none' : 'fa-comments', + color: '#fff', + bgColor, + backgroundImage, + imageClass: 'cover', + numRecentReplies: 1, + // followerCount, + // followingCount, + + url, + inbox, + sharedInbox: endpoints ? endpoints.sharedInbox : null, + followersUrl: followers, + + _activitypub: { + postingRestrictedToMods, + }, + }; + + return payload; + })); + + return categories; +}; + Mocks.post = async (objects) => { let single = false; if (!Array.isArray(objects)) { @@ -258,7 +351,7 @@ Mocks.post = async (objects) => { attributedTo: uid, inReplyTo: toPid, published, updated, name, content, sourceContent, - type, to, cc, audience, attachment, tag, image, + to, cc, audience, attachment, tag, image, } = object; await activitypub.actors.assert(uid); @@ -272,21 +365,17 @@ Mocks.post = async (objects) => { let edited = new Date(updated); edited = Number.isNaN(edited.valueOf()) ? undefined : edited; - if (type === 'Video') { - attachment = attachment || []; - attachment.push({ url }); - } - const payload = { uid, pid, // tid, --> purposely omitted - name, content, sourceContent, timestamp, toPid, + title: name, // used in post.edit + edited, editor: edited ? uid : undefined, _activitypub: { to, cc, audience, attachment, tag, url, image }, @@ -304,8 +393,11 @@ Mocks.message = async (object) => { const message = { mid: object.id, uid: object.attributedTo, - content: object.content, - // ip: caller.ip, + content: object.sourceContent || object.content, + + _activitypub: { + attachment: object.attachment, + }, }; return message; @@ -380,45 +472,54 @@ Mocks.actors.user = async (uid) => { }); return { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - ], - id: `${nconf.get('url')}/uid/${uid}`, - url: `${nconf.get('url')}/user/${userslug}`, - followers: `${nconf.get('url')}/uid/${uid}/followers`, - following: `${nconf.get('url')}/uid/${uid}/following`, - inbox: `${nconf.get('url')}/uid/${uid}/inbox`, - outbox: `${nconf.get('url')}/uid/${uid}/outbox`, + ...{ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + ], + id: `${nconf.get('url')}/uid/${uid}`, + url: `${nconf.get('url')}/user/${userslug}`, + followers: `${nconf.get('url')}/uid/${uid}/followers`, + following: `${nconf.get('url')}/uid/${uid}/following`, + inbox: `${nconf.get('url')}/uid/${uid}/inbox`, + outbox: `${nconf.get('url')}/uid/${uid}/outbox`, - type: 'Person', - name: username !== displayname ? fullname : username, // displayname is escaped, fullname is not - preferredUsername: userslug, - summary: aboutmeParsed, - icon: picture, - image: cover, - published: new Date(joindate).toISOString(), - attachment, + type: 'Person', + name: username !== displayname ? fullname : username, // displayname is escaped, fullname is not + preferredUsername: userslug, + summary: aboutmeParsed, + published: new Date(joindate).toISOString(), + attachment, - publicKey: { - id: `${nconf.get('url')}/uid/${uid}#key`, - owner: `${nconf.get('url')}/uid/${uid}`, - publicKeyPem: publicKey, - }, - - endpoints: { - sharedInbox: `${nconf.get('url')}/inbox`, + publicKey: { + id: `${nconf.get('url')}/uid/${uid}#key`, + owner: `${nconf.get('url')}/uid/${uid}`, + publicKeyPem: publicKey, + }, + + endpoints: { + sharedInbox: `${nconf.get('url')}/inbox`, + }, }, + ...(picture && { icon: picture }), + ...(cover && { image: cover }), }; }; Mocks.actors.category = async (cid) => { - let { - name, handle: preferredUsername, slug, - descriptionParsed: summary, federatedDescription, backgroundImage, - } = await categories.getCategoryFields(cid, - ['name', 'handle', 'slug', 'description', 'descriptionParsed', 'federatedDescription', 'backgroundImage']); - const publicKey = await activitypub.getPublicKey('cid', cid); + const [ + { + name, handle: preferredUsername, slug, + descriptionParsed: summary, backgroundImage, + }, + publicKey, + canPost, + ] = await Promise.all([ + categories.getCategoryFields(cid, + ['name', 'handle', 'slug', 'description', 'descriptionParsed', 'backgroundImage']), + activitypub.getPublicKey('cid', cid), + privileges.categories.can('topics:create', cid, -2), + ]); let icon; if (backgroundImage) { @@ -438,14 +539,11 @@ Mocks.actors.category = async (cid) => { }; } - // Append federated desc. - const fallback = await translator.translate('[[admin/manage/categories:federatedDescription.default]]'); - summary += `

${federatedDescription || fallback}

\n`; - return { '@context': [ 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', + 'https://join-lemmy.org/context.json', ], id: `${nconf.get('url')}/category/${cid}`, url: `${nconf.get('url')}/category/${slug}`, @@ -455,11 +553,12 @@ Mocks.actors.category = async (cid) => { outbox: `${nconf.get('url')}/category/${cid}/outbox`, type: 'Group', - name, + name: utils.decodeHTMLEntities(name), preferredUsername, - summary, + summary: utils.decodeHTMLEntities(summary), // image, // todo once categories have cover photos icon, + postingRestrictedToMods: !canPost, publicKey: { id: `${nconf.get('url')}/category/${cid}#key`, @@ -492,7 +591,6 @@ Mocks.notes.public = async (post) => { const published = post.timestampISO; const updated = post.edited ? post.editedISO : null; - // todo: post visibility const to = new Set([activitypub._constants.publicAddress]); const cc = new Set([`${nconf.get('url')}/uid/${post.user.uid}/followers`]); @@ -500,7 +598,7 @@ Mocks.notes.public = async (post) => { let tag = null; let followersUrl; - let name = null; + let name; ({ titleRaw: name } = await topics.getTopicFields(post.tid, ['title'])); if (post.toPid) { // direct reply @@ -585,27 +683,7 @@ Mocks.notes.public = async (post) => { } let attachment = await posts.attachments.get(post.pid) || []; - const uploads = await posts.uploads.listWithSizes(post.pid); - uploads.forEach(({ name, width, height }) => { - const mediaType = mime.getType(name); - const url = `${nconf.get('url') + nconf.get('upload_url')}/${name}`; - attachment.push({ mediaType, url, width, height }); - }); - - // Inspect post content for external imagery as well - let match = posts.imgRegex.exec(post.content); - while (match !== null) { - if (match[1]) { - const { hostname, pathname, href: url } = new URL(match[1]); - if (hostname !== nconf.get('url_parsed').hostname) { - const mediaType = mime.getType(pathname); - attachment.push({ mediaType, url }); - } - } - match = posts.imgRegex.exec(post.content); - } - - attachment = attachment.map(({ mediaType, url, width, height }) => { + const normalizeAttachment = attachment => attachment.map(({ mediaType, url, width, height }) => { let type; switch (true) { @@ -630,6 +708,73 @@ Mocks.notes.public = async (post) => { return payload; }); + // Special handling for main posts (as:Article w/ as:Note preview) + const plaintext = posts.sanitizePlaintext(content); + const isArticle = post.pid === post.topic.mainPid && plaintext.length > 500; + + if (post.isMainPost) { + const thumbs = await topics.thumbs.get(post.tid); + thumbs.forEach(({ name, path }) => { + const mediaType = mime.getType(name); + const url = `${nconf.get('url') + nconf.get('upload_url')}${path}`; + attachment.push({ mediaType, url }); + }); + } else { + const uploads = await posts.uploads.listWithSizes(post.pid); + uploads.forEach(({ name, width, height }) => { + const mediaType = mime.getType(name); + const url = `${nconf.get('url') + nconf.get('upload_url')}${name}`; + attachment.push({ mediaType, url, width, height }); + }); + } + + // Inspect post content for external imagery as well + let match = posts.imgRegex.exec(post.content); + while (match !== null) { + if (match[1]) { + const { hostname, pathname, href: url } = new URL(match[1]); + if (hostname !== nconf.get('url_parsed').hostname) { + const mediaType = mime.getType(pathname); + attachment.push({ mediaType, url }); + } + } + match = posts.imgRegex.exec(post.content); + } + + attachment = normalizeAttachment(attachment); + const image = attachment.filter(entry => entry.type === 'Image')?.shift(); + let preview; + let summary = null; + if (isArticle) { + preview = { + type: 'Note', + attributedTo: `${nconf.get('url')}/uid/${post.user.uid}`, + content: post.content, + published, + attachment, + }; + + const sentences = tokenizer.sentences(post.content, { newline_boundaries: true }); + // Append sentences to summary until it contains just under 500 characters of content + const limit = 500; + let remaining = limit; + summary = sentences.reduce((memo, sentence) => { + const clean = sanitize(sentence, { + allowedTags: [], + allowedAttributes: {}, + }); + remaining = remaining - clean.length; + if (remaining > 0) { + memo += ` ${sentence}`; + } + + return memo; + }, ''); + + // Final sanitization to clean up tags + summary = posts.sanitize(summary); + } + let context = await posts.getPostField(post.pid, 'context'); context = context || `${nconf.get('url')}/topic/${post.topic.tid}`; @@ -637,18 +782,20 @@ Mocks.notes.public = async (post) => { * audience is exposed as part of 1b12 but is now ignored by Lemmy. * Remove this and most references to audience in 2026. */ - let audience = `${nconf.get('url')}/category/${post.category.cid}`; // default + let audience = utils.isNumber(post.category.cid) ? // default + `${nconf.get('url')}/category/${post.category.cid}` : post.category.cid; if (inReplyTo) { const chain = await activitypub.notes.getParentChain(post.uid, inReplyTo); chain.forEach((post) => { audience = post.audience || audience; }); } + to.add(audience); let object = { '@context': 'https://www.w3.org/ns/activitystreams', id, - type: 'Note', + type: isArticle ? 'Article' : 'Note', to: Array.from(to), cc: Array.from(cc), inReplyTo, @@ -658,12 +805,14 @@ Mocks.notes.public = async (post) => { attributedTo: `${nconf.get('url')}/uid/${post.user.uid}`, context, audience, - summary: null, + summary, name, + preview, content: post.content, source, tag, attachment, + image, replies: `${id}/replies`, }; @@ -690,6 +839,13 @@ Mocks.notes.private = async ({ messageObj }) => { const published = messageObj.timestampISO; const updated = messageObj.edited ? messageObj.editedISO : undefined; + const content = await messaging.getMessageField(messageObj.mid, 'content'); + messageObj.content = content; // re-send raw content into parsePost + const parsed = await posts.parsePost(messageObj, 'activitypub.note'); + messageObj.content = sanitize(parsed.content, sanitizeConfig); + messageObj.content = posts.relativeToAbsolute(messageObj.content, posts.urlRegex); + messageObj.content = posts.relativeToAbsolute(messageObj.content, posts.imgRegex); + let source; const markdownEnabled = await plugins.isActive('nodebb-plugin-markdown'); if (markdownEnabled) { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index b47730ffc7..d8fb26bfbb 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -2,6 +2,8 @@ const winston = require('winston'); const nconf = require('nconf'); +const tokenizer = require('sbd'); +const pretty = require('pretty'); const db = require('../database'); const batch = require('../batch'); @@ -13,19 +15,44 @@ const notifications = require('../notifications'); const user = require('../user'); const topics = require('../topics'); const posts = require('../posts'); +const api = require('../api'); +const ttlCache = require('../cache/ttl'); +const websockets = require('../socket.io'); const utils = require('../utils'); const activitypub = module.parent.exports; const Notes = module.exports; -async function lock(value) { - const count = await db.incrObjectField('locks', value); - return count <= 1; -} +const backfillCache = ttlCache({ + name: 'ap-backfill-cache', + max: 500, + ttl: 1000 * 60 * 2, // 2 minutes +}); -async function unlock(value) { - await db.deleteObjectField('locks', value); -} +Notes._normalizeTags = async (tag, cid) => { + const systemTags = (meta.config.systemTags || '').split(','); + const maxTags = await categories.getCategoryField(cid, 'maxTags'); + let tags = tag || []; + + if (!Array.isArray(tags)) { // the "|| []" should handle null/undefined values... #famouslastwords + tags = [tags]; + } + + tags = tags + .filter(({ type }) => type === 'Hashtag') + .map((tag) => { + tag.name = tag.name.startsWith('#') ? tag.name.slice(1) : tag.name; + return tag; + }) + .filter(({ name }) => !systemTags.includes(name)) + .map(t => t.name); + + if (tags.length > maxTags) { + tags.length = maxTags; + } + + return tags; +}; Notes.assert = async (uid, input, options = { skipChecks: false }) => { /** @@ -37,197 +64,258 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { return null; } - const id = !activitypub.helpers.isUri(input) ? input.id : input; - const lockStatus = await lock(id, '[[error:activitypub.already-asserting]]'); + let id = !activitypub.helpers.isUri(input) ? input.id : input; + + let lockStatus = await db.incrObjectField('locks', id); + lockStatus = lockStatus <= 1; if (!lockStatus) { // unable to achieve lock, stop processing. + winston.warn(`[activitypub/notes.assert] Unable to acquire lock, skipping processing of ${id}`); return null; } - let chain; - let context = await activitypub.contexts.get(uid, id); - if (context.tid) { - unlock(id); - const { tid } = context; - return { tid, count: 0 }; - } else if (context.context) { - chain = Array.from(await activitypub.contexts.getItems(uid, context.context, { input })); - if (chain && chain.length) { - // Context resolves, use in later topic creation - context = context.context; - } - } else { - context = undefined; - } - - if (!chain || !chain.length) { - // Fall back to inReplyTo traversal on context retrieval failure - chain = Array.from(await Notes.getParentChain(uid, input)); - chain.reverse(); - } - - // Can't resolve — give up. - if (!chain.length) { - unlock(id); - return null; - } - - // Reorder chain items by timestamp - chain = chain.sort((a, b) => a.timestamp - b.timestamp); - - const mainPost = chain[0]; - let { pid: mainPid, tid, uid: authorId, timestamp, name, content, sourceContent, _activitypub } = mainPost; - const hasTid = !!tid; - - const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; - if (options.cid && cid === -1) { - // Move topic if currently uncategorized - await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); - } - - const members = await db.isSortedSetMembers(`tid:${tid}:posts`, chain.slice(1).map(p => p.pid)); - members.unshift(await posts.exists(mainPid)); - if (tid && members.every(Boolean)) { - // All cached, return early. - activitypub.helpers.log('[notes/assert] No new notes to process.'); - unlock(id); - return { tid, count: 0 }; - } - - let title; - if (hasTid) { - mainPid = await topics.getTopicField(tid, 'mainPid'); - } else { - // Check recipients/audience for local category - const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); - const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id))); - const recipientCids = resolved - .filter(Boolean) - .filter(({ type }) => type === 'category') - .map(obj => obj.id); - if (recipientCids.length) { - // Overrides passed-in value, respect addressing from main post over booster - options.cid = recipientCids.shift(); + try { + if (!(options.skipChecks || process.env.hasOwnProperty('CI'))) { + id = (await activitypub.checkHeader(id)) || id; } - // mainPid ok to leave as-is - title = name || activitypub.helpers.generateTitle(utils.decodeHTMLEntities(content || sourceContent)); + let chain; + let context = await activitypub.contexts.get(uid, id); + if (context.tid) { + const { tid } = context; + return { tid, count: 0 }; + } else if (context.context) { + chain = Array.from(await activitypub.contexts.getItems(uid, context.context, { input })); + if (chain && chain.length) { + // Context resolves, use in later topic creation + context = context.context; + } + } else { + context = undefined; + } - // Remove any custom emoji from title - if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { - _activitypub.tag - .filter(tag => tag.type === 'Emoji') - .forEach((tag) => { - title = title.replace(new RegExp(tag.name, 'g'), ''); + if (!chain || !chain.length) { + // Fall back to inReplyTo traversal on context retrieval failure + chain = Array.from(await Notes.getParentChain(uid, input)); + chain.reverse(); + } + + // Can't resolve — give up. + if (!chain.length) { + return null; + } + + // Reorder chain items by timestamp + chain = chain.sort((a, b) => a.timestamp - b.timestamp); + + const mainPost = chain[0]; + let { pid: mainPid, tid, uid: authorId, timestamp, title, content, sourceContent, _activitypub } = mainPost; + const hasTid = !!tid; + + const authorBanned = await user.bans.isBanned(authorId); + if (!hasTid && authorBanned) { // New topics only + activitypub.helpers.log('[notes/assert] OP is banned, not asserting topic.'); + return null; + } + + const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; + let crosspostCid = false; + + if (options.cid && cid === -1) { + // Move topic if currently uncategorized + await api.topics.move({ uid: 'system' }, { tid, cid: options.cid }); + } + + const exists = await posts.exists(chain.map(p => p.pid)); + if (tid && exists.every(Boolean)) { + // All cached, return early. + activitypub.helpers.log('[notes/assert] No new notes to process.'); + return { tid, count: 0 }; + } + + let generatedTitle; + if (hasTid) { + mainPid = await topics.getTopicField(tid, 'mainPid'); + } else { + // Check recipients/audience for category (local or remote) + const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); + await activitypub.actors.assert(Array.from(set)); + + // Local + const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id))); + const recipientCids = resolved + .filter(Boolean) + .filter(({ type }) => type === 'category') + .map(obj => obj.id); + + // Remote + let remoteCid; + const assertedGroups = await categories.exists(Array.from(set)); + try { + const { hostname } = new URL(mainPid); + remoteCid = Array.from(set).filter((id, idx) => { + const { hostname: cidHostname } = new URL(id); + const explicitAudience = Array.isArray(_activitypub.audience) ? + _activitypub.audience.includes(id) : + _activitypub.audience === id; + + return assertedGroups[idx] && (explicitAudience || cidHostname === hostname); + }).shift(); + } catch (e) { + // noop + winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); + } + + if (remoteCid || recipientCids.length) { + // Overrides passed-in value, respect addressing from main post over booster + options.cid = remoteCid || recipientCids.shift(); + } + + // Auto-categorization (takes place only if all other categorization efforts fail) + crosspostCid = await assignCategory(mainPost); + if (!options.cid) { + options.cid = crosspostCid; + crosspostCid = false; + } + + // mainPid ok to leave as-is + if (!title) { + let prettified = pretty(content || sourceContent); + + // Remove any lines that contain quote-post fallbacks + prettified = prettified.split('\n').filter(line => !line.startsWith('

n.pid).indexOf(id); + const hasRelation = + uid || hasTid || + options.skipChecks || options.cid || + await assertRelation(chain[inputIndex !== -1 ? inputIndex : 0]); + + const privilege = `topics:${tid ? 'reply' : 'create'}`; + const allowed = await privileges.categories.can(privilege, options.cid || cid, activitypub._constants.uid); + if (!hasRelation || !allowed) { + if (!hasRelation) { + activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`); + } + + return null; + } + + tid = tid || utils.generateUUID(); + mainPost.tid = tid; + + const urlMap = chain.reduce((map, post) => (post.url ? map.set(post.url, post.id) : map), new Map()); + let unprocessed = chain.map((post) => { + post.tid = tid; // add tid to post hash + + // Ensure toPids in replies are ids + if (urlMap.has(post.toPid)) { + post.toPid = urlMap.get(post.toPid); + } + + return post; + }).filter((p, idx) => !exists[idx]); + const count = unprocessed.length; + activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); + + if (!hasTid) { + const { to, cc } = mainPost._activitypub; + const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); + + try { + await topics.post({ + tid, + uid: authorId, + cid: options.cid || cid, + pid: mainPid, + title, + timestamp, + tags, + content: mainPost.content, + sourceContent: mainPost.sourceContent, + generatedTitle, + _activitypub: mainPost._activitypub, }); - } - } - mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; + unprocessed.shift(); + } catch (e) { + activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); + return null; + } - // Relation & privilege check for local categories - const inputIndex = chain.map(n => n.pid).indexOf(id); - const hasRelation = - uid || hasTid || - options.skipChecks || options.cid || - await assertRelation(chain[inputIndex !== -1 ? inputIndex : 0]); - const privilege = `topics:${tid ? 'reply' : 'create'}`; - const allowed = await privileges.categories.can(privilege, cid, activitypub._constants.uid); - if (!hasRelation || !allowed) { - if (!hasRelation) { - activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`); - } - - unlock(id); - return null; - } - - tid = tid || utils.generateUUID(); - mainPost.tid = tid; - - const urlMap = chain.reduce((map, post) => (post.url ? map.set(post.url, post.id) : map), new Map()); - const unprocessed = chain.map((post) => { - post.tid = tid; // add tid to post hash - - // Ensure toPids in replies are ids - if (urlMap.has(post.toPid)) { - post.toPid = urlMap.get(post.toPid); - } - - return post; - }).filter((p, idx) => !members[idx]); - const count = unprocessed.length; - activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); - - let tags; - if (!hasTid) { - const { to, cc, attachment } = mainPost._activitypub; - const systemTags = (meta.config.systemTags || '').split(','); - const maxTags = await categories.getCategoryField(cid, 'maxTags'); - tags = (mainPost._activitypub.tag || []) - .map((tag) => { - tag.name = tag.name.startsWith('#') ? tag.name.slice(1) : tag.name; - return tag; - }) - .filter(o => o.type === 'Hashtag' && !systemTags.includes(o.name)) - .map(t => t.name); - - if (tags.length > maxTags) { - tags.length = maxTags; - } - - await Promise.all([ - topics.post({ - tid, - uid: authorId, - cid: options.cid || cid, - pid: mainPid, - title, - timestamp, - tags, - content: mainPost.content, - sourceContent: mainPost.sourceContent, - _activitypub: mainPost._activitypub, - }), - Notes.updateLocalRecipients(mainPid, { to, cc }), - ]); - unprocessed.shift(); - - // These must come after topic is posted - await Promise.all([ - mainPost._activitypub.image ? topics.thumbs.associate({ - id: tid, - path: mainPost._activitypub.image, - }) : null, - posts.attachments.update(mainPid, attachment), - ]); - - if (context) { - activitypub.helpers.log(`[activitypub/notes.assert] Associating tid ${tid} with context ${context}`); - await topics.setTopicField(tid, 'context', context); - } - } - - for (const post of unprocessed) { - const { to, cc, attachment } = post._activitypub; - - try { - // eslint-disable-next-line no-await-in-loop - await topics.reply(post); - // eslint-disable-next-line no-await-in-loop + // These must come after topic is posted await Promise.all([ - Notes.updateLocalRecipients(post.pid, { to, cc }), - posts.attachments.update(post.pid, attachment), + Notes.updateLocalRecipients(mainPid, { to, cc }), + mainPost._activitypub.image ? topics.thumbs.associate({ + id: tid, + path: mainPost._activitypub.image, + }) : null, ]); - } catch (e) { - activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); + + if (context) { + activitypub.helpers.log(`[activitypub/notes.assert] Associating tid ${tid} with context ${context}`); + await topics.setTopicField(tid, 'context', context); + } } + + const uids = Array.from(unprocessed.reduce((uids, post) => { + uids.add(post.uid); + return uids; + }, new Set())); + const isBanned = await user.bans.isBanned(uids); + const banned = uids.filter((_, idx) => isBanned[idx]); + unprocessed = unprocessed.filter(post => !banned.includes(post.uid)); + + let added = []; + await Promise.all(unprocessed.map(async (post) => { + const { to, cc } = post._activitypub; + + try { + const postData = await topics.reply(post); + added.push(postData); + await Notes.updateLocalRecipients(post.pid, { to, cc }); + } catch (e) { + activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); + } + })); + + if (added.length) { + // Because replies are added in parallel, `index` is calculated incorrectly + const indices = await posts.getPostIndices(added, uid); + added = added.map((post, idx) => { + post.index = indices[idx]; + return post; + }); + websockets.in(`topic_${tid}`).emit('event:new_post', { posts: added }); + } + + await Notes.syncUserInboxes(tid, uid); + + if (crosspostCid) { + await topics.crossposts.add(tid, crosspostCid, 0); + } + + if (!hasTid && uid && options.cid) { + // New topic, have category announce it + await activitypub.out.announce.topic(tid); + } + + return { tid, count }; + } catch (e) { + winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}).`); + return null; + } finally { + winston.verbose(`[activitypub/notes.assert] Releasing lock (${id})`); + await db.deleteObjectField('locks', id); } - - await Promise.all([ - Notes.syncUserInboxes(tid, uid), - unlock(id), - ]); - - return { tid, count }; }; Notes.assertPrivate = async (object) => { @@ -289,6 +377,17 @@ Notes.assertPrivate = async (object) => { const payload = await activitypub.mocks.message(object); + // Naive image appending (using src/posts/attachments.js is likely better, but not worth the effort) + const attachments = payload._activitypub.attachment; + if (attachments && Array.isArray(attachments)) { + const images = attachments.filter((attachment) => { + return attachment.mediaType.startsWith('image/'); + }).map(({ url, href }) => url || href); + images.forEach((url) => { + payload.content += `

`; + }); + } + try { await messaging.checkContent(payload.content, false); } catch (e) { @@ -359,6 +458,39 @@ async function assertRelation(post) { return followers > 0 || uids.length; } +async function assignCategory(post) { + activitypub.helpers.log('[activitypub] Checking auto-categorization rules.'); + let cid = undefined; + const rules = await activitypub.rules.list(); + let tags = await Notes._normalizeTags(post._activitypub.tag || []); + tags = tags.map(tag => tag.toLowerCase()); + + cid = rules.reduce((cid, { type, value, cid: target }) => { + if (!cid) { + switch (type) { + case 'hashtag': { + if (tags.includes(value.toLowerCase())) { + activitypub.helpers.log(`[activitypub] - Rule match: #${value}; cid: ${target}`); + return target; + } + break; + } + + case 'user': { + if (post.uid === value) { + activitypub.helpers.log(`[activitypub] - Rule match: user ${value}; cid: ${target}`); + return target; + } + } + } + } + + return cid; + }, cid); + + return cid; +} + Notes.updateLocalRecipients = async (id, { to, cc }) => { const recipients = new Set([...(to || []), ...(cc || [])]); const uids = new Set(); @@ -371,7 +503,7 @@ Notes.updateLocalRecipients = async (id, { to, cc }) => { const followedUid = await db.getObjectField('followersUrl:uid', recipient); if (followedUid) { - const { uids: followers } = await activitypub.actors.getLocalFollowers(followedUid); + const { uids: followers } = await activitypub.actors.getFollowers(followedUid); if (followers.size > 0) { followers.forEach((uid) => { uids.add(uid); @@ -398,7 +530,7 @@ Notes.getParentChain = async (uid, input) => { } const postData = await posts.getPostData(id); - if (postData) { + if (postData && postData.pid) { chain.add(postData); if (postData.toPid) { await traverse(uid, postData.toPid); @@ -454,6 +586,12 @@ Notes.syncUserInboxes = async function (tid, uid) { uids.add(uid); }); + // Category followers + const categoryFollowers = await activitypub.actors.getFollowers(cid); + categoryFollowers.uids.forEach((uid) => { + uids.add(uid); + }); + const keys = Array.from(uids).map(uid => `uid:${uid}:inbox`); const score = await db.sortedSetScore(`cid:${cid}:tids`, tid); @@ -477,8 +615,29 @@ Notes.getCategoryFollowers = async (cid) => { return uids; }; +Notes.backfill = async (pids) => { + if (!Array.isArray(pids)) { + pids = [pids]; + } + + return Promise.all(pids.map(async (pid) => { + if (backfillCache.has(pid)) { + return; + } + + await Notes.assert(0, pid, { skipChecks: 1 }); + backfillCache.set(pid, 1); + })); +}; + Notes.announce = {}; +Notes.announce._cache = ttlCache({ + name: 'ap-note-announce-cache', + max: 500, + ttl: 1000 * 60 * 60, // 1 hour +}); + Notes.announce.list = async ({ pid, tid }) => { let pids = []; if (pid) { @@ -496,8 +655,24 @@ Notes.announce.list = async ({ pid, tid }) => { return []; } - const keys = pids.map(pid => `pid:${pid}:announces`); - let announces = await db.getSortedSetsMembersWithScores(keys); + const missing = []; + let announces = pids.map((pid, idx) => { + const cached = Notes.announce._cache.get(pid); + if (!cached) { + missing.push(idx); + } + return cached; + }); + + if (missing.length) { + const toCache = await db.getSortedSetsMembersWithScores(missing.map(idx => `pid:${pids[idx]}:announces`)); + toCache.forEach((value, idx) => { + const pid = pids[missing[idx]]; + Notes.announce._cache.set(pid, value); + announces[missing[idx]] = value; + }); + } + announces = announces.reduce((memo, cur, idx) => { if (cur.length) { const pid = pids[idx]; @@ -516,6 +691,7 @@ Notes.announce.add = async (pid, actor, timestamp = Date.now()) => { posts.getPostField(pid, 'tid'), db.sortedSetAdd(`pid:${pid}:announces`, timestamp, actor), ]); + Notes.announce._cache.del(`pid:${pid}:announces`); await Promise.all([ posts.setPostField(pid, 'announces', await db.sortedSetCard(`pid:${pid}:announces`)), topics.tools.share(tid, actor, timestamp), @@ -524,6 +700,8 @@ Notes.announce.add = async (pid, actor, timestamp = Date.now()) => { Notes.announce.remove = async (pid, actor) => { await db.sortedSetRemove(`pid:${pid}:announces`, actor); + Notes.announce._cache.del(`pid:${pid}:announces`); + const count = await db.sortedSetCard(`pid:${pid}:announces`); if (count > 0) { await posts.setPostField(pid, 'announces', count); @@ -537,6 +715,7 @@ Notes.announce.removeAll = async (pid) => { db.delete(`pid:${pid}:announces`), db.deleteObjectField(`post:${pid}`, 'announces'), ]); + Notes.announce._cache.del(`pid:${pid}:announces`); }; Notes.delete = async (pids) => { diff --git a/src/activitypub/out.js b/src/activitypub/out.js new file mode 100644 index 0000000000..dbe3a3e8e4 --- /dev/null +++ b/src/activitypub/out.js @@ -0,0 +1,619 @@ +'use strict'; + +/** + * This method deals unilaterally with federating activities outward. + * There _shouldn't_ be any activities sent out that don't go through this file + * This _should_ be the only file that calls activitypub.send() + * + * YMMV. + */ + +const winston = require('winston'); +const nconf = require('nconf'); + +const db = require('../database'); +const user = require('../user'); +const categories = require('../categories'); +const meta = require('../meta'); +const privileges = require('../privileges'); +const topics = require('../topics'); +const posts = require('../posts'); +const messaging = require('../messaging'); +const utils = require('../utils'); +const activitypub = module.parent.exports; + +const Out = module.exports; + +function enabledCheck(next) { + return async function (...args) { + if (meta.config.activitypubEnabled) { + try { + await next.apply(null, args); + } catch (e) { + winston.error(`[activitypub/api] Error\n${e.stack}`); + } + } + }; +} + +Out.follow = enabledCheck(async (type, id, actor) => { + // Privilege checks should be done upstream + const acceptedTypes = ['uid', 'cid']; + const assertion = await activitypub.actors.assert(actor); + if (!acceptedTypes.includes(type) || !assertion || (Array.isArray(assertion) && assertion.length)) { + throw new Error('[[error:activitypub.invalid-id]]'); + } + + if (actor.includes('@')) { + const [uid, cid] = await Promise.all([ + user.getUidByUserslug(actor), + categories.getCidByHandle(actor), + ]); + + actor = uid || cid; + } + + const isFollowing = await db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor); + if (isFollowing) { // already following + return; + } + + const timestamp = Date.now(); + + await db.sortedSetAdd(`followRequests:${type}.${id}`, timestamp, actor); + try { + await activitypub.send(type, id, [actor], { + id: `${nconf.get('url')}/${type}/${id}#activity/follow/${encodeURIComponent(actor)}/${timestamp}`, + type: 'Follow', + object: actor, + }); + } catch (e) { + await db.sortedSetRemove(`followRequests:${type}.${id}`, actor); + throw e; + } +}); + +Out.create = {}; + +Out.create.note = enabledCheck(async (uid, post) => { + if (utils.isNumber(post)) { + post = (await posts.getPostSummaryByPids([post], uid, { stripTags: false })).pop(); + if (!post) { + return; + } + } + const { pid } = post; + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating creation of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const { activity, targets } = await activitypub.mocks.activities.create(pid, uid, post); + + await Promise.all([ + activitypub.send('uid', uid, Array.from(targets), activity), + activitypub.feps.announce(pid, activity), + // utils.isNumber(post.cid) ? activitypubApi.add(caller, { pid }) : undefined, + ]); +}); + +Out.create.privateNote = enabledCheck(async (messageObj) => { + const { roomId } = messageObj; + let targets = await messaging.getUidsInRoom(roomId, 0, -1); + targets = targets.filter(uid => !utils.isNumber(uid)); // remote uids only + + const object = await activitypub.mocks.notes.private({ messageObj }); + + const payload = { + id: `${object.id}#activity/create/${Date.now()}`, + type: 'Create', + actor: object.attributedTo, + to: object.to, + object, + }; + + await activitypub.send('uid', messageObj.fromuid, targets, payload); +}); + +Out.update = {}; + +Out.update.profile = enabledCheck(async (uid, actorUid) => { + // Local users only + if (!utils.isNumber(uid)) { + return; + } + + const [object, targets] = await Promise.all([ + activitypub.mocks.actors.user(uid), + db.getSortedSetMembers(`followersRemote:${uid}`), + ]); + + await activitypub.send('uid', actorUid || uid, targets, { + id: `${object.id}#activity/update/${Date.now()}`, + type: 'Update', + actor: object.id, + to: [activitypub._constants.publicAddress], + cc: [], + object, + }); +}); + +Out.update.category = enabledCheck(async (cid) => { + // Local categories only + if (!utils.isNumber(cid)) { + return; + } + + const [object, targets] = await Promise.all([ + activitypub.mocks.actors.category(cid), + activitypub.notes.getCategoryFollowers(cid), + ]); + + await activitypub.send('cid', cid, targets, { + id: `${object.id}#activity/update/${Date.now()}`, + type: 'Update', + actor: object.id, + to: [activitypub._constants.publicAddress], + cc: [], + object, + }); +}); + +Out.update.note = enabledCheck(async (uid, post) => { + // Only applies to local posts + if (!utils.isNumber(post.pid)) { + return; + } + + const object = await activitypub.mocks.notes.public(post); + const { to, cc, targets } = await activitypub.buildRecipients(object, { pid: post.pid, uid: post.user.uid }); + object.to = to; + object.cc = cc; + + const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${post.pid} to the fediverse due to privileges.`); + return; + } + + const payload = { + id: `${object.id}#activity/update/${post.edited || Date.now()}`, + type: 'Update', + actor: object.attributedTo, + to, + cc, + object, + }; + + await Promise.all([ + activitypub.send('uid', uid, Array.from(targets), payload), + activitypub.feps.announce(post.pid, payload), + ]); +}); + +Out.update.privateNote = enabledCheck(async (uid, messageObj) => { + if (!utils.isNumber(messageObj.mid)) { + return; + } + + const { roomId } = messageObj; + let uids = await messaging.getUidsInRoom(roomId, 0, -1); + uids = uids.filter(uid => String(uid) !== String(messageObj.fromuid)); // no author + const to = uids.map(uid => (utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid)); + const targets = uids.filter(uid => !utils.isNumber(uid)); // remote uids only + + const object = await activitypub.mocks.notes.private({ messageObj }); + + const payload = { + id: `${object.id}#activity/create/${Date.now()}`, + type: 'Update', + actor: object.attributedTo, + to, + object, + }; + + await activitypub.send('uid', uid, targets, payload); +}); + +Out.delete = {}; + +Out.delete.note = enabledCheck(async (uid, pid) => { + // Only applies to local posts + if (!utils.isNumber(pid)) { + return; + } + + const id = `${nconf.get('url')}/post/${pid}`; + const post = (await posts.getPostSummaryByPids([pid], uid, { stripTags: false })).pop(); + const object = await activitypub.mocks.notes.public(post); + const { to, cc, targets } = await activitypub.buildRecipients(object, { pid, uid: post.user.uid }); + + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const payload = { + id: `${id}#activity/delete/${Date.now()}`, + type: 'Delete', + actor: object.attributedTo, + to, + cc, + object: id, + origin: object.context, + }; + + await Promise.all([ + activitypub.send('uid', uid, Array.from(targets), payload), + activitypub.feps.announce(pid, payload), + ]); +}); + +Out.like = {}; + +Out.like.note = enabledCheck(async (uid, pid) => { + const payload = { + id: `${nconf.get('url')}/uid/${uid}#activity/like/${encodeURIComponent(pid)}`, + type: 'Like', + actor: `${nconf.get('url')}/uid/${uid}`, + object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, + }; + + if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes + await activitypub.feps.announce(pid, payload); + return; + } + + const recipient = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(recipient)) { + return; + } + + await Promise.all([ + activitypub.send('uid', uid, [recipient], payload), + activitypub.feps.announce(pid, payload), + ]); +}); + +Out.dislike = {}; + +Out.dislike.note = enabledCheck(async (uid, pid) => { + const payload = { + id: `${nconf.get('url')}/uid/${uid}#activity/dislike/${encodeURIComponent(pid)}`, + type: 'Dislike', + actor: `${nconf.get('url')}/uid/${uid}`, + object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, + }; + + if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes + await activitypub.feps.announce(pid, payload); + return; + } + + const recipient = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(recipient)) { + return; + } + + await Promise.all([ + activitypub.send('uid', uid, [recipient], payload), + activitypub.feps.announce(pid, payload), + ]); +}); + +Out.announce = {}; + +Out.announce.topic = enabledCheck(async (tid, uid) => { + const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); + + if (uid) { + const exists = await user.exists(uid); + if (!exists) { + return; + } + } else { + // Only local categories can announce + if (!utils.isNumber(cid) || parseInt(cid, 10) < 1) { + return; + } + } + + const authorUid = await posts.getPostField(pid, 'uid'); // author + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + id: pid, + to: [activitypub._constants.publicAddress], + }, uid ? { uid } : { cid }); + if (!utils.isNumber(authorUid)) { + cc.push(authorUid); + targets.add(authorUid); + } + + const payload = uid ? { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/uid/${uid}`, + type: 'Announce', + actor: `${nconf.get('url')}/uid/${uid}`, + } : { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/cid/${cid}`, + type: 'Announce', + actor: `${nconf.get('url')}/category/${cid}`, + }; + await activitypub.send(uid ? 'uid' : 'cid', uid || cid, Array.from(targets), { + ...payload, + to, + cc, + object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, + }); +}); + +Out.flag = enabledCheck(async (uid, flag) => { + if (!activitypub.helpers.isUri(flag.targetId)) { + return; + } + const reportedIds = [flag.targetId]; + if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { + reportedIds.push(flag.targetUid); + } + const reason = flag.reason || + (flag.reports && flag.reports.filter(report => report.reporter.uid === uid).at(-1).value); + await activitypub.send('uid', uid, reportedIds, { + id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${uid}`, + type: 'Flag', + actor: `${nconf.get('url')}/uid/${uid}`, + object: reportedIds, + content: reason, + }); + await db.sortedSetAdd(`flag:${flag.flagId}:remote`, Date.now(), uid); +}); + +Out.remove = {}; + +Out.remove.context = enabledCheck(async (uid, tid) => { + // Federates Remove(Context); where Context is the tid + const now = new Date(); + const cid = await topics.getTopicField(tid, 'oldCid'); + + // Only local categories + if (!utils.isNumber(cid) || parseInt(cid, 10) < 1) { + return; + } + + const allowed = await privileges.categories.can('topics:read', cid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating deletion of tid ${tid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + to: [activitypub._constants.publicAddress], + cc: [], + }, { cid }); + + await activitypub.send('uid', uid, Array.from(targets), { + id: `${nconf.get('url')}/topic/${tid}#activity/remove/${now.getTime()}`, + type: 'Remove', + actor: `${nconf.get('url')}/uid/${uid}`, + to, + cc, + object: `${nconf.get('url')}/topic/${tid}`, + target: `${nconf.get('url')}/category/${cid}`, + }); +}); + +Out.move = {}; + +Out.move.context = enabledCheck(async (uid, tid) => { + // Federates Move(Context); where Context is the tid + const now = new Date(); + const { cid, oldCid } = await topics.getTopicFields(tid, ['cid', 'oldCid']); + + // This check may be revised if inter-community moderation becomes real. + const isLocal = id => utils.isNumber(id) && parseInt(id, 10) > 0; + if (isLocal(oldCid) && !isLocal(cid)) { // moving to remote/uncategorized + return Out.remove.context(uid, tid); + } else if ( + (isLocal(cid) && !isLocal(oldCid)) || // stealing, or + [cid, oldCid].every(id => !isLocal(id)) // remote-to-remote + ) { + return; + } + + const allowed = await privileges.categories.can('topics:read', cid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating move of tid ${tid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + to: [activitypub._constants.publicAddress], + cc: [], + }, { cid: [cid, oldCid] }); + + await activitypub.send('uid', uid, Array.from(targets), { + id: `${nconf.get('url')}/topic/${tid}#activity/move/${now.getTime()}`, + type: 'Move', + actor: `${nconf.get('url')}/uid/${uid}`, + to, + cc, + object: `${nconf.get('url')}/topic/${tid}`, + origin: `${nconf.get('url')}/category/${oldCid}`, + target: `${nconf.get('url')}/category/${cid}`, + }); +}); + +Out.undo = {}; + +Out.undo.follow = enabledCheck(async (type, id, actor) => { + const acceptedTypes = ['uid', 'cid']; + const assertion = await activitypub.actors.assert(actor); + if (!acceptedTypes.includes(type) || !assertion) { + throw new Error('[[error:activitypub.invalid-id]]'); + } + + if (actor.includes('@')) { + const [uid, cid] = await Promise.all([ + user.getUidByUserslug(actor), + categories.getCidByHandle(actor), + ]); + + actor = uid || cid; + } + + const [isFollowing, isPending] = await Promise.all([ + db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor), + db.isSortedSetMember(`followRequests:${type === 'uid' ? 'uid' : 'cid'}.${id}`, actor), + ]); + + if (!isFollowing && !isPending) { // already not following/pending + return; + } + + const timestamps = await db.sortedSetsScore([ + `followRequests:${type}.${id}`, + type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, + ], actor); + const timestamp = timestamps[0] || timestamps[1]; + + const object = { + id: `${nconf.get('url')}/${type}/${id}#activity/follow/${encodeURIComponent(actor)}/${timestamp}`, + type: 'Follow', + object: actor, + }; + if (type === 'uid') { + object.actor = `${nconf.get('url')}/uid/${id}`; + } else if (type === 'cid') { + object.actor = `${nconf.get('url')}/category/${id}`; + } + + await activitypub.send(type, id, [actor], { + id: `${nconf.get('url')}/${type}/${id}#activity/undo:follow/${encodeURIComponent(actor)}/${timestamp}`, + type: 'Undo', + actor: object.actor, + object, + }); + + if (type === 'uid') { + await Promise.all([ + db.sortedSetRemove(`followingRemote:${id}`, actor), + db.sortedSetRemove(`followRequests:uid.${id}`, actor), + db.sortedSetRemove(`followersRemote:${actor}`, id), + db.decrObjectField(`user:${id}`, 'followingRemoteCount'), + ]); + } else if (type === 'cid') { + await Promise.all([ + db.sortedSetRemove(`cid:${id}:following`, actor), + db.sortedSetRemove(`followRequests:cid.${id}`, actor), + db.sortedSetRemove(`followersRemote:${actor}`, `cid|${id}`), + ]); + } + activitypub.actors._followerCache.del(actor); +}); + +Out.undo.like = enabledCheck(async (uid, pid) => { + if (!activitypub.helpers.isUri(pid)) { + return; + } + + const author = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(author)) { + return; + } + + const payload = { + id: `${nconf.get('url')}/uid/${uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, + type: 'Undo', + actor: `${nconf.get('url')}/uid/${uid}`, + object: { + actor: `${nconf.get('url')}/uid/${uid}`, + id: `${nconf.get('url')}/uid/${uid}#activity/like/${encodeURIComponent(pid)}`, + type: 'Like', + object: pid, + }, + }; + + await Promise.all([ + activitypub.send('uid', uid, [author], payload), + activitypub.feps.announce(pid, payload), + ]); +}); + +Out.undo.flag = enabledCheck(async (uid, flag) => { + if (!activitypub.helpers.isUri(flag.targetId)) { + return; + } + const reportedIds = [flag.targetId]; + if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { + reportedIds.push(flag.targetUid); + } + const reason = flag.reason || + (flag.reports && flag.reports.filter(report => report.reporter.uid === uid).at(-1).value); + await activitypub.send('uid', uid, reportedIds, { + id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/undo:flag/${uid}/${Date.now()}`, + type: 'Undo', + actor: `${nconf.get('url')}/uid/${uid}`, + object: { + id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${uid}`, + actor: `${nconf.get('url')}/uid/${uid}`, + type: 'Flag', + object: reportedIds, + content: reason, + }, + }); + await db.sortedSetRemove(`flag:${flag.flagId}:remote`, uid); +}); + +Out.undo.announce = enabledCheck(async (type, id, tid) => { + if (!utils.isNumber(id) || !['uid', 'cid'].includes(type)) { + throw new Error('[[error:invalid-data]]'); + } + + const exists = await Promise.all([ + topics.exists(tid), + type === 'uid' ? user.exists(id) : categories.exists(id), + ]); + if (!exists.every(Boolean)) { + throw new Error('[[error:invalid-data]]'); + } + + const baseUrl = `${nconf.get('url')}/${type === 'uid' ? 'uid' : 'category'}/${id}`; + const { uid, mainPid: pid } = await topics.getTopicFields(tid, ['uid', 'mainPid']); + const allowed = await privileges.topics.can('topics:read', tid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + id: pid, + to: [activitypub._constants.publicAddress], + cc: [`${baseUrl}/followers`, uid], + }, { + uid: type === 'uid' && id, + cid: type === 'cid' && id, + }); + + + // Just undo the announce. + await activitypub.send(type, id, Array.from(targets), { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/undo:announce/${type}/${id}`, + type: 'Undo', + actor: baseUrl, + to, + cc, + object: { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${type}/${id}`, + type: 'Announce', + actor: baseUrl, + to, + cc, + object: pid, + }, + }); +}); \ No newline at end of file diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js new file mode 100644 index 0000000000..afaeaaa05f --- /dev/null +++ b/src/activitypub/relays.js @@ -0,0 +1,126 @@ +'use strict'; + +const nconf = require('nconf'); + +const db = require('../database'); + +const activitypub = module.parent.exports; +const Relays = module.exports; + +Relays.is = async (actor) => { + return db.isSortedSetMember('relays:createtime', actor); +}; + +Relays.list = async () => { + let relays = await db.getSortedSetMembersWithScores('relays:state'); + relays = relays.reduce((memo, { value, score }) => { + let label = '[[admin/settings/activitypub:relays.state-0]]'; + switch(score) { + case 1: { + label = '[[admin/settings/activitypub:relays.state-1]]'; + break; + } + + case 2: { + label = '[[admin/settings/activitypub:relays.state-2]]'; + break; + } + } + + memo.push({ + url: value, + state: score, + label, + }); + + return memo; + }, []); + + return relays; +}; + +Relays.add = async (url) => { + const now = Date.now(); + await activitypub.send('uid', 0, url, { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://pleroma.example/schemas/litepub-0.1.jsonld', + ], + id: `${nconf.get('url')}/actor#activity/follow/${encodeURIComponent(url)}/${now}`, + type: 'Follow', + to: [url], + object: url, + state: 'pending', + }); + + await Promise.all([ + db.sortedSetAdd('relays:createtime', now, url), + db.sortedSetAdd('relays:state', 0, url), + ]); +}; + +Relays.remove = async (url) => { + const now = new Date(); + const createtime = await db.sortedSetScore('relays:createtime', url); + if (!createtime) { + throw new Error('[[error:invalid-data]]'); + } + + await activitypub.send('uid', 0, url, { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://pleroma.example/schemas/litepub-0.1.jsonld', + ], + id: `${nconf.get('url')}/actor#activity/undo:follow/${encodeURIComponent(url)}/${now.getTime()}`, + type: 'Undo', + to: [url], + published: now.toISOString(), + object: { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://pleroma.example/schemas/litepub-0.1.jsonld', + ], + id: `${nconf.get('url')}/actor#activity/follow/${encodeURIComponent(url)}/${createtime}`, + type: 'Follow', + actor: `${nconf.get('url')}/actor`, + to: [url], + object: url, + state: 'cancelled', + }, + }); + + await Promise.all([ + db.sortedSetRemove('relays:createtime', url), + db.sortedSetRemove('relays:state', url), + ]); +}; + +Relays.handshake = async (object) => { + const now = new Date(); + const { type, actor } = object; + + // Confirm relay was added + const exists = await db.isSortedSetMember('relays:createtime', actor); + if (!exists) { + throw new Error('[[error:api.400]]'); + } + + if (type === 'Follow') { + await db.sortedSetIncrBy('relays:state', 1, actor); + await activitypub.send('uid', 0, actor, { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://pleroma.example/schemas/litepub-0.1.jsonld', + ], + id: `${nconf.get('url')}/actor#activity/accept/${encodeURIComponent(actor)}/${now.getTime()}`, + type: 'Accept', + to: [actor], + published: now.toISOString(), + object, + }); + } else if (type === 'Accept') { + await db.sortedSetIncrBy('relays:state', 1, actor); + } else { + throw new Error('[[error:api.400]]'); + } +}; \ No newline at end of file diff --git a/src/activitypub/rules.js b/src/activitypub/rules.js new file mode 100644 index 0000000000..656f03e2de --- /dev/null +++ b/src/activitypub/rules.js @@ -0,0 +1,52 @@ +'use strict'; + +const db = require('../database'); +const utils = require('../utils'); + +const activitypub = require('.'); + +const Rules = module.exports; + +Rules.list = async () => { + const rids = await db.getSortedSetMembers('categorization:rid'); + let rules = await db.getObjects(rids.map(rid => `rid:${rid}`)); + rules = rules.map((rule, idx) => { + rule.rid = rids[idx]; + return rule; + }); + + return rules; +}; + +Rules.add = async (type, value, cid) => { + const uuid = utils.generateUUID(); + + // normalize user rule values into a uid + if (type === 'user' && value.indexOf('@') !== -1) { + const response = await activitypub.actors.assert(value); + if (!response) { + throw new Error('[[error:no-user]]'); + } + value = await db.getObjectField('handle:uid', String(value).toLowerCase()); + } + + await Promise.all([ + db.setObject(`rid:${uuid}`, { type, value, cid }), + db.sortedSetAdd('categorization:rid', Date.now(), uuid), + ]); +}; + +Rules.delete = async (rid) => { + await Promise.all([ + db.sortedSetRemove('categorization:rid', rid), + db.delete(`rid:${rid}`), + ]); +}; + +Rules.reorder = async (rids) => { + const exists = await db.isSortedSetMembers('categorization:rid', rids); + rids = rids.filter((_, idx) => exists[idx]); + const scores = Array.from({ length: rids.length }, (_, idx) => idx); + + await db.sortedSetAdd('categorization:rid', scores, rids); +}; \ No newline at end of file diff --git a/src/admin/search.js b/src/admin/search.js index a1ba466191..905b22ed5f 100644 --- a/src/admin/search.js +++ b/src/admin/search.js @@ -39,7 +39,7 @@ function sanitize(html) { return sanitizeHTML(html, { allowedTags: [], allowedAttributes: [], - }); + }).replace(/"/g, ''); } function simplify(translations) { @@ -89,15 +89,33 @@ async function initDict(language) { return await Promise.all(namespaces.map(ns => buildNamespace(language, ns))); } +async function getTemplateDataFields(namespace) { + if (!namespace.startsWith('admin/settings')) { + return []; + } + try { + const template = await fs.promises.readFile( + path.resolve(nconf.get('views_dir'), `${namespace}.tpl`), 'utf8' + ); + const fields = [...template.matchAll(/data-field="([^"]+)"/g)].map(m => m[1]); + return fields.filter(f => !f.startsWith('{')); // dont include stuff like {./name} + } catch (err) { + return []; + } +} + async function buildNamespace(language, namespace) { const translator = Translator.create(language); try { - const translations = await translator.getTranslation(namespace); + const [translations, dataFields] = await Promise.all([ + translator.getTranslation(namespace), + getTemplateDataFields(namespace), + ]); if (!translations || !Object.keys(translations).length) { return await fallback(namespace); } // join all translations into one string separated by newlines - let str = Object.keys(translations).map(key => translations[key]).join('\n'); + let str = Object.values(translations).concat(dataFields).join('\n'); str = sanitize(str); let title = namespace; diff --git a/src/analytics.js b/src/analytics.js index b70aec7e5c..773f7088af 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -21,6 +21,7 @@ let local = { pageViewsRegistered: 0, pageViewsGuest: 0, pageViewsBot: 0, + apPageViews: 0, uniquevisitors: 0, }; const empty = _.cloneDeep(local); @@ -101,8 +102,17 @@ Analytics.pageView = async function (payload) { local.pageViewsGuest += 1; } - if (payload.ip) { - const score = await db.sortedSetScore('ip:recent', payload.ip); + await incrementUniqueVisitors(payload.ip); +}; + +Analytics.apPageView = async function ({ ip }) { + local.apPageViews += 1; + await incrementUniqueVisitors(ip); +}; + +async function incrementUniqueVisitors(ip) { + if (ip) { + const score = await db.sortedSetScore('ip:recent', ip); let record = !score; if (score) { const today = new Date(); @@ -112,10 +122,10 @@ Analytics.pageView = async function (payload) { if (record) { local.uniquevisitors += 1; - await db.sortedSetAdd('ip:recent', Date.now(), payload.ip); + await db.sortedSetAdd('ip:recent', Date.now(), ip); } } -}; +} Analytics.writeData = async function () { const today = new Date(); @@ -162,6 +172,12 @@ Analytics.writeData = async function () { total.pageViewsBot = 0; } + if (total.apPageViews > 0) { + incrByBulk.push(['analytics:pageviews:ap', total.apPageViews, today.getTime()]); + incrByBulk.push(['analytics:pageviews:ap:month', total.apPageViews, month.getTime()]); + total.apPageViews = 0; + } + if (total.uniquevisitors > 0) { incrByBulk.push(['analytics:uniquevisitors', total.uniquevisitors, today.getTime()]); total.uniquevisitors = 0; diff --git a/src/api/activitypub.js b/src/api/activitypub.js deleted file mode 100644 index a6ba295b06..0000000000 --- a/src/api/activitypub.js +++ /dev/null @@ -1,441 +0,0 @@ -'use strict'; - -/** - * DEVELOPMENT NOTE - * - * THIS FILE IS UNDER ACTIVE DEVELOPMENT AND IS EXPLICITLY EXCLUDED FROM IMMUTABILITY GUARANTEES - * - * If you use api methods in this file, be prepared that they may be removed or modified with no warning. - */ - -const nconf = require('nconf'); -const winston = require('winston'); - -const db = require('../database'); -const user = require('../user'); -const meta = require('../meta'); -const privileges = require('../privileges'); -const activitypub = require('../activitypub'); -const posts = require('../posts'); -const topics = require('../topics'); -const messaging = require('../messaging'); -const utils = require('../utils'); - -const activitypubApi = module.exports; - -function enabledCheck(next) { - return async function (caller, params) { - if (meta.config.activitypubEnabled) { - try { - await next(caller, params); - } catch (e) { - winston.error(`[activitypub/api] Error\n${e.stack}`); - } - } - }; -} - -activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => { - // Privilege checks should be done upstream - const assertion = await activitypub.actors.assert(actor); - if (!assertion || (Array.isArray(assertion) && assertion.length)) { - throw new Error('[[error:activitypub.invalid-id]]'); - } - - actor = actor.includes('@') ? await user.getUidByUserslug(actor) : actor; - const handle = await user.getUserField(actor, 'username'); - const timestamp = Date.now(); - - await db.sortedSetAdd(`followRequests:${type}.${id}`, timestamp, actor); - try { - await activitypub.send(type, id, [actor], { - id: `${nconf.get('url')}/${type}/${id}#activity/follow/${handle}/${timestamp}`, - type: 'Follow', - object: actor, - }); - } catch (e) { - await db.sortedSetRemove(`followRequests:${type}.${id}`, actor); - throw e; - } -}); - -// should be .undo.follow -activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => { - const assertion = await activitypub.actors.assert(actor); - if (!assertion) { - throw new Error('[[error:activitypub.invalid-id]]'); - } - - actor = actor.includes('@') ? await user.getUidByUserslug(actor) : actor; - const handle = await user.getUserField(actor, 'username'); - const timestamps = await db.sortedSetsScore([ - `followRequests:${type}.${id}`, - type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, - ], actor); - const timestamp = timestamps[0] || timestamps[1]; - - const object = { - id: `${nconf.get('url')}/${type}/${id}#activity/follow/${handle}/${timestamp}`, - type: 'Follow', - object: actor, - }; - if (type === 'uid') { - object.actor = `${nconf.get('url')}/uid/${id}`; - } else if (type === 'cid') { - object.actor = `${nconf.get('url')}/category/${id}`; - } - - await activitypub.send(type, id, [actor], { - id: `${nconf.get('url')}/${type}/${id}#activity/undo:follow/${handle}/${timestamp}`, - type: 'Undo', - object, - }); - - if (type === 'uid') { - await Promise.all([ - db.sortedSetRemove(`followingRemote:${id}`, actor), - db.sortedSetRemove(`followRequests:uid.${id}`, actor), - db.decrObjectField(`user:${id}`, 'followingRemoteCount'), - ]); - } else if (type === 'cid') { - await Promise.all([ - db.sortedSetRemove(`cid:${id}:following`, actor), - db.sortedSetRemove(`followRequests:cid.${id}`, actor), - db.sortedSetRemove(`followersRemote:${actor}`, `cid|${id}`), - ]); - } -}); - -activitypubApi.create = {}; - -activitypubApi.create.note = enabledCheck(async (caller, { pid, post }) => { - if (!post) { - post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); - if (!post) { - return; - } - } else { - pid = post.pid; - } - - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating creation of pid ${pid} to the fediverse due to privileges.`); - return; - } - - const { activity, targets } = await activitypub.mocks.activities.create(pid, caller.uid, post); - - await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), activity), - activitypub.feps.announce(pid, activity), - // activitypubApi.add(caller, { pid }), - ]); -}); - -activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj }) => { - const { roomId } = messageObj; - let targets = await messaging.getUidsInRoom(roomId, 0, -1); - targets = targets.filter(uid => !utils.isNumber(uid)); // remote uids only - - const object = await activitypub.mocks.notes.private({ messageObj }); - - const payload = { - id: `${object.id}#activity/create/${Date.now()}`, - type: 'Create', - actor: object.attributedTo, - to: object.to, - object, - }; - - await activitypub.send('uid', messageObj.fromuid, targets, payload); -}); - -activitypubApi.update = {}; - -activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { - const [object, targets] = await Promise.all([ - activitypub.mocks.actors.user(uid), - db.getSortedSetMembers(`followersRemote:${caller.uid}`), - ]); - - await activitypub.send('uid', caller.uid, targets, { - id: `${object.id}#activity/update/${Date.now()}`, - type: 'Update', - actor: object.id, - to: [activitypub._constants.publicAddress], - cc: [], - object, - }); -}); - -activitypubApi.update.category = enabledCheck(async (caller, { cid }) => { - const [object, targets] = await Promise.all([ - activitypub.mocks.actors.category(cid), - activitypub.notes.getCategoryFollowers(cid), - ]); - - await activitypub.send('cid', cid, targets, { - id: `${object.id}#activity/update/${Date.now()}`, - type: 'Update', - actor: object.id, - to: [activitypub._constants.publicAddress], - cc: [], - object, - }); -}); - -activitypubApi.update.note = enabledCheck(async (caller, { post }) => { - // Only applies to local posts - if (!utils.isNumber(post.pid)) { - return; - } - - const object = await activitypub.mocks.notes.public(post); - const { to, cc, targets } = await activitypub.buildRecipients(object, { pid: post.pid, uid: post.user.uid }); - object.to = to; - object.cc = cc; - - const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${post.pid} to the fediverse due to privileges.`); - return; - } - - const payload = { - id: `${object.id}#activity/update/${post.edited || Date.now()}`, - type: 'Update', - actor: object.attributedTo, - to, - cc, - object, - }; - - await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), payload), - activitypub.feps.announce(post.pid, payload), - ]); -}); - -activitypubApi.update.privateNote = enabledCheck(async (caller, { messageObj }) => { - if (!utils.isNumber(messageObj.mid)) { - return; - } - - const { roomId } = messageObj; - let uids = await messaging.getUidsInRoom(roomId, 0, -1); - uids = uids.filter(uid => String(uid) !== String(messageObj.fromuid)); // no author - const to = uids.map(uid => (utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid)); - const targets = uids.filter(uid => !utils.isNumber(uid)); // remote uids only - - const object = await activitypub.mocks.notes.private({ messageObj }); - - const payload = { - id: `${object.id}#activity/create/${Date.now()}`, - type: 'Update', - actor: object.attributedTo, - to, - object, - }; - - await activitypub.send('uid', caller.uid, targets, payload); -}); - -activitypubApi.delete = {}; - -activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => { - // Only applies to local posts - if (!utils.isNumber(pid)) { - return; - } - - const id = `${nconf.get('url')}/post/${pid}`; - const post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); - const object = await activitypub.mocks.notes.public(post); - const { to, cc, targets } = await activitypub.buildRecipients(object, { pid, uid: post.user.uid }); - - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${pid} to the fediverse due to privileges.`); - return; - } - - const payload = { - id: `${id}#activity/delete/${Date.now()}`, - type: 'Delete', - actor: object.attributedTo, - to, - cc, - object: id, - origin: object.context, - }; - - await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), payload), - activitypub.feps.announce(pid, payload), - ]); -}); - -activitypubApi.like = {}; - -activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { - if (!activitypub.helpers.isUri(pid)) { // remote only - return; - } - - const uid = await posts.getPostField(pid, 'uid'); - if (!activitypub.helpers.isUri(uid)) { - return; - } - - const payload = { - id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, - type: 'Like', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: pid, - }; - - await Promise.all([ - activitypub.send('uid', caller.uid, [uid], payload), - activitypub.feps.announce(pid, payload), - ]); -}); - -activitypubApi.announce = {}; - -activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => { - const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); - - // Only remote posts can be announced to real categories - if (utils.isNumber(pid) || parseInt(cid, 10) === -1) { - return; - } - - const uid = await posts.getPostField(pid, 'uid'); // author - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); - return; - } - - const { to, cc, targets } = await activitypub.buildRecipients({ - id: pid, - to: [activitypub._constants.publicAddress], - cc: [`${nconf.get('url')}/uid/${caller.uid}/followers`, uid], - }, { uid: caller.uid }); - - await activitypub.send('uid', caller.uid, Array.from(targets), { - id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${Date.now()}`, - type: 'Announce', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - to, - cc, - object: pid, - target: `${nconf.get('url')}/category/${cid}`, - }); -}); - -activitypubApi.undo = {}; - -// activitypubApi.undo.follow = - -activitypubApi.undo.like = enabledCheck(async (caller, { pid }) => { - if (!activitypub.helpers.isUri(pid)) { - return; - } - - const uid = await posts.getPostField(pid, 'uid'); - if (!activitypub.helpers.isUri(uid)) { - return; - } - - const payload = { - id: `${nconf.get('url')}/uid/${caller.uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, - type: 'Undo', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: { - actor: `${nconf.get('url')}/uid/${caller.uid}`, - id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, - type: 'Like', - object: pid, - }, - }; - - await Promise.all([ - activitypub.send('uid', caller.uid, [uid], payload), - activitypub.feps.announce(pid, payload), - ]); -}); - -activitypubApi.flag = enabledCheck(async (caller, flag) => { - if (!activitypub.helpers.isUri(flag.targetId)) { - return; - } - const reportedIds = [flag.targetId]; - if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { - reportedIds.push(flag.targetUid); - } - const reason = flag.reason || - (flag.reports && flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1).value); - await activitypub.send('uid', caller.uid, reportedIds, { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, - type: 'Flag', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: reportedIds, - content: reason, - }); - await db.sortedSetAdd(`flag:${flag.flagId}:remote`, Date.now(), caller.uid); -}); - -/* -activitypubApi.add = enabledCheck((async (_, { pid }) => { - let localId; - if (String(pid).startsWith(nconf.get('url'))) { - ({ id: localId } = await activitypub.helpers.resolveLocalId(pid)); - } - - const tid = await posts.getPostField(localId || pid, 'tid'); - const cid = await posts.getCidByPid(localId || pid); - if (!utils.isNumber(tid) || cid <= 0) { // `Add` only federated on categorized topics started locally - return; - } - - let to = [activitypub._constants.publicAddress]; - let cc = []; - let targets; - ({ to, cc, targets } = await activitypub.buildRecipients({ to, cc }, { pid: localId || pid, cid })); - - await activitypub.send('cid', cid, Array.from(targets), { - id: `${nconf.get('url')}/post/${encodeURIComponent(localId || pid)}#activity/add/${Date.now()}`, - type: 'Add', - to, - cc, - object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, - target: `${nconf.get('url')}/topic/${tid}`, - }); -})); -*/ -activitypubApi.undo.flag = enabledCheck(async (caller, flag) => { - if (!activitypub.helpers.isUri(flag.targetId)) { - return; - } - const reportedIds = [flag.targetId]; - if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { - reportedIds.push(flag.targetUid); - } - const reason = flag.reason || - (flag.reports && flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1).value); - await activitypub.send('uid', caller.uid, reportedIds, { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/undo:flag/${caller.uid}/${Date.now()}`, - type: 'Undo', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, - actor: `${nconf.get('url')}/uid/${caller.uid}`, - type: 'Flag', - object: reportedIds, - content: reason, - }, - }); - await db.sortedSetRemove(`flag:${flag.flagId}:remote`, caller.uid); -}); diff --git a/src/api/categories.js b/src/api/categories.js index 4768d813ac..4806868f93 100644 --- a/src/api/categories.js +++ b/src/api/categories.js @@ -7,8 +7,8 @@ const events = require('../events'); const user = require('../user'); const groups = require('../groups'); const privileges = require('../privileges'); - -const activitypubApi = require('./activitypub'); +const activitypub = require('../activitypub'); +const utils = require('../utils'); const categoriesAPI = module.exports; @@ -65,7 +65,7 @@ categoriesAPI.update = async function (caller, data) { const payload = {}; payload[cid] = values; await categories.update(payload); - activitypubApi.update.category(caller, { cid }); // background + activitypub.out.update.category(cid); // background }; categoriesAPI.delete = async function (caller, { cid }) { @@ -126,7 +126,7 @@ categoriesAPI.getTopics = async (caller, data) => { throw new Error('[[error:no-privileges]]'); } - const infScrollTopicsPerPage = 20; + const infScrollTopicsPerPage = settings.topicsPerPage; const sort = data.sort || data.categoryTopicSort || meta.config.categoryTopicSort || 'recently_replied'; let start = Math.max(0, parseInt(data.after || 0, 10)); @@ -157,7 +157,9 @@ categoriesAPI.getTopics = async (caller, data) => { categoriesAPI.setWatchState = async (caller, { cid, state, uid }) => { let targetUid = caller.uid; - const cids = Array.isArray(cid) ? cid.map(cid => parseInt(cid, 10)) : [parseInt(cid, 10)]; + let cids = Array.isArray(cid) ? cid : [cid]; + cids = cids.map(cid => (utils.isNumber(cid) ? parseInt(cid, 10) : cid)); + if (uid) { targetUid = uid; } diff --git a/src/api/chats.js b/src/api/chats.js index e1538c4426..5099479b51 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -162,9 +162,17 @@ chatsAPI.update = async (caller, data) => { await db.setObjectField(`chat:room:${data.roomId}`, 'groups', JSON.stringify(data.groups)); } } - if (data.hasOwnProperty('notificationSetting') && isAdmin) { - await db.setObjectField(`chat:room:${data.roomId}`, 'notificationSetting', data.notificationSetting); + if (isAdmin) { + const updateData = {}; + if (data.hasOwnProperty('notificationSetting')) { + updateData.notificationSetting = data.notificationSetting; + } + if (data.hasOwnProperty('joinLeaveMessages')) { + updateData.joinLeaveMessages = data.joinLeaveMessages; + } + await db.setObject(`chat:room:${data.roomId}`, updateData); } + const loadedRoom = await messaging.loadRoom(caller.uid, { roomId: data.roomId, }); diff --git a/src/api/helpers.js b/src/api/helpers.js index e0d3bbc0bb..5867e2822c 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -1,11 +1,12 @@ 'use strict'; -const url = require('url'); const user = require('../user'); const topics = require('../topics'); const posts = require('../posts'); const privileges = require('../privileges'); const plugins = require('../plugins'); +const activitypub = require('../activitypub'); +const utils = require('../utils'); const socketHelpers = require('../socket.io/helpers'); const websockets = require('../socket.io'); const events = require('../events'); @@ -23,11 +24,15 @@ exports.buildReqObject = (req, payload) => { const headers = req.headers || (req.request && req.request.headers) || {}; const session = req.session || (req.request && req.request.session) || {}; const encrypted = req.connection ? !!req.connection.encrypted : false; - let { host } = headers; + let host = headers.host || ''; const referer = headers.referer || ''; - if (!host) { - host = url.parse(referer).host || ''; + if (!host && referer) { + try { + host = new URL(referer).host; + } catch (err) { + // ignore invalid referer + } } return { @@ -35,7 +40,7 @@ exports.buildReqObject = (req, payload) => { params: req.params, method: req.method, body: payload || req.body, - session: session, + session: JSON.parse(JSON.stringify(session)), ip: req.ip, host: host, protocol: encrypted ? 'https' : 'http', @@ -44,7 +49,7 @@ exports.buildReqObject = (req, payload) => { path: referer.slice(referer.indexOf(host) + host.length), baseUrl: req.baseUrl, originalUrl: req.originalUrl, - headers: headers, + headers: { ...headers }, }; }; @@ -65,11 +70,22 @@ exports.doTopicAction = async function (action, event, caller, { tids }) { const uids = await user.getUidsFromSet('users:online', 0, -1); await Promise.all(tids.map(async (tid) => { - const title = await topics.getTopicField(tid, 'title'); + const { title, cid, mainPid } = await topics.getTopicFields(tid, ['title', 'cid', 'mainPid']); const data = await topics.tools[action](tid, caller.uid); const notifyUids = await privileges.categories.filterUids('topics:read', data.cid, uids); socketHelpers.emitToUids(event, data, notifyUids); await logTopicAction(action, caller, tid, title); + + switch(action) { + case 'delete': // falls through + case 'purge': { + if (utils.isNumber(cid) && parseInt(cid, 10) > 0) { + activitypub.out.remove.context(caller.uid, tid); // 7888-style + activitypub.out.delete.note(caller.uid, mainPid); // 1b12-style + activitypub.out.undo.announce('cid', cid, tid); // microblogs + } + } + } })); }; @@ -129,20 +145,39 @@ exports.postCommand = async function (caller, command, eventName, notification, }; async function executeCommand(caller, command, eventName, notification, data) { - const api = require('.'); const result = await posts[command](data.pid, caller.uid); if (result && eventName) { websockets.in(`uid_${caller.uid}`).emit(`posts.${command}`, result); websockets.in(data.room_id).emit(`event:${eventName}`, result); } - if (result && command === 'upvote') { - socketHelpers.upvote(result, notification); - api.activitypub.like.note(caller, { pid: data.pid }); - } else if (result && notification) { - socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification); - } else if (result && command === 'unvote') { - socketHelpers.rescindUpvoteNotification(data.pid, caller.uid); - api.activitypub.undo.like(caller, { pid: data.pid }); + + if (result) { + switch (command) { + case 'upvote': { + socketHelpers.upvote(result, notification); + await activitypub.out.like.note(caller.uid, data.pid); + break; + } + + case 'downvote': { + await activitypub.out.dislike.note(caller.uid, data.pid); + break; + } + + case 'unvote': { + socketHelpers.rescindUpvoteNotification(data.pid, caller.uid); + await activitypub.out.undo.like(caller.uid, data.pid); + break; + } + + default: { + if (notification) { + socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification); + } + break; + } + } } + return result; } diff --git a/src/api/index.js b/src/api/index.js index 18cd8678f1..c454de93a5 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -11,7 +11,6 @@ module.exports = { categories: require('./categories'), search: require('./search'), flags: require('./flags'), - activitypub: require('./activitypub'), files: require('./files'), utils: require('./utils'), }; diff --git a/src/api/posts.js b/src/api/posts.js index a7111e0c22..767ea8eb1c 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -17,6 +17,8 @@ const activitypub = require('../activitypub'); const apiHelpers = require('./helpers'); const websockets = require('../socket.io'); const socketHelpers = require('../socket.io/helpers'); +const translator = require('../translator'); +const notifications = require('../notifications'); const postsAPI = module.exports; @@ -72,7 +74,7 @@ postsAPI.getRaw = async (caller, { pid }) => { return null; } - const postData = await posts.getPostFields(pid, ['content', 'deleted']); + const postData = await posts.getPostFields(pid, ['content', 'sourceContent', 'deleted']); const selfPost = caller.uid && caller.uid === parseInt(postData.uid, 10); if (postData.deleted && !(userPrivilege.isAdminOrMod || selfPost)) { @@ -80,7 +82,7 @@ postsAPI.getRaw = async (caller, { pid }) => { } postData.pid = pid; const result = await plugins.hooks.fire('filter:post.getRawPost', { uid: caller.uid, postData: postData }); - return result.postData.content; + return result.postData.sourceContent || result.postData.content; }; postsAPI.edit = async function (caller, data) { @@ -118,9 +120,7 @@ postsAPI.edit = async function (caller, data) { data.timestamp = parseInt(data.timestamp, 10) || Date.now(); const editResult = await posts.edit(data); - if (editResult.topic.isMainPost) { - await topics.thumbs.migrate(data.uuid, editResult.topic.tid); - } + const selfPost = parseInt(caller.uid, 10) === parseInt(editResult.post.uid, 10); if (!selfPost && editResult.post.changed) { await events.log({ @@ -151,7 +151,7 @@ postsAPI.edit = async function (caller, data) { if (!editResult.post.deleted) { websockets.in(`topic_${editResult.topic.tid}`).emit('event:post_edited', editResult); setTimeout(() => { - require('.').activitypub.update.note(caller, { post: postObj[0] }); + activitypub.out.update.note(caller.uid, postObj[0]); }, 5000); return returnData; @@ -190,9 +190,12 @@ async function deleteOrRestore(caller, data, params) { if (!data || !data.pid) { throw new Error('[[error:invalid-data]]'); } - const postData = await posts.tools[params.command](caller.uid, data.pid); - const results = await isMainAndLastPost(data.pid); - if (results.isMain && results.isLast) { + const [postData, { isMain, isLast }] = await Promise.all([ + posts.tools[params.command](caller.uid, data.pid), + isMainAndLastPost(data.pid), + activitypub.out.delete.note(caller.uid, data.pid), + ]); + if (isMain && isLast) { await deleteOrRestoreTopicOf(params.command, data.pid, caller); } @@ -205,11 +208,6 @@ async function deleteOrRestore(caller, data, params) { tid: postData.tid, ip: caller.ip, }); - - // Explicitly non-awaited - posts.getPostSummaryByPids([data.pid], caller.uid, { extraFields: ['edited'] }).then(([post]) => { - require('.').activitypub.update.note(caller, { post }); - }); } async function deleteOrRestoreTopicOf(command, pid, caller) { @@ -254,7 +252,7 @@ postsAPI.purge = async function (caller, data) { posts.clearCachedPost(data.pid); await Promise.all([ posts.purge(data.pid, caller.uid), - require('.').activitypub.delete.note(caller, { pid: data.pid }), + activitypub.out.delete.note(caller.uid, data.pid), ]); websockets.in(`topic_${postData.tid}`).emit('event:post_purged', postData); @@ -489,10 +487,12 @@ async function diffsPrivilegeCheck(pid, uid) { postsAPI.getDiffs = async (caller, data) => { await diffsPrivilegeCheck(data.pid, caller.uid); - const timestamps = await posts.diffs.list(data.pid); - const post = await posts.getPostFields(data.pid, ['timestamp', 'uid']); + const [timestamps, post, diffs] = await Promise.all([ + posts.diffs.list(data.pid), + posts.getPostFields(data.pid, ['timestamp', 'uid']), + posts.diffs.get(data.pid), + ]); - const diffs = await posts.diffs.get(data.pid); const uids = diffs.map(diff => diff.uid || null); uids.push(post.uid); let usernames = await user.getUsersFields(uids, ['username']); @@ -506,18 +506,21 @@ postsAPI.getDiffs = async (caller, data) => { // timestamps returned by posts.diffs.list are strings timestamps.push(String(post.timestamp)); - - return { + const result = await plugins.hooks.fire('filter:post.getDiffs', { + uid: caller.uid, + pid: data.pid, timestamps: timestamps, revisions: timestamps.map((timestamp, idx) => ({ timestamp: timestamp, username: usernames[idx], + uid: uids[idx], })), // Only admins, global mods and moderator of that cid can delete a diff deletable: isAdmin || isModerator, // These and post owners can restore to a different post version editable: isAdmin || isModerator || parseInt(caller.uid, 10) === parseInt(post.uid, 10), - }; + }); + return result; }; postsAPI.loadDiff = async (caller, data) => { @@ -574,3 +577,117 @@ postsAPI.getReplies = async (caller, { pid }) => { return postData; }; + +postsAPI.acceptQueuedPost = async (caller, data) => { + await canEditQueue(caller.uid, data, 'accept'); + const result = await posts.submitFromQueue(data.id); + if (result && caller.uid !== parseInt(result.uid, 10)) { + await sendQueueNotification('post-queue-accepted', result.uid, `/post/${result.pid}`); + } + await logQueueEvent(caller, result, 'accept'); + return { type: result.type, pid: result.pid, tid: result.tid }; +}; + +postsAPI.removeQueuedPost = async (caller, data) => { + await canEditQueue(caller.uid, data, 'reject'); + const result = await posts.removeFromQueue(data.id); + if (result && caller.uid !== parseInt(result.uid, 10)) { + const msg = validator.escape(String(data.message ? data.message : '')); + await sendQueueNotification( + msg ? 'post-queue-rejected-for-reason' : 'post-queue-rejected', result.uid, '/', msg + ); + } + await logQueueEvent(caller, result, 'reject'); +}; + +postsAPI.editQueuedPost = async (caller, data) => { + if (!data || !data.id || (!data.content && !data.title && !data.cid)) { + throw new Error('[[error:invalid-data]]'); + } + await posts.editQueuedContent(caller.uid, data); + if (data.content) { + return await plugins.hooks.fire('filter:parse.post', { postData: data }); + } + return { postData: data }; +}; + +postsAPI.notifyQueuedPostOwner = async (caller, data) => { + await canEditQueue(caller.uid, data, 'notify'); + const result = await posts.getFromQueue(data.id); + if (result) { + await sendQueueNotification('post-queue-notify', result.uid, `/post-queue/${data.id}`, validator.escape(String(data.message || ''))); + } +}; + +async function canEditQueue(uid, data, action) { + const [canEditQueue, queuedPost] = await Promise.all([ + posts.canEditQueue(uid, data, action), + posts.getFromQueue(data.id), + ]); + if (!queuedPost) { + throw new Error('[[error:no-post]]'); + } + if (!canEditQueue) { + throw new Error('[[error:no-privileges]]'); + } +} + +async function logQueueEvent(caller, result, type) { + const eventData = { + type: `post-queue-${result.type}-${type}`, + uid: caller.uid, + ip: caller.ip, + content: result.data.content, + targetUid: result.uid, + }; + if (result.type === 'topic') { + eventData.cid = result.data.cid; + eventData.title = result.data.title; + } else { + eventData.tid = result.data.tid; + } + if (result.pid) { + eventData.pid = result.pid; + } + await events.log(eventData); +} + +async function sendQueueNotification(type, targetUid, path, notificationText) { + const bodyShort = notificationText ? + translator.compile(`notifications:${type}`, notificationText) : + translator.compile(`notifications:${type}`); + const notifData = { + type: type, + nid: `${type}-${targetUid}-${path}`, + bodyShort: bodyShort, + path: path, + }; + if (parseInt(meta.config.postQueueNotificationUid, 10) > 0) { + notifData.from = meta.config.postQueueNotificationUid; + } + const notifObj = await notifications.create(notifData); + await notifications.push(notifObj, [targetUid]); +} + +postsAPI.changeOwner = async function (caller, data) { + if (!data || !Array.isArray(data.pids) || !data.uid) { + throw new Error('[[error:invalid-data]]'); + } + const isAdminOrGlobalMod = await user.isAdminOrGlobalMod(caller.uid); + if (!isAdminOrGlobalMod) { + throw new Error('[[error:no-privileges]]'); + } + + const postData = await posts.changeOwner(data.pids, data.uid); + const logs = postData.map(({ pid, uid, cid }) => (events.log({ + type: 'post-change-owner', + uid: caller.uid, + ip: caller.ip, + targetUid: data.uid, + pid: pid, + originalUid: uid, + cid: cid, + }))); + + await Promise.all(logs); +}; \ No newline at end of file diff --git a/src/api/search.js b/src/api/search.js index 78e120743f..ce1d1e2282 100644 --- a/src/api/search.js +++ b/src/api/search.js @@ -9,6 +9,7 @@ const messaging = require('../messaging'); const privileges = require('../privileges'); const meta = require('../meta'); const plugins = require('../plugins'); +const utils = require('../utils'); const controllersHelpers = require('../controllers/helpers'); @@ -16,8 +17,6 @@ const searchApi = module.exports; searchApi.categories = async (caller, data) => { // used by categorySearch module - - let cids = []; let matchedCids = []; const privilege = data.privilege || 'topics:read'; data.states = (data.states || ['watching', 'tracking', 'notwatching', 'ignoring']).map( @@ -25,13 +24,17 @@ searchApi.categories = async (caller, data) => { ); data.parentCid = parseInt(data.parentCid || 0, 10); + let cids; if (data.search) { ({ cids, matchedCids } = await findMatchedCids(caller.uid, data)); } else { cids = await loadCids(caller.uid, data.parentCid); - if (meta.config.activitypubEnabled) { + if (!data.hideUncategorized && meta.config.activitypubEnabled) { cids.unshift(-1); } + if (data.localOnly) { + cids = cids.filter(cid => utils.isNumber(cid)); + } } const visibleCategories = await controllersHelpers.getVisibleCategories({ @@ -66,6 +69,7 @@ async function findMatchedCids(uid, data) { query: data.search, qs: data.query, paginate: false, + localOnly: data.localOnly, }); let matchedCids = result.categories.map(c => c.cid); diff --git a/src/api/topics.js b/src/api/topics.js index 572183a195..45be990a9b 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -1,7 +1,5 @@ 'use strict'; -const validator = require('validator'); - const user = require('../user'); const topics = require('../topics'); const categories = require('../categories'); @@ -11,8 +9,8 @@ const privileges = require('../privileges'); const events = require('../events'); const batch = require('../batch'); const activitypub = require('../activitypub'); +const utils = require('../utils'); -const activitypubApi = require('./activitypub'); const apiHelpers = require('./helpers'); const { doTopicAction } = apiHelpers; @@ -23,17 +21,13 @@ const socketHelpers = require('../socket.io/helpers'); const topicsAPI = module.exports; topicsAPI._checkThumbPrivileges = async function ({ tid, uid }) { - // req.params.tid could be either a tid (pushing a new thumb to an existing topic) - // or a post UUID (a new topic being composed) - const isUUID = validator.isUUID(tid); - // Sanity-check the tid if it's strictly not a uuid - if (!isUUID && (isNaN(parseInt(tid, 10)) || !await topics.exists(tid))) { + if ((isNaN(parseInt(tid, 10)) || !await topics.exists(tid))) { throw new Error('[[error:no-topic]]'); } // While drafts are not protected, tids are - if (!isUUID && !await privileges.topics.canEdit(tid, uid)) { + if (!await privileges.topics.canEdit(tid, uid)) { throw new Error('[[error:no-privileges]]'); } }; @@ -62,6 +56,7 @@ topicsAPI.create = async function (caller, data) { const payload = { ...data }; delete payload.tid; + delete payload.generatedTitle; payload.tags = payload.tags || []; apiHelpers.setDefaultPostData(caller, payload); const isScheduling = parseInt(data.timestamp, 10) > payload.timestamp; @@ -80,16 +75,13 @@ topicsAPI.create = async function (caller, data) { } const result = await topics.post(payload); - await topics.thumbs.migrate(data.uuid, result.topicData.tid); socketHelpers.emitToUids('event:new_post', { posts: [result.postData] }, [caller.uid]); socketHelpers.emitToUids('event:new_topic', result.topicData, [caller.uid]); socketHelpers.notifyNew(caller.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); if (!isScheduling) { - setTimeout(() => { - activitypubApi.create.note(caller, { pid: result.postData.pid }); - }, 5000); + await activitypub.out.create.note(caller.uid, result.postData.pid); } return result.topicData; @@ -125,7 +117,7 @@ topicsAPI.reply = async function (caller, data) { } socketHelpers.notifyNew(caller.uid, 'newPost', result); - activitypubApi.create.note(caller, { post: postData }); + await activitypub.out.create.note(caller.uid, postData); return postData; }; @@ -235,17 +227,6 @@ topicsAPI.getThumbs = async (caller, { tid, thumbsOnly }) => { return await topics.thumbs.get(tid, { thumbsOnly }); }; -// topicsAPI.addThumb - -topicsAPI.migrateThumbs = async (caller, { from, to }) => { - await Promise.all([ - topicsAPI._checkThumbPrivileges({ tid: from, uid: caller.uid }), - topicsAPI._checkThumbPrivileges({ tid: to, uid: caller.uid }), - ]); - - await topics.thumbs.migrate(from, to); -}; - topicsAPI.deleteThumb = async (caller, { tid, path }) => { await topicsAPI._checkThumbPrivileges({ tid: tid, uid: caller.uid }); await topics.thumbs.delete(tid, path); @@ -329,6 +310,7 @@ topicsAPI.move = async (caller, { tid, cid }) => { throw new Error('[[error:no-privileges]]'); } const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'mainPid', 'slug', 'deleted']); + topicData.toCid = cid; if (!cids.includes(topicData.cid)) { cids.push(topicData.cid); } @@ -341,9 +323,15 @@ topicsAPI.move = async (caller, { tid, cid }) => { socketHelpers.emitToUids('event:topic_moved', topicData, notifyUids); if (!topicData.deleted) { socketHelpers.sendNotificationToTopicOwner(tid, caller.uid, 'move', 'notifications:moved-your-topic'); - activitypubApi.announce.note(caller, { tid }); - const { activity } = await activitypub.mocks.activities.create(topicData.mainPid, caller.uid); - await activitypub.feps.announce(topicData.mainPid, activity); + + if (utils.isNumber(cid) && parseInt(cid, 10) === -1) { + activitypub.out.remove.context(caller.uid, tid); // 7888-style + activitypub.out.delete.note(caller.uid, topicData.mainPid); // 1b12-style + } else { + activitypub.out.move.context(caller.uid, tid); + activitypub.out.announce.topic(tid); + } + activitypub.out.undo.announce('cid', topicData.cid, tid); // microblogs } await events.log({ diff --git a/src/api/users.js b/src/api/users.js index d3f9d8f7a3..7a311bbb12 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -57,7 +57,7 @@ usersAPI.update = async function (caller, data) { throw new Error('[[error:invalid-data]]'); } - const oldUserData = await user.getUserFields(data.uid, ['email', 'username']); + const oldUserData = await db.getObjectFields(`user:${data.uid}`, ['email', 'username']); if (!oldUserData || !oldUserData.username) { throw new Error('[[error:invalid-data]]'); } @@ -86,14 +86,14 @@ usersAPI.update = async function (caller, data) { await user.updateProfile(caller.uid, data); const userData = await user.getUserData(data.uid); - - if (userData.username !== oldUserData.username) { + const oldUsernameEscaped = validator.escape(String(oldUserData.username)); + if (userData.username !== oldUsernameEscaped) { await events.log({ type: 'username-change', uid: caller.uid, targetUid: data.uid, ip: caller.ip, - oldUsername: oldUserData.username, + oldUsername: oldUsernameEscaped, newUsername: userData.username, }); } @@ -615,15 +615,14 @@ usersAPI.changePicture = async (caller, data) => { throw new Error('[[error:invalid-data]]'); } - const { type, url } = data; - let picture = ''; - await user.checkMinReputation(caller.uid, data.uid, 'min:rep:profile-picture'); const canEdit = await privileges.users.canEdit(caller.uid, data.uid); if (!canEdit) { throw new Error('[[error:no-privileges]]'); } + const { type, url } = data; + let picture; if (type === 'default') { picture = ''; } else if (type === 'uploaded') { diff --git a/src/cache/lru.js b/src/cache/lru.js index 094d3a3e93..0d08ef9b4a 100644 --- a/src/cache/lru.js +++ b/src/cache/lru.js @@ -5,6 +5,7 @@ module.exports = function (opts) { const os = require('os'); const pubsub = require('../pubsub'); + const tracker = require('./tracker'); // lru-cache@7 deprecations const winston = require('winston'); @@ -31,6 +32,9 @@ module.exports = function (opts) { }); const lruCache = new LRUCache(opts); + if (!opts.name) { + winston.warn(`[cache/init] ${chalk.white.bgRed.bold('WARNING')} The cache name is not set. This will be required in the future.\n ${new Error('t').stack} `); + } const cache = {}; cache.name = opts.name; @@ -92,6 +96,9 @@ module.exports = function (opts) { }; cache.del = function (keys) { + if (!cache.enabled) { + return; + } if (!Array.isArray(keys)) { keys = [keys]; } @@ -156,5 +163,6 @@ module.exports = function (opts) { return lruCache.peek(key); }; + tracker.addCache(opts.name, cache); return cache; }; diff --git a/src/cache/tracker.js b/src/cache/tracker.js new file mode 100644 index 0000000000..43dfe17bb4 --- /dev/null +++ b/src/cache/tracker.js @@ -0,0 +1,59 @@ +'use strict'; + + +const utils = require('../utils'); + +const cacheList = Object.create(null); + +exports.addCache = function (key, cache) { + if (Object.hasOwn(cacheList, key)) { + throw new Error(`[cache/tracker] Cache with key "${key}" already exists. This will overwrite the existing cache.`); + } + cacheList[key] = cache; +}; + +exports.getCacheList = async function (sort = 'hits') { + const result = []; + for (const value of Object.values(cacheList)) { + result.push(getInfo(value, process.uptime())); + } + + result.sort((a, b) => b[sort].replace(/,/g, '') - a[sort].replace(/,/g, '')); + + result.sort(function (a, b) { + const A = a[sort].replace(/,/g, ''); + const B = b[sort].replace(/,/g, ''); + const numA = parseFloat(A); + const numB = parseFloat(B); + + if (!isNaN(numA) && !isNaN(numB)) { + return numB - numA; + } + return B.localeCompare(A); + }); + + return result; +}; + +exports.findCacheByName = function (name) { + return cacheList[name]; +}; + +function getInfo(cache, uptimeInSeconds) { + return { + name: cache.name, + length: cache.length, + max: cache.max, + maxSize: cache.maxSize, + itemCount: cache.itemCount, + percentFull: cache.name === 'post' ? + ((cache.length / cache.maxSize) * 100).toFixed(2) : + ((cache.itemCount / cache.max) * 100).toFixed(2), + hits: utils.addCommas(String(cache.hits)), + hitsPerSecond: (cache.hits / uptimeInSeconds).toFixed(2), + misses: utils.addCommas(String(cache.misses)), + hitRatio: ((cache.hits / (cache.hits + cache.misses) || 0)).toFixed(4), + enabled: cache.enabled, + ttl: cache.ttl, + }; +} \ No newline at end of file diff --git a/src/cache/ttl.js b/src/cache/ttl.js index 8647d0b9ac..a6990c6c6d 100644 --- a/src/cache/ttl.js +++ b/src/cache/ttl.js @@ -1,12 +1,18 @@ 'use strict'; module.exports = function (opts) { - const TTLCache = require('@isaacs/ttlcache'); + const { TTLCache } = require('@isaacs/ttlcache'); const os = require('os'); + const winston = require('winston'); + const chalk = require('chalk'); const pubsub = require('../pubsub'); + const tracker = require('./tracker'); const ttlCache = new TTLCache(opts); + if (!opts.name) { + winston.warn(`[cache/init] ${chalk.white.bgRed.bold('WARNING')} The cache name is not set. This will be required in the future.\n ${new Error('t').stack} `); + } const cache = {}; cache.name = opts.name; @@ -65,6 +71,9 @@ module.exports = function (opts) { }; cache.del = function (keys) { + if (!cache.enabled) { + return; + } if (!Array.isArray(keys)) { keys = [keys]; } @@ -129,5 +138,6 @@ module.exports = function (opts) { return ttlCache.get(key, { updateAgeOnGet: false }); }; + tracker.addCache(opts.name, cache); return cache; }; diff --git a/src/categories/create.js b/src/categories/create.js index f2e8f8811d..92f63f27e6 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -59,6 +59,7 @@ module.exports = function (Categories) { 'groups:topics:read', 'groups:topics:create', 'groups:topics:reply', + 'groups:topics:crosspost', 'groups:topics:tag', 'groups:posts:edit', 'groups:posts:history', @@ -151,7 +152,15 @@ module.exports = function (Categories) { } async function generateHandle(slug) { - let taken = await meta.slugTaken(slug); + let taken; + try { + taken = await meta.slugTaken(slug); + } catch (e) { + // invalid slug passed in + slug = 'category'; + taken = true; + } + let suffix; while (taken) { suffix = utils.generateUUID().slice(0, 8); diff --git a/src/categories/data.js b/src/categories/data.js index 9ad2783203..4f9afa7932 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -36,8 +36,8 @@ module.exports = function (Categories) { return []; } - cids = cids.map(cid => parseInt(cid, 10)); - const keys = cids.map(cid => `category:${cid}`); + cids = cids.map(cid => (utils.isNumber(cid) ? parseInt(cid, 10) : cid)); + const keys = cids.map(cid => (utils.isNumber(cid) ? `category:${cid}` : `categoryRemote:${cid}`)); const categories = await db.getObjects(keys, fields); // Handle cid -1 @@ -87,16 +87,16 @@ module.exports = function (Categories) { }; Categories.setCategoryField = async function (cid, field, value) { - await db.setObjectField(`category:${cid}`, field, value); + await db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, field, value); }; Categories.incrementCategoryFieldBy = async function (cid, field, value) { - await db.incrObjectFieldBy(`category:${cid}`, field, value); + await db.incrObjectFieldBy(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, field, value); }; }; -function defaultIntField(category, fields, fieldName, defaultField) { - if (!fields.length || fields.includes(fieldName)) { +function defaultIntField(category, hasField, fieldName, defaultField) { + if (hasField(fieldName)) { const useDefault = !category.hasOwnProperty(fieldName) || category[fieldName] === null || category[fieldName] === '' || @@ -111,32 +111,44 @@ function modifyCategory(category, fields) { return; } - defaultIntField(category, fields, 'minTags', 'minimumTagsPerTopic'); - defaultIntField(category, fields, 'maxTags', 'maximumTagsPerTopic'); - defaultIntField(category, fields, 'postQueue', 'postQueue'); + const hasField = utils.createFieldChecker(fields); + + defaultIntField(category, hasField, 'minTags', 'minimumTagsPerTopic'); + defaultIntField(category, hasField, 'maxTags', 'maximumTagsPerTopic'); + defaultIntField(category, hasField, 'postQueue', 'postQueue'); db.parseIntFields(category, intFields, fields); - const escapeFields = ['name', 'description', 'federatedDescription', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'class', 'link']; + const escapeFields = [ + 'name', 'nickname', 'description', 'color', 'bgColor', + 'backgroundImage', 'imageClass', 'class', 'link', + ]; escapeFields.forEach((field) => { - if (category.hasOwnProperty(field)) { + if (hasField(field)) { category[field] = validator.escape(String(category[field] || '')); } }); - if (category.hasOwnProperty('icon')) { + if (hasField('icon')) { category.icon = category.icon || 'hidden'; + if (category.icon === 'fa-none') { + category.icon = 'fa-nbb-none'; + } } - if (category.hasOwnProperty('post_count')) { + if (hasField('post_count')) { category.totalPostCount = category.post_count; } - if (category.hasOwnProperty('topic_count')) { + if (hasField('topic_count')) { category.totalTopicCount = category.topic_count; } - if (category.description) { + if (hasField('description')) { category.descriptionParsed = category.descriptionParsed || category.description; } + + if (category.nickname) { + category.name = category.nickname; + } } diff --git a/src/categories/delete.js b/src/categories/delete.js index 6581098c10..243b310106 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -7,7 +7,9 @@ const plugins = require('../plugins'); const topics = require('../topics'); const groups = require('../groups'); const privileges = require('../privileges'); +const activitypub = require('../activitypub'); const cache = require('../cache'); +const utils = require('../utils'); module.exports = function (Categories) { Categories.purge = async function (cid, uid) { @@ -15,12 +17,14 @@ module.exports = function (Categories) { await async.eachLimit(tids, 10, async (tid) => { await topics.purgePostsAndTopic(tid, uid); }); + await db.sortedSetRemove(`cid:${cid}:tids`, tids); }, { alwaysStartAt: 0 }); const pinnedTids = await db.getSortedSetRevRange(`cid:${cid}:tids:pinned`, 0, -1); await async.eachLimit(pinnedTids, 10, async (tid) => { await topics.purgePostsAndTopic(tid, uid); }); + await db.sortedSetRemove(`cid:${cid}:tids:pinned`, pinnedTids); const categoryData = await Categories.getCategoryData(cid); await purgeCategory(cid, categoryData); plugins.hooks.fire('action:category.delete', { cid: cid, uid: uid, category: categoryData }); @@ -38,6 +42,7 @@ module.exports = function (Categories) { await removeFromParent(cid); await deleteTags(cid); + await activitypub.actors.removeGroup(cid); await db.deleteAll([ `cid:${cid}:tids`, `cid:${cid}:tids:pinned`, @@ -51,7 +56,7 @@ module.exports = function (Categories) { `cid:${cid}:uid:watch:state`, `cid:${cid}:children`, `cid:${cid}:tag:whitelist`, - `category:${cid}`, + `${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, ]); const privilegeList = await privileges.categories.getPrivilegeList(); await groups.destroy(privilegeList.map(privilege => `cid:${cid}:privileges:${privilege}`)); diff --git a/src/categories/icon.js b/src/categories/icon.js index 93f5614630..7aa94ab802 100644 --- a/src/categories/icon.js +++ b/src/categories/icon.js @@ -5,7 +5,7 @@ const fs = require('fs/promises'); const nconf = require('nconf'); const winston = require('winston'); const { default: satori } = require('satori'); -const { Resvg } = require('@resvg/resvg-js'); +const sharp = require('sharp'); const utils = require('../utils'); @@ -96,9 +96,9 @@ Icons.regenerate = async (cid) => { await fs.writeFile(path.resolve(nconf.get('upload_path'), 'category', `category-${cid}-icon.svg`), svg); // Generate and save PNG - const resvg = new Resvg(Buffer.from(svg)); - const pngData = resvg.render(); - const pngBuffer = pngData.asPng(); + const pngBuffer = await sharp(Buffer.from(svg)) + .png() + .toBuffer(); await fs.writeFile(path.resolve(nconf.get('upload_path'), 'category', `category-${cid}-icon.png`), pngBuffer); diff --git a/src/categories/index.js b/src/categories/index.js index cde2f02d6a..948ff86226 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -8,8 +8,10 @@ const user = require('../user'); const topics = require('../topics'); const plugins = require('../plugins'); const privileges = require('../privileges'); +const activitypub = require('../activitypub'); const cache = require('../cache'); const meta = require('../meta'); +const utils = require('../utils'); const Categories = module.exports; @@ -26,9 +28,14 @@ require('./search')(Categories); Categories.icons = require('./icon'); Categories.exists = async function (cids) { - return await db.exists( - Array.isArray(cids) ? cids.map(cid => `category:${cid}`) : `category:${cids}` - ); + let keys; + if (Array.isArray(cids)) { + keys = cids.map(cid => (utils.isNumber(cid) ? `category:${cid}` : `categoryRemote:${cid}`)); + } else { + keys = utils.isNumber(cids) ? `category:${cids}` : `categoryRemote:${cids}`; + } + + return await db.exists(keys); }; Categories.existsByHandle = async function (handle) { @@ -51,12 +58,13 @@ Categories.getCategoryById = async function (data) { Categories.getTopicCount(data), Categories.getWatchState([data.cid], data.uid), getChildrenTree(category, data.uid), + !utils.isNumber(data.cid) ? activitypub.actors.getFollowers(data.cid) : null, ]; if (category.parentCid) { promises.push(Categories.getCategoryData(category.parentCid)); } - const [topics, topicCount, watchState, , parent] = await Promise.all(promises); + const [topics, topicCount, watchState, , localFollowers, parent] = await Promise.all(promises); category.topics = topics.topics; category.nextStart = topics.nextStart; @@ -65,6 +73,7 @@ Categories.getCategoryById = async function (data) { category.isTracked = watchState[0] === Categories.watchStates.tracking; category.isNotWatched = watchState[0] === Categories.watchStates.notwatching; category.isIgnored = watchState[0] === Categories.watchStates.ignoring; + category.hasFollowers = localFollowers ? (localFollowers.uids.size + localFollowers.cids.size) > 0 : localFollowers; category.parent = parent; calculateTopicPostCount(category); @@ -76,7 +85,16 @@ Categories.getCategoryById = async function (data) { }; Categories.getCidByHandle = async function (handle) { - return await db.sortedSetScore('categoryhandle:cid', handle); + if (!handle) { + return null; + } + let cid = await db.sortedSetScore('categoryhandle:cid', handle); + if (!cid) { + // remote cids + cid = await db.getObjectField('handle:cid', handle); + } + + return cid; }; Categories.getAllCidsFromSet = async function (key) { @@ -86,7 +104,7 @@ Categories.getAllCidsFromSet = async function (key) { } cids = await db.getSortedSetRange(key, 0, -1); - cids = cids.map(cid => parseInt(cid, 10)); + cids = cids.map(cid => utils.isNumber(cid) ? parseInt(cid, 10) : cid); cache.set(key, cids); return cids.slice(); }; @@ -250,7 +268,6 @@ async function getChildrenTree(category, uid) { } let childrenData = await Categories.getCategoriesData(childrenCids); childrenData = childrenData.filter(Boolean); - childrenCids = childrenData.map(child => child.cid); Categories.getTree([category].concat(childrenData), category.parentCid); } @@ -259,7 +276,7 @@ Categories.getChildrenTree = getChildrenTree; Categories.getParentCids = async function (currentCid) { let cid = currentCid; const parents = []; - while (parseInt(cid, 10)) { + while (utils.isNumber(cid) ? parseInt(cid, 10) : cid) { // eslint-disable-next-line cid = await Categories.getCategoryField(cid, 'parentCid'); if (cid) { @@ -274,12 +291,12 @@ Categories.getChildrenCids = async function (rootCid) { async function recursive(keys) { let childrenCids = await db.getSortedSetRange(keys, 0, -1); - childrenCids = childrenCids.filter(cid => !allCids.includes(parseInt(cid, 10))); + childrenCids = childrenCids.filter(cid => !allCids.includes(utils.isNumber(cid) ? parseInt(cid, 10) : cid)); if (!childrenCids.length) { return; } keys = childrenCids.map(cid => `cid:${cid}:children`); - childrenCids.forEach(cid => allCids.push(parseInt(cid, 10))); + childrenCids.forEach(cid => allCids.push(utils.isNumber(cid) ? parseInt(cid, 10) : cid)); await recursive(keys); } const key = `cid:${rootCid}:children`; diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index cb7056bfbb..d5f4c99764 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -10,6 +10,7 @@ const topics = require('../topics'); const privileges = require('../privileges'); const plugins = require('../plugins'); const batch = require('../batch'); +const utils = require('../utils'); module.exports = function (Categories) { Categories.getRecentReplies = async function (cid, uid, start, stop) { @@ -27,7 +28,7 @@ module.exports = function (Categories) { Categories.updateRecentTid = async function (cid, tid) { const [count, numRecentReplies] = await Promise.all([ db.sortedSetCard(`cid:${cid}:recent_tids`), - db.getObjectField(`category:${cid}`, 'numRecentReplies'), + db.getObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, 'numRecentReplies'), ]); if (count >= numRecentReplies) { @@ -71,7 +72,7 @@ module.exports = function (Categories) { return; } const categoriesToLoad = categoryData.filter(c => c && c.numRecentReplies && parseInt(c.numRecentReplies, 10) > 0); - let keys = []; + let keys; if (plugins.hooks.hasListeners('filter:categories.getRecentTopicReplies')) { const result = await plugins.hooks.fire('filter:categories.getRecentTopicReplies', { categories: categoriesToLoad, @@ -95,10 +96,14 @@ module.exports = function (Categories) { }; async function getTopics(tids, uid) { - const topicData = await topics.getTopicsFields( - tids, - ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'] - ); + const [topicData, crossposts] = await Promise.all([ + topics.getTopicsFields( + tids, + ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'] + ), + topics.crossposts.get(tids), + ]); + topicData.forEach((topic) => { if (topic) { topic.teaserPid = topic.teaserPid || topic.mainPid; @@ -123,6 +128,7 @@ module.exports = function (Categories) { slug: topicData[index].slug, title: topicData[index].title, }; + teaser.crossposts = crossposts[index]; } }); return teasers.filter(Boolean); @@ -131,12 +137,20 @@ module.exports = function (Categories) { function assignTopicsToCategories(categories, topics) { categories.forEach((category) => { if (category) { - category.posts = topics.filter(t => t.cid && (t.cid === category.cid || t.parentCids.includes(category.cid))) + category.posts = topics.filter(t => + t.cid && + (t.cid === category.cid || + (t.parentCids && t.parentCids.includes(category.cid)) || + (t.crossposts.some(({ cid }) => parseInt(cid, 10) === category.cid)) + )) .sort((a, b) => b.timestamp - a.timestamp) .slice(0, parseInt(category.numRecentReplies, 10)); } }); - topics.forEach((t) => { t.parentCids = undefined; }); + topics.forEach((t) => { + t.parentCids = undefined; + t.crossposts = undefined; + }); } function bubbleUpChildrenPosts(categoryData) { diff --git a/src/categories/search.js b/src/categories/search.js index 685628f32c..722ecb01d9 100644 --- a/src/categories/search.js +++ b/src/categories/search.js @@ -3,19 +3,29 @@ const _ = require('lodash'); const privileges = require('../privileges'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); const db = require('../database'); +const utils = require('../utils'); module.exports = function (Categories) { Categories.search = async function (data) { const query = data.query || ''; const page = data.page || 1; const uid = data.uid || 0; + const localOnly = data.localOnly || false; const paginate = data.hasOwnProperty('paginate') ? data.paginate : true; const startTime = process.hrtime(); + if (activitypub.helpers.isWebfinger(query)) { + await activitypub.actors.assertGroup([query]); + } + let cids = await findCids(query, data.hardCap); + if (localOnly) { + cids = cids.filter(cid => utils.isNumber(cid)); + } const result = await plugins.hooks.fire('filter:categories.search', { data: data, @@ -38,7 +48,8 @@ module.exports = function (Categories) { const childrenCids = await getChildrenCids(cids, uid); const uniqCids = _.uniq(cids.concat(childrenCids)); - const categoryData = await Categories.getCategories(uniqCids); + let categoryData = await Categories.getCategories(uniqCids); + categoryData = categoryData.filter(Boolean); Categories.getTree(categoryData, 0); await Categories.getRecentTopicReplies(categoryData, uid, data.qs); @@ -58,7 +69,7 @@ module.exports = function (Categories) { return c1.order - c2.order; }); searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2); - searchResult.categories = categoryData.filter(c => cids.includes(c.cid)); + searchResult.categories = categoryData.filter(c => cids.includes(String(c.cid))); return searchResult; }; @@ -71,7 +82,12 @@ module.exports = function (Categories) { match: `*${String(query).toLowerCase()}*`, limit: hardCap || 500, }); - return data.map(data => parseInt(data.split(':').pop(), 10)); + return data.map((data) => { + const split = data.split(':'); + split.shift(); + const cid = split.join(':'); + return cid; + }); } async function getChildrenCids(cids, uid) { diff --git a/src/categories/topics.js b/src/categories/topics.js index 64e9046614..d7cf16c061 100644 --- a/src/categories/topics.js +++ b/src/categories/topics.js @@ -9,6 +9,7 @@ const user = require('../user'); const notifications = require('../notifications'); const translator = require('../translator'); const batch = require('../batch'); +const utils = require('../utils'); module.exports = function (Categories) { Categories.getCategoryTopics = async function (data) { @@ -82,7 +83,7 @@ module.exports = function (Categories) { const set = await Categories.buildTopicsSortedSet(data); if (Array.isArray(set)) { return await db.sortedSetIntersectCard(set); - } else if (data.targetUid && set) { + } else if (parseInt(data.cid, 10) === -1 || (data.targetUid && set)) { return await db.sortedSetCard(set); } return data.category.topic_count; @@ -186,7 +187,7 @@ module.exports = function (Categories) { } const promises = [ db.sortedSetAdd(`cid:${cid}:pids`, postData.timestamp, postData.pid), - db.incrObjectField(`category:${cid}`, 'post_count'), + db.incrObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, 'post_count'), ]; if (!pinned) { promises.push(db.sortedSetIncrBy(`cid:${cid}:tids:posts`, 1, postData.tid)); @@ -236,10 +237,14 @@ module.exports = function (Categories) { } const { displayname } = postData.user; - const categoryName = await Categories.getCategoryField(cid, 'name'); + const [categoryName, title] = await Promise.all([ + Categories.getCategoryField(cid, 'name'), + topics.getTopicField(postData.topic.tid, 'title'), + ]); + const notifBase = 'notifications:user-posted-topic-in-category'; - const bodyShort = translator.compile(notifBase, displayname, categoryName); + const bodyShort = translator.compile(notifBase, displayname, title, categoryName); const notification = await notifications.create({ type: 'new-topic-in-category', @@ -254,18 +259,29 @@ module.exports = function (Categories) { notifications.push(notification, followers); }; - Categories.sortTidsBySet = async (tids, cid, sort) => { - sort = sort || meta.config.categoryTopicSort || 'recently_replied'; - const sortToSet = { - recently_replied: `cid:${cid}:tids`, - recently_created: `cid:${cid}:tids:create`, - most_posts: `cid:${cid}:tids:posts`, - most_votes: `cid:${cid}:tids:votes`, - most_views: `cid:${cid}:tids:views`, - }; + Categories.sortTidsBySet = async (tids, sort) => { + let cids = await topics.getTopicsFields(tids, ['cid']); + cids = cids.map(({ cid }) => cid); + + function getSet(cid, sort) { + sort = sort || meta.config.categoryTopicSort || 'recently_replied'; + const sortToSet = { + recently_replied: `cid:${cid}:tids`, + recently_created: `cid:${cid}:tids:create`, + most_posts: `cid:${cid}:tids:posts`, + most_votes: `cid:${cid}:tids:votes`, + most_views: `cid:${cid}:tids:views`, + }; + + return sortToSet[sort]; + } + + const scores = await Promise.all(tids.map(async (tid, idx) => { + const cid = cids[idx]; + const orderBy = getSet(cid, sort); + return await db.sortedSetScore(orderBy, tid); + })); - const orderBy = sortToSet[sort]; - const scores = await db.sortedSetScores(orderBy, tids); const sorted = tids .map((tid, idx) => [tid, scores[idx]]) .sort(([, a], [, b]) => b - a) diff --git a/src/categories/update.js b/src/categories/update.js index 2f2effd96d..ff4d6e4d11 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -60,7 +60,7 @@ module.exports = function (Categories) { return await updateOrder(cid, value); } - await db.setObjectField(`category:${cid}`, key, value); + await db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, key, value); if (key === 'description') { await Categories.parseDescription(cid, value); } @@ -83,7 +83,7 @@ module.exports = function (Categories) { await Promise.all([ db.sortedSetRemove(`cid:${oldParent}:children`, cid), db.sortedSetAdd(`cid:${newParent}:children`, categoryData.order, cid), - db.setObjectField(`category:${cid}`, 'parentCid', newParent), + db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, 'parentCid', newParent), ]); cache.del([ @@ -104,8 +104,12 @@ module.exports = function (Categories) { } async function updateOrder(cid, order) { - const parentCid = await Categories.getCategoryField(cid, 'parentCid'); - await db.sortedSetsAdd('categories:cid', order, cid); + const parentCid = (await Categories.getCategoryField(cid, 'parentCid')) || 0; + const isLocal = utils.isNumber(cid); + + if (isLocal) { + await db.sortedSetsAdd('categories:cid', order, cid); + } const childrenCids = await db.getSortedSetRange( `cid:${parentCid}:children`, 0, -1 @@ -128,7 +132,7 @@ module.exports = function (Categories) { ); await db.setObjectBulk( - childrenCids.map((cid, index) => [`category:${cid}`, { order: index + 1 }]) + childrenCids.map((cid, index) => [`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, { order: index + 1 }]) ); cache.del([ @@ -161,9 +165,9 @@ module.exports = function (Categories) { throw new Error('[[error:category.handle-taken]]'); } + await db.sortedSetRemove('categoryhandle:cid', existing); await Promise.all([ db.setObjectField(`category:${cid}`, 'handle', handle), - db.sortedSetRemove('categoryhandle:cid', existing), db.sortedSetAdd('categoryhandle:cid', cid, handle), ]); } diff --git a/src/categories/watch.js b/src/categories/watch.js index 4f53ea01e5..8eb8e9b7c0 100644 --- a/src/categories/watch.js +++ b/src/categories/watch.js @@ -3,6 +3,7 @@ const db = require('../database'); const user = require('../user'); const activitypub = require('../activitypub'); +const utils = require('../utils'); module.exports = function (Categories) { Categories.watchStates = { @@ -32,7 +33,11 @@ module.exports = function (Categories) { user.getSettings(uid), db.sortedSetsScore(keys, uid), ]); - return states.map(state => state || Categories.watchStates[userSettings.categoryWatchState]); + + const fallbacks = cids.map(cid => (utils.isNumber(cid) ? + Categories.watchStates[userSettings.categoryWatchState] : Categories.watchStates.notwatching)); + + return states.map((state, idx) => state || fallbacks[idx]); }; Categories.getIgnorers = async function (cid, start, stop) { diff --git a/src/cli/index.js b/src/cli/index.js index a413ec6ef1..8e3db4e344 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -5,6 +5,15 @@ const path = require('path'); require('../../require-main'); +// https://github.com/NodeBB/NodeBB/issues/13734 +// check dev flag early so packageInstall.installAll() can use it +const isDev = process.argv.some(arg => + arg === '-d' || + arg === '--dev' || + (arg.startsWith('-') && !arg.startsWith('--') && arg.includes('d'))); + +process.env.NODE_ENV = isDev ? 'development' : (process.env.NODE_ENV || 'production'); + const packageInstall = require('./package-install'); const { paths } = require('../constants'); @@ -42,7 +51,7 @@ try { checkVersion('lru-cache'); } catch (e) { if (['ENOENT', 'DEP_WRONG_VERSION', 'MODULE_NOT_FOUND'].includes(e.code)) { - console.warn('Dependencies outdated or not yet installed.'); + console.warn(`Dependencies outdated or not yet installed. Error Code: ${e.code}\n${e.stack}`); console.log('Installing them now...\n'); packageInstall.updatePackageFile(); @@ -96,8 +105,7 @@ nconf.argv(opts).env({ separator: '__', }); -process.env.NODE_ENV = process.env.NODE_ENV || 'production'; -global.env = process.env.NODE_ENV || 'production'; + prestart.setupWinston(); @@ -139,7 +147,6 @@ program .description('Start NodeBB in verbose development mode') .action(() => { process.env.NODE_ENV = 'development'; - global.env = 'development'; require('./running').start({ ...program.opts(), dev: true }); }); program @@ -206,7 +213,6 @@ program .action((targets, options) => { if (program.opts().dev) { process.env.NODE_ENV = 'development'; - global.env = 'development'; } require('./manage').build(targets.length ? targets : true, options); }) @@ -296,7 +302,6 @@ program options.unattended = program.opts().unattended; if (program.opts().dev) { process.env.NODE_ENV = 'development'; - global.env = 'development'; } require('./upgrade').upgrade(scripts.length ? scripts : true, options); }); diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index 61a13399a0..1192687a5f 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -143,7 +143,7 @@ async function renderRoute(name, req, res) { } editController.uploadPicture = async function (req, res, next) { - const userPhoto = req.files.files[0]; + const userPhoto = req.files[0]; try { const updateUid = await user.getUidByUserslug(req.params.userslug); const isAllowed = await privileges.users.canEdit(req.uid, updateUid); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 53ecea8534..af5963002e 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -119,13 +119,6 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {}) userData.signature = escape(userData.signature); userData.birthday = validator.escape(String(userData.birthday || '')); userData.moderationNote = validator.escape(String(userData.moderationNote || '')); - - if (userData['cover:url']) { - userData['cover:url'] = userData['cover:url'].startsWith('http') ? userData['cover:url'] : (nconf.get('relative_path') + userData['cover:url']); - } else { - userData['cover:url'] = require('../../coverPhoto').getDefaultProfileCover(userData.uid); - } - 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']; diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index b22e6eb73c..fab317fe17 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -52,6 +52,11 @@ profileController.get = async function (req, res, next) { if (meta.config.activitypubEnabled) { // Include link header for richer parsing res.set('Link', `<${nconf.get('url')}/uid/${userData.uid}>; rel="alternate"; type="application/activity+json"`); + + if (!utils.isNumber(userData.uid)) { + res.set('Link', `<${userData.url || userData.uid}>; rel="canonical"`); + res.set('x-robots-tag', 'noindex'); + } } res.render('account/profile', userData); @@ -163,10 +168,21 @@ function addTags(res, userData) { res.locals.linkTags = []; - res.locals.linkTags.push({ - rel: 'canonical', - href: `${url}/user/${userData.userslug}`, - }); + if (utils.isNumber(userData.uid)) { + res.locals.linkTags.push({ + rel: 'canonical', + href: `${url}/user/${userData.userslug}`, + }); + } else { + res.locals.linkTags.push({ + rel: 'canonical', + href: userData.url || userData.uid, + }); + res.locals.metaTags.push({ + name: 'robots', + content: 'noindex', + }); + } if (meta.config.activitypubEnabled) { res.locals.linkTags.push({ diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index a5ab46e3da..cc88056409 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -43,6 +43,7 @@ settingsController.get = async function (req, res, next) { getNotificationSettings(userData), getHomePageRoutes(userData), getSkinOptions(userData), + getChatAllowDenyList(userData), ]); userData.customSettings = data.customSettings; @@ -254,3 +255,13 @@ async function getSkinOptions(userData) { }); return bootswatchSkinOptions; } + +async function getChatAllowDenyList(userData) { + const [chatAllowListUsers, chatDenyListUsers] = await Promise.all([ + user.getUsersFields(userData.settings.chatAllowList, ['uid', 'username', 'picture']), + user.getUsersFields(userData.settings.chatDenyList, ['uid', 'username', 'picture']), + ]); + + userData.settings.chatAllowListUsers = chatAllowListUsers; + userData.settings.chatDenyListUsers = chatDenyListUsers; +}; diff --git a/src/controllers/activitypub/actors.js b/src/controllers/activitypub/actors.js index f9212ba09d..dddd402f2d 100644 --- a/src/controllers/activitypub/actors.js +++ b/src/controllers/activitypub/actors.js @@ -77,14 +77,14 @@ Actors.note = async function (req, res, next) { } const payload = await activitypub.mocks.notes.public(post); - const { to, cc } = await activitypub.buildRecipients(payload, { pid: post.pid, uid: post.user.uid }); + const { to, cc } = await activitypub.buildRecipients(payload, { pid: post.pid, uid: post.user.uid, targets: false }); payload.to = to; payload.cc = cc; res.status(200).json(payload); }; -Actors.replies = async function (req, res) { +Actors.replies = async function (req, res, next) { const allowed = utils.isNumber(req.params.pid) && await privileges.posts.can('topics:read', req.params.pid, activitypub._constants.uid); const exists = await posts.exists(req.params.pid); if (!allowed || !exists) { @@ -92,15 +92,22 @@ Actors.replies = async function (req, res) { } const page = parseInt(req.query.page, 10); - const replies = await activitypub.helpers.generateCollection({ - set: `pid:${req.params.pid}:replies`, - page, - perPage: meta.config.postsPerPage, - url: `${nconf.get('url')}/post/${req.params.pid}/replies`, - }); + let replies; + try { + replies = await activitypub.helpers.generateCollection({ + set: `pid:${req.params.pid}:replies`, + page, + perPage: meta.config.postsPerPage, + url: `${nconf.get('url')}/post/${req.params.pid}/replies`, + }); + } catch (e) { + return next(); // invalid page; 404 + } // Convert pids to urls - replies.orderedItems = replies.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + if (replies.orderedItems) { + replies.orderedItems = replies.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + } const object = { '@context': 'https://www.w3.org/ns/activitystreams', @@ -126,19 +133,25 @@ Actors.topic = async function (req, res, next) { return next(); } - let [collection, pids] = await Promise.all([ - activitypub.helpers.generateCollection({ - set: `tid:${req.params.tid}:posts`, - method: posts.getPidsFromSet, - page, - perPage, - url: `${nconf.get('url')}/topic/${req.params.tid}/posts`, - }), - db.getSortedSetMembers(`tid:${req.params.tid}:posts`), - ]); + let collection; + let pids; + try { + // pids are used in generation of digest only. + ([collection, pids] = await Promise.all([ + activitypub.helpers.generateCollection({ + set: `tid:${req.params.tid}:posts`, + method: posts.getPidsFromSet, + page, + perPage, + url: `${nconf.get('url')}/topic/${req.params.tid}`, + }), + db.getSortedSetMembers(`tid:${req.params.tid}:posts`), + ])); + } catch (e) { + return next(); // invalid page; 404 + } pids.push(mainPid); pids = pids.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); - collection.totalItems += 1; // account for mainPid // Generate digest for ETag const digest = activitypub.helpers.generateDigest(new Set(pids)); @@ -155,12 +168,17 @@ Actors.topic = async function (req, res, next) { } res.set('ETag', digest); - // Convert pids to urls - if (page || collection.totalItems < meta.config.postsPerPage) { + // Add OP to collection on first (or only) page + if (page || collection.totalItems < perPage) { collection.orderedItems = collection.orderedItems || []; - if (!page || page === 1) { // add OP to collection + if (!page || page === 1) { collection.orderedItems.unshift(mainPid); + collection.totalItems += 1; } + } + + // Convert pids to urls + if (collection.orderedItems) { collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); } diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index ccf85b2012..c64ef9ef57 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -6,6 +6,7 @@ const winston = require('winston'); const meta = require('../../meta'); const user = require('../../user'); const activitypub = require('../../activitypub'); +const utils = require('../../utils'); const helpers = require('../helpers'); const Controller = module.exports; @@ -30,7 +31,7 @@ Controller.fetch = async (req, res, next) => { if (typeof result === 'string') { return helpers.redirect(res, result); } else if (result) { - const { id, type } = await activitypub.get('uid', req.uid || 0, url.href); + const { id, type } = await activitypub.get('uid', req.uid, url.href); switch (true) { case activitypub._constants.acceptedPostTypes.includes(type): { return helpers.redirect(res, `/post/${encodeURIComponent(id)}`); @@ -47,6 +48,11 @@ Controller.fetch = async (req, res, next) => { } } + // Force outgoing links page on direct access + if (!res.locals.isAPI) { + url = new URL(`outgoing?url=${encodeURIComponent(url.href)}`, nconf.get('url')); + } + helpers.redirect(res, url.href, false); } catch (e) { if (!url || !url.href) { @@ -60,71 +66,53 @@ Controller.fetch = async (req, res, next) => { Controller.getFollowing = async (req, res) => { const { followingCount, followingRemoteCount } = await user.getUserFields(req.params.uid, ['followingCount', 'followingRemoteCount']); const totalItems = parseInt(followingCount || 0, 10) + parseInt(followingRemoteCount || 0, 10); - let orderedItems; - let next = (totalItems && `${nconf.get('url')}/uid/${req.params.uid}/following?page=`) || null; - if (totalItems) { - if (req.query.page) { - const page = parseInt(req.query.page, 10) || 1; - const resultsPerPage = 50; - const start = Math.max(0, page - 1) * resultsPerPage; - const stop = start + resultsPerPage - 1; + const count = totalItems; + const collection = await activitypub.helpers.generateCollection({ + method: user.getFollowing.bind(null, req.params.uid), + count, + perPage: 50, + page: req.query.page, + url: `${nconf.get('url')}/uid/${req.params.uid}/following`, + }); - orderedItems = await user.getFollowing(req.params.uid, start, stop); - orderedItems = orderedItems.map(({ userslug }) => `${nconf.get('url')}/user/${userslug}`); - if (stop < totalItems - 1) { - next = `${next}${page + 1}`; - } else { - next = null; + if (collection.hasOwnProperty('orderedItems')) { + collection.orderedItems = collection.orderedItems.map(({ uid }) => { + if (utils.isNumber(uid)) { + return `${nconf.get('url')}/uid/${uid}`; } - } else { - orderedItems = []; - next = `${next}1`; - } + + return uid; + }); } - res.status(200).json({ - '@context': 'https://www.w3.org/ns/activitystreams', - type: 'OrderedCollection', - totalItems, - orderedItems, - next, - }); + res.status(200).json(collection); }; Controller.getFollowers = async (req, res) => { const { followerCount, followerRemoteCount } = await user.getUserFields(req.params.uid, ['followerCount', 'followerRemoteCount']); const totalItems = parseInt(followerCount || 0, 10) + parseInt(followerRemoteCount || 0, 10); - let orderedItems = []; - let next = (totalItems && `${nconf.get('url')}/uid/${req.params.uid}/followers?page=`) || null; - if (totalItems) { - if (req.query.page) { - const page = parseInt(req.query.page, 10) || 1; - const resultsPerPage = 50; - const start = Math.max(0, page - 1) * resultsPerPage; - const stop = start + resultsPerPage - 1; + const count = totalItems; + const collection = await activitypub.helpers.generateCollection({ + method: user.getFollowers.bind(null, req.params.uid), + count, + perPage: 50, + page: req.query.page, + url: `${nconf.get('url')}/uid/${req.params.uid}/followers`, + }); - orderedItems = await user.getFollowers(req.params.uid, start, stop); - orderedItems = orderedItems.map(({ userslug }) => `${nconf.get('url')}/user/${userslug}`); - if (stop < totalItems - 1) { - next = `${next}${page + 1}`; - } else { - next = null; + if (collection.hasOwnProperty('orderedItems')) { + collection.orderedItems = collection.orderedItems.map(({ uid }) => { + if (utils.isNumber(uid)) { + return `${nconf.get('url')}/uid/${uid}`; } - } else { - orderedItems = []; - next = `${next}1`; - } + + return uid; + }); } - res.status(200).json({ - '@context': 'https://www.w3.org/ns/activitystreams', - type: 'OrderedCollection', - totalItems, - orderedItems, - next, - }); + res.status(200).json(collection); }; Controller.getOutbox = async (req, res) => { @@ -161,15 +149,15 @@ Controller.postInbox = async (req, res) => { // Note: underlying methods are internal use only, hence no exposure via src/api const method = String(req.body.type).toLowerCase(); if (!activitypub.inbox.hasOwnProperty(method)) { - winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`); - return res.sendStatus(501); + activitypub.helpers.log(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`); + return res.sendStatus(200); } try { await activitypub.inbox[method](req); await activitypub.record(req.body); - helpers.formatApiResponse(202, res); + await helpers.formatApiResponse(202, res); } catch (e) { - helpers.formatApiResponse(500, res, e); + helpers.formatApiResponse(500, res, e).catch(err => winston.error(err.stack)); } }; diff --git a/src/controllers/activitypub/topics.js b/src/controllers/activitypub/topics.js index afcc7eb942..11cc8368f4 100644 --- a/src/controllers/activitypub/topics.js +++ b/src/controllers/activitypub/topics.js @@ -1,24 +1,19 @@ 'use strict'; -const nconf = require('nconf'); +const _ = require('lodash'); +const meta = require('../../meta'); const user = require('../../user'); const topics = require('../../topics'); - +const posts = require('../../posts'); +const categories = require('../../categories'); +const translator = require('../../translator'); const pagination = require('../../pagination'); +const utils = require('../../utils'); const helpers = require('../helpers'); -const categories = require('../../categories'); -const privileges = require('../../privileges'); -const translator = require('../../translator'); -const meta = require('../../meta'); - const controller = module.exports; -const validSorts = [ - 'recently_replied', 'recently_created', 'most_posts', 'most_votes', 'most_views', -]; - controller.list = async function (req, res) { if (!req.uid) { return helpers.redirect(res, '/recent?cid=-1', false); @@ -29,20 +24,14 @@ controller.list = async function (req, res) { const start = Math.max(0, (page - 1) * topicsPerPage); const stop = start + topicsPerPage - 1; - const [userPrivileges, tagData, userSettings, rssToken] = await Promise.all([ - privileges.categories.get('-1', req.uid), - helpers.getSelectedTag(req.query.tag), - user.getSettings(req.uid), - user.auth.getFeedToken(req.uid), - ]); - const sort = validSorts.includes(req.query.sort) ? req.query.sort : userSettings.categoryTopicSort; + const userSettings = await user.getSettings(req.uid); const targetUid = await user.getUidByUserslug(req.query.author); - const cidQuery = { + let cidQuery = { uid: req.uid, cid: '-1', start: start, stop: stop, - sort: sort, + sort: req.query.sort, settings: userSettings, query: req.query, tag: req.query.tag, @@ -50,27 +39,88 @@ controller.list = async function (req, res) { }; const data = await categories.getCategoryById(cidQuery); delete data.children; + data.sort = req.query.sort; - let tids = await categories.getTopicIds(cidQuery); - tids = await categories.sortTidsBySet(tids, -1, sort); // sorting not handled if cid is -1 - data.topicCount = tids.length; - data.topics = await topics.getTopicsByTids(tids, { uid: req.uid }); - topics.calculateTopicIndices(data.topics, start); + let tids; + let topicCount; + if (req.query.sort === 'popular') { + cidQuery = { + ...cidQuery, + cids: ['-1'], + sort: 'posts', + term: req.query.term || 'day', + }; + delete cidQuery.cid; + ({ tids, topicCount } = await topics.getSortedTopics(cidQuery)); + tids = tids.slice(start, stop !== -1 ? stop + 1 : undefined); + } else { + tids = await categories.getTopicIds(cidQuery); + topicCount = await categories.getTopicCount(cidQuery); + } + data.topicCount = topicCount; + + const mainPids = await topics.getMainPids(tids); + const postData = await posts.getPostSummaryByPids(mainPids, req.uid, { + stripTags: false, + extraFields: ['bookmarks'], + }); + const uniqTids = _.uniq(postData.map(p => p.tid)); + const [topicData, { upvotes }, bookmarkStatus] = await Promise.all([ + topics.getTopicsFields(uniqTids, ['tid', 'numThumbs', 'thumbs', 'mainPid']), + posts.getVoteStatusByPostIDs(mainPids, req.uid), + posts.hasBookmarked(mainPids, req.uid), + ]); + + const thumbs = await topics.thumbs.load(topicData); + const tidToThumbs = _.zipObject(uniqTids, thumbs); + const teasers = await topics.getTeasers(postData.map(p => p.topic), { uid: req.uid }); + postData.forEach((p, index) => { + p.pid = encodeURIComponent(p.pid); + if (p.topic) { + p.topic = { ...p.topic }; + p.topic.thumbs = tidToThumbs[p.tid]; + p.topic.postcount = Math.max(0, p.topic.postcount - 1); + p.topic.teaser = teasers[index]; + } + p.upvoted = upvotes[index]; + p.bookmarked = bookmarkStatus[index]; + if (!p.isMainPost) { + p.repliedString = translator.compile('feed:replied-in-ago', p.topic.title, p.timestampISO); + } + p.index = start + index; + }); + data.showThumbs = req.loggedIn || meta.config.privateUploads !== 1; + data.posts = postData; + data.showTopicTools = true; + data.showSelect = true; + + // Tracked/watched categories + let cids = await user.getCategoriesByStates(req.uid, [ + categories.watchStates.tracking, categories.watchStates.watching, + ]); + cids = cids.filter(cid => !utils.isNumber(cid)); + const [categoryData, watchState] = await Promise.all([ + categories.getCategories(cids), + categories.getWatchState(cids, req.uid), + ]); + data.categories = categories.getTree(categoryData, 0); + await Promise.all([ + categories.getRecentTopicReplies(categoryData, req.uid, req.query), + categories.setUnread(data.categories, cids, req.uid), + ]); + data.categories.forEach((category, idx) => { + if (category) { + helpers.trimChildren(category); + helpers.setCategoryTeaser(category); + category.isWatched = watchState[idx] === categories.watchStates.watching; + category.isTracked = watchState[idx] === categories.watchStates.tracking; + category.isNotWatched = watchState[idx] === categories.watchStates.notwatching; + category.isIgnored = watchState[idx] === categories.watchStates.ignoring; + } + }); data.title = translator.escape(data.name); - data.privileges = userPrivileges; - data.selectedTag = tagData.selectedTag; - data.selectedTags = tagData.selectedTags; - - data.breadcrumbs = helpers.buildBreadcrumbs([{ text: `[[pages:world]]` }]); - data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; - data['reputation:disabled'] = meta.config['reputation:disabled']; - if (!meta.config['feeds:disableRSS']) { - data.rssFeedUrl = `${nconf.get('url')}/category/${data.cid}.rss`; - if (req.loggedIn) { - data.rssFeedUrl += `?uid=${req.uid}&token=${rssToken}`; - } - } + data.breadcrumbs = helpers.buildBreadcrumbs([]); const pageCount = Math.max(1, Math.ceil(data.topicCount / topicsPerPage)); data.pagination = pagination.create(page, pageCount, req.query); diff --git a/src/controllers/admin/cache.js b/src/controllers/admin/cache.js index f46d7fd41e..fa1abe81d4 100644 --- a/src/controllers/admin/cache.js +++ b/src/controllers/admin/cache.js @@ -2,61 +2,24 @@ const cacheController = module.exports; -const utils = require('../../utils'); -const plugins = require('../../plugins'); +const tracker = require('../../cache/tracker'); cacheController.get = async function (req, res) { - const postCache = require('../../posts/cache').getOrCreate(); - const groupCache = require('../../groups').cache; - const { objectCache } = require('../../database'); - const localCache = require('../../cache'); - const uptimeInSeconds = process.uptime(); - function getInfo(cache) { - return { - length: cache.length, - max: cache.max, - maxSize: cache.maxSize, - itemCount: cache.itemCount, - percentFull: cache.name === 'post' ? - ((cache.length / cache.maxSize) * 100).toFixed(2) : - ((cache.itemCount / cache.max) * 100).toFixed(2), - hits: utils.addCommas(String(cache.hits)), - hitsPerSecond: (cache.hits / uptimeInSeconds).toFixed(2), - misses: utils.addCommas(String(cache.misses)), - hitRatio: ((cache.hits / (cache.hits + cache.misses) || 0)).toFixed(4), - enabled: cache.enabled, - ttl: cache.ttl, - }; - } - let caches = { - post: postCache, - group: groupCache, - local: localCache, - }; - if (objectCache) { - caches.object = objectCache; - } - caches = await plugins.hooks.fire('filter:admin.cache.get', caches); - for (const [key, value] of Object.entries(caches)) { - caches[key] = getInfo(value); - } + // force post cache to get created + require('../../posts/cache').getOrCreate(); + + const caches = await tracker.getCacheList(); res.render('admin/advanced/cache', { caches }); }; cacheController.dump = async function (req, res, next) { - let caches = { - post: require('../../posts/cache').getOrCreate(), - object: require('../../database').objectCache, - group: require('../../groups').cache, - local: require('../../cache'), - }; - caches = await plugins.hooks.fire('filter:admin.cache.get', caches); - if (!caches.hasOwnProperty(req.query.name)) { + const foundCache = await tracker.findCacheByName(req.query.name); + if (!foundCache || !foundCache.dump) { return next(); } - const data = JSON.stringify(caches[req.query.name].dump(), null, 4); + const data = JSON.stringify(foundCache.dump(), null, 4); res.setHeader('Content-disposition', `attachment; filename= ${req.query.name}-cache.json`); res.setHeader('Content-type', 'application/json'); res.write(data, (err) => { diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 7d9eb61a18..5e503b1964 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -12,6 +12,8 @@ const meta = require('../../meta'); const activitypub = require('../../activitypub'); const helpers = require('../helpers'); const pagination = require('../../pagination'); +const utils = require('../../utils'); +const cache = require('../../cache'); const categoriesController = module.exports; @@ -48,14 +50,14 @@ categoriesController.get = async function (req, res, next) { categoriesController.getAll = async function (req, res) { const rootCid = parseInt(req.query.cid, 10) || 0; + const rootChildren = await categories.getAllCidsFromSet(`cid:${rootCid}:children`); async function getRootAndChildren() { - const rootChildren = await categories.getAllCidsFromSet(`cid:${rootCid}:children`); const childCids = _.flatten(await Promise.all(rootChildren.map(cid => categories.getChildrenCids(cid)))); return [rootCid].concat(rootChildren.concat(childCids)); } // Categories list will be rendered on client side with recursion, etc. - const cids = await (rootCid ? getRootAndChildren() : categories.getAllCidsFromSet('categories:cid')); + const cids = await getRootAndChildren(); let rootParent = 0; if (rootCid) { @@ -63,13 +65,19 @@ categoriesController.getAll = async function (req, res) { } const fields = [ - 'cid', 'name', 'icon', 'parentCid', 'disabled', 'link', + 'cid', 'name', 'nickname', 'icon', 'parentCid', 'disabled', 'link', 'order', 'color', 'bgColor', 'backgroundImage', 'imageClass', - 'subCategoriesPerPage', 'description', + 'subCategoriesPerPage', 'description', 'descriptionParsed', ]; - const categoriesData = await categories.getCategoriesFields(cids, fields); - const result = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields }); - let tree = categories.getTree(result.categories, rootParent); + let categoriesData = await categories.getCategoriesFields(cids, fields); + ({ categories: categoriesData } = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields })); + + categoriesData = categoriesData.map((category) => { + category.isLocal = utils.isNumber(category.cid); + return category; + }); + + let tree = categories.getTree(categoriesData, rootParent); const cidsCount = rootCid && tree[0] ? tree[0].children.length : tree.length; const pageCount = Math.max(1, Math.ceil(cidsCount / meta.config.categoriesPerPage)); @@ -176,3 +184,54 @@ categoriesController.getFederation = async function (req, res) { followers, }); }; + +categoriesController.addRemote = async function (req, res) { + let { handle, id } = req.body; + if (handle && !id) { + ({ actorUri: id } = await activitypub.helpers.query(handle)); + } + + if (!id) { + return res.sendStatus(404); + } + + await activitypub.actors.assertGroup(id); + const exists = await categories.exists(id); + + if (!exists) { + return res.sendStatus(404); + } + + const score = await db.sortedSetCard('cid:0:children'); + const order = score + 1; // order is 1-based lol + await Promise.all([ + db.sortedSetAdd('cid:0:children', order, id), + categories.setCategoryField(id, 'order', order), + ]); + cache.del('cid:0:children'); + + res.sendStatus(200); +}; + +categoriesController.renameRemote = async (req, res) => { + if (utils.isNumber(req.params.cid)) { + return helpers.formatApiResponse(400, res); + } + + const { name } = req.body; + await categories.setCategoryField(req.params.cid, 'nickname', name); + + res.sendStatus(200); +}; + +categoriesController.removeRemote = async function (req, res) { + if (utils.isNumber(req.params.cid)) { + return helpers.formatApiResponse(400, res); + } + + const parentCid = await categories.getCategoryField(req.params.cid, 'parentCid'); + await db.sortedSetRemove(`cid:${parentCid || 0}:children`, req.params.cid); + cache.del(`cid:${parentCid || 0}:children`); + + res.sendStatus(200); +}; diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index aa173eca07..91d6e401c1 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -69,7 +69,7 @@ async function getNotices() { }); } - if (global.env !== 'production') { + if (process.env.NODE_ENV !== 'production') { notices.push({ done: false, notDoneText: '[[admin/dashboard:running-in-development]]', @@ -91,7 +91,7 @@ async function getLatestVersion() { dashboardController.getAnalytics = async (req, res, next) => { // Basic validation const validUnits = ['days', 'hours']; - const validSets = ['uniquevisitors', 'pageviews', 'pageviews:registered', 'pageviews:bot', 'pageviews:guest']; + const validSets = ['uniquevisitors', 'pageviews', 'pageviews:registered', 'pageviews:bot', 'pageviews:guest', 'pageviews:ap']; const until = req.query.until ? new Date(parseInt(req.query.until, 10)) : Date.now(); const count = req.query.count || (req.query.units === 'hours' ? 24 : 30); if (isNaN(until) || !validUnits.includes(req.query.units)) { diff --git a/src/controllers/admin/events.js b/src/controllers/admin/events.js index 9f3321276a..9b4d493071 100644 --- a/src/controllers/admin/events.js +++ b/src/controllers/admin/events.js @@ -1,5 +1,6 @@ 'use strict'; +const validator = require('validator'); const db = require('../../database'); const events = require('../../events'); const pagination = require('../../pagination'); @@ -58,6 +59,12 @@ eventsController.get = async function (req, res) { events: eventData, pagination: pagination.create(page, pageCount, req.query), types: types, - query: req.query, + query: { + start: validator.escape(String(req.query.start || '')), + end: validator.escape(String(req.query.end || '')), + username: validator.escape(String(req.query.username || '')), + group: validator.escape(String(req.query.group || '')), + perPage: validator.escape(String(req.query.perPage || '')), + }, }); }; diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 6f63faf8a9..14041b2ebe 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -92,7 +92,8 @@ async function getNodeInfo() { }, }; - data.process.memoryUsage.humanReadable = (data.process.memoryUsage.rss / (1024 * 1024 * 1024)).toFixed(3); + data.process.memoryUsage.rssReadable = (data.process.memoryUsage.rss / (1024 * 1024 * 1024)).toFixed(2); + data.process.memoryUsage.heapUsedReadable = (data.process.memoryUsage.heapUsed / (1024 * 1024 * 1024)).toFixed(2); data.process.uptimeHumanReadable = humanReadableUptime(data.process.uptime); data.os.freemem = (data.os.freemem / (1024 * 1024 * 1024)).toFixed(2); data.os.totalmem = (data.os.totalmem / (1024 * 1024 * 1024)).toFixed(2); @@ -117,14 +118,18 @@ function getCpuUsage() { } function humanReadableUptime(seconds) { + const oneHourInSeconds = 3600; + const oneDayInSeconds = oneHourInSeconds * 24; if (seconds < 60) { return `${Math.floor(seconds)}s`; - } else if (seconds < 3600) { + } else if (seconds < oneHourInSeconds) { return `${Math.floor(seconds / 60)}m`; - } else if (seconds < 3600 * 24) { + } else if (seconds < oneDayInSeconds) { return `${Math.floor(seconds / (60 * 60))}h`; } - return `${Math.floor(seconds / (60 * 60 * 24))}d`; + const days = Math.floor(seconds / (oneDayInSeconds)); + const hours = Math.floor((seconds % (oneDayInSeconds)) / oneHourInSeconds); + return `${days}d ${hours}h`; } async function getGitInfo() { diff --git a/src/controllers/admin/logs.js b/src/controllers/admin/logs.js index 51ed116eca..1fe0bb86c9 100644 --- a/src/controllers/admin/logs.js +++ b/src/controllers/admin/logs.js @@ -4,6 +4,7 @@ const validator = require('validator'); const winston = require('winston'); const meta = require('../../meta'); +const translator = require('../../translator'); const logsController = module.exports; @@ -15,6 +16,6 @@ logsController.get = async function (req, res) { winston.error(err.stack); } res.render('admin/advanced/logs', { - data: validator.escape(logs), + data: translator.escape(validator.escape(logs)), }); }; diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js index 69d74ddbae..f27ecbae67 100644 --- a/src/controllers/admin/settings.js +++ b/src/controllers/admin/settings.js @@ -14,6 +14,7 @@ const api = require('../../api'); const pagination = require('../../pagination'); const helpers = require('../helpers'); const translator = require('../../translator'); +const plugins = require('../../plugins'); const settingsController = module.exports; @@ -114,9 +115,14 @@ settingsController.uploads = async (req, res) => { settingsController.email = async (req, res) => { const emails = await emailer.getTemplates(meta.config); + const hooks = plugins.loadedHooks['static:email.send']; + const emailerPlugin = hooks && hooks.length ? hooks[0].id : null; + const smtpEnabled = parseInt(meta.config['email:smtpTransport:enabled'], 10) === 1; res.render('admin/settings/email', { title: '[[admin/menu:settings/email]]', + emailerPlugin, + smtpEnabled, emails: emails, sendable: emails.filter(e => !e.path.includes('_plaintext') && !e.path.includes('partials')).map(tpl => tpl.path), services: emailer.listServices(), @@ -159,11 +165,17 @@ settingsController.api = async (req, res) => { }; settingsController.activitypub = async (req, res) => { - const instanceCount = await activitypub.instances.getCount(); + const [instanceCount, rules, relays] = await Promise.all([ + activitypub.instances.getCount(), + activitypub.rules.list(), + activitypub.relays.list(), + ]); res.render('admin/settings/activitypub', { title: `[[admin/menu:settings/activitypub]]`, instanceCount, + rules, + relays, }); }; @@ -186,7 +198,3 @@ settingsController.advanced = async (req, res) => { groupsExemptFromMaintenanceMode: groupData, }); }; - - - - diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 56d64674cf..cd9463fce2 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -4,7 +4,6 @@ const path = require('path'); const nconf = require('nconf'); const fs = require('fs'); const winston = require('winston'); -const sanitizeHtml = require('sanitize-html'); const meta = require('../../meta'); const posts = require('../../posts'); @@ -13,7 +12,9 @@ const image = require('../../image'); const plugins = require('../../plugins'); const pagination = require('../../pagination'); -const allowedImageTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml']; +const allowedImageTypes = [ + 'image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml', +]; const uploadsController = module.exports; @@ -24,7 +25,7 @@ uploadsController.get = async function (req, res, next) { } const itemsPerPage = 20; const page = parseInt(req.query.page, 10) || 1; - let files = []; + let files; try { await checkSymLinks(req.query.dir); files = await getFilesInFolder(currentFolder); @@ -147,8 +148,8 @@ async function getFileData(currentDir, file) { } uploadsController.uploadCategoryPicture = async function (req, res, next) { - const uploadedFile = req.files.files[0]; - let params = null; + const uploadedFile = req.files[0]; + let params; try { params = JSON.parse(req.body.params); @@ -157,67 +158,22 @@ uploadsController.uploadCategoryPicture = async function (req, res, next) { return next(new Error('[[error:invalid-json]]')); } - if (uploadedFile.path.endsWith('.svg')) { - await sanitizeSvg(uploadedFile.path); - } - await validateUpload(uploadedFile, allowedImageTypes); const filename = `category-${params.cid}${path.extname(uploadedFile.name)}`; await uploadImage(filename, 'category', uploadedFile, req, res, next); }; -async function sanitizeSvg(filePath) { - const dirty = await fs.promises.readFile(filePath, 'utf8'); - const clean = sanitizeHtml(dirty, { - allowedTags: [ - 'svg', 'g', 'defs', 'linearGradient', 'radialGradient', 'stop', - 'circle', 'ellipse', 'polygon', 'polyline', 'path', 'rect', - 'line', 'text', 'tspan', 'use', 'symbol', 'clipPath', 'mask', 'pattern', - 'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feMerge', 'feMergeNode', - ], - allowedAttributes: { - '*': [ - // Geometry - 'x', 'y', 'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', - 'width', 'height', 'd', 'points', 'viewBox', 'transform', - - // Presentation - 'fill', 'stroke', 'stroke-width', 'opacity', - 'stop-color', 'stop-opacity', 'offset', 'style', 'class', - - // Text - 'text-anchor', 'font-size', 'font-family', - - // Misc - 'id', 'clip-path', 'mask', 'filter', 'gradientUnits', 'gradientTransform', - 'xmlns', 'preserveAspectRatio', - ], - }, - parser: { - lowerCaseTags: false, - lowerCaseAttributeNames: false, - }, - }); - await fs.promises.writeFile(filePath, clean); -} - uploadsController.uploadFavicon = async function (req, res, next) { - const uploadedFile = req.files.files[0]; + const uploadedFile = req.files[0]; const allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon']; await validateUpload(uploadedFile, allowedTypes); - try { - const imageObj = await file.saveFileToLocal('favicon.ico', 'system', uploadedFile.path); - res.json([{ name: uploadedFile.name, url: imageObj.url }]); - } catch (err) { - next(err); - } finally { - file.delete(uploadedFile.path); - } + const filename = 'favicon' + path.extname(uploadedFile.name); + await uploadImage(filename, 'system', uploadedFile, req, res, next); }; uploadsController.uploadTouchIcon = async function (req, res, next) { - const uploadedFile = req.files.files[0]; + const uploadedFile = req.files[0]; const allowedTypes = ['image/png']; const sizes = [36, 48, 72, 96, 144, 192, 512]; @@ -244,7 +200,7 @@ uploadsController.uploadTouchIcon = async function (req, res, next) { uploadsController.uploadMaskableIcon = async function (req, res, next) { - const uploadedFile = req.files.files[0]; + const uploadedFile = req.files[0]; const allowedTypes = ['image/png']; await validateUpload(uploadedFile, allowedTypes); @@ -258,12 +214,8 @@ uploadsController.uploadMaskableIcon = async function (req, res, next) { } }; -uploadsController.uploadLogo = async function (req, res, next) { - await upload('site-logo', req, res, next); -}; - uploadsController.uploadFile = async function (req, res, next) { - const uploadedFile = req.files.files[0]; + const uploadedFile = req.files[0]; let params; try { params = JSON.parse(req.body.params); @@ -285,6 +237,10 @@ uploadsController.uploadFile = async function (req, res, next) { } }; +uploadsController.uploadLogo = async function (req, res, next) { + await upload('site-logo', req, res, next); +}; + uploadsController.uploadDefaultAvatar = async function (req, res, next) { await upload('avatar-default', req, res, next); }; @@ -294,7 +250,7 @@ uploadsController.uploadOgImage = async function (req, res, next) { }; async function upload(name, req, res, next) { - const uploadedFile = req.files.files[0]; + const uploadedFile = req.files[0]; await validateUpload(uploadedFile, allowedImageTypes); const filename = name + path.extname(uploadedFile.name); @@ -312,7 +268,11 @@ async function uploadImage(filename, folder, uploadedFile, req, res, next) { let imageData; try { if (plugins.hooks.hasListeners('filter:uploadImage')) { - imageData = await plugins.hooks.fire('filter:uploadImage', { image: uploadedFile, uid: req.uid, folder: folder }); + imageData = await plugins.hooks.fire('filter:uploadImage', { + image: uploadedFile, + uid: req.uid, + folder: folder, + }); } else { imageData = await file.saveFileToLocal(filename, folder, uploadedFile.path); } @@ -337,7 +297,14 @@ async function uploadImage(filename, folder, uploadedFile, req, res, next) { 'og:image:height': size.height, }); } - res.json([{ name: uploadedFile.name, url: imageData.url.startsWith('http') ? imageData.url : nconf.get('relative_path') + imageData.url }]); + res.json([ + { + name: uploadedFile.name, + url: imageData.url.startsWith('http') ? + imageData.url : + nconf.get('relative_path') + imageData.url, + }, + ]); } catch (err) { next(err); } finally { diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index 14e50bf9eb..a1f48f35b1 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -77,7 +77,7 @@ async function getUsers(req, res) { } async function getUids(set) { - let uids = []; + let uids; if (Array.isArray(set)) { const weights = set.map((s, index) => (index ? 0 : 1)); uids = await db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({ @@ -314,3 +314,8 @@ usersController.customFields = async function (req, res) { }); res.render('admin/manage/users/custom-fields', { fields: fields }); }; + +usersController.banReasons = async function (req, res) { + const reasons = await user.bans.getCustomReasons(); + res.render('admin/manage/users/custom-reasons', { reasons }); +}; \ No newline at end of file diff --git a/src/controllers/api.js b/src/controllers/api.js index f55b411bfa..4fd83dcd5b 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -64,6 +64,7 @@ apiController.loadConfig = async function (req) { topicsPerPage: meta.config.topicsPerPage || 20, postsPerPage: meta.config.postsPerPage || 20, maximumFileSize: meta.config.maximumFileSize, + convertPastedImageTo: meta.config.convertPastedImageTo, 'theme:id': meta.config['theme:id'], 'theme:src': meta.config['theme:src'], defaultLang: meta.config.defaultLang || 'en-GB', diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index fef6f088b6..f948e053b5 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -41,13 +41,11 @@ async function registerAndLoginUser(req, res, userData) { return; } - const queue = await user.shouldQueueUser(req.ip); - const result = await plugins.hooks.fire('filter:register.shouldQueue', { req: req, res: res, userData: userData, queue: queue }); - if (result.queue) { - return await addToApprovalQueue(req, userData); + const { queued, uid, message } = await user.createOrQueue(req, userData); + if (queued) { + return { message }; } - const uid = await user.create(userData); if (res.locals.processLogin) { const hasLoginPrivilege = await privileges.global.can('local:login', uid); if (hasLoginPrivilege) { @@ -86,26 +84,12 @@ authenticationController.register = async function (req, res) { await user.verifyInvitation(userData); } - if ( - !userData.username || - userData.username.length < meta.config.minimumUsernameLength || - slugify(userData.username).length < meta.config.minimumUsernameLength - ) { - throw new Error('[[error:username-too-short]]'); - } - - if (userData.username.length > meta.config.maximumUsernameLength) { - throw new Error('[[error:username-too-long]]'); - } + user.checkUsernameLength(userData.username); if (userData.password !== userData['password-confirm']) { throw new Error('[[user:change-password-error-match]]'); } - if (userData.password.length > 512) { - throw new Error('[[error:password-too-long]]'); - } - user.isPasswordValid(userData.password); await plugins.hooks.fire('filter:password.check', { password: userData.password, uid: 0, userData: userData }); @@ -125,22 +109,6 @@ authenticationController.register = async function (req, res) { } }; -async function addToApprovalQueue(req, userData) { - userData.ip = req.ip; - await user.addToApprovalQueue(userData); - let message = '[[register:registration-added-to-queue]]'; - if (meta.config.showAverageApprovalTime) { - const average_time = await db.getObjectField('registration:queue:approval:times', 'average'); - if (average_time > 0) { - message += ` [[register:registration-queue-average-time, ${Math.floor(average_time / 60)}, ${Math.floor(average_time % 60)}]]`; - } - } - if (meta.config.autoApproveTime > 0) { - message += ` [[register:registration-queue-auto-approve-time, ${meta.config.autoApproveTime}]]`; - } - return { message: message }; -} - authenticationController.registerComplete = async function (req, res) { try { // For the interstitials that respond, execute the callback with the form body @@ -262,6 +230,8 @@ authenticationController.login = async (req, res, next) => { const username = await user.getUsernameByEmail(req.body.username); if (username !== '[[global:guest]]') { req.body.username = username; + } else { + return errorHandler(req, res, '[[error:invalid-email]]', 400); } } if (isEmailLogin || isUsernameLogin) { @@ -420,6 +390,10 @@ authenticationController.localLogin = async function (req, username, password, n } const userslug = slugify(username); + if (!utils.isUserNameValid(username) || !userslug) { + return next(new Error('[[error:invalid-username]]')); + } + const uid = await user.getUidByUserslug(userslug); try { const [userData, isAdminOrGlobalMod, canLoginIfBanned] = await Promise.all([ @@ -484,7 +458,7 @@ authenticationController.logout = async function (req, res) { }; await plugins.hooks.fire('filter:user.logout', payload); - if (req.body?.noscript === 'true') { + if (req.body?.noscript === 'true' || res.locals.logoutRedirect === true) { return res.redirect(payload.next); } res.status(200).send(payload); diff --git a/src/controllers/category.js b/src/controllers/category.js index 27b5c749f9..dd6caeea3b 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -26,14 +26,25 @@ const validSorts = [ ]; categoryController.get = async function (req, res, next) { - const cid = req.params.category_id; + let cid = req.params.category_id; if (cid === '-1') { return helpers.redirect(res, `${res.locals.isAPI ? '/api' : ''}/world?${qs.stringify(req.query)}`); } + if (!utils.isNumber(cid)) { + const assertion = await activitypub.actors.assertGroup([cid]); + if (!activitypub.helpers.isUri(cid)) { + cid = await db.getObjectField('handle:cid', cid); + } + + if (!assertion || !cid) { + return next(); + } + } + let currentPage = parseInt(req.query.page, 10) || 1; let topicIndex = utils.isNumber(req.params.topic_index) ? parseInt(req.params.topic_index, 10) - 1 : 0; - if ((req.params.topic_index && !utils.isNumber(req.params.topic_index)) || !utils.isNumber(cid)) { + if ((req.params.topic_index && !utils.isNumber(req.params.topic_index))) { return next(); } @@ -58,7 +69,7 @@ categoryController.get = async function (req, res, next) { return helpers.notAllowed(req, res); } - if (!res.locals.isAPI && !req.params.slug && (categoryFields.slug && categoryFields.slug !== `${cid}/`)) { + if (utils.isNumber(cid) && !res.locals.isAPI && !req.params.slug && (categoryFields.slug && categoryFields.slug !== `${cid}/`)) { return helpers.redirect(res, `/category/${categoryFields.slug}?${qs.stringify(req.query)}`, true); } @@ -140,7 +151,7 @@ categoryController.get = async function (req, res, next) { categoryData.selectedTags = tagData.selectedTags; categoryData.sortOptionLabel = `[[topic:${validator.escape(String(sort)).replace(/_/g, '-')}]]`; - if (!meta.config['feeds:disableRSS']) { + if (utils.isNumber(categoryData.cid) && !meta.config['feeds:disableRSS']) { categoryData.rssFeedUrl = `${url}/category/${categoryData.cid}.rss`; if (req.loggedIn) { categoryData.rssFeedUrl += `?uid=${req.uid}&token=${rssToken}`; @@ -161,13 +172,18 @@ categoryController.get = async function (req, res, next) { if (meta.config.activitypubEnabled) { // Include link header for richer parsing - res.set('Link', `<${nconf.get('url')}/actegory/${cid}>; rel="alternate"; type="application/activity+json"`); + res.set('Link', `<${nconf.get('url')}/category/${cid}>; rel="alternate"; type="application/activity+json"`); // Category accessible - const remoteOk = await privileges.categories.can('read', cid, activitypub._constants.uid); - if (remoteOk) { + const federating = await privileges.categories.can('read', cid, activitypub._constants.uid); + if (federating) { categoryData.handleFull = `${categoryData.handle}@${nconf.get('url_parsed').host}`; } + + // Some remote categories don't have `url`, assume same as id + if (!utils.isNumber(categoryData.cid) && !categoryData.hasOwnProperty('url')) { + categoryData.url = categoryData.cid; + } } res.render('category', categoryData); @@ -211,12 +227,14 @@ function addTags(categoryData, res, currentPage) { ]; if (categoryData.backgroundImage) { - if (!categoryData.backgroundImage.startsWith('http')) { - categoryData.backgroundImage = url + categoryData.backgroundImage; + let { backgroundImage } = categoryData; + backgroundImage = utils.decodeHTMLEntities(backgroundImage); + if (!backgroundImage.startsWith('http')) { + backgroundImage = url + backgroundImage.replace(new RegExp(`^${nconf.get('relative_path')}`), ''); } res.locals.metaTags.push({ property: 'og:image', - content: categoryData.backgroundImage, + content: backgroundImage, noEscape: true, }); } @@ -234,7 +252,7 @@ function addTags(categoryData, res, currentPage) { }, ]; - if (!categoryData['feeds:disableRSS']) { + if (categoryData.rssFeedUrl && !categoryData['feeds:disableRSS']) { res.locals.linkTags.push({ rel: 'alternate', type: 'application/rss+xml', @@ -246,7 +264,7 @@ function addTags(categoryData, res, currentPage) { res.locals.linkTags.push({ rel: 'alternate', type: 'application/activity+json', - href: `${nconf.get('url')}/actegory/${categoryData.cid}`, + href: `${nconf.get('url')}/category/${categoryData.cid}`, }); } } diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index a6ade8c73b..27366459a9 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -1,6 +1,7 @@ 'use strict'; const nconf = require('nconf'); +const winston = require('winston'); const validator = require('validator'); const querystring = require('querystring'); const _ = require('lodash'); @@ -23,8 +24,10 @@ const url = nconf.get('url'); helpers.noScriptErrors = async function (req, res, error, httpStatus) { if (req.body.noscript !== 'true') { if (typeof error === 'string') { + winston.error(`${new Error(error).stack}`); return res.status(httpStatus).send(error); } + winston.error(`${new Error(JSON.stringify(error)).stack}`); return res.status(httpStatus).json(error); } const middleware = require('../middleware'); @@ -40,6 +43,7 @@ helpers.noScriptErrors = async function (req, res, error, httpStatus) { }; helpers.terms = { + alltime: 'alltime', daily: 'day', weekly: 'week', monthly: 'month', @@ -101,7 +105,7 @@ helpers.buildFilters = function (url, filter, query) { helpers.buildTerms = function (url, term, query) { return [{ name: '[[recent:alltime]]', - url: url + helpers.buildQueryString(query, 'term', ''), + url: url + helpers.buildQueryString(query, 'term', 'alltime'), selected: term === 'alltime', term: 'alltime', }, { @@ -506,7 +510,7 @@ helpers.formatApiResponse = async (statusCode, res, payload) => { const returnPayload = await helpers.generateError(statusCode, message, res); returnPayload.response = response; - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { returnPayload.stack = payload.stack; process.stdout.write(`[${chalk.yellow('api')}] Exception caught, error with stack trace follows:\n`); process.stdout.write(payload.stack); diff --git a/src/controllers/home.js b/src/controllers/home.js index 6629df57dd..f70331937d 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -1,6 +1,6 @@ 'use strict'; -const url = require('url'); + const querystring = require('querystring'); const plugins = require('../plugins'); @@ -33,12 +33,12 @@ async function rewrite(req, res, next) { let parsedUrl; try { - parsedUrl = url.parse(route, true); + parsedUrl = new URL(route, 'http://localhost.com'); } catch (err) { return next(err); } - const { pathname } = parsedUrl; + const pathname = parsedUrl.pathname.replace(/^\/+/, ''); const hook = `action:homepage.get:${pathname}`; if (!plugins.hooks.hasListeners(hook)) { const queryString = querystring.stringify(req.query); @@ -46,6 +46,8 @@ async function rewrite(req, res, next) { } else { res.locals.homePageRoute = pathname; } + // TODO: cant write to req.query in express 5.x+ + //req.query = Object.assign(Object.fromEntries(parsedUrl.searchParams), req.query); next(); } diff --git a/src/controllers/index.js b/src/controllers/index.js index f8152a6933..879774c17f 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -235,12 +235,6 @@ Controllers.confirmEmail = async (req, res) => { return renderPage(); } try { - if (req.loggedIn) { - const emailValidated = await user.getUserField(req.uid, 'email:confirmed'); - if (emailValidated) { - return renderPage({ alreadyValidated: true }); - } - } await user.email.confirmByCode(req.params.code, req.session.id); if (req.session.registration) { // After confirmation, no need to send user back to email change form diff --git a/src/controllers/mods.js b/src/controllers/mods.js index 4976dd9e82..2726e600d4 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -1,6 +1,7 @@ 'use strict'; const _ = require('lodash'); +const validator = require('validator'); const user = require('../user'); const groups = require('../groups'); @@ -43,9 +44,9 @@ modsController.flags.list = async function (req, res) { filters = filters.reduce((memo, cur) => { if (req.query.hasOwnProperty(cur)) { if (typeof req.query[cur] === 'string' && req.query[cur].trim() !== '') { - memo[cur] = req.query[cur].trim(); + memo[cur] = validator.escape(String(req.query[cur].trim())); } else if (Array.isArray(req.query[cur]) && req.query[cur].length) { - memo[cur] = req.query[cur]; + memo[cur] = req.query[cur].map(item => validator.escape(String(item).trim())); } } @@ -236,6 +237,7 @@ modsController.postQueue = async function (req, res, next) { .map((post) => { const isSelf = post.user.uid === req.uid; post.canAccept = !isSelf && (isAdmin || isGlobalMod || !!moderatedCids.length); + post.canEdit = isSelf || isAdmin || isGlobalMod; return post; }); diff --git a/src/controllers/ping.js b/src/controllers/ping.js index dc4baed1f8..672c0e46e5 100644 --- a/src/controllers/ping.js +++ b/src/controllers/ping.js @@ -5,7 +5,7 @@ const db = require('../database'); module.exports.ping = async function (req, res, next) { try { - await db.getObject('config'); + await db.getSortedSetRange('plugins:active', 0, 0); res.status(200).send(req.path === `${nconf.get('relative_path')}/sping` ? 'healthy' : '200'); } catch (err) { next(err); diff --git a/src/controllers/recent.js b/src/controllers/recent.js index 5699fee1b7..73d5348c0d 100644 --- a/src/controllers/recent.js +++ b/src/controllers/recent.js @@ -22,9 +22,9 @@ recentController.get = async function (req, res, next) { res.render('recent', data); }; -recentController.getData = async function (req, url, sort) { +recentController.getData = async function (req, url, sort, selectedTerm = 'alltime') { const page = parseInt(req.query.page, 10) || 1; - let term = helpers.terms[req.query.term]; + let term = helpers.terms[req.query.term || selectedTerm]; const { cid, tag } = req.query; const filter = req.query.filter || ''; @@ -79,6 +79,7 @@ recentController.getData = async function (req, url, sort) { data.selectedTag = tagData.selectedTag; data.selectedTags = tagData.selectedTags; data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; + data['reputation:disabled'] = meta.config['reputation:disabled']; if (!meta.config['feeds:disableRSS']) { data.rssFeedUrl = `${relative_path}/${url}.rss`; if (req.loggedIn) { diff --git a/src/controllers/topics.js b/src/controllers/topics.js index ae68290729..a459c3a99f 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -1,6 +1,7 @@ 'use strict'; const nconf = require('nconf'); +const path = require('path'); const qs = require('querystring'); const validator = require('validator'); @@ -14,6 +15,7 @@ const helpers = require('./helpers'); const pagination = require('../pagination'); const utils = require('../utils'); const analytics = require('../analytics'); +const activitypub = require('../activitypub'); const topicsController = module.exports; @@ -26,7 +28,7 @@ topicsController.get = async function getTopic(req, res, next) { const tid = req.params.topic_id; if ( (req.params.post_index && !utils.isNumber(req.params.post_index) && req.params.post_index !== 'unread') || - (!utils.isNumber(tid) && !validator.isUUID(tid)) + (!utils.isNumber(tid) && !validator.isUUID(String(tid))) ) { return next(); } @@ -123,8 +125,9 @@ topicsController.get = async function getTopic(req, res, next) { p => parseInt(p.index, 10) === parseInt(Math.max(0, postIndex - 1), 10) ); - const [author] = await Promise.all([ + const [author, crossposts] = await Promise.all([ user.getUserFields(topicData.uid, ['username', 'userslug']), + topics.crossposts.get(topicData.tid), buildBreadcrumbs(topicData), addOldCategory(topicData, userPrivileges), addTags(topicData, req, res, currentPage, postAtIndex), @@ -134,17 +137,25 @@ topicsController.get = async function getTopic(req, res, next) { ]); topicData.author = author; + topicData.crossposts = crossposts; topicData.pagination = pagination.create(currentPage, pageCount, req.query); topicData.pagination.rel.forEach((rel) => { rel.href = `${url}/topic/${topicData.slug}${rel.href}`; res.locals.linkTags.push(rel); }); - if (meta.config.activitypubEnabled && postAtIndex) { - // Include link header for richer parsing - const { pid } = postAtIndex; - const href = utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid; - res.set('Link', `<${href}>; rel="alternate"; type="application/activity+json"`); + if (meta.config.activitypubEnabled) { + if (postAtIndex) { + // Include link header for richer parsing + const { pid } = postAtIndex; + const href = utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid; + res.set('Link', `<${href}>; rel="alternate"; type="application/activity+json"`); + } + + if (req.uid > 0 && !utils.isNumber(topicData.mainPid)) { + // not awaited on purpose so topic loading is not blocked + activitypub.notes.backfill(topicData.mainPid); + } } res.render('topic', topicData); @@ -309,17 +320,15 @@ async function addTags(topicData, req, res, currentPage, postAtIndex) { async function addOGImageTags(res, topicData, postAtIndex) { const uploads = postAtIndex ? await posts.uploads.listWithSizes(postAtIndex.pid) : []; - const images = uploads.map((upload) => { - upload.name = `${url + upload_url}/${upload.name}`; - return upload; - }); + const images = uploads.filter(Boolean); + if (topicData.thumbs) { - const path = require('path'); const thumbs = topicData.thumbs.filter( - t => t && images.every(img => path.normalize(img.name) !== path.normalize(url + t.url)) + t => t && images.every(img => path.normalize(img.name) !== path.normalize(t.path)) ); - images.push(...thumbs.map(thumbObj => ({ name: url + thumbObj.url }))); + images.push(...thumbs.map(t => t.path)); } + if (topicData.category.backgroundImage && (!postAtIndex || !postAtIndex.index)) { images.push(topicData.category.backgroundImage); } @@ -330,13 +339,15 @@ async function addOGImageTags(res, topicData, postAtIndex) { } function addOGImageTag(res, image) { - let imageUrl; - if (typeof image === 'string' && !image.startsWith('http')) { - imageUrl = url + image.replace(new RegExp(`^${relative_path}`), ''); - } else if (typeof image === 'object') { - imageUrl = image.name; - } else { - imageUrl = image; + const isObject = typeof image === 'object' && image.name; + let imageUrl = isObject ? image.name : image; + if (!(typeof imageUrl === 'string')) { + return; + } + + if (!imageUrl.startsWith('http')) { + // (https://domain.com/forum) + (/assets/uploads) + (/files/imagePath) + imageUrl = url + path.posix.join(upload_url, imageUrl); } res.locals.metaTags.push({ @@ -349,7 +360,7 @@ function addOGImageTag(res, image) { noEscape: true, }); - if (typeof image === 'object' && image.width && image.height) { + if (isObject && image.width && image.height) { res.locals.metaTags.push({ property: 'og:image:width', content: String(image.width), diff --git a/src/controllers/unread.js b/src/controllers/unread.js index 64e5257d38..b74ec67aa4 100644 --- a/src/controllers/unread.js +++ b/src/controllers/unread.js @@ -75,6 +75,7 @@ unreadController.get = async function (req, res) { data.selectedTags = tagData.selectedTags; data.filters = helpers.buildFilters(baseUrl, filter, req.query); data.selectedFilter = data.filters.find(filter => filter && filter.selected); + data['reputation:disabled'] = meta.config['reputation:disabled']; res.render('unread', data); }; diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index d2e392b5d3..c150c7144d 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -18,7 +18,7 @@ const uploadsController = module.exports; uploadsController.upload = async function (req, res, filesIterator) { let files; try { - files = req.files.files; + files = req.files; } catch (e) { return helpers.formatApiResponse(400, res); } @@ -27,9 +27,6 @@ uploadsController.upload = async function (req, res, filesIterator) { if (!Array.isArray(files)) { return helpers.formatApiResponse(500, res, new Error('[[error:invalid-file]]')); } - if (Array.isArray(files[0])) { - files = files[0]; - } try { const images = []; @@ -64,7 +61,7 @@ async function uploadAsImage(req, uploadedFile) { throw new Error('[[error:no-privileges]]'); } await image.checkDimensions(uploadedFile.path); - await image.stripEXIF(uploadedFile.path); + await image.stripEXIF({ path: uploadedFile.path, type: uploadedFile.type }); if (plugins.hooks.hasListeners('filter:uploadImage')) { return await plugins.hooks.fire('filter:uploadImage', { @@ -126,7 +123,7 @@ async function resizeImage(fileObj) { uploadsController.uploadThumb = async function (req, res) { if (!meta.config.allowTopicsThumbnail) { - deleteTempFiles(req.files.files); + deleteTempFiles(req.files); return helpers.formatApiResponse(503, res, new Error('[[error:topic-thumbnails-are-disabled]]')); } @@ -196,12 +193,18 @@ async function saveFileToLocal(uid, folder, uploadedFile) { }; await user.associateUpload(uid, upload.url.replace(`${nconf.get('upload_url')}`, '')); - const data = await plugins.hooks.fire('filter:uploadStored', { uid: uid, uploadedFile: uploadedFile, storedFile: storedFile }); + const data = await plugins.hooks.fire('filter:uploadStored', { + uid, + uploadedFile, + storedFile, + }); return data.storedFile; } function deleteTempFiles(files) { - files.forEach(fileObj => file.delete(fileObj.path)); + if (Array.isArray(files)) { + files.forEach(fileObj => file.delete(fileObj.path)); + } } require('../promisify')(uploadsController, ['upload', 'uploadPost', 'uploadThumb']); diff --git a/src/controllers/write/admin.js b/src/controllers/write/admin.js index c4c8e29c8c..53662f6b34 100644 --- a/src/controllers/write/admin.js +++ b/src/controllers/write/admin.js @@ -1,9 +1,11 @@ 'use strict'; +const categories = require('../../categories'); const api = require('../../api'); const helpers = require('../helpers'); const messaging = require('../../messaging'); const events = require('../../events'); +const activitypub = require('../../activitypub'); const Admin = module.exports; @@ -82,3 +84,42 @@ Admin.chats.deleteRoom = async (req, res) => { Admin.listGroups = async (req, res) => { helpers.formatApiResponse(200, res, await api.admin.listGroups()); }; + +Admin.activitypub = {}; + +Admin.activitypub.addRule = async (req, res) => { + const { type, value, cid } = req.body; + const exists = await categories.exists(cid); + if (!value || !exists) { + return helpers.formatApiResponse(400, res); + } + + await activitypub.rules.add(type, value, cid); + helpers.formatApiResponse(200, res, await activitypub.rules.list()); +}; + +Admin.activitypub.deleteRule = async (req, res) => { + const { rid } = req.params; + await activitypub.rules.delete(rid); + helpers.formatApiResponse(200, res, await activitypub.rules.list()); +}; + +Admin.activitypub.reorderRules = async (req, res) => { + const { rids } = req.body; + await activitypub.rules.reorder(rids); + helpers.formatApiResponse(200, res, await activitypub.rules.list()); +}; + +Admin.activitypub.addRelay = async (req, res) => { + const { url } = req.body; + + await activitypub.relays.add(url); + helpers.formatApiResponse(200, res, await activitypub.relays.list()); +}; + +Admin.activitypub.removeRelay = async (req, res) => { + const { url } = req.params; + + await activitypub.relays.remove(url); + helpers.formatApiResponse(200, res, await activitypub.relays.list()); +}; diff --git a/src/controllers/write/categories.js b/src/controllers/write/categories.js index 8a2a002713..996a9d386a 100644 --- a/src/controllers/write/categories.js +++ b/src/controllers/write/categories.js @@ -2,6 +2,7 @@ const categories = require('../../categories'); const meta = require('../../meta'); +const activitypub = require('../../activitypub'); const api = require('../../api'); const helpers = require('../helpers'); @@ -107,6 +108,7 @@ Categories.setModerator = async (req, res) => { }; Categories.follow = async (req, res, next) => { + // Priv check done in route middleware const { actor } = req.body; const id = parseInt(req.params.cid, 10); @@ -114,11 +116,7 @@ Categories.follow = async (req, res, next) => { return next(); } - await api.activitypub.follow(req, { - type: 'cid', - id, - actor, - }); + await activitypub.out.follow('cid', id, actor); helpers.formatApiResponse(200, res, {}); }; @@ -131,11 +129,6 @@ Categories.unfollow = async (req, res, next) => { return next(); } - await api.activitypub.unfollow(req, { - type: 'cid', - id, - actor, - }); - + await activitypub.out.undo.follow('cid', id, actor); helpers.formatApiResponse(200, res, {}); }; diff --git a/src/controllers/write/posts.js b/src/controllers/write/posts.js index 884517c126..0c0a480dfb 100644 --- a/src/controllers/write/posts.js +++ b/src/controllers/write/posts.js @@ -189,3 +189,32 @@ Posts.getReplies = async (req, res) => { helpers.formatApiResponse(200, res, { replies }); }; + +Posts.acceptQueuedPost = async (req, res) => { + const post = await api.posts.acceptQueuedPost(req, { id: req.params.id }); + helpers.formatApiResponse(200, res, { post }); +}; + +Posts.removeQueuedPost = async (req, res) => { + await api.posts.removeQueuedPost(req, { id: req.params.id, message: req.body.message }); + helpers.formatApiResponse(200, res); +}; + +Posts.editQueuedPost = async (req, res) => { + const result = await api.posts.editQueuedPost(req, { id: req.params.id, ...req.body }); + helpers.formatApiResponse(200, res, result); +}; + +Posts.notifyQueuedPostOwner = async (req, res) => { + const { id } = req.params; + await api.posts.notifyQueuedPostOwner(req, { id, message: req.body.message }); + helpers.formatApiResponse(200, res); +}; + +Posts.changeOwner = async (req, res) => { + await api.posts.changeOwner(req, { + pids: req.body.pids || (req.params.pid ? [req.params.pid] : []), + uid: req.body.uid, + }); + helpers.formatApiResponse(200, res); +}; \ No newline at end of file diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index b46002bb65..868fc08e8e 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -3,6 +3,7 @@ const db = require('../../database'); const api = require('../../api'); const topics = require('../../topics'); +const activitypub = require('../../activitypub'); const helpers = require('../helpers'); const middleware = require('../../middleware'); @@ -138,25 +139,18 @@ Topics.addThumb = async (req, res) => { const files = await uploadsController.uploadThumb(req, res); // response is handled here - // Add uploaded files to topic zset + // Add uploaded files to topic hash if (files && files.length) { - await Promise.all(files.map(async (fileObj) => { + for (const fileObj of files) { + // eslint-disable-next-line no-await-in-loop await topics.thumbs.associate({ id: req.params.tid, path: fileObj.url, }); - })); + } } }; -Topics.migrateThumbs = async (req, res) => { - await api.topics.migrateThumbs(req, { - from: req.params.tid, - to: req.body.tid, - }); - - helpers.formatApiResponse(200, res, await api.topics.getThumbs(req, { tid: req.body.tid })); -}; Topics.deleteThumb = async (req, res) => { if (!req.body.path.startsWith('http')) { @@ -220,3 +214,24 @@ Topics.move = async (req, res) => { helpers.formatApiResponse(200, res); }; + +Topics.getCrossposts = async (req, res) => { + const crossposts = await topics.crossposts.get(req.params.tid); + helpers.formatApiResponse(200, res, { crossposts }); +}; + +Topics.crosspost = async (req, res) => { + const { cid } = req.body; + const crossposts = await topics.crossposts.add(req.params.tid, cid, req.uid); + await activitypub.out.announce.topic(req.params.tid, req.uid); + + helpers.formatApiResponse(200, res, { crossposts }); +}; + +Topics.uncrosspost = async (req, res) => { + const { cid } = req.body; + const crossposts = await topics.crossposts.remove(req.params.tid, cid, req.uid); + await activitypub.out.undo.announce('uid', req.uid, req.params.tid); + + helpers.formatApiResponse(200, res, { crossposts }); +}; diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js index b884ef93fb..a5cde6dad2 100644 --- a/src/controllers/write/users.js +++ b/src/controllers/write/users.js @@ -5,6 +5,7 @@ const path = require('path'); const crypto = require('crypto'); const api = require('../../api'); +const activitypub = require('../../activitypub'); const user = require('../../user'); const helpers = require('../helpers'); @@ -94,11 +95,7 @@ Users.changePassword = async (req, res) => { Users.follow = async (req, res) => { const remote = String(req.params.uid).includes('@'); if (remote) { - await api.activitypub.follow(req, { - type: 'uid', - id: req.uid, - actor: req.params.uid, - }); + await activitypub.out.follow('uid', req.uid, req.params.uid); } else { await api.users.follow(req, req.params); } @@ -109,11 +106,7 @@ Users.follow = async (req, res) => { Users.unfollow = async (req, res) => { const remote = String(req.params.uid).includes('@'); if (remote) { - await api.activitypub.unfollow(req, { - type: 'uid', - id: req.uid, - actor: req.params.uid, - }); + await activitypub.out.undo.follow('uid', req.uid, req.params.uid); } else { await api.users.unfollow(req, req.params); } diff --git a/src/database/helpers.js b/src/database/helpers.js index 2717428e2c..598c1d8812 100644 --- a/src/database/helpers.js +++ b/src/database/helpers.js @@ -16,7 +16,7 @@ helpers.mergeBatch = function (batchData, start, stop, sort) { } return selectedArray.length ? selectedArray.shift() : null; } - let item = null; + let item; const result = []; do { item = getFirst(batchData); diff --git a/src/database/mongo.js b/src/database/mongo.js index 3a9be4e3a7..310867cef5 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -65,7 +65,7 @@ mongoModule.init = async function (opts) { }; mongoModule.createSessionStore = async function (options) { - const MongoStore = require('connect-mongo'); + const { MongoStore } = require('connect-mongo'); const meta = require('../meta'); const store = MongoStore.create({ diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 958d5384f3..59ea48e1c9 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -95,7 +95,7 @@ module.exports = function (module) { }; module.getObjectField = async function (key, field) { - if (!key) { + if (!key || !field) { return null; } const cachedData = {}; @@ -104,7 +104,11 @@ module.exports = function (module) { return cachedData[key].hasOwnProperty(field) ? cachedData[key][field] : null; } field = helpers.fieldToString(field); - const item = await module.client.collection('objects').findOne({ _key: key }, { projection: { _id: 0, [field]: 1 } }); + const item = await module.client.collection('objects').findOne({ + _key: key, + }, { + projection: { _id: 0, [field]: 1 }, + }); if (!item) { return null; } diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 0c84a0f210..eacf60ed7a 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -68,6 +68,31 @@ module.exports = function (module) { } }; + module.setAddBulk = async function (data) { + if (!data.length) { + return; + } + + const bulk = module.client.collection('objects').initializeUnorderedBulkOp(); + + data.forEach(([key, member]) => { + bulk.find({ _key: key }).upsert().updateOne({ + $addToSet: { + members: helpers.valueToString(member), + }, + }); + }); + try { + await bulk.execute(); + } catch (err) { + if (err && err.message.includes('E11000 duplicate key error')) { + console.log(new Error('e11000').stack, data); + return await module.setAddBulk(data); + } + throw err; + } + }; + module.setRemove = async function (key, value) { if (!Array.isArray(value)) { value = [value]; @@ -75,11 +100,17 @@ module.exports = function (module) { value = value.map(v => helpers.valueToString(v)); - await module.client.collection('objects').updateMany({ + const coll = module.client.collection('objects'); + await coll.updateMany({ _key: Array.isArray(key) ? { $in: key } : key, }, { $pullAll: { members: value }, }); + + await coll.deleteMany({ + _key: Array.isArray(key) ? { $in: key } : key, + members: { $size: 0 }, + }); }; module.setsRemove = async function (keys, value) { @@ -88,11 +119,17 @@ module.exports = function (module) { } value = helpers.valueToString(value); - await module.client.collection('objects').updateMany({ + const coll = module.client.collection('objects'); + await coll.updateMany({ _key: { $in: keys }, }, { $pull: { members: value }, }); + + await coll.deleteMany({ + _key: { $in: keys }, + members: { $size: 0 }, + }); }; module.isSetMember = async function (key, value) { diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 08869d5b5f..b24007e98a 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -82,7 +82,7 @@ module.exports = function (module) { limit = 0; } - let result = []; + let result; async function doQuery(_key, fields, skip, limit) { return await module.client.collection('objects').find({ ...query, ...{ _key: _key }, diff --git a/src/database/postgres.js b/src/database/postgres.js index 84a456ca86..a625b5e498 100644 --- a/src/database/postgres.js +++ b/src/database/postgres.js @@ -1,5 +1,7 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const winston = require('winston'); const nconf = require('nconf'); const session = require('express-session'); @@ -360,7 +362,7 @@ postgresModule.createIndices = async function () { }; postgresModule.checkCompatibility = function (callback) { - const postgresPkg = require('pg/package.json'); + const postgresPkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../node_modules/pg/package.json'), 'utf8')); postgresModule.checkCompatibilityVersion(postgresPkg.version, callback); }; diff --git a/src/database/postgres/connection.js b/src/database/postgres/connection.js index 19d796d7ed..c49e7caac4 100644 --- a/src/database/postgres/connection.js +++ b/src/database/postgres/connection.js @@ -1,5 +1,6 @@ 'use strict'; +const fs = require('fs'); const nconf = require('nconf'); const winston = require('winston'); const _ = require('lodash'); @@ -32,6 +33,18 @@ connection.getConnectionOptions = function (postgres) { connectionTimeoutMillis: 90000, }; + if (typeof postgres.ssl === 'object' && !Array.isArray(postgres.ssl) && postgres.ssl !== null) { + const { ssl } = postgres; + connOptions.ssl = { + rejectUnauthorized: ssl.rejectUnauthorized, + }; + ['ca', 'key', 'cert'].forEach((prop) => { + if (ssl.hasOwnProperty(prop)) { + connOptions.ssl[prop] = fs.readFileSync(ssl[prop]).toString(); + } + }); + } + return _.merge(connOptions, postgres.options || {}); }; diff --git a/src/database/postgres/hash.js b/src/database/postgres/hash.js index 5e3a842d22..834d46ec3e 100644 --- a/src/database/postgres/hash.js +++ b/src/database/postgres/hash.js @@ -153,7 +153,7 @@ SELECT h."data" }; module.getObjectField = async function (key, field) { - if (!key) { + if (!key || !field) { return null; } diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index c0838b45a0..f082521a1d 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -28,6 +28,11 @@ module.exports = function (module) { return members.map(member => member.length > 0); } + async function checkIfSetsExist(keys) { + const members = await Promise.all(keys.map(module.getSetMembers)); + return members.map(member => member.length > 0); + } + async function checkIfKeysExist(keys) { const res = await module.pool.query({ name: 'existsArray', @@ -44,13 +49,16 @@ module.exports = function (module) { if (isArray) { const types = await Promise.all(key.map(module.type)); const zsetKeys = key.filter((_key, i) => types[i] === 'zset'); - const otherKeys = key.filter((_key, i) => types[i] !== 'zset'); - const [zsetExits, otherExists] = await Promise.all([ + const setKeys = key.filter((_key, i) => types[i] === 'set'); + const otherKeys = key.filter((_key, i) => types[i] !== 'zset' && types[i] !== 'set'); + const [zsetExits, setExists, otherExists] = await Promise.all([ checkIfzSetsExist(zsetKeys), + checkIfSetsExist(setKeys), checkIfKeysExist(otherKeys), ]); const existsMap = Object.create(null); zsetKeys.forEach((k, i) => { existsMap[k] = zsetExits[i]; }); + setKeys.forEach((k, i) => { existsMap[k] = setExists[i]; }); otherKeys.forEach((k, i) => { existsMap[k] = otherExists[i]; }); return key.map(k => existsMap[k]); } @@ -58,6 +66,9 @@ module.exports = function (module) { if (type === 'zset') { const members = await module.getSortedSetRange(key, 0, 0); return members.length > 0; + } else if (type === 'set') { + const members = await module.getSetMembers(key); + return members.length > 0; } const res = await module.pool.query({ name: 'exists', @@ -85,7 +96,8 @@ module.exports = function (module) { text: ` SELECT o."_key" FROM "legacy_object_live" o - WHERE o."_key" LIKE '${match}'`, + WHERE o."_key" LIKE $1`, + values: [match], }); return res.rows.map(r => r._key); diff --git a/src/database/postgres/sets.js b/src/database/postgres/sets.js index 82c3f16aff..41a05c8953 100644 --- a/src/database/postgres/sets.js +++ b/src/database/postgres/sets.js @@ -54,6 +54,32 @@ DO NOTHING`, }); }; + module.setAddBulk = async function (data) { + if (!data.length) { + return; + } + const keys = []; + const members = []; + + for (const [key, member] of data) { + keys.push(key); + members.push(member); + } + await module.transaction(async (client) => { + await helpers.ensureLegacyObjectsType(client, keys, 'set'); + await client.query({ + name: 'setAddBulk', + text: ` +INSERT INTO "legacy_set" ("_key", "member") +SELECT k, m +FROM UNNEST($1::TEXT[], $2::TEXT[]) AS t(k, m) +ON CONFLICT ("_key", "member") +DO NOTHING;`, + values: [keys, members], + }); + }); + }; + module.setRemove = async function (key, value) { if (!Array.isArray(key)) { key = [key]; diff --git a/src/database/postgres/sorted.js b/src/database/postgres/sorted.js index 916425e0c1..351fe3e059 100644 --- a/src/database/postgres/sorted.js +++ b/src/database/postgres/sorted.js @@ -228,7 +228,7 @@ SELECT o."_key" k, if (!Array.isArray(keys)) { keys = [keys]; } - let counts = []; + let counts; if (min !== '-inf' || max !== '+inf') { if (min === '-inf') { min = null; @@ -707,9 +707,9 @@ SELECT z."value", ON o."_key" = z."_key" AND o."type" = z."type" WHERE o."_key" = $1::TEXT - AND z."value" LIKE '${match}' + AND z."value" LIKE $3 LIMIT $2::INTEGER`, - values: [params.key, params.limit], + values: [params.key, params.limit, match], }); if (!params.withScores) { return res.rows.map(r => r.value); diff --git a/src/database/redis.js b/src/database/redis.js index f73ee79313..d2118aa925 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -61,7 +61,7 @@ redisModule.checkCompatibilityVersion = function (version, callback) { }; redisModule.close = async function () { - await redisModule.client.quit(); + await redisModule.client.close(); if (redisModule.objectCache) { redisModule.objectCache.reset(); } @@ -104,8 +104,11 @@ redisModule.info = async function (cxn) { redisModule.socketAdapter = async function () { const redisAdapter = require('@socket.io/redis-adapter'); - const pub = await connection.connect(nconf.get('redis')); - const sub = await connection.connect(nconf.get('redis')); + const redisConfig = nconf.get('redis'); + const [pub, sub] = await Promise.all([ + connection.connect(redisConfig), + connection.connect(redisConfig), + ]); return redisAdapter(pub, sub, { key: `db:${nconf.get('redis:database')}:adapter_key`, }); diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index a4ba757ef6..b97f75a4a4 100644 --- a/src/database/redis/connection.js +++ b/src/database/redis/connection.js @@ -1,7 +1,7 @@ 'use strict'; const nconf = require('nconf'); -const Redis = require('ioredis'); +const { createClient, createCluster, createSentinel } = require('redis'); const winston = require('winston'); const connection = module.exports; @@ -13,28 +13,39 @@ connection.connect = async function (options) { let cxn; if (options.cluster) { - cxn = new Redis.Cluster(options.cluster, options.options); - } else if (options.sentinels) { - cxn = new Redis({ - sentinels: options.sentinels, + const rootNodes = options.cluster.map(node => ({ url : `redis://${node.host}:${node.port}` })); + cxn = createCluster({ ...options.options, + rootNodes: rootNodes, + }); + } else if (options.sentinels) { + const sentinelRootNodes = options.sentinels.map(sentinel => ({ host: sentinel.host, port: sentinel.port })); + cxn = createSentinel({ + ...options.options, + sentinelRootNodes, }); } else if (redis_socket_or_host && String(redis_socket_or_host).indexOf('/') >= 0) { // If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock - cxn = new Redis({ + cxn = createClient({ ...options.options, - path: redis_socket_or_host, password: options.password, - db: options.database, + database: options.database, + socket: { + path: redis_socket_or_host, + reconnectStrategy: 3000, + }, }); } else { // Else, connect over tcp/ip - cxn = new Redis({ + cxn = createClient({ ...options.options, - host: redis_socket_or_host, - port: options.port, password: options.password, - db: options.database, + database: options.database, + socket: { + host: redis_socket_or_host, + port: options.port, + reconnectStrategy: 3000, + }, }); } @@ -47,15 +58,15 @@ connection.connect = async function (options) { winston.error(err.stack); reject(err); }); - cxn.on('ready', () => { - // back-compat with node_redis - cxn.batch = cxn.pipeline; - resolve(cxn); - }); - if (options.password) { - cxn.auth(options.password); - } + cxn.connect().then(() => { + // back-compat with node_redis + cxn.batch = cxn.multi; + winston.info('Connected to Redis successfully'); + resolve(cxn); + }).catch((err) => { + winston.error('Error connecting to Redis:', err); + }); }); }; diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 4c6e7b374f..f046e62180 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -25,12 +25,13 @@ module.exports = function (module) { if (!Object.keys(data).length) { return; } + const strObj = helpers.objectFieldsToString(data); if (Array.isArray(key)) { const batch = module.client.batch(); - key.forEach(k => batch.hmset(k, data)); + key.forEach(k => batch.hSet(k, strObj)); await helpers.execBatch(batch); } else { - await module.client.hmset(key, data); + await module.client.hSet(key, strObj); } cache.del(key); @@ -49,10 +50,16 @@ module.exports = function (module) { const batch = module.client.batch(); data.forEach((item) => { + Object.keys(item[1]).forEach((key) => { + if (item[1][key] === undefined || item[1][key] === null) { + delete item[1][key]; + } + }); if (Object.keys(item[1]).length) { - batch.hmset(item[0], item[1]); + batch.hSet(item[0], helpers.objectFieldsToString(item[1])); } }); + await helpers.execBatch(batch); cache.del(data.map(item => item[0])); }; @@ -61,12 +68,15 @@ module.exports = function (module) { if (!field) { return; } + if (value === null || value === undefined) { + return; + } if (Array.isArray(key)) { const batch = module.client.batch(); - key.forEach(k => batch.hset(k, field, value)); + key.forEach(k => batch.hSet(k, field, String(value))); await helpers.execBatch(batch); } else { - await module.client.hset(key, field, value); + await module.client.hSet(key, field, String(value)); } cache.del(key); @@ -86,15 +96,15 @@ module.exports = function (module) { }; module.getObjectField = async function (key, field) { - if (!key) { + if (!key || !field) { return null; } const cachedData = {}; cache.getUnCachedKeys([key], cachedData); if (cachedData[key]) { - return cachedData[key].hasOwnProperty(field) ? cachedData[key][field] : null; + return Object.hasOwn(cachedData[key], field) ? cachedData[key][field] : null; } - return await module.client.hget(key, String(field)); + return await module.client.hGet(key, String(field)); }; module.getObjectFields = async function (key, fields) { @@ -116,10 +126,10 @@ module.exports = function (module) { let data = []; if (unCachedKeys.length > 1) { const batch = module.client.batch(); - unCachedKeys.forEach(k => batch.hgetall(k)); + unCachedKeys.forEach(k => batch.hGetAll(k)); data = await helpers.execBatch(batch); } else if (unCachedKeys.length === 1) { - data = [await module.client.hgetall(unCachedKeys[0])]; + data = [await module.client.hGetAll(unCachedKeys[0])]; } // convert empty objects into null for back-compat with node_redis @@ -149,21 +159,21 @@ module.exports = function (module) { }; module.getObjectKeys = async function (key) { - return await module.client.hkeys(key); + return await module.client.hKeys(key); }; module.getObjectValues = async function (key) { - return await module.client.hvals(key); + return await module.client.hVals(key); }; module.isObjectField = async function (key, field) { - const exists = await module.client.hexists(key, field); + const exists = await module.client.hExists(key, String(field)); return exists === 1; }; module.isObjectFields = async function (key, fields) { const batch = module.client.batch(); - fields.forEach(f => batch.hexists(String(key), String(f))); + fields.forEach(f => batch.hExists(String(key), String(f))); const results = await helpers.execBatch(batch); return Array.isArray(results) ? helpers.resultsToBool(results) : null; }; @@ -174,7 +184,7 @@ module.exports = function (module) { } field = field.toString(); if (field) { - await module.client.hdel(key, field); + await module.client.hDel(key, field); cache.del(key); } }; @@ -189,10 +199,10 @@ module.exports = function (module) { } if (Array.isArray(key)) { const batch = module.client.batch(); - key.forEach(k => batch.hdel(k, fields)); + key.forEach(k => batch.hDel(k, fields)); await helpers.execBatch(batch); } else { - await module.client.hdel(key, fields); + await module.client.hDel(key, fields); } cache.del(key); @@ -214,10 +224,10 @@ module.exports = function (module) { let result; if (Array.isArray(key)) { const batch = module.client.batch(); - key.forEach(k => batch.hincrby(k, field, value)); + key.forEach(k => batch.hIncrBy(k, field, value)); result = await helpers.execBatch(batch); } else { - result = await module.client.hincrby(key, field, value); + result = await module.client.hIncrBy(key, field, value); } cache.del(key); return Array.isArray(result) ? result.map(value => parseInt(value, 10)) : parseInt(result, 10); @@ -231,7 +241,7 @@ module.exports = function (module) { const batch = module.client.batch(); data.forEach((item) => { for (const [field, value] of Object.entries(item[1])) { - batch.hincrby(item[0], field, value); + batch.hIncrBy(item[0], field, value); } }); await helpers.execBatch(batch); diff --git a/src/database/redis/helpers.js b/src/database/redis/helpers.js index 8961da8255..39585b1f88 100644 --- a/src/database/redis/helpers.js +++ b/src/database/redis/helpers.js @@ -5,13 +5,8 @@ const helpers = module.exports; helpers.noop = function () {}; helpers.execBatch = async function (batch) { - const results = await batch.exec(); - return results.map(([err, res]) => { - if (err) { - throw err; - } - return res; - }); + const results = await batch.execAsPipeline(); + return results; }; helpers.resultsToBool = function (results) { @@ -21,10 +16,29 @@ helpers.resultsToBool = function (results) { return results; }; -helpers.zsetToObjectArray = function (data) { - const objects = new Array(data.length / 2); - for (let i = 0, k = 0; i < objects.length; i += 1, k += 2) { - objects[i] = { value: data[k], score: parseFloat(data[k + 1]) }; - } - return objects; +helpers.objectFieldsToString = function (obj) { + const stringified = Object.fromEntries( + Object.entries(obj).map(([key, value]) => [key, String(value)]) + ); + return stringified; +}; + +helpers.normalizeLexRange = function (min, max, reverse) { + let minmin; + let maxmax; + if (reverse) { + minmin = '+'; + maxmax = '-'; + } else { + minmin = '-'; + maxmax = '+'; + } + + if (min !== minmin && !min.match(/^[[(]/)) { + min = `[${min}`; + } + if (max !== maxmax && !max.match(/^[[(]/)) { + max = `[${max}`; + } + return { lmin: min, lmax: max }; }; diff --git a/src/database/redis/list.js b/src/database/redis/list.js index 101ef178e3..229069b6ed 100644 --- a/src/database/redis/list.js +++ b/src/database/redis/list.js @@ -1,27 +1,25 @@ 'use strict'; module.exports = function (module) { - const helpers = require('./helpers'); - module.listPrepend = async function (key, value) { if (!key) { return; } - await module.client.lpush(key, value); + await module.client.lPush(key, Array.isArray(value) ? value.map(String) : String(value)); }; module.listAppend = async function (key, value) { if (!key) { return; } - await module.client.rpush(key, value); + await module.client.rPush(key, Array.isArray(value) ? value.map(String) : String(value)); }; module.listRemoveLast = async function (key) { if (!key) { return; } - return await module.client.rpop(key); + return await module.client.rPop(key); }; module.listRemoveAll = async function (key, value) { @@ -29,11 +27,11 @@ module.exports = function (module) { return; } if (Array.isArray(value)) { - const batch = module.client.batch(); - value.forEach(value => batch.lrem(key, 0, value)); - await helpers.execBatch(batch); + const batch = module.client.multi(); + value.forEach(value => batch.lRem(key, 0, value)); + await batch.execAsPipeline(); } else { - await module.client.lrem(key, 0, value); + await module.client.lRem(key, 0, value); } }; @@ -41,17 +39,17 @@ module.exports = function (module) { if (!key) { return; } - await module.client.ltrim(key, start, stop); + await module.client.lTrim(key, start, stop); }; module.getListRange = async function (key, start, stop) { if (!key) { return; } - return await module.client.lrange(key, start, stop); + return await module.client.lRange(key, start, stop); }; module.listLength = async function (key) { - return await module.client.llen(key); + return await module.client.lLen(key); }; }; diff --git a/src/database/redis/main.js b/src/database/redis/main.js index b849361a8e..a6506ba701 100644 --- a/src/database/redis/main.js +++ b/src/database/redis/main.js @@ -4,7 +4,7 @@ module.exports = function (module) { const helpers = require('./helpers'); module.flushdb = async function () { - await module.client.send_command('flushdb', []); + await module.client.sendCommand(['FLUSHDB']); }; module.emptydb = async function () { @@ -32,9 +32,9 @@ module.exports = function (module) { const seen = Object.create(null); do { /* eslint-disable no-await-in-loop */ - const res = await module.client.scan(cursor, 'MATCH', params.match, 'COUNT', 10000); - cursor = res[0]; - const values = res[1].filter((value) => { + const res = await module.client.scan(cursor, { MATCH: params.match, COUNT: 10000 }); + cursor = res.cursor; + const values = res.keys.filter((value) => { const isSeen = !!seen[value]; if (!isSeen) { seen[value] = 1; @@ -67,7 +67,7 @@ module.exports = function (module) { if (!keys || !Array.isArray(keys) || !keys.length) { return []; } - return await module.client.mget(keys); + return await module.client.mGet(keys); }; module.set = async function (key, value) { @@ -96,26 +96,26 @@ module.exports = function (module) { }; module.expire = async function (key, seconds) { - await module.client.expire(key, seconds); + await module.client.EXPIRE(key, seconds); }; module.expireAt = async function (key, timestamp) { - await module.client.expireat(key, timestamp); + await module.client.EXPIREAT(key, timestamp); }; module.pexpire = async function (key, ms) { - await module.client.pexpire(key, ms); + await module.client.PEXPIRE(key, ms); }; module.pexpireAt = async function (key, timestamp) { - await module.client.pexpireat(key, timestamp); + await module.client.PEXPIREAT(key, timestamp); }; module.ttl = async function (key) { - return await module.client.ttl(key); + return await module.client.TTL(key); }; module.pttl = async function (key) { - return await module.client.pttl(key); + return await module.client.PTTL(key); }; }; diff --git a/src/database/redis/pubsub.js b/src/database/redis/pubsub.js index a7d220682d..1868ac86ad 100644 --- a/src/database/redis/pubsub.js +++ b/src/database/redis/pubsub.js @@ -13,12 +13,7 @@ const PubSub = function () { self.queue = []; connection.connect().then((client) => { self.subClient = client; - self.subClient.subscribe(channelName); - self.subClient.on('message', (channel, message) => { - if (channel !== channelName) { - return; - } - + self.subClient.subscribe(channelName, (message) => { try { const msg = JSON.parse(message); self.emit(msg.event, msg.data); diff --git a/src/database/redis/sets.js b/src/database/redis/sets.js index b2b390598b..861bf275ea 100644 --- a/src/database/redis/sets.js +++ b/src/database/redis/sets.js @@ -10,15 +10,33 @@ module.exports = function (module) { if (!value.length) { return; } - await module.client.sadd(key, value); + await module.client.sAdd(key, value.map(String)); }; module.setsAdd = async function (keys, value) { - if (!Array.isArray(keys) || !keys.length) { + if (!Array.isArray(keys) || !keys.length || !value) { + return; + } + if (!Array.isArray(value)) { + value = [value]; + } + if (!value.length) { return; } const batch = module.client.batch(); - keys.forEach(k => batch.sadd(String(k), String(value))); + keys.forEach((k) => { + value.forEach(v => batch.sAdd(String(k), String(v))); + }); + await helpers.execBatch(batch); + }; + + module.setAddBulk = async function (data) { + if (!data.length) { + return; + } + + const batch = module.client.batch(); + data.forEach(([key, member]) => batch.sAdd(String(key), String(member))); await helpers.execBatch(batch); }; @@ -34,57 +52,57 @@ module.exports = function (module) { } const batch = module.client.batch(); - key.forEach(k => batch.srem(String(k), value)); + key.forEach(k => batch.sRem(String(k), value.map(String))); await helpers.execBatch(batch); }; module.setsRemove = async function (keys, value) { const batch = module.client.batch(); - keys.forEach(k => batch.srem(String(k), value)); + keys.forEach(k => batch.sRem(String(k), String(value))); await helpers.execBatch(batch); }; module.isSetMember = async function (key, value) { - const result = await module.client.sismember(key, value); + const result = await module.client.sIsMember(key, String(value)); return result === 1; }; module.isSetMembers = async function (key, values) { const batch = module.client.batch(); - values.forEach(v => batch.sismember(String(key), String(v))); + values.forEach(v => batch.sIsMember(String(key), String(v))); const results = await helpers.execBatch(batch); return results ? helpers.resultsToBool(results) : null; }; module.isMemberOfSets = async function (sets, value) { const batch = module.client.batch(); - sets.forEach(s => batch.sismember(String(s), String(value))); + sets.forEach(s => batch.sIsMember(String(s), String(value))); const results = await helpers.execBatch(batch); return results ? helpers.resultsToBool(results) : null; }; module.getSetMembers = async function (key) { - return await module.client.smembers(key); + return await module.client.sMembers(key); }; module.getSetsMembers = async function (keys) { const batch = module.client.batch(); - keys.forEach(k => batch.smembers(String(k))); + keys.forEach(k => batch.sMembers(String(k))); return await helpers.execBatch(batch); }; module.setCount = async function (key) { - return await module.client.scard(key); + return await module.client.sCard(key); }; module.setsCount = async function (keys) { const batch = module.client.batch(); - keys.forEach(k => batch.scard(String(k))); + keys.forEach(k => batch.sCard(String(k))); return await helpers.execBatch(batch); }; module.setRemoveRandom = async function (key) { - return await module.client.spop(key); + return await module.client.sPop(key); }; return module; diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 013477da5a..5b9652e6e0 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -11,34 +11,74 @@ module.exports = function (module) { require('./sorted/intersect')(module); module.getSortedSetRange = async function (key, start, stop) { - return await sortedSetRange('zrange', key, start, stop, '-inf', '+inf', false); + return await sortedSetRange(key, start, stop, '-inf', '+inf', false, false, false); }; module.getSortedSetRevRange = async function (key, start, stop) { - return await sortedSetRange('zrevrange', key, start, stop, '-inf', '+inf', false); + return await sortedSetRange(key, start, stop, '-inf', '+inf', false, true, false); }; module.getSortedSetRangeWithScores = async function (key, start, stop) { - return await sortedSetRange('zrange', key, start, stop, '-inf', '+inf', true); + return await sortedSetRange(key, start, stop, '-inf', '+inf', true, false, false); }; module.getSortedSetRevRangeWithScores = async function (key, start, stop) { - return await sortedSetRange('zrevrange', key, start, stop, '-inf', '+inf', true); + return await sortedSetRange(key, start, stop, '-inf', '+inf', true, true, false); }; - async function sortedSetRange(method, key, start, stop, min, max, withScores) { + module.getSortedSetRangeByScore = async function (key, start, count, min, max) { + return await sortedSetRangeByScore(key, start, count, min, max, false, false); + }; + + module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) { + return await sortedSetRangeByScore(key, start, count, max, min, false, true); + }; + + module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) { + return await sortedSetRangeByScore(key, start, count, min, max, true, false); + }; + + module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) { + return await sortedSetRangeByScore(key, start, count, max, min, true, true); + }; + + async function sortedSetRangeByScore(key, start, count, min, max, withScores, rev) { + if (parseInt(count, 10) === 0) { + return []; + } + const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1); + return await sortedSetRange(key, start, stop, min, max, withScores, rev, true); + } + + async function sortedSetRange(key, start, stop, min, max, withScores, rev, byScore) { + const opts = {}; + const cmd = withScores ? 'zRangeWithScores' : 'zRange'; + if (byScore) { + opts.BY = 'SCORE'; + opts.LIMIT = { offset: start, count: stop !== -1 ? stop + 1 : stop }; + } + if (rev) { + opts.REV = true; + } + if (Array.isArray(key)) { if (!key.length) { return []; } const batch = module.client.batch(); - key.forEach(key => batch[method](genParams(method, key, 0, stop, min, max, true))); + + if (byScore) { + key.forEach(key => batch.zRangeWithScores(key, min, max, { + ...opts, + LIMIT: { offset: 0, count: stop !== -1 ? stop + 1 : stop }, + })); + } else { + key.forEach(key => batch.zRangeWithScores(key, 0, stop, { ...opts })); + } + const data = await helpers.execBatch(batch); - - const batchData = data.map(setData => helpers.zsetToObjectArray(setData)); - - let objects = dbHelpers.mergeBatch(batchData, 0, stop, method === 'zrange' ? 1 : -1); - + const batchData = data; + let objects = dbHelpers.mergeBatch(batchData, 0, stop, rev ? -1 : 1); if (start > 0) { objects = objects.slice(start, stop !== -1 ? stop + 1 : undefined); } @@ -48,63 +88,25 @@ module.exports = function (module) { return objects; } - const params = genParams(method, key, start, stop, min, max, withScores); - const data = await module.client[method](params); + let data; + if (byScore) { + data = await module.client[cmd](key, min, max, opts); + } else { + data = await module.client[cmd](key, start, stop, opts); + } + if (!withScores) { return data; } - const objects = helpers.zsetToObjectArray(data); - return objects; - } - - function genParams(method, key, start, stop, min, max, withScores) { - const params = { - zrevrange: [key, start, stop], - zrange: [key, start, stop], - zrangebyscore: [key, min, max], - zrevrangebyscore: [key, max, min], - }; - if (withScores) { - params[method].push('WITHSCORES'); - } - - if (method === 'zrangebyscore' || method === 'zrevrangebyscore') { - const count = stop !== -1 ? stop - start + 1 : stop; - params[method].push('LIMIT', start, count); - } - return params[method]; - } - - module.getSortedSetRangeByScore = async function (key, start, count, min, max) { - return await sortedSetRangeByScore('zrangebyscore', key, start, count, min, max, false); - }; - - module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) { - return await sortedSetRangeByScore('zrevrangebyscore', key, start, count, min, max, false); - }; - - module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) { - return await sortedSetRangeByScore('zrangebyscore', key, start, count, min, max, true); - }; - - module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) { - return await sortedSetRangeByScore('zrevrangebyscore', key, start, count, min, max, true); - }; - - async function sortedSetRangeByScore(method, key, start, count, min, max, withScores) { - if (parseInt(count, 10) === 0) { - return []; - } - const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1); - return await sortedSetRange(method, key, start, stop, min, max, withScores); + return data; } module.sortedSetCount = async function (key, min, max) { - return await module.client.zcount(key, min, max); + return await module.client.zCount(key, min, max); }; module.sortedSetCard = async function (key) { - return await module.client.zcard(key); + return await module.client.zCard(key); }; module.sortedSetsCard = async function (keys) { @@ -112,7 +114,7 @@ module.exports = function (module) { return []; } const batch = module.client.batch(); - keys.forEach(k => batch.zcard(String(k))); + keys.forEach(k => batch.zCard(String(k))); return await helpers.execBatch(batch); }; @@ -125,26 +127,26 @@ module.exports = function (module) { } const batch = module.client.batch(); if (min !== '-inf' || max !== '+inf') { - keys.forEach(k => batch.zcount(String(k), min, max)); + keys.forEach(k => batch.zCount(String(k), min, max)); } else { - keys.forEach(k => batch.zcard(String(k))); + keys.forEach(k => batch.zCard(String(k))); } const counts = await helpers.execBatch(batch); return counts.reduce((acc, val) => acc + val, 0); }; module.sortedSetRank = async function (key, value) { - return await module.client.zrank(key, value); + return await module.client.zRank(key, String(value)); }; module.sortedSetRevRank = async function (key, value) { - return await module.client.zrevrank(key, value); + return await module.client.zRevRank(key, String(value)); }; module.sortedSetsRanks = async function (keys, values) { const batch = module.client.batch(); for (let i = 0; i < values.length; i += 1) { - batch.zrank(keys[i], String(values[i])); + batch.zRank(keys[i], String(values[i])); } return await helpers.execBatch(batch); }; @@ -152,7 +154,7 @@ module.exports = function (module) { module.sortedSetsRevRanks = async function (keys, values) { const batch = module.client.batch(); for (let i = 0; i < values.length; i += 1) { - batch.zrevrank(keys[i], String(values[i])); + batch.zRevRank(keys[i], String(values[i])); } return await helpers.execBatch(batch); }; @@ -160,7 +162,7 @@ module.exports = function (module) { module.sortedSetRanks = async function (key, values) { const batch = module.client.batch(); for (let i = 0; i < values.length; i += 1) { - batch.zrank(key, String(values[i])); + batch.zRank(key, String(values[i])); } return await helpers.execBatch(batch); }; @@ -168,7 +170,7 @@ module.exports = function (module) { module.sortedSetRevRanks = async function (key, values) { const batch = module.client.batch(); for (let i = 0; i < values.length; i += 1) { - batch.zrevrank(key, String(values[i])); + batch.zRevRank(key, String(values[i])); } return await helpers.execBatch(batch); }; @@ -177,8 +179,7 @@ module.exports = function (module) { if (!key || value === undefined) { return null; } - - const score = await module.client.zscore(key, value); + const score = await module.client.zScore(key, String(value)); return score === null ? score : parseFloat(score); }; @@ -187,7 +188,7 @@ module.exports = function (module) { return []; } const batch = module.client.batch(); - keys.forEach(key => batch.zscore(String(key), String(value))); + keys.forEach(key => batch.zScore(String(key), String(value))); const scores = await helpers.execBatch(batch); return scores.map(d => (d === null ? d : parseFloat(d))); }; @@ -197,7 +198,7 @@ module.exports = function (module) { return []; } const batch = module.client.batch(); - values.forEach(value => batch.zscore(String(key), String(value))); + values.forEach(value => batch.zScore(String(key), String(value))); const scores = await helpers.execBatch(batch); return scores.map(d => (d === null ? d : parseFloat(d))); }; @@ -211,9 +212,9 @@ module.exports = function (module) { if (!values.length) { return []; } - const batch = module.client.batch(); - values.forEach(v => batch.zscore(key, String(v))); - const results = await helpers.execBatch(batch); + const batch = module.client.multi(); + values.forEach(v => batch.zScore(key, String(v))); + const results = await batch.execAsPipeline(); return results.map(utils.isNumber); }; @@ -221,20 +222,18 @@ module.exports = function (module) { if (!Array.isArray(keys) || !keys.length) { return []; } - const batch = module.client.batch(); - keys.forEach(k => batch.zscore(k, String(value))); - const results = await helpers.execBatch(batch); + const batch = module.client.multi(); + keys.forEach(k => batch.zScore(k, String(value))); + const results = await batch.execAsPipeline(); return results.map(utils.isNumber); }; module.getSortedSetMembers = async function (key) { - return await module.client.zrange(key, 0, -1); + return await module.client.zRange(key, 0, -1); }; module.getSortedSetMembersWithScores = async function (key) { - return helpers.zsetToObjectArray( - await module.client.zrange(key, 0, -1, 'WITHSCORES') - ); + return await module.client.zRangeWithScores(key, 0, -1); }; module.getSortedSetsMembers = async function (keys) { @@ -242,7 +241,7 @@ module.exports = function (module) { return []; } const batch = module.client.batch(); - keys.forEach(k => batch.zrange(k, 0, -1)); + keys.forEach(k => batch.zRange(k, 0, -1)); return await helpers.execBatch(batch); }; @@ -251,87 +250,73 @@ module.exports = function (module) { return []; } const batch = module.client.batch(); - keys.forEach(k => batch.zrange(k, 0, -1, 'WITHSCORES')); + keys.forEach(k => batch.zRangeWithScores(k, 0, -1)); const res = await helpers.execBatch(batch); - return res.map(helpers.zsetToObjectArray); + return res; }; module.sortedSetIncrBy = async function (key, increment, value) { - const newValue = await module.client.zincrby(key, increment, value); + const newValue = await module.client.zIncrBy(key, increment, String(value)); return parseFloat(newValue); }; module.sortedSetIncrByBulk = async function (data) { const multi = module.client.multi(); data.forEach((item) => { - multi.zincrby(item[0], item[1], item[2]); + multi.zIncrBy(item[0], item[1], String(item[2])); }); const result = await multi.exec(); - return result.map(item => item && parseFloat(item[1])); + return result; }; - module.getSortedSetRangeByLex = async function (key, min, max, start, count) { - return await sortedSetLex('zrangebylex', false, key, min, max, start, count); + module.getSortedSetRangeByLex = async function (key, min, max, start = 0, count = -1) { + const { lmin, lmax } = helpers.normalizeLexRange(min, max, false); + return await module.client.zRange(key, lmin, lmax, { + BY: 'LEX', + LIMIT: { offset: start, count: count }, + }); }; - module.getSortedSetRevRangeByLex = async function (key, max, min, start, count) { - return await sortedSetLex('zrevrangebylex', true, key, max, min, start, count); + module.getSortedSetRevRangeByLex = async function (key, max, min, start = 0, count = -1) { + const { lmin, lmax } = helpers.normalizeLexRange(max, min, true); + return await module.client.zRange(key, lmin, lmax, { + REV: true, + BY: 'LEX', + LIMIT: { offset: start, count: count }, + }); }; module.sortedSetRemoveRangeByLex = async function (key, min, max) { - await sortedSetLex('zremrangebylex', false, key, min, max); + const { lmin, lmax } = helpers.normalizeLexRange(min, max, false); + await module.client.zRemRangeByLex(key, lmin, lmax); }; module.sortedSetLexCount = async function (key, min, max) { - return await sortedSetLex('zlexcount', false, key, min, max); + const { lmin, lmax } = helpers.normalizeLexRange(min, max, false); + return await module.client.zLexCount(key, lmin, lmax); }; - async function sortedSetLex(method, reverse, key, min, max, start, count) { - let minmin; - let maxmax; - if (reverse) { - minmin = '+'; - maxmax = '-'; - } else { - minmin = '-'; - maxmax = '+'; - } - - if (min !== minmin && !min.match(/^[[(]/)) { - min = `[${min}`; - } - if (max !== maxmax && !max.match(/^[[(]/)) { - max = `[${max}`; - } - const args = [key, min, max]; - if (count) { - args.push('LIMIT', start, count); - } - return await module.client[method](args); - } - module.getSortedSetScan = async function (params) { let cursor = '0'; const returnData = []; - let done = false; + let done; const seen = Object.create(null); do { /* eslint-disable no-await-in-loop */ - const res = await module.client.zscan(params.key, cursor, 'MATCH', params.match, 'COUNT', 5000); - cursor = res[0]; + const res = await module.client.zScan(params.key, cursor, { MATCH: params.match, COUNT: 5000 }); + cursor = res.cursor; done = cursor === '0'; - const data = res[1]; - for (let i = 0; i < data.length; i += 2) { - const value = data[i]; - if (!seen[value]) { - seen[value] = 1; + for (let i = 0; i < res.members.length; i ++) { + const item = res.members[i]; + if (!seen[item.value]) { + seen[item.value] = 1; if (params.withScores) { - returnData.push({ value: value, score: parseFloat(data[i + 1]) }); + returnData.push({ value: item.value, score: parseFloat(item.score) }); } else { - returnData.push(value); + returnData.push(item.value); } if (params.limit && returnData.length >= params.limit) { done = true; diff --git a/src/database/redis/sorted/add.js b/src/database/redis/sorted/add.js index 660618b8a4..264c877845 100644 --- a/src/database/redis/sorted/add.js +++ b/src/database/redis/sorted/add.js @@ -1,7 +1,6 @@ 'use strict'; module.exports = function (module) { - const helpers = require('../helpers'); const utils = require('../../../utils'); module.sortedSetAdd = async function (key, score, value) { @@ -14,7 +13,8 @@ module.exports = function (module) { if (!utils.isNumber(score)) { throw new Error(`[[error:invalid-score, ${score}]]`); } - await module.client.zadd(key, score, String(value)); + + await module.client.zAdd(key, { score, value: String(value) }); }; async function sortedSetAddMulti(key, scores, values) { @@ -30,11 +30,8 @@ module.exports = function (module) { throw new Error(`[[error:invalid-score, ${scores[i]}]]`); } } - const args = [key]; - for (let i = 0; i < scores.length; i += 1) { - args.push(scores[i], String(values[i])); - } - await module.client.zadd(args); + const members = scores.map((score, i) => ({ score, value: String(values[i])})); + await module.client.zAdd(key, members); } module.sortedSetsAdd = async function (keys, scores, value) { @@ -51,13 +48,16 @@ module.exports = function (module) { throw new Error('[[error:invalid-data]]'); } - const batch = module.client.batch(); + const batch = module.client.multi(); for (let i = 0; i < keys.length; i += 1) { if (keys[i]) { - batch.zadd(keys[i], isArrayOfScores ? scores[i] : scores, String(value)); + batch.zAdd(keys[i], { + score: isArrayOfScores ? scores[i] : scores, + value: String(value), + }); } } - await helpers.execBatch(batch); + await batch.execAsPipeline(); }; module.sortedSetAddBulk = async function (data) { @@ -69,8 +69,8 @@ module.exports = function (module) { if (!utils.isNumber(item[1])) { throw new Error(`[[error:invalid-score, ${item[1]}]]`); } - batch.zadd(item[0], item[1], item[2]); + batch.zAdd(item[0], { score: item[1], value: String(item[2]) }); }); - await helpers.execBatch(batch); + await batch.execAsPipeline(); }; }; diff --git a/src/database/redis/sorted/intersect.js b/src/database/redis/sorted/intersect.js index 2b2ed1fe90..983c11abc4 100644 --- a/src/database/redis/sorted/intersect.js +++ b/src/database/redis/sorted/intersect.js @@ -8,52 +8,46 @@ module.exports = function (module) { return 0; } const tempSetName = `temp_${Date.now()}`; - - const interParams = [tempSetName, keys.length].concat(keys); - const multi = module.client.multi(); - multi.zinterstore(interParams); - multi.zcard(tempSetName); + multi.zInterStore(tempSetName, keys); + multi.zCard(tempSetName); multi.del(tempSetName); const results = await helpers.execBatch(multi); return results[1] || 0; }; module.getSortedSetIntersect = async function (params) { - params.method = 'zrange'; + params.reverse = false; return await getSortedSetRevIntersect(params); }; module.getSortedSetRevIntersect = async function (params) { - params.method = 'zrevrange'; + params.reverse = true; return await getSortedSetRevIntersect(params); }; async function getSortedSetRevIntersect(params) { - const { sets } = params; + let { sets } = params; const start = params.hasOwnProperty('start') ? params.start : 0; const stop = params.hasOwnProperty('stop') ? params.stop : -1; const weights = params.weights || []; const tempSetName = `temp_${Date.now()}`; - let interParams = [tempSetName, sets.length].concat(sets); + const interParams = {}; if (weights.length) { - interParams = interParams.concat(['WEIGHTS'].concat(weights)); + sets = sets.map((set, index) => ({ key: set, weight: weights[index] })); } if (params.aggregate) { - interParams = interParams.concat(['AGGREGATE', params.aggregate]); + interParams['AGGREGATE'] = params.aggregate.toUpperCase(); } - const rangeParams = [tempSetName, start, stop]; - if (params.withScores) { - rangeParams.push('WITHSCORES'); - } + const rangeCmd = params.withScores ? 'zRangeWithScores' : 'zRange'; const multi = module.client.multi(); - multi.zinterstore(interParams); - multi[params.method](rangeParams); + multi.zInterStore(tempSetName, sets, interParams); + multi[rangeCmd](tempSetName, start, stop, { REV: params.reverse}); multi.del(tempSetName); let results = await helpers.execBatch(multi); @@ -61,6 +55,6 @@ module.exports = function (module) { return results ? results[1] : null; } results = results[1] || []; - return helpers.zsetToObjectArray(results); + return results; } }; diff --git a/src/database/redis/sorted/remove.js b/src/database/redis/sorted/remove.js index 0c2b0164b0..df4c980b11 100644 --- a/src/database/redis/sorted/remove.js +++ b/src/database/redis/sorted/remove.js @@ -18,10 +18,10 @@ module.exports = function (module) { if (Array.isArray(key)) { const batch = module.client.batch(); - key.forEach(k => batch.zrem(k, value)); + key.forEach(k => batch.zRem(k, value.map(String))); await helpers.execBatch(batch); } else { - await module.client.zrem(key, value); + await module.client.zRem(key, value.map(String)); } }; @@ -31,7 +31,7 @@ module.exports = function (module) { module.sortedSetsRemoveRangeByScore = async function (keys, min, max) { const batch = module.client.batch(); - keys.forEach(k => batch.zremrangebyscore(k, min, max)); + keys.forEach(k => batch.zRemRangeByScore(k, min, max)); await helpers.execBatch(batch); }; @@ -40,7 +40,7 @@ module.exports = function (module) { return; } const batch = module.client.batch(); - data.forEach(item => batch.zrem(item[0], item[1])); + data.forEach(item => batch.zRem(item[0], String(item[1]))); await helpers.execBatch(batch); }; }; diff --git a/src/database/redis/sorted/union.js b/src/database/redis/sorted/union.js index acd57c2db0..329c5f125f 100644 --- a/src/database/redis/sorted/union.js +++ b/src/database/redis/sorted/union.js @@ -4,25 +4,20 @@ module.exports = function (module) { const helpers = require('../helpers'); module.sortedSetUnionCard = async function (keys) { - const tempSetName = `temp_${Date.now()}`; if (!keys.length) { return 0; } - const multi = module.client.multi(); - multi.zunionstore([tempSetName, keys.length].concat(keys)); - multi.zcard(tempSetName); - multi.del(tempSetName); - const results = await helpers.execBatch(multi); - return Array.isArray(results) && results.length ? results[1] : 0; + const results = await module.client.zUnion(keys); + return results ? results.length : 0; }; module.getSortedSetUnion = async function (params) { - params.method = 'zrange'; + params.reverse = false; return await module.sortedSetUnion(params); }; module.getSortedSetRevUnion = async function (params) { - params.method = 'zrevrange'; + params.reverse = true; return await module.sortedSetUnion(params); }; @@ -32,21 +27,16 @@ module.exports = function (module) { } const tempSetName = `temp_${Date.now()}`; - - const rangeParams = [tempSetName, params.start, params.stop]; - if (params.withScores) { - rangeParams.push('WITHSCORES'); - } - + const rangeCmd = params.withScores ? 'zRangeWithScores' : 'zRange'; const multi = module.client.multi(); - multi.zunionstore([tempSetName, params.sets.length].concat(params.sets)); - multi[params.method](rangeParams); + multi.zUnionStore(tempSetName, params.sets); + multi[rangeCmd](tempSetName, params.start, params.stop, { REV: params.reverse }); multi.del(tempSetName); let results = await helpers.execBatch(multi); if (!params.withScores) { return results ? results[1] : null; } results = results[1] || []; - return helpers.zsetToObjectArray(results); + return results; }; }; diff --git a/src/emailer.js b/src/emailer.js index f280c21399..876d077a1a 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -6,7 +6,6 @@ const Benchpress = require('benchpressjs'); const nodemailer = require('nodemailer'); const wellKnownServices = require('nodemailer/lib/well-known/services'); const { htmlToText } = require('html-to-text'); -const url = require('url'); const path = require('path'); const fs = require('fs'); const _ = require('lodash'); @@ -56,8 +55,7 @@ const smtpSettingsChanged = (config) => { const getHostname = () => { const configUrl = nconf.get('url'); - const parsed = url.parse(configUrl); - return parsed.hostname; + return new URL(configUrl).hostname; }; const buildCustomTemplates = async (config) => { @@ -120,51 +118,55 @@ Emailer.setupFallbackTransport = (config) => { winston.verbose('[emailer] Setting up fallback transport'); // Enable SMTP transport if enabled in ACP if (parseInt(config['email:smtpTransport:enabled'], 10) === 1) { - const smtpOptions = { - name: getHostname(), - pool: config['email:smtpTransport:pool'], - }; - - if (config['email:smtpTransport:user'] || config['email:smtpTransport:pass']) { - smtpOptions.auth = { - user: config['email:smtpTransport:user'], - pass: config['email:smtpTransport:pass'], - }; - } - - if (config['email:smtpTransport:service'] === 'nodebb-custom-smtp') { - smtpOptions.port = config['email:smtpTransport:port']; - smtpOptions.host = config['email:smtpTransport:host']; - - if (config['email:smtpTransport:security'] === 'NONE') { - smtpOptions.secure = false; - smtpOptions.requireTLS = false; - smtpOptions.ignoreTLS = true; - } else if (config['email:smtpTransport:security'] === 'STARTTLS') { - smtpOptions.secure = false; - smtpOptions.requireTLS = true; - smtpOptions.ignoreTLS = false; - } else { - // meta.config['email:smtpTransport:security'] === 'ENCRYPTED' or undefined - smtpOptions.secure = true; - smtpOptions.requireTLS = true; - smtpOptions.ignoreTLS = false; - } - } else { - smtpOptions.service = String(config['email:smtpTransport:service']); - } - if (config['email:smtpTransport:allow-self-signed']) { - smtpOptions.tls = { - rejectUnauthorized: false, - }; - } - Emailer.transports.smtp = nodemailer.createTransport(smtpOptions); + Emailer.transports.smtp = Emailer.createSmtpTransport(config); Emailer.fallbackTransport = Emailer.transports.smtp; } else { Emailer.fallbackTransport = Emailer.transports.sendmail; } }; +Emailer.createSmtpTransport = (config) => { + const smtpOptions = { + name: getHostname(), + pool: config['email:smtpTransport:pool'], + }; + + if (config['email:smtpTransport:user'] || config['email:smtpTransport:pass']) { + smtpOptions.auth = { + user: config['email:smtpTransport:user'], + pass: config['email:smtpTransport:pass'], + }; + } + + if (config['email:smtpTransport:service'] === 'nodebb-custom-smtp') { + smtpOptions.port = config['email:smtpTransport:port']; + smtpOptions.host = config['email:smtpTransport:host']; + + if (config['email:smtpTransport:security'] === 'NONE') { + smtpOptions.secure = false; + smtpOptions.requireTLS = false; + smtpOptions.ignoreTLS = true; + } else if (config['email:smtpTransport:security'] === 'STARTTLS') { + smtpOptions.secure = false; + smtpOptions.requireTLS = true; + smtpOptions.ignoreTLS = false; + } else { + // meta.config['email:smtpTransport:security'] === 'ENCRYPTED' or undefined + smtpOptions.secure = true; + smtpOptions.requireTLS = true; + smtpOptions.ignoreTLS = false; + } + } else { + smtpOptions.service = String(config['email:smtpTransport:service']); + } + if (config['email:smtpTransport:allow-self-signed']) { + smtpOptions.tls = { + rejectUnauthorized: false, + }; + } + return nodemailer.createTransport(smtpOptions); +}; + Emailer.registerApp = (expressApp) => { app = expressApp; @@ -344,10 +346,7 @@ Emailer.sendToEmail = async (template, email, language, params) => { const usingFallback = !Plugins.hooks.hasListeners('filter:email.send') && !Plugins.hooks.hasListeners('static:email.send'); try { - if (Plugins.hooks.hasListeners('filter:email.send')) { - // Deprecated, remove in v1.19.0 - await Plugins.hooks.fire('filter:email.send', data); - } else if (Plugins.hooks.hasListeners('static:email.send')) { + if (Plugins.hooks.hasListeners('static:email.send')) { await Plugins.hooks.fire('static:email.send', data); } else { await Emailer.sendViaFallback(data); diff --git a/src/events.js b/src/events.js index 6a293c1bdd..f189a4e377 100644 --- a/src/events.js +++ b/src/events.js @@ -119,7 +119,7 @@ events.getEvents = async function (options) { const from = options.hasOwnProperty('from') ? options.from : '-inf'; const to = options.hasOwnProperty('to') ? options.to : '+inf'; const { filter, start, stop, uids } = options; - let eids = []; + let eids; if (Array.isArray(uids)) { if (filter === '') { @@ -261,6 +261,7 @@ events.deleteEvents = async function (eids) { events.deleteAll = async function () { await batch.processSortedSet('events:time', async (eids) => { await events.deleteEvents(eids); + await db.sortedSetRemove('events:time', eids); }, { alwaysStartAt: 0, batch: 500 }); }; diff --git a/src/file.js b/src/file.js index 639cc9f58c..7bfc957a94 100644 --- a/src/file.js +++ b/src/file.js @@ -7,6 +7,7 @@ const winston = require('winston'); const { mkdirp } = require('mkdirp'); const mime = require('mime'); const graceful = require('graceful-fs'); +const sanitizeHtml = require('sanitize-html'); const slugify = require('./slugify'); @@ -27,6 +28,10 @@ file.saveFileToLocal = async function (filename, folder, tempPath) { winston.verbose(`Saving file ${filename} to : ${uploadPath}`); await mkdirp(path.dirname(uploadPath)); + if (filename.endsWith('.svg')) { + await sanitizeSvg(tempPath); + } + await fs.promises.copyFile(tempPath, uploadPath); return { url: `/assets/uploads/${folder ? `${folder}/` : ''}${filename}`, @@ -155,4 +160,39 @@ file.walk = async function (dir) { return files.reduce((a, f) => a.concat(f), []); }; +async function sanitizeSvg(filePath) { + const dirty = await fs.promises.readFile(filePath, 'utf8'); + const clean = sanitizeHtml(dirty, { + allowedTags: [ + 'svg', 'g', 'defs', 'linearGradient', 'radialGradient', 'stop', + 'circle', 'ellipse', 'polygon', 'polyline', 'path', 'rect', + 'line', 'text', 'tspan', 'use', 'symbol', 'clipPath', 'mask', 'pattern', + 'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feMerge', 'feMergeNode', + ], + allowedAttributes: { + '*': [ + // Geometry + 'x', 'y', 'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', + 'width', 'height', 'd', 'points', 'viewBox', 'transform', + + // Presentation + 'fill', 'stroke', 'stroke-width', 'opacity', + 'stop-color', 'stop-opacity', 'offset', 'style', 'class', + + // Text + 'text-anchor', 'font-size', 'font-family', + + // Misc + 'id', 'clip-path', 'mask', 'filter', 'gradientUnits', 'gradientTransform', + 'xmlns', 'preserveAspectRatio', + ], + }, + parser: { + lowerCaseTags: false, + lowerCaseAttributeNames: false, + }, + }); + await fs.promises.writeFile(filePath, clean); +} + require('./promisify')(file); diff --git a/src/flags.js b/src/flags.js index d19da08d95..98949b6b5a 100644 --- a/src/flags.js +++ b/src/flags.js @@ -5,7 +5,6 @@ const winston = require('winston'); const validator = require('validator'); const activitypub = require('./activitypub'); -const activitypubApi = require('./api/activitypub'); const db = require('./database'); const user = require('./user'); const groups = require('./groups'); @@ -19,6 +18,7 @@ const privileges = require('./privileges'); const plugins = require('./plugins'); const utils = require('./utils'); const batch = require('./batch'); +const translator = require('./translator'); const Flags = module.exports; @@ -96,7 +96,6 @@ Flags.init = async function () { }; try { - ({ filters: Flags._filters } = await plugins.hooks.fire('filter:flags.getFilters', hookData)); ({ filters: Flags._filters, states: Flags._states } = await plugins.hooks.fire('filter:flags.init', hookData)); } catch (err) { winston.error(`[flags/init] Could not retrieve filters\n${err.stack}`); @@ -478,8 +477,7 @@ Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = fal const flagObj = await Flags.get(flagId); if (notifyRemote && activitypub.helpers.isUri(id)) { - const caller = await user.getUserData(uid); - activitypubApi.flag(caller, { ...flagObj, reason }); + activitypub.out.flag(uid, { ...flagObj, reason }); } plugins.hooks.fire('action:flags.create', { flag: flagObj }); @@ -532,7 +530,7 @@ Flags.purge = async function (flagIds) { flagData.flatMap( async (flagObj, i) => allReporterUids[i].map(async (uid) => { if (await db.isSortedSetMember(`flag:${flagObj.flagId}:remote`, uid)) { - await activitypubApi.undo.flag({ uid }, flagObj); + await activitypub.out.undo.flag(uid, flagObj); } }) ), @@ -570,7 +568,7 @@ Flags.addReport = async function (flagId, type, id, uid, reason, timestamp, targ ]); if (notifyRemote && activitypub.helpers.isUri(id)) { - await activitypubApi.flag({ uid }, { flagId, type, targetId: id, targetUid, uid, reason, timestamp }); + await activitypub.out.flag(uid, { flagId, type, targetId: id, targetUid, uid, reason, timestamp }); } plugins.hooks.fire('action:flags.addReport', { flagId, type, id, uid, reason, timestamp }); @@ -600,7 +598,7 @@ Flags.rescindReport = async (type, id, uid) => { if (await db.isSortedSetMember(`flag:${flagId}:remote`, uid)) { const flag = await Flags.get(flagId); - await activitypubApi.undo.flag({ uid }, flag); + await activitypub.out.undo.flag(uid, flag); } await db.sortedSetRemoveBulk([ @@ -750,7 +748,6 @@ Flags.update = async function (flagId, uid, changeset) { const notifObj = await notifications.create({ type: 'my-flags', bodyShort: `[[notifications:flag-assigned-to-you, ${flagId}]]`, - bodyLong: '', path: `/flags/${flagId}`, nid: `flags:assign:${flagId}:uid:${assigneeId}`, from: uid, @@ -758,8 +755,7 @@ Flags.update = async function (flagId, uid, changeset) { await notifications.push(notifObj, [assigneeId]); }; const isAssignable = async function (assigneeId) { - let allowed = false; - allowed = await user.isAdminOrGlobalMod(assigneeId); + let allowed = await user.isAdminOrGlobalMod(assigneeId); // Mods are also allowed to be assigned, if flag target is post in uid's moderated cid if (!allowed && current.type === 'post') { @@ -921,7 +917,7 @@ Flags.notify = async function (flagObj, uid, notifySelf = false) { groups.getMembers('Global Moderators', 0, -1), ]); let uids = admins.concat(globalMods); - let notifObj = null; + let notifObj; const { displayname } = flagObj.reports[flagObj.reports.length - 1].reporter; @@ -932,12 +928,12 @@ Flags.notify = async function (flagObj, uid, notifySelf = false) { ]); const modUids = await categories.getModeratorUids([cid]); - const titleEscaped = utils.decodeHTMLEntities(title).replace(/%/g, '%').replace(/,/g, ','); + const titleEscaped = utils.decodeHTMLEntities(title); notifObj = await notifications.create({ type: 'new-post-flag', - bodyShort: `[[notifications:user-flagged-post-in, ${displayname}, ${titleEscaped}]]`, - bodyLong: await plugins.hooks.fire('filter:parse.raw', String(flagObj.description || '')), + bodyShort: translator.compile('notifications:user-flagged-post-in', displayname, titleEscaped), + bodyLong: String(flagObj.target?.content || ''), pid: flagObj.targetId, path: `/flags/${flagObj.flagId}`, nid: `flag:post:${flagObj.targetId}:${uid}`, diff --git a/src/groups/data.js b/src/groups/data.js index 1dc52e44ad..51647e9ef5 100644 --- a/src/groups/data.js +++ b/src/groups/data.js @@ -7,6 +7,11 @@ const db = require('../database'); const plugins = require('../plugins'); const utils = require('../utils'); const translator = require('../translator'); +const coverPhoto = require('../coverPhoto'); + +const relative_path = nconf.get('relative_path'); + +const prependRelativePath = url => url.startsWith('http') ? url : relative_path + url; const intFields = [ 'createtime', 'memberCount', 'hidden', 'system', 'private', @@ -67,42 +72,70 @@ module.exports = function (Groups) { function modifyGroup(group, fields) { if (group) { + const hasField = utils.createFieldChecker(fields); + + if (hasField('private')) { + // Default to private if not set, as groups are private by default + group.private = ([null, undefined].includes(group.private)) ? 1 : group.private; + } + db.parseIntFields(group, intFields, fields); - escapeGroupData(group); - group.userTitleEnabled = ([null, undefined].includes(group.userTitleEnabled)) ? 1 : group.userTitleEnabled; - group.labelColor = validator.escape(String(group.labelColor || '#000000')); - group.textColor = validator.escape(String(group.textColor || '#ffffff')); - group.icon = validator.escape(String(group.icon || '')); - group.createtimeISO = utils.toISOString(group.createtime); - group.private = ([null, undefined].includes(group.private)) ? 1 : group.private; - group.memberPostCids = group.memberPostCids || ''; - group.memberPostCidsArray = group.memberPostCids.split(',').map(cid => parseInt(cid, 10)).filter(Boolean); + escapeGroupData(group, hasField); - group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url']; - - if (group['cover:url']) { - group['cover:url'] = group['cover:url'].startsWith('http') ? group['cover:url'] : (nconf.get('relative_path') + group['cover:url']); - } else { - group['cover:url'] = require('../coverPhoto').getDefaultGroupCover(group.name); + if (hasField('labelColor')) { + group.labelColor = validator.escape(String(group.labelColor || '#000000')); } - if (group['cover:thumb:url']) { - group['cover:thumb:url'] = group['cover:thumb:url'].startsWith('http') ? group['cover:thumb:url'] : (nconf.get('relative_path') + group['cover:thumb:url']); - } else { - group['cover:thumb:url'] = require('../coverPhoto').getDefaultGroupCover(group.name); + if (hasField('textColor')) { + group.textColor = validator.escape(String(group.textColor || '#ffffff')); } - group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%')); + if (hasField('icon')) { + group.icon = validator.escape(String(group.icon || '')); + } + + if (hasField('createtime')) { + group.createtimeISO = utils.toISOString(group.createtime); + } + + if (hasField('memberPostCids')) { + group.memberPostCids = group.memberPostCids || ''; + group.memberPostCidsArray = group.memberPostCids.split(',').map(cid => parseInt(cid, 10)).filter(Boolean); + } + + if (hasField('cover:thumb:url')) { + group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url']; + + group['cover:thumb:url'] = group['cover:thumb:url'] ? + prependRelativePath(group['cover:thumb:url']) : + coverPhoto.getDefaultGroupCover(group.name); + } + + if (hasField('cover:url')) { + group['cover:url'] = group['cover:url'] ? + prependRelativePath(group['cover:url']) : + coverPhoto.getDefaultGroupCover(group.name); + } + + if (hasField('cover:position')) { + group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%')); + } } } -function escapeGroupData(group) { +function escapeGroupData(group, hasField) { if (group) { - group.nameEncoded = encodeURIComponent(group.name); - group.displayName = validator.escape(String(group.name)); - group.description = validator.escape(String(group.description || '')); - group.userTitle = validator.escape(String(group.userTitle || '')); - group.userTitleEscaped = translator.escape(group.userTitle); + if (hasField('name')) { + group.nameEncoded = encodeURIComponent(group.name); + group.displayName = validator.escape(String(group.name)); + } + if (hasField('description')) { + group.description = validator.escape(String(group.description || '')); + } + if (hasField('userTitle')) { + group.userTitle = validator.escape(String(group.userTitle || '')); + group.userTitleEscaped = translator.escape(group.userTitle); + } } } diff --git a/src/groups/invite.js b/src/groups/invite.js index 590c72c7ec..747111d156 100644 --- a/src/groups/invite.js +++ b/src/groups/invite.js @@ -66,7 +66,6 @@ module.exports = function (Groups) { const notificationData = await Promise.all(uids.map(uid => notifications.create({ type: 'group-invite', bodyShort: `[[groups:invited.notification-title, ${groupName}]]`, - bodyLong: '', nid: `group:${groupName}:uid:${uid}:invite`, path: `/groups/${slugify(groupName)}`, icon: 'fa-users', diff --git a/src/image.js b/src/image.js index 06e3f2d76d..0d60fddc6a 100644 --- a/src/image.js +++ b/src/image.js @@ -102,14 +102,15 @@ image.size = async function (path) { return imageData ? { width: imageData.width, height: imageData.height } : undefined; }; -image.stripEXIF = async function (path) { - if (!meta.config.stripEXIFData || path.endsWith('.gif') || path.endsWith('.svg')) { +image.stripEXIF = async function ({ path, type }) { + if (!meta.config.stripEXIFData || type === 'image/gif' || type === 'image/svg+xml') { return; } try { if (plugins.hooks.hasListeners('filter:image.stripEXIF')) { await plugins.hooks.fire('filter:image.stripEXIF', { path: path, + type: type, }); return; } diff --git a/src/install.js b/src/install.js index f0903d3a16..c360868521 100644 --- a/src/install.js +++ b/src/install.js @@ -1,7 +1,6 @@ 'use strict'; const fs = require('fs'); -const url = require('url'); const path = require('path'); const prompt = require('prompt'); const winston = require('winston'); @@ -217,19 +216,17 @@ async function completeConfigSetup(config) { } // If port is explicitly passed via install vars, use it. Otherwise, glean from url if set. - const urlObj = url.parse(config.url); + const urlObj = new URL(config.url); if (urlObj.port && (!install.values || !install.values.hasOwnProperty('port'))) { config.port = urlObj.port; } - // Remove trailing slash from non-subfolder installs - if (urlObj.path === '/') { - urlObj.path = ''; - urlObj.pathname = ''; + config.url = urlObj.toString(); + // Remove trailing slash from URL + if (config.url.endsWith('/')) { + config.url = config.url.slice(0, -1); } - config.url = url.format(urlObj); - // ref: https://github.com/indexzero/nconf/issues/300 delete config.type; @@ -366,6 +363,8 @@ async function createAdmin() { username: results.username, password: results.password, email: results.email, + }, { + emailVerification: 'verify', }); await Groups.join('administrators', adminUid); await Groups.show('administrators'); @@ -526,6 +525,7 @@ async function enableDefaultPlugins() { let defaultEnabled = [ 'nodebb-plugin-composer-default', + 'nodebb-plugin-dbsearch', 'nodebb-plugin-markdown', 'nodebb-plugin-mentions', 'nodebb-plugin-web-push', diff --git a/src/messaging/data.js b/src/messaging/data.js index 2c3257c74f..c55f944b44 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -196,11 +196,12 @@ module.exports = function (Messaging) { async function modifyMessage(message, fields, mid) { if (message) { + const hasField = utils.createFieldChecker(fields); db.parseIntFields(message, intFields, fields); - if (message.hasOwnProperty('timestamp')) { + if (hasField('timestamp')) { message.timestampISO = utils.toISOString(message.timestamp); } - if (message.hasOwnProperty('edited')) { + if (hasField('edited')) { message.editedISO = utils.toISOString(message.edited); } } diff --git a/src/messaging/delete.js b/src/messaging/delete.js index 9e0f23c797..c754e4fc9f 100644 --- a/src/messaging/delete.js +++ b/src/messaging/delete.js @@ -2,7 +2,7 @@ const sockets = require('../socket.io'); const plugins = require('../plugins'); -const api = require('../api'); +const activitypub = require('../activitypub'); module.exports = function (Messaging) { Messaging.deleteMessage = async (mid, uid) => await doDeleteRestore(mid, 1, uid); @@ -30,6 +30,6 @@ module.exports = function (Messaging) { plugins.hooks.fire('action:messaging.restore', { message: msgData }); } - api.activitypub.update.privateNote({ uid }, { messageObj: msgData }); + activitypub.out.update.privateNote(uid, msgData); } }; diff --git a/src/messaging/edit.js b/src/messaging/edit.js index 358e73f4be..86d8b07b6c 100644 --- a/src/messaging/edit.js +++ b/src/messaging/edit.js @@ -4,7 +4,7 @@ const db = require('../database'); const meta = require('../meta'); const user = require('../user'); const plugins = require('../plugins'); -const api = require('../api'); +const activitypub = require('../activitypub'); const privileges = require('../privileges'); const utils = require('../utils'); @@ -39,7 +39,7 @@ module.exports = function (Messaging) { }); if (!isPublic && utils.isNumber(messages[0].fromuid)) { - api.activitypub.update.privateNote({ uid: messages[0].fromuid }, { messageObj: messages[0] }); + activitypub.out.update.privateNote(messages[0].fromuid, messages[0]); } } diff --git a/src/messaging/index.js b/src/messaging/index.js index 0c8bd0eded..8881d41b2a 100644 --- a/src/messaging/index.js +++ b/src/messaging/index.js @@ -250,7 +250,7 @@ Messaging.generateChatWithMessage = async function (room, callerUid, userLang) { const usernames = users.map(u => (utils.isNumber(u.uid) ? `${u.displayname}` : `${u.displayname}`)); - let compiled = ''; + let compiled; if (!users.length) { return '[[modules:chat.no-users-in-room]]'; } @@ -358,19 +358,27 @@ Messaging.canMessageUser = async (uid, toUid) => { throw new Error('[[error:no-privileges]]'); } - const [settings, isAdmin, isModerator, isFollowing, isBlocked] = await Promise.all([ + const [settings, isAdmin, isModerator, isBlocked] = await Promise.all([ user.getSettings(toUid), user.isAdministrator(uid), user.isModeratorOfAnyCategory(uid), - user.isFollowing(toUid, uid), user.blocks.is(uid, toUid), ]); if (isBlocked) { throw new Error('[[error:chat-user-blocked]]'); } - if (settings.restrictChat && !isAdmin && !isModerator && !isFollowing) { - throw new Error('[[error:chat-restricted]]'); + const isPrivileged = isAdmin || isModerator; + if (!isPrivileged) { + if (settings.disableIncomingChats) { + throw new Error('[[error:chat-restricted]]'); + } + if (settings.chatAllowList.length && !settings.chatAllowList.includes(String(uid))) { + throw new Error('[[error:chat-restricted]]'); + } + if (settings.chatDenyList.length && settings.chatDenyList.includes(String(uid))) { + throw new Error('[[error:chat-restricted]]'); + } } await plugins.hooks.fire('static:messaging.canMessageUser', { diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js index 58611b6bcf..1c9475608d 100644 --- a/src/messaging/notifications.js +++ b/src/messaging/notifications.js @@ -8,7 +8,7 @@ const db = require('../database'); const notifications = require('../notifications'); const user = require('../user'); const io = require('../socket.io'); -const api = require('../api'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); const utils = require('../utils'); @@ -83,7 +83,7 @@ module.exports = function (Messaging) { await Promise.all([ sendNotification(fromUid, roomId, messageObj), !isPublic && utils.isNumber(fromUid) ? - api.activitypub.create.privateNote({ uid: fromUid }, { messageObj }) : null, + activitypub.out.create.privateNote(messageObj) : null, ]); } catch (err) { winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`); diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js index 8b57b81da7..83de1c07c7 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -12,17 +12,17 @@ const privileges = require('../privileges'); const meta = require('../meta'); const io = require('../socket.io'); const cache = require('../cache'); -const cacheCreate = require('../cacheCreate'); +const cacheCreate = require('../cache/lru'); const utils = require('../utils'); const roomUidCache = cacheCreate({ - name: 'chat:room:uids', + name: 'chat-room-uids', max: 500, ttl: 0, }); const intFields = [ - 'roomId', 'timestamp', 'userCount', 'messageCount', + 'roomId', 'timestamp', 'userCount', 'messageCount', 'joinLeaveMessages', ]; module.exports = function (Messaging) { @@ -88,6 +88,7 @@ module.exports = function (Messaging) { timestamp: now, notificationSetting: data.notificationSetting, messageCount: 0, + joinLeaveMessages: data.joinLeaveMessages || 0, }; if (data.hasOwnProperty('roomName') && data.roomName) { @@ -104,11 +105,15 @@ module.exports = function (Messaging) { await Promise.all([ db.setObject(`chat:room:${roomId}`, room), db.sortedSetAdd('chat:rooms', now, roomId), - db.sortedSetAdd(`chat:room:${roomId}:owners`, now, uid), - db.sortedSetsAdd([ - `chat:room:${roomId}:uids`, - `chat:room:${roomId}:uids:online`, - ], now, uid), + db.sortedSetAddBulk([ + [`chat:room:${roomId}:uids`, now, uid], + [`chat:room:${roomId}:uids:online`, now, uid], + ...( + isPublic ? + [`chat:room:${roomId}:owners`, now, uid] : + [uid].concat(data.uids).map(uid => ([`chat:room:${roomId}:owners`, now, uid])) + ), + ]), ]); await Promise.all([ @@ -126,7 +131,7 @@ module.exports = function (Messaging) { 'chat:rooms:public:order:all', ]); - if (!isPublic) { + if (!isPublic && parseInt(room.joinLeaveMessages, 10) === 1) { // chat owner should also get the user-join system message await Messaging.addSystemMessage('user-join', uid, roomId); } @@ -280,12 +285,22 @@ module.exports = function (Messaging) { async function addUidsToRoom(uids, roomId) { const now = Date.now(); const timestamps = uids.map(() => now); + await Promise.all([ db.sortedSetAdd(`chat:room:${roomId}:uids`, timestamps, uids), db.sortedSetAdd(`chat:room:${roomId}:uids:online`, timestamps, uids), ]); await updateUserCount([roomId]); - await Promise.all(uids.map(uid => Messaging.addSystemMessage('user-join', uid, roomId))); + if (await joinLeaveMessagesEnabled(roomId)) { + await Promise.all( + uids.map(uid => Messaging.addSystemMessage('user-join', uid, roomId)) + ); + } + } + + async function joinLeaveMessagesEnabled(roomId) { + const roomData = await Messaging.getRoomData(roomId, ['joinLeaveMessages']); + return roomData && roomData.joinLeaveMessages === 1; } Messaging.removeUsersFromRoom = async (uid, uids, roomId) => { @@ -319,7 +334,9 @@ module.exports = function (Messaging) { } Messaging.leaveRoom = async (uids, roomId) => { - const isInRoom = await Promise.all(uids.map(uid => Messaging.isUserInRoom(uid, roomId))); + const isInRoom = await Promise.all( + uids.map(uid => Messaging.isUserInRoom(uid, roomId)) + ); uids = uids.filter((uid, index) => isInRoom[index]); const keys = uids @@ -334,8 +351,11 @@ module.exports = function (Messaging) { ], uids), db.sortedSetsRemove(keys, roomId), ]); - - await Promise.all(uids.map(uid => Messaging.addSystemMessage('user-leave', uid, roomId))); + if (await joinLeaveMessagesEnabled(roomId)) { + await Promise.all( + uids.map(uid => Messaging.addSystemMessage('user-leave', uid, roomId)) + ); + } await updateOwner(roomId); await updateUserCount([roomId]); }; @@ -357,10 +377,13 @@ module.exports = function (Messaging) { ], roomIds), ]); - await Promise.all( - roomIds.map(roomId => updateOwner(roomId)) - .concat(roomIds.map(roomId => Messaging.addSystemMessage('user-leave', uid, roomId))) - ); + await Promise.all(roomIds.map(async (roomId) => { + await updateOwner(roomId); + if (await joinLeaveMessagesEnabled(roomId)) { + await Messaging.addSystemMessage('user-leave', uid, roomId); + } + })); + await updateUserCount(roomIds); }; diff --git a/src/meta/css.js b/src/meta/css.js index 4b7e999383..304cc2554d 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -16,7 +16,7 @@ const utils = require('../utils'); const CSS = module.exports; CSS.supportedSkins = [ - 'cerulean', 'cosmo', 'cyborg', 'darkly', 'flatly', 'journal', 'litera', + 'brite', 'cerulean', 'cosmo', 'cyborg', 'darkly', 'flatly', 'journal', 'litera', 'lumen', 'lux', 'materia', 'minty', 'morph', 'pulse', 'quartz', 'sandstone', 'simplex', 'sketchy', 'slate', 'solar', 'spacelab', 'superhero', 'united', 'vapor', 'yeti', 'zephyr', @@ -56,9 +56,15 @@ function boostrapImport(themeData) { function bsvariables() { if (bootswatchSkin) { if (isCustomSkin) { - return themeData._variables || ''; + return ` + ${bsVariables} + ${themeData._variables || ''} + `; } - return `@import "bootswatch/dist/${bootswatchSkin}/variables";`; + return ` + ${bsVariables} + @import "bootswatch/dist/${bootswatchSkin}/variables"; + `; } return bsVariables; } @@ -270,7 +276,7 @@ CSS.getSkinSwitcherOptions = async function (uid) { { name: '[[user:no-skin]]', value: 'noskin', selected: userSettings.bootswatchSkin === 'noskin' }, ]; const lightSkins = [ - 'cerulean', 'cosmo', 'flatly', 'journal', 'litera', + 'brite', 'cerulean', 'cosmo', 'flatly', 'journal', 'litera', 'lumen', 'lux', 'materia', 'minty', 'morph', 'pulse', 'sandstone', 'simplex', 'sketchy', 'spacelab', 'united', 'yeti', 'zephyr', ]; diff --git a/src/meta/dependencies.js b/src/meta/dependencies.js index 5bb2a73c2a..dc697de20c 100644 --- a/src/meta/dependencies.js +++ b/src/meta/dependencies.js @@ -24,7 +24,7 @@ Dependencies.check = async function () { if (depsMissing) { throw new Error('dependencies-missing'); - } else if (depsOutdated && global.env !== 'development') { + } else if (depsOutdated && process.env.NODE_ENV !== 'development') { throw new Error('dependencies-out-of-date'); } }; diff --git a/src/meta/errors.js b/src/meta/errors.js index 0b7740b4ae..d1e11d67d8 100644 --- a/src/meta/errors.js +++ b/src/meta/errors.js @@ -4,7 +4,7 @@ const nconf = require('nconf'); const winston = require('winston'); const validator = require('validator'); const cronJob = require('cron').CronJob; -const { setTimeout } = require('timers/promises') +const { setTimeout } = require('timers/promises'); const db = require('../database'); const analytics = require('../analytics'); diff --git a/src/meta/minifier.js b/src/meta/minifier.js index 6a617cc45d..ce6fd84a09 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -162,11 +162,12 @@ actions.buildCSS = async function buildCSS(data) { try { const opts = { loadPaths: data.paths, + importers: [new sass.NodePackageImporter()], }; if (data.minify) { opts.silenceDeprecations = [ - 'legacy-js-api', 'mixed-decls', 'color-functions', - 'global-builtin', 'import', + 'legacy-js-api', 'color-functions', + 'global-builtin', 'import', 'if-function', ]; } const scssOutput = await sass.compileStringAsync(data.source, opts); diff --git a/src/meta/tags.js b/src/meta/tags.js index b59760b167..5afac6236d 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -57,8 +57,8 @@ Tags.parse = async (req, data, meta, link) => { }); } - const faviconPath = `${relative_path}/assets/uploads/system/favicon.ico`; - const cacheBuster = `${Meta.config['cache-buster'] ? `?${Meta.config['cache-buster']}` : ''}`; + const faviconPath = Meta.config['brand:favicon'] || `${relative_path}/assets/uploads/system/favicon.ico`; + const cacheBuster = Meta.config['cache-buster'] || ''; // Link Tags const defaultLinks = isAPI ? [] : [{ diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js index 4d3edb016c..2805801561 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -1,13 +1,21 @@ 'use strict'; const db = require('../database'); +const user = require('../user'); const meta = require('../meta'); const activitypub = require('../activitypub'); +const analytics = require('../analytics'); +const helpers = require('./helpers'); const middleware = module.exports; middleware.enabled = async (req, res, next) => next(!meta.config.activitypubEnabled ? 'route' : undefined); +middleware.pageview = async (req, res, next) => { + await analytics.apPageView({ ip: req.ip }); + next(); +}; + middleware.assertS2S = async function (req, res, next) { // For whatever reason, express accepts does not recognize "profile" as a valid differentiator // Therefore, manual header parsing is used here. @@ -33,10 +41,12 @@ middleware.verify = async function (req, res, next) { return next(); } - const verified = await activitypub.verify(req); - if (!verified && req.method === 'POST') { - activitypub.helpers.log('[middleware/activitypub] HTTP signature verification failed.'); - return res.sendStatus(400); + if (req.method === 'POST') { + const verified = await activitypub.verify(req); + if (!verified) { + activitypub.helpers.log('[middleware/activitypub] HTTP signature verification failed.'); + return res.sendStatus(400); + } } // Set calling user @@ -51,10 +61,16 @@ middleware.verify = async function (req, res, next) { next(); }; -middleware.assertPayload = async function (req, res, next) { +middleware.assertPayload = helpers.try(async function (req, res, next) { // Checks the validity of the incoming payload against the sender and rejects on failure activitypub.helpers.log('[middleware/activitypub] Validating incoming payload...'); + // Reject from banned users + const isBanned = await user.bans.isBanned(req.uid); + if (isBanned) { + return res.sendStatus(403); + } + // Sanity-check payload schema const required = ['id', 'type', 'actor', 'object']; if (!required.every(prop => req.body.hasOwnProperty(prop))) { @@ -84,12 +100,12 @@ middleware.assertPayload = async function (req, res, next) { // Domain check const { hostname } = new URL(actor); - const allowed = await activitypub.instances.isAllowed(hostname); + const allowed = activitypub.instances.isAllowed(hostname); if (!allowed) { activitypub.helpers.log(`[middleware/activitypub] Blocked incoming activity from ${hostname}.`); return res.sendStatus(403); } - await db.sortedSetAdd('instances:lastSeen', Date.now(), hostname); + activitypub.instances.log(hostname); // Origin checking if (typeof object !== 'string' && object.hasOwnProperty('id')) { @@ -105,7 +121,11 @@ middleware.assertPayload = async function (req, res, next) { // Cross-check key ownership against received actor await activitypub.actors.assert(actor); - const compare = ((await db.getObjectField(`userRemote:${actor}:keys`, 'id')) || '').replace(/#[\w-]+$/, ''); + let compare = await db.getObjectsFields([ + `userRemote:${actor}:keys`, `categoryRemote:${actor}:keys`, + ], ['id']); + compare = compare.reduce((keyId, { id }) => keyId || id || '', '').replace(/#[\w-]+$/, ''); + const { signature } = req.headers; let keyId = new Map(signature.split(',').filter(Boolean).map((v) => { const index = v.indexOf('='); @@ -119,7 +139,7 @@ middleware.assertPayload = async function (req, res, next) { activitypub.helpers.log('[middleware/activitypub] Key ownership cross-check passed.'); next(); -}; +}); middleware.resolveObjects = async function (req, res, next) { const { type, object } = req.body; diff --git a/src/middleware/index.js b/src/middleware/index.js index a23bb6c117..b2bef3c758 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -1,12 +1,10 @@ 'use strict'; -const async = require('async'); const path = require('path'); const validator = require('validator'); const nconf = require('nconf'); const toobusy = require('toobusy-js'); const util = require('util'); -const multipart = require('connect-multiparty'); const { csrfSynchronisedProtection } = require('./csrf'); const plugins = require('../plugins'); @@ -25,10 +23,11 @@ const controllers = { }; const delayCache = cacheCreate({ + name: 'delay-middleware', ttl: 1000 * 60, max: 200, }); -const multipartMiddleware = multipart(); + const middleware = module.exports; @@ -91,11 +90,6 @@ middleware.pageView = helpers.try(async (req, res, next) => { }); middleware.pluginHooks = helpers.try(async (req, res, next) => { - // TODO: Deprecate in v2.0 - await async.each(plugins.loadedHooks['filter:router.page'] || [], (hookObj, next) => { - hookObj.method(req, res, next); - }); - await plugins.hooks.fire('response:router.page', { req: req, res: res, @@ -107,17 +101,30 @@ middleware.pluginHooks = helpers.try(async (req, res, next) => { }); middleware.validateFiles = function validateFiles(req, res, next) { - if (!req.files.files) { + if (!req.files) { return next(new Error(['[[error:invalid-files]]'])); } - - if (Array.isArray(req.files.files) && req.files.files.length) { - return next(); + function makeFilesCompatible(files) { + if (Array.isArray(files)) { + // multer uses originalname and mimetype, but we use name and type + files.forEach((file) => { + if (file.originalname) { + file.name = file.originalname; + } + if (file.mimetype) { + file.type = file.mimetype; + } + }); + } + next(); + } + if (Array.isArray(req.files) && req.files.length) { + return makeFilesCompatible(req.files); } - if (typeof req.files.files === 'object') { - req.files.files = [req.files.files]; - return next(); + if (typeof req.files === 'object') { + req.files = [req.files]; + return makeFilesCompatible(req.files); } return next(new Error(['[[error:invalid-files]]'])); @@ -138,12 +145,18 @@ middleware.logApiUsage = async function logApiUsage(req, res, next) { }; middleware.routeTouchIcon = function routeTouchIcon(req, res) { - if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) { - return res.redirect(meta.config['brand:touchIcon']); + const brandTouchIcon = meta.config['brand:touchIcon']; + if (brandTouchIcon && validator.isURL(brandTouchIcon)) { + return res.redirect(brandTouchIcon); } - let iconPath = ''; - if (meta.config['brand:touchIcon']) { - iconPath = path.join(nconf.get('upload_path'), meta.config['brand:touchIcon'].replace(/assets\/uploads/, '')); + + let iconPath; + if (brandTouchIcon) { + const uploadPath = nconf.get('upload_path'); + iconPath = path.join(uploadPath, brandTouchIcon.replace(/assets\/uploads/, '')); + if (!iconPath.startsWith(uploadPath)) { + return res.status(404).send('Not found'); + } } else { iconPath = path.join(nconf.get('base_dir'), 'public/images/touch/512.png'); } @@ -209,7 +222,7 @@ middleware.privateUploads = function privateUploads(req, res, next) { }; middleware.busyCheck = function busyCheck(req, res, next) { - if (global.env === 'production' && meta.config.eventLoopCheckEnabled && toobusy()) { + if (process.env.NODE_ENV === 'production' && meta.config.eventLoopCheckEnabled && toobusy()) { analytics.increment('errors:503'); res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html')); } else { @@ -230,11 +243,11 @@ middleware.delayLoading = function delayLoading(req, res, next) { // Introduces an artificial delay during load so that brute force attacks are effectively mitigated // Add IP to cache so if too many requests are made, subsequent requests are blocked for a minute - let timesSeen = delayCache.get(req.ip) || 0; + const timesSeen = delayCache.get(req.ip) || 0; if (timesSeen > 10) { return res.sendStatus(429); } - delayCache.set(req.ip, timesSeen += 1); + delayCache.set(req.ip, timesSeen + 1); setTimeout(next, 1000); }; @@ -261,10 +274,19 @@ middleware.buildSkinAsset = helpers.try(async (req, res, next) => { middleware.addUploadHeaders = function addUploadHeaders(req, res, next) { // Trim uploaded files' timestamps when downloading + force download if html let basename = path.basename(req.path); - const extname = path.extname(req.path); - if (req.path.startsWith('/uploads/files/') && middleware.regexes.timestampedUpload.test(basename)) { - basename = basename.slice(14); - res.header('Content-Disposition', `${extname.startsWith('.htm') ? 'attachment' : 'inline'}; filename="${basename}"`); + const extname = path.extname(req.path).toLowerCase(); + const unsafeExtensions = [ + '.html', '.htm', '.xhtml', '.mht', '.mhtml', '.stm', '.shtm', '.shtml', + '.svg', '.svgz', + '.xml', '.xsl', '.xslt', + ]; + const isInlineSafe = !unsafeExtensions.includes(extname); + const dispositionType = isInlineSafe ? 'inline' : 'attachment'; + if (req.path.startsWith('/uploads/files/')) { + if (middleware.regexes.timestampedUpload.test(basename)) { + basename = basename.slice(14); + } + res.header('Content-Disposition', `${dispositionType}; filename="${basename}"`); } next(); @@ -298,14 +320,3 @@ middleware.checkRequired = function (fields, req, res, next) { controllers.helpers.formatApiResponse(400, res, new Error(`[[error:required-parameters-missing, ${missing.join(' ')}]]`)); }; - -middleware.handleMultipart = (req, res, next) => { - // Applies multipart handler on applicable content-type - const { 'content-type': contentType } = req.headers; - - if (contentType && !contentType.startsWith('multipart/form-data')) { - return next(); - } - - multipartMiddleware(req, res, next); -}; diff --git a/src/middleware/multer.js b/src/middleware/multer.js new file mode 100644 index 0000000000..e288fe309e --- /dev/null +++ b/src/middleware/multer.js @@ -0,0 +1,17 @@ +'use strict'; + +const multer = require('multer'); +const storage = multer.diskStorage({}); +const upload = multer({ + storage, + // from https://github.com/TriliumNext/Trilium/pull/3058/files + fileFilter: (req, file, cb) => { + // UTF-8 file names are not well decoded by multer/busboy, so we handle the conversion on our side. + // See https://github.com/expressjs/multer/pull/1102. + file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf-8'); + cb(null, true); + }, +}); + +module.exports = upload; + diff --git a/src/middleware/render.js b/src/middleware/render.js index e01110936f..f348236884 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -45,7 +45,7 @@ module.exports = function (middleware) { options.loggedInUser = await getLoggedInUser(req); options.relative_path = relative_path; options.template = { name: template, [template]: true }; - options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); + options.url = options.url || (req.baseUrl + req.path.replace(/^\/api/, '')); options.bodyClass = helpers.buildBodyClass(req, res, options); if (req.loggedIn) { @@ -88,7 +88,7 @@ module.exports = function (middleware) { if (req.route && req.route.path === '/api/') { options.title = '[[pages:home]]'; } - req.app.set('json spaces', global.env === 'development' || req.query.pretty ? 4 : 0); + req.app.set('json spaces', process.env.NODE_ENV === 'development' || req.query.pretty ? 4 : 0); return res.json(options); } const optionsString = JSON.stringify(options).replace(/<\//g, '<\\/'); @@ -150,6 +150,7 @@ module.exports = function (middleware) { async function loadClientHeaderFooterData(req, res, options) { const registrationType = meta.config.registrationType || 'normal'; res.locals.config = res.locals.config || {}; + const userLang = res.locals.config.userLang || meta.config.userLang || 'en-GB'; const templateValues = { title: meta.config.title || '', 'title:url': meta.config['title:url'] || '', @@ -180,9 +181,9 @@ module.exports = function (middleware) { blocks: user.blocks.list(req.uid), user: user.getUserData(req.uid), isEmailConfirmSent: req.uid <= 0 ? false : await user.email.isValidationPending(req.uid), - languageDirection: translator.translate('[[language:dir]]', res.locals.config.userLang), - timeagoCode: languages.userTimeagoCode(res.locals.config.userLang), - browserTitle: translator.translate(controllersHelpers.buildTitle(title)), + languageDirection: translator.translate('[[language:dir]]', userLang), + timeagoCode: languages.userTimeagoCode(userLang), + browserTitle: translator.translate(controllersHelpers.buildTitle(title), userLang), navigation: navigation.get(req.uid), roomIds: req.uid > 0 ? db.getSortedSetRevRange(`uid:${req.uid}:chat:rooms`, 0, 0) : [], }); diff --git a/src/middleware/uploads.js b/src/middleware/uploads.js index d1ce5b09b2..fdbfc3dbda 100644 --- a/src/middleware/uploads.js +++ b/src/middleware/uploads.js @@ -20,10 +20,12 @@ exports.ratelimit = helpers.try(async (req, res, next) => { } if (!cache) { cache = cacheCreate({ + name: 'upload-rate-limit-cache', + max: 100, ttl: meta.config.uploadRateLimitCooldown * 1000, }); } - const count = (cache.get(`${req.ip}:uploaded_file_count`) || 0) + req.files.files.length; + const count = (cache.get(`${req.ip}:uploaded_file_count`) || 0) + req.files.length; if (count > meta.config.uploadRateLimitThreshold) { return next(new Error(['[[error:upload-ratelimit-reached]]'])); } diff --git a/src/middleware/user.js b/src/middleware/user.js index 8fb3f51b57..620c4bad9c 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -53,6 +53,12 @@ module.exports = function (middleware) { } if (req.loggedIn) { + const exists = await user.exists(req.uid); + if (!exists) { + res.locals.logoutRedirect = true; + return controllers.authentication.logout(req, res); + } + return true; } else if (req.headers.hasOwnProperty('authorization')) { const user = await passportAuthenticateAsync(req, res); @@ -259,8 +265,12 @@ module.exports = function (middleware) { return res.redirect(`${nconf.get('relative_path')}${newPath}`); } } + try { + res.locals.userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query); + } catch (err) { + return next(err); + } - res.locals.userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query); if (!res.locals.userData) { return next('route'); } diff --git a/src/notifications.js b/src/notifications.js index df7cf51fb4..12ed99f7d5 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -9,6 +9,7 @@ const _ = require('lodash'); const db = require('./database'); const User = require('./user'); +const categories = require('./categories'); const posts = require('./posts'); const groups = require('./groups'); const meta = require('./meta'); @@ -22,12 +23,15 @@ const Notifications = module.exports; // ttlcache for email-only chat notifications const notificationCache = ttlCache({ + name: 'notification-email-cache', max: 1000, ttl: (meta.config.notificationSendDelay || 60) * 1000, noDisposeOnSet: true, dispose: sendEmail, }); +Notifications.delayCache = notificationCache; + Notifications.baseTypes = [ 'notificationType_upvote', 'notificationType_new-topic', @@ -82,10 +86,29 @@ Notifications.getMultiple = async function (nids) { const keys = nids.map(nid => `notifications:${nid}`); const notifications = await db.getObjects(keys); - const userKeys = notifications.map(n => n && n.from); - const usersData = await User.getUsersFields(userKeys, ['username', 'userslug', 'picture']); + const userKeys = notifications.filter(n => n && n.from).map(n => n.from); + let [usersData, categoriesData] = await Promise.all([ + User.getUsersFields(userKeys, ['username', 'userslug', 'picture']), + categories.getCategoriesFields(userKeys, ['cid', 'name', 'slug', 'picture']), + ]); + // Merge valid categoriesData into usersData + usersData = usersData.map((userData, idx) => { + const categoryData = categoriesData[idx]; + if (!userData.uid && categoryData.cid) { + return { + username: categoryData.slug, + displayname: categoryData.name, + userslug: categoryData.slug, + picture: categoryData.picture, + }; + } - notifications.forEach((notification, index) => { + return userData; + }); + // from can be either uid or cid + const userMap = new Map(userKeys.map((from, index) => [String(from), usersData[index]])); + + notifications.forEach((notification) => { if (notification) { intFields.forEach((field) => { if (notification.hasOwnProperty(field)) { @@ -102,8 +125,10 @@ Notifications.getMultiple = async function (nids) { if (notification.bodyLong) { notification.bodyLong = utils.stripHTMLTags(notification.bodyLong, ['img', 'p', 'a']); } - - notification.user = usersData[index]; + const fromUser = userMap.get(String(notification.from)); + if (fromUser !== undefined) { + notification.user = fromUser; + } if (notification.user && notification.from) { notification.image = notification.user.picture || null; if (notification.user.username === '[[global:guest]]') { @@ -145,7 +170,7 @@ Notifications.create = async function (data) { const oldNotif = await db.getObject(`notifications:${data.nid}`); if ( oldNotif && - parseInt(oldNotif.pid, 10) === parseInt(data.pid, 10) && + String(oldNotif.pid, 10) === String(data.pid, 10) && parseInt(oldNotif.importance, 10) > parseInt(data.importance, 10) ) { return null; @@ -158,6 +183,9 @@ Notifications.create = async function (data) { if (!result.data) { return null; } + if (data.bodyShort) { + data.bodyShort = utils.stripBidiControls(data.bodyShort); + } await Promise.all([ db.sortedSetAdd('notifications', now, data.nid), db.setObject(`notifications:${data.nid}`, data), @@ -260,7 +288,10 @@ async function pushToUids(uids, notification) { notificationCache.delete(cacheKey); } } else { - await sendEmail({ uids: results.uidsToEmail, notification }); + notificationCache.set(`delayed:nid:${notification.nid}`, { + uids: results.uidsToEmail, + notification, + }); } } @@ -272,11 +303,22 @@ async function pushToUids(uids, notification) { }); } -async function sendEmail({ uids, notification }, mergeId, reason) { +async function sendEmail({ uids, notification }, cacheKey, reason) { if ((reason && reason === 'set') || !uids.length) { return; } + // check if notification already read by users + // if so don't send email, https://github.com/NodeBB/NodeBB/issues/5867 + const hasRead = await db.isMemberOfSortedSets( + uids.map(uid => `uid:${uid}:notifications:read`), + notification.nid + ); + uids = uids.filter((uid, index) => !hasRead[index]); + if (!uids.length) { + return; + } + // Update CTA messaging (as not all notification types need custom text) if (['new-reply', 'new-chat'].includes(notification.type)) { notification['cta-type'] = notification.type; @@ -382,10 +424,20 @@ Notifications.markReadMultiple = async function (nids, uid) { ]); }; -Notifications.markAllRead = async function (uid) { +Notifications.markAllRead = async function (uid, filter = '') { await batch.processSortedSet(`uid:${uid}:notifications:unread`, async (unreadNotifs) => { - const nids = unreadNotifs.map(n => n && n.value); - const datetimes = unreadNotifs.map(n => n && n.score); + let nids = unreadNotifs.map(n => n && n.value); + let datetimes = unreadNotifs.map(n => n && n.score); + if (filter !== '') { + const notificationKeys = nids.map(nid => `notifications:${nid}`); + let notificationData = await db.getObjectsFields(notificationKeys, ['nid', 'type', 'datetime']); + notificationData = notificationData.filter(n => n && n.nid && n.type === filter); + if (!notificationData.length) { + return; + } + nids = notificationData.map(n => n.nid); + datetimes = notificationData.map(n => n.datetime || Date.now()); + } await Promise.all([ db.sortedSetRemove(`uid:${uid}:notifications:unread`, nids), db.sortedSetAdd(`uid:${uid}:notifications:read`, datetimes, nids), diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 553e90db45..a64b152b23 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -8,97 +8,11 @@ const als = require('../als'); const Hooks = module.exports; Hooks._deprecated = new Map([ - ['filter:email.send', { - new: 'static:email.send', - since: 'v1.17.0', - until: 'v2.0.0', - }], - ['filter:router.page', { - new: 'response:router.page', - since: 'v1.15.3', - until: 'v2.1.0', - }], - ['filter:post.purge', { - new: 'filter:posts.purge', - since: 'v1.19.6', - until: 'v2.1.0', - }], - ['action:post.purge', { - new: 'action:posts.purge', - since: 'v1.19.6', - until: 'v2.1.0', - }], - ['filter:user.verify.code', { - new: 'filter:user.verify', - since: 'v2.2.0', - until: 'v3.0.0', - }], - ['filter:flags.getFilters', { - new: 'filter:flags.init', - since: 'v2.7.0', - until: 'v3.0.0', - }], - ['filter:privileges.global.list', { - new: 'static:privileges.global.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.global.groups.list', { - new: 'static:privileges.global.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.global.list_human', { - new: 'static:privileges.global.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.global.groups.list_human', { - new: 'static:privileges.global.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.list', { - new: 'static:privileges.categories.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.groups.list', { - new: 'static:privileges.categories.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.list_human', { - new: 'static:privileges.categories.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.groups.list_human', { - new: 'static:privileges.categories.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - - ['filter:privileges.admin.list', { - new: 'static:privileges.admin.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.admin.groups.list', { - new: 'static:privileges.admin.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.admin.list_human', { - new: 'static:privileges.admin.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], - ['filter:privileges.admin.groups.list_human', { - new: 'static:privileges.admin.init', - since: 'v3.5.0', - until: 'v4.0.0', - }], + /* ['filter:old.hook.name', { + new: 'filter:new.hook.name', + since: 'v4.0.0', + until: 'v5.0.0', + }], */ ]); Hooks.internals = { @@ -176,7 +90,7 @@ Hooks.unregister = function (id, hook, method) { Hooks.fire = async function (hook, params) { const hookList = plugins.loadedHooks[hook]; const hookType = hook.split(':')[0]; - if (global.env === 'development' && hook !== 'action:plugins.firehook' && hook !== 'filter:plugins.firehook') { + if (process.env.NODE_ENV === 'development' && hook !== 'action:plugins.firehook' && hook !== 'filter:plugins.firehook') { winston.debug(`[plugins/fireHook] ${hook}`); } @@ -246,7 +160,7 @@ async function fireFilterHook(hook, hookList, params) { async function fireMethod(hookObj, params) { if (typeof hookObj.method !== 'function') { - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.warn(`[plugins] Expected method for hook '${hook}' in plugin '${hookObj.id}' not found, skipping.`); } return params; @@ -271,7 +185,7 @@ async function fireActionHook(hook, hookList, params) { } for (const hookObj of hookList) { if (typeof hookObj.method !== 'function') { - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.warn(`[plugins] Expected method for hook '${hook}' in plugin '${hookObj.id}' not found, skipping.`); } } else { @@ -305,7 +219,7 @@ async function fireStaticHook(hook, hookList, params) { async function fireMethod(hookObj, params) { if (typeof hookObj.method !== 'function') { - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.warn(`[plugins] Expected method for hook '${hook}' in plugin '${hookObj.id}' not found, skipping.`); } return params; @@ -342,7 +256,7 @@ async function fireResponseHook(hook, hookList, params) { } for (const hookObj of hookList) { if (typeof hookObj.method !== 'function') { - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.warn(`[plugins] Expected method for hook '${hook}' in plugin '${hookObj.id}' not found, skipping.`); } } else { diff --git a/src/plugins/index.js b/src/plugins/index.js index 47676a5197..9fdc241eb9 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -78,12 +78,12 @@ Plugins.init = async function (nbbApp, nbbMiddleware) { middleware = nbbMiddleware; } - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.verbose('[plugins] Initializing plugins system'); } await Plugins.reload(); - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.info('[plugins] Plugins OK'); } @@ -194,7 +194,7 @@ Plugins.listTrending = async () => { Plugins.normalise = async function (apiReturn) { const pluginMap = {}; - const { dependencies } = require(paths.currentPackage); + const { dependencies } = require(paths.installPackage); apiReturn = Array.isArray(apiReturn) ? apiReturn : []; apiReturn.forEach((packageData) => { packageData.id = packageData.name; @@ -229,7 +229,7 @@ Plugins.normalise = async function (apiReturn) { pluginMap[plugin.id].settingsRoute = plugin.settingsRoute; pluginMap[plugin.id].license = plugin.license; - // If package.json defines a version to use, stick to that + // If install/package.json defines a version to use, stick to that if (dependencies.hasOwnProperty(plugin.id) && semver.valid(dependencies[plugin.id])) { pluginMap[plugin.id].latest = dependencies[plugin.id]; } else { diff --git a/src/plugins/install.js b/src/plugins/install.js index 21d993226d..14f410555f 100644 --- a/src/plugins/install.js +++ b/src/plugins/install.js @@ -14,6 +14,7 @@ const meta = require('../meta'); const pubsub = require('../pubsub'); const { paths, pluginNamePattern } = require('../constants'); const pkgInstall = require('../cli/package-install'); +const cache = require('../cache'); const packageManager = pkgInstall.getPackageManager(); let packageManagerExecutable = packageManager; @@ -70,6 +71,7 @@ module.exports = function (Plugins) { const count = await db.sortedSetCard('plugins:active'); await db.sortedSetAdd('plugins:active', count, id); } + cache.set(`plugin:isActive:${id}`, !isActive); meta.reloadRequired = true; const hook = isActive ? 'deactivate' : 'activate'; Plugins.hooks.fire(`action:plugin.${hook}`, { id: id }); @@ -156,11 +158,27 @@ module.exports = function (Plugins) { } }; + Plugins.isSystemPlugin = async function (id) { + const pluginDir = path.join(paths.nodeModules, id, 'plugin.json'); + try { + const pluginData = JSON.parse(await fs.readFile(pluginDir, 'utf8')); + return pluginData && pluginData.system === true; + } catch (err) { + return false; + } + }; + Plugins.isActive = async function (id) { if (nconf.get('plugins:active')) { return nconf.get('plugins:active').includes(id); } - return await db.isSortedSetMember('plugins:active', id); + const cached = cache.get(`plugin:isActive:${id}`); + if (cached !== undefined) { + return cached; + } + const isActive = await db.isSortedSetMember('plugins:active', id); + cache.set(`plugin:isActive:${id}`, isActive); + return isActive; }; Plugins.getActive = async function () { diff --git a/src/plugins/usage.js b/src/plugins/usage.js index 69e3a44441..f36370b50f 100644 --- a/src/plugins/usage.js +++ b/src/plugins/usage.js @@ -18,7 +18,7 @@ module.exports = function (Plugins) { }; Plugins.submitUsageData = async function () { - if (!meta.config.submitPluginUsage || !Plugins.loadedPlugins.length || global.env !== 'production') { + if (!meta.config.submitPluginUsage || !Plugins.loadedPlugins.length || process.env.NODE_ENV !== 'production') { return; } diff --git a/src/posts/cache.js b/src/posts/cache.js index bb65026ae4..0953d6b84f 100644 --- a/src/posts/cache.js +++ b/src/posts/cache.js @@ -11,7 +11,7 @@ exports.getOrCreate = function () { maxSize: meta.config.postCacheSize, sizeCalculation: function (n) { return n.length || 1; }, ttl: 0, - enabled: global.env === 'production', + enabled: process.env.NODE_ENV === 'production', }); } diff --git a/src/posts/create.js b/src/posts/create.js index 656ae68ab0..1cce927c11 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -7,7 +7,6 @@ const user = require('../user'); const topics = require('../topics'); const categories = require('../categories'); const groups = require('../groups'); -const privileges = require('../privileges'); const activitypub = require('../activitypub'); const utils = require('../utils'); @@ -18,13 +17,14 @@ module.exports = function (Posts) { const content = data.content.toString(); const timestamp = data.timestamp || Date.now(); const isMain = data.isMain || false; + let hasAttachment = false; if (!uid && parseInt(uid, 10) !== 0) { throw new Error('[[error:invalid-uid]]'); } - if (data.toPid) { - await checkToPid(data.toPid, uid); + if (data.toPid && !utils.isNumber(data.toPid) && !activitypub.helpers.isUri(data.toPid)) { + throw new Error('[[error:invalid-pid]]'); } const pid = data.pid || await db.incrObjectField('global', 'nextPid'); @@ -46,23 +46,12 @@ module.exports = function (Posts) { if (_activitypub.audience) { postData.audience = _activitypub.audience; } - } - // Rewrite emoji references to inline image assets - if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { - _activitypub.tag - .filter(tag => tag.type === 'Emoji' && - tag.icon && tag.icon.type === 'Image') - .forEach((tag) => { - if (!tag.name.startsWith(':')) { - tag.name = `:${tag.name}`; - } - if (!tag.name.endsWith(':')) { - tag.name = `${tag.name}:`; - } + // Rewrite emoji references to inline image assets + const property = postData.sourceContent && !postData.content ? 'sourceContent' : 'content'; + postData[property] = activitypub.helpers.renderEmoji(postData[property], _activitypub.tag); - postData.content = postData.content.replace(new RegExp(tag.name, 'g'), ``); - }); + hasAttachment = _activitypub && _activitypub.attachment && _activitypub.attachment.length; } ({ post: postData } = await plugins.hooks.fire('filter:post.create', { post: postData, data: data })); @@ -79,7 +68,8 @@ module.exports = function (Posts) { categories.onNewPostMade(topicData.cid, topicData.pinned, postData), groups.onNewPostMade(postData), addReplyTo(postData, timestamp), - Posts.uploads.sync(postData.pid), + Posts.uploads.sync(pid), + hasAttachment ? Posts.attachments.update(pid, _activitypub.attachment) : null, ]); const result = await plugins.hooks.fire('filter:post.get', { post: postData, uid: data.uid }); @@ -97,19 +87,4 @@ module.exports = function (Posts) { db.incrObjectField(`post:${postData.toPid}`, 'replies'), ]); } - - async function checkToPid(toPid, uid) { - if (!utils.isNumber(toPid) && !activitypub.helpers.isUri(toPid)) { - throw new Error('[[error:invalid-pid]]'); - } - - const [toPost, canViewToPid] = await Promise.all([ - Posts.getPostFields(toPid, ['pid', 'deleted']), - privileges.posts.can('posts:view_deleted', toPid, uid), - ]); - const toPidExists = !!toPost.pid; - if (!toPidExists || (toPost.deleted && !canViewToPid)) { - throw new Error('[[error:invalid-pid]]'); - } - } }; diff --git a/src/posts/data.js b/src/posts/data.js index 688c131b8a..32e8d5443f 100644 --- a/src/posts/data.js +++ b/src/posts/data.js @@ -7,7 +7,7 @@ const utils = require('../utils'); const intFields = [ 'uid', 'pid', 'tid', 'deleted', 'timestamp', 'upvotes', 'downvotes', 'deleterUid', 'edited', - 'replies', 'bookmarks', + 'replies', 'bookmarks', 'announces', ]; module.exports = function (Posts) { @@ -58,17 +58,31 @@ module.exports = function (Posts) { function modifyPost(post, fields) { if (post) { db.parseIntFields(post, intFields, fields); - if (post.hasOwnProperty('upvotes') && post.hasOwnProperty('downvotes')) { + + const hasField = utils.createFieldChecker(fields); + + if (hasField('upvotes') && hasField('downvotes')) { post.votes = post.upvotes - post.downvotes; } - if (post.hasOwnProperty('timestamp')) { + + if (hasField('timestamp')) { post.timestampISO = utils.toISOString(post.timestamp); } - if (post.hasOwnProperty('edited')) { + + if (hasField('edited')) { post.editedISO = post.edited !== 0 ? utils.toISOString(post.edited) : ''; } - if (!fields.length || fields.includes('attachments')) { + + if (hasField('attachments')) { post.attachments = (post.attachments || '').split(',').filter(Boolean); } + + if (hasField('uploads')) { + try { + post.uploads = post.uploads ? JSON.parse(post.uploads) : []; + } catch (err) { + post.uploads = []; + } + } } } diff --git a/src/posts/delete.js b/src/posts/delete.js index 6ea4b55453..ba637ccff5 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -34,6 +34,7 @@ module.exports = function (Posts) { await Promise.all([ topics.updateLastPostTimeFromLastPid(postData.tid), topics.updateTeaser(postData.tid), + isDeleting ? activitypub.notes.delete(pid) : null, isDeleting ? db.sortedSetRemove(`cid:${topicData.cid}:pids`, pid) : db.sortedSetAdd(`cid:${topicData.cid}:pids`, postData.timestamp, pid), @@ -63,10 +64,6 @@ module.exports = function (Posts) { p.cid = tidToTopic[p.tid] && tidToTopic[p.tid].cid; }); - // deprecated hook - await Promise.all(postData.map(p => plugins.hooks.fire('filter:post.purge', { post: p, pid: p.pid, uid: uid }))); - - // new hook await plugins.hooks.fire('filter:posts.purge', { posts: postData, pids: postData.map(p => p.pid), @@ -90,10 +87,6 @@ module.exports = function (Posts) { await resolveFlags(postData, uid); - // deprecated hook - Promise.all(postData.map(p => plugins.hooks.fire('action:post.purge', { post: p, uid: uid }))); - - // new hook plugins.hooks.fire('action:posts.purge', { posts: postData, uid: uid }); await db.deleteAll(postData.map(p => `post:${p.pid}`)); @@ -204,20 +197,15 @@ module.exports = function (Posts) { } async function deleteFromReplies(postData) { - const arrayOfReplyPids = await db.getSortedSetsMembers(postData.map(p => `pid:${p.pid}:replies`)); - const allReplyPids = _.flatten(arrayOfReplyPids); - const promises = [ - db.deleteObjectFields( - allReplyPids.map(pid => `post:${pid}`), ['toPid'] - ), - db.deleteAll(postData.map(p => `pid:${p.pid}:replies`)), - ]; + // Any replies to deleted posts will retain toPid reference (gh#13527) + await db.deleteAll(postData.map(p => `pid:${p.pid}:replies`)); + // Remove post(s) from parents' replies zsets const postsWithParents = postData.filter(p => parseInt(p.toPid, 10)); const bulkRemove = postsWithParents.map(p => [`pid:${p.toPid}:replies`, p.pid]); - promises.push(db.sortedSetRemoveBulk(bulkRemove)); - await Promise.all(promises); + await db.sortedSetRemoveBulk(bulkRemove); + // Recalculate reply count const parentPids = _.uniq(postsWithParents.map(p => p.toPid)); const counts = await db.sortedSetsCard(parentPids.map(pid => `pid:${pid}:replies`)); await db.setObjectBulk(parentPids.map((pid, index) => [`post:${pid}`, { replies: counts[index] }])); diff --git a/src/posts/edit.js b/src/posts/edit.js index 610a659e4f..3d6d729351 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -29,13 +29,13 @@ module.exports = function (Posts) { } const topicData = await topics.getTopicFields(postData.tid, [ - 'cid', 'mainPid', 'title', 'timestamp', 'scheduled', 'slug', 'tags', + 'cid', 'mainPid', 'title', 'timestamp', 'scheduled', 'slug', 'tags', 'thumbs', ]); await scheduledTopicCheck(data, topicData); data.content = data.content === null ? postData.content : data.content; - const oldContent = postData.content; // for diffing purposes + const oldContent = postData.sourceContent || postData.content; // for diffing purposes const editPostData = getEditPostData(data, topicData, postData); if (data.handle) { @@ -49,13 +49,15 @@ module.exports = function (Posts) { uid: data.uid, }); + // needs to be before editMainPost, otherwise scheduled topics use wrong timestamp + await Posts.setPostFields(data.pid, result.post); + const [editor, topic] = await Promise.all([ user.getUserFields(data.uid, ['username', 'userslug']), editMainPost(data, postData, topicData), ]); - await Posts.setPostFields(data.pid, result.post); - const contentChanged = data.content !== oldContent || + const contentChanged = ((data.sourceContent || data.content) !== oldContent) || topic.renamed || topic.tagsupdated; @@ -82,20 +84,25 @@ module.exports = function (Posts) { returnPostData.oldContent = oldContent; returnPostData.newContent = data.content; - await topics.notifyFollowers(returnPostData, data.uid, { - type: 'post-edit', - bodyShort: translator.compile('notifications:user-edited-post', editor.username, topic.title), - nid: `edit_post:${data.pid}:uid:${data.uid}`, - }); await topics.syncBacklinks(returnPostData); - plugins.hooks.fire('action:post.edit', { post: { ...returnPostData, _activitypub }, data: data, uid: data.uid }); + plugins.hooks.fire('action:post.edit', { + post: { ...returnPostData, _activitypub }, + data: data, + uid: data.uid, + }); Posts.clearCachedPost(String(postData.pid)); pubsub.publish('post:edit', String(postData.pid)); await Posts.parsePost(returnPostData); + await topics.notifyFollowers(returnPostData, data.uid, { + type: 'post-edit', + bodyShort: translator.compile('notifications:user-edited-post', editor.username, topic.title), + nid: `edit_post:${data.pid}:uid:${data.uid}`, + }); + return { topic: topic, editor: editor, @@ -142,6 +149,15 @@ module.exports = function (Posts) { await topics.validateTags(data.tags, topicData.cid, data.uid, tid); } + const thumbs = topics.thumbs.filterThumbs(data.thumbs); + const thumbsupdated = Array.isArray(data.thumbs) && + !_.isEqual(data.thumbs, topicData.thumbs); + + if (thumbsupdated) { + newTopicData.thumbs = JSON.stringify(thumbs); + newTopicData.numThumbs = thumbs.length; + } + const results = await plugins.hooks.fire('filter:topic.edit', { req: data.req, topic: newTopicData, @@ -172,6 +188,7 @@ module.exports = function (Posts) { renamed: renamed, tagsupdated: tagsupdated, tags: tags, + thumbsupdated: thumbsupdated, oldTags: topicData.tags, rescheduled: rescheduling(data, topicData), }; @@ -194,6 +211,7 @@ module.exports = function (Posts) { function getEditPostData(data, topicData, postData) { const editPostData = { content: data.content, + sourceContent: data.sourceContent, editor: data.uid, }; diff --git a/src/posts/parse.js b/src/posts/parse.js index 8d0c902e46..b67c14526e 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -1,7 +1,6 @@ 'use strict'; const nconf = require('nconf'); -const url = require('url'); const winston = require('winston'); const sanitize = require('sanitize-html'); const _ = require('lodash'); @@ -88,16 +87,9 @@ module.exports = function (Posts) { while (current !== null) { if (current[1]) { try { - parsed = url.parse(current[1]); - if (!parsed.protocol) { - if (current[1].startsWith('/')) { - // Internal link - absolute = nconf.get('base_url') + current[1]; - } else { - // External link - absolute = `//${current[1]}`; - } - + parsed = new URL(current[1], nconf.get('url')); + absolute = parsed.toString(); + if (absolute !== current[1]) { const offset = current[0].indexOf(current[1]); content = content.slice(0, current.index + offset) + absolute + diff --git a/src/posts/queue.js b/src/posts/queue.js index cc3b1078c8..6aca0d781c 100644 --- a/src/posts/queue.js +++ b/src/posts/queue.js @@ -24,7 +24,8 @@ module.exports = function (Posts) { if (!postData) { const ids = await db.getSortedSetRange('post:queue', 0, -1); const keys = ids.map(id => `post:queue:${id}`); - postData = await db.getObjects(keys); + postData = (await db.getObjects(keys)).filter(Boolean); + postData.forEach((data) => { if (data) { data.data = JSON.parse(data.data); @@ -50,7 +51,7 @@ module.exports = function (Posts) { cache.set('post-queue', _.cloneDeep(postData)); } if (filter.id) { - postData = postData.filter(p => p.id === filter.id); + postData = postData.filter(p => p && p.id === filter.id); } if (options.metadata) { await Promise.all(postData.map(addMetaData)); @@ -59,11 +60,11 @@ module.exports = function (Posts) { // Filter by tid if present if (filter.tid) { const tid = String(filter.tid); - postData = postData.filter(item => item.data.tid && String(item.data.tid) === tid); + postData = postData.filter(item => item && item.data.tid && String(item.data.tid) === tid); } else if (Array.isArray(filter.tid)) { const tids = filter.tid.map(String); postData = postData.filter( - item => item.data.tid && tids.includes(String(item.data.tid)) + item => item && item.data.tid && tids.includes(String(item.data.tid)) ); } @@ -188,13 +189,16 @@ module.exports = function (Posts) { data: data, }; payload = await plugins.hooks.fire('filter:post-queue.save', payload); - payload.data = JSON.stringify(data); await db.sortedSetAdd('post:queue', now, id); - await db.setObject(`post:queue:${id}`, payload); + await db.setObject(`post:queue:${id}`, { + ...payload, + data: JSON.stringify(payload.data), + }); await user.setUserField(data.uid, 'lastqueuetime', now); cache.del('post-queue'); + await plugins.hooks.fire('action:post-queue.save', payload); const cid = await getCid(type, data); const uids = await getNotificationUids(cid); const bodyLong = await parseBodyLong(cid, type, data); @@ -206,6 +210,7 @@ module.exports = function (Posts) { bodyShort: '[[notifications:post-awaiting-review]]', bodyLong: bodyLong, path: `/post-queue/${id}`, + from: data.uid, }); await notifications.push(notifObj, uids); return { @@ -304,9 +309,11 @@ module.exports = function (Posts) { if (data.type === 'topic') { const result = await createTopic(data.data); data.pid = result.postData.pid; + data.tid = result.topicData.tid; } else if (data.type === 'reply') { const result = await createReply(data.data); data.pid = result.pid; + data.tid = result.tid; } await removeFromQueue(id); plugins.hooks.fire('action:post-queue:submitFromQueue', { data: data }); diff --git a/src/posts/summary.js b/src/posts/summary.js index 5995514eb6..1f0df7b04c 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -93,7 +93,7 @@ module.exports = function (Posts) { async function getTopicAndCategories(tids) { const topicsData = await topics.getTopicsFields(tids, [ - 'uid', 'tid', 'title', 'cid', 'tags', 'slug', + 'uid', 'tid', 'title', 'generatedTitle', 'cid', 'tags', 'slug', 'deleted', 'scheduled', 'postcount', 'mainPid', 'teaserPid', ]); diff --git a/src/posts/uploads.js b/src/posts/uploads.js index 17e82250ba..372c30ca1e 100644 --- a/src/posts/uploads.js +++ b/src/posts/uploads.js @@ -46,12 +46,14 @@ module.exports = function (Posts) { Posts.uploads.sync = async function (pid) { // Scans a post's content and updates sorted set of uploads - const [content, currentUploads, isMainPost] = await Promise.all([ - Posts.getPostField(pid, 'content'), - Posts.uploads.list(pid), + const [postData, isMainPost] = await Promise.all([ + Posts.getPostFields(pid, ['content', 'uploads']), Posts.isMain(pid), ]); + const content = postData.content || ''; + const currentUploads = postData.uploads || []; + // Extract upload file paths from post content let match = searchRegex.exec(content); let uploads = new Set(); @@ -75,14 +77,19 @@ module.exports = function (Posts) { // Create add/remove sets const add = uploads.filter(path => !currentUploads.includes(path)); const remove = currentUploads.filter(path => !uploads.includes(path)); - await Promise.all([ - Posts.uploads.associate(pid, add), - Posts.uploads.dissociate(pid, remove), - ]); + await Posts.uploads.associate(pid, add); + await Posts.uploads.dissociate(pid, remove); }; - Posts.uploads.list = async function (pid) { - return await db.getSortedSetMembers(`post:${pid}:uploads`); + Posts.uploads.list = async function (pids) { + const isArray = Array.isArray(pids); + if (isArray) { + const uploads = await Posts.getPostsFields(pids, ['uploads']); + return uploads.map(p => p.uploads || []); + } + + const uploads = await Posts.getPostField(pids, 'uploads'); + return uploads; }; Posts.uploads.listWithSizes = async function (pid) { @@ -157,33 +164,38 @@ module.exports = function (Posts) { }; Posts.uploads.associate = async function (pid, filePaths) { - // Adds an upload to a post's sorted set of uploads filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths; if (!filePaths.length) { return; } filePaths = await _filterValidPaths(filePaths); // Only process files that exist and are within uploads directory + const currentUploads = await Posts.uploads.list(pid); + filePaths.forEach((path) => { + if (!currentUploads.includes(path)) { + currentUploads.push(path); + } + }); const now = Date.now(); - const scores = filePaths.map((p, i) => now + i); const bulkAdd = filePaths.map(path => [`upload:${md5(path)}:pids`, now, pid]); + await Promise.all([ - db.sortedSetAdd(`post:${pid}:uploads`, scores, filePaths), + db.setObjectField(`post:${pid}`, 'uploads', JSON.stringify(currentUploads)), db.sortedSetAddBulk(bulkAdd), Posts.uploads.saveSize(filePaths), ]); }; Posts.uploads.dissociate = async function (pid, filePaths) { - // Removes an upload from a post's sorted set of uploads filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths; if (!filePaths.length) { return; } - + let currentUploads = await Posts.uploads.list(pid); + currentUploads = currentUploads.filter(upload => !filePaths.includes(upload)); const bulkRemove = filePaths.map(path => [`upload:${md5(path)}:pids`, pid]); const promises = [ - db.sortedSetRemove(`post:${pid}:uploads`, filePaths), + db.setObjectField(`post:${pid}`, 'uploads', JSON.stringify(currentUploads)), db.sortedSetRemoveBulk(bulkRemove), ]; diff --git a/src/posts/votes.js b/src/posts/votes.js index 599c4f92d0..64724778c1 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -177,16 +177,18 @@ module.exports = function (Posts) { } const now = Date.now(); - if (type === 'upvote' && !unvote) { - await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); - } else { - await db.sortedSetRemove(`uid:${uid}:upvote`, pid); - } + if (utils.isNumber(uid)) { + if (type === 'upvote' && !unvote) { + await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); + } else { + await db.sortedSetRemove(`uid:${uid}:upvote`, pid); + } - if (type === 'upvote' || unvote) { - await db.sortedSetRemove(`uid:${uid}:downvote`, pid); - } else { - await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + if (type === 'upvote' || unvote) { + await db.sortedSetRemove(`uid:${uid}:downvote`, pid); + } else { + await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + } } const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); @@ -269,27 +271,30 @@ module.exports = function (Posts) { async function updateTopicVoteCount(postData) { const topicData = await topics.getTopicFields(postData.tid, ['mainPid', 'cid', 'pinned']); - + const { cid } = topicData; if (postData.uid) { if (postData.votes !== 0) { - await db.sortedSetAdd(`cid:${topicData.cid}:uid:${postData.uid}:pids:votes`, postData.votes, postData.pid); + await db.sortedSetAdd(`cid:${cid}:uid:${postData.uid}:pids:votes`, postData.votes, postData.pid); } else { - await db.sortedSetRemove(`cid:${topicData.cid}:uid:${postData.uid}:pids:votes`, postData.pid); + await db.sortedSetRemove(`cid:${cid}:uid:${postData.uid}:pids:votes`, postData.pid); } } if (String(topicData.mainPid) !== String(postData.pid)) { return await db.sortedSetAdd(`tid:${postData.tid}:posts:votes`, postData.votes, postData.pid); } + const isRemoteCid = !utils.isNumber(cid) || cid === -1; const promises = [ topics.setTopicFields(postData.tid, { upvotes: postData.upvotes, downvotes: postData.downvotes, }), - db.sortedSetAdd('topics:votes', postData.votes, postData.tid), + isRemoteCid ? + Promise.resolve() : + db.sortedSetAdd('topics:votes', postData.votes, postData.tid), ]; if (!topicData.pinned) { - promises.push(db.sortedSetAdd(`cid:${topicData.cid}:tids:votes`, postData.votes, postData.tid)); + promises.push(db.sortedSetAdd(`cid:${cid}:tids:votes`, postData.votes, postData.tid)); } await Promise.all(promises); } diff --git a/src/prestart.js b/src/prestart.js index 57b5f0590e..f6a76cae3c 100644 --- a/src/prestart.js +++ b/src/prestart.js @@ -1,7 +1,6 @@ 'use strict'; const nconf = require('nconf'); -const url = require('url'); const winston = require('winston'); const path = require('path'); const chalk = require('chalk'); @@ -89,12 +88,21 @@ function loadConfig(configFile) { if (!nconf.get('sessionKey')) { nconf.set('sessionKey', 'express.sid'); } + const url = nconf.get('url'); + if (url) { + const urlObject = new URL(url); - if (nconf.get('url')) { - nconf.set('url', nconf.get('url').replace(/\/$/, '')); - nconf.set('url_parsed', url.parse(nconf.get('url'))); + nconf.set('url', url.replace(/\/$/, '')); + nconf.set('url_parsed', { + href: urlObject.href, + origin: urlObject.origin, + protocol: urlObject.protocol, + host: urlObject.host, + hostname: urlObject.hostname, + port: urlObject.port, + pathname: urlObject.pathname, + }); // Parse out the relative_url and other goodies from the configured URL - const urlObject = url.parse(nconf.get('url')); const relativePath = urlObject.pathname !== '/' ? urlObject.pathname.replace(/\/+$/, '') : ''; nconf.set('base_url', `${urlObject.protocol}//${urlObject.host}`); nconf.set('secure', urlObject.protocol === 'https:'); diff --git a/src/privileges/admin.js b/src/privileges/admin.js index 958b4cfe49..422e9c1060 100644 --- a/src/privileges/admin.js +++ b/src/privileges/admin.js @@ -39,8 +39,8 @@ privsAdmin.init = async () => { } }; -privsAdmin.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.admin.list', Array.from(_privilegeMap.keys())); -privsAdmin.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.admin.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`)); +privsAdmin.getUserPrivilegeList = () => Array.from(_privilegeMap.keys()); +privsAdmin.getGroupPrivilegeList = () => Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`); privsAdmin.getPrivilegeList = async () => { const [user, group] = await Promise.all([ privsAdmin.getUserPrivilegeList(), @@ -149,18 +149,12 @@ privsAdmin.list = async function (uid) { groupPrivilegeList.splice(idx, 1); } - const labels = await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.admin.list_human', privilegeLabels.slice()), - groups: plugins.hooks.fire('filter:privileges.admin.groups.list_human', privilegeLabels.slice()), - }); - const keys = { users: userPrivilegeList, groups: groupPrivilegeList, }; const payload = await utils.promiseParallel({ - labels, labelData: Array.from(_privilegeMap.values()), users: helpers.getUserPrivileges(0, keys.users), groups: helpers.getGroupPrivileges(0, keys.groups), diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 6061b28abe..61fda1317a 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -23,6 +23,7 @@ const _privilegeMap = new Map([ ['topics:read', { label: '[[admin/manage/privileges:access-topics]]', type: 'viewing' }], ['topics:create', { label: '[[admin/manage/privileges:create-topics]]', type: 'posting' }], ['topics:reply', { label: '[[admin/manage/privileges:reply-to-topics]]', type: 'posting' }], + ['topics:crosspost', { label: '[[admin/manage/privileges:crosspost-topics]]', type: 'posting' }], ['topics:schedule', { label: '[[admin/manage/privileges:schedule-topics]]', type: 'posting' }], ['topics:tag', { label: '[[admin/manage/privileges:tag-topics]]', type: 'posting' }], ['posts:edit', { label: '[[admin/manage/privileges:edit-posts]]', type: 'posting' }], @@ -53,8 +54,8 @@ privsCategories.getType = function (privilege) { return priv && priv.type ? priv.type : ''; }; -privsCategories.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.list', Array.from(_privilegeMap.keys())); -privsCategories.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`)); +privsCategories.getUserPrivilegeList = () => Array.from(_privilegeMap.keys()); +privsCategories.getGroupPrivilegeList = () => Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`); privsCategories.getPrivilegeList = async () => { const [user, group] = await Promise.all([ @@ -72,27 +73,20 @@ privsCategories.getPrivilegesByFilter = function (filter) { // Method used in admin/category controller to show all users/groups with privs in that given cid privsCategories.list = async function (cid) { - let labels = Array.from(_privilegeMap.values()).map(data => data.label); - labels = await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.list_human', labels.slice()), - groups: plugins.hooks.fire('filter:privileges.groups.list_human', labels.slice()), - }); - const keys = await utils.promiseParallel({ users: privsCategories.getUserPrivilegeList(), groups: privsCategories.getGroupPrivilegeList(), }); const payload = await utils.promiseParallel({ - labels, labelData: Array.from(_privilegeMap.values()), users: helpers.getUserPrivileges(cid, keys.users), groups: helpers.getGroupPrivileges(cid, keys.groups), }); payload.keys = keys; - payload.columnCountUserOther = payload.labels.users.length - privsCategories._coreSize; - payload.columnCountGroupOther = payload.labels.groups.length - privsCategories._coreSize; + payload.columnCountUserOther = payload.labelData.length - privsCategories._coreSize; + payload.columnCountGroupOther = payload.labelData.length - privsCategories._coreSize; return payload; }; @@ -103,14 +97,16 @@ privsCategories.get = async function (cid, uid) { 'topics:tag', 'read', 'posts:view_deleted', ]; - const [userPrivileges, isAdministrator, isModerator] = await Promise.all([ + let [userPrivileges, isAdministrator, isModerator] = await Promise.all([ helpers.isAllowedTo(privs, uid, cid), user.isAdministrator(uid), user.isModerator(uid, cid), ]); - const combined = userPrivileges.map(allowed => allowed || isAdministrator); - const privData = _.zipObject(privs, combined); + if (utils.isNumber(cid)) { + userPrivileges = userPrivileges.map(allowed => allowed || isAdministrator); + } + const privData = _.zipObject(privs, userPrivileges); const isAdminOrMod = isAdministrator || isModerator; return await plugins.hooks.fire('filter:privileges.categories.get', { diff --git a/src/privileges/global.js b/src/privileges/global.js index aca4d85250..9de1beba8d 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -54,8 +54,8 @@ privsGlobal.getType = function (privilege) { return priv && priv.type ? priv.type : ''; }; -privsGlobal.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.global.list', Array.from(_privilegeMap.keys())); -privsGlobal.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.global.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`)); +privsGlobal.getUserPrivilegeList = () => Array.from(_privilegeMap.keys()); +privsGlobal.getGroupPrivilegeList = () => Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`); privsGlobal.getPrivilegeList = async () => { const [user, group] = await Promise.all([ privsGlobal.getUserPrivilegeList(), @@ -65,21 +65,12 @@ privsGlobal.getPrivilegeList = async () => { }; privsGlobal.list = async function () { - async function getLabels() { - const labels = Array.from(_privilegeMap.values()).map(data => data.label); - return await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.global.list_human', labels.slice()), - groups: plugins.hooks.fire('filter:privileges.global.groups.list_human', labels.slice()), - }); - } - const keys = await utils.promiseParallel({ users: privsGlobal.getUserPrivilegeList(), groups: privsGlobal.getGroupPrivilegeList(), }); const payload = await utils.promiseParallel({ - labels: getLabels(), labelData: Array.from(_privilegeMap.values()), users: helpers.getUserPrivileges(0, keys.users), groups: helpers.getGroupPrivileges(0, keys.groups), diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index c858eebe60..08fc60e320 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -4,11 +4,13 @@ const _ = require('lodash'); const validator = require('validator'); +const db = require('../database'); const groups = require('../groups'); const user = require('../user'); const categories = require('../categories'); const plugins = require('../plugins'); const translator = require('../translator'); +const utils = require('../utils'); const helpers = module.exports; @@ -19,22 +21,48 @@ const uidToSystemGroup = { }; 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.hooks.fire('filter:privileges:isUsersAllowedTo', { allowed: allowed, privilege: privilege, uids: uids, cid: cid }); + // Remote categories inherit world pseudo-category privileges + if (!utils.isNumber(cid)) { + cid = -1; + } + + let allowed; + const masked = await db.isSetMember(`cid:${cid}:privilegeMask`, privilege); + if (!masked) { + const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([ + groups.isMembers(uids, `cid:${cid}:privileges:${privilege}`), + groups.isMembersOfGroupList(uids, `cid:${cid}:privileges:groups:${privilege}`), + ]); + allowed = uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]); + } else { + allowed = uids.map(() => false); + } + + const result = await plugins.hooks.fire('filter:privileges:isUsersAllowedTo', { allowed, privilege, uids, cid }); return result.allowed; }; helpers.isAllowedTo = async function (privilege, uidOrGroupName, cid) { + const _cid = cid; // original passed-in cid needed for privilege mask checks + + // Remote categories (non-numeric) inherit world privileges + if (Array.isArray(cid)) { + cid = cid.map(cid => (utils.isNumber(cid) ? cid : -1)); + } else { + cid = utils.isNumber(cid) ? cid : -1; + } + let allowed; if (Array.isArray(privilege) && !Array.isArray(cid)) { + const mask = await db.isSetMembers(`cid:${_cid}:privilegeMask`, privilege); allowed = await isAllowedToPrivileges(privilege, uidOrGroupName, cid); + allowed = allowed.map((allowed, idx) => mask[idx] ? false : allowed); } else if (Array.isArray(cid) && !Array.isArray(privilege)) { + const mask = await db.isMemberOfSets(_cid.map(cid => `cid:${cid}:privilegeMask`), privilege); allowed = await isAllowedToCids(privilege, uidOrGroupName, cid); + allowed = allowed.map((allowed, idx) => mask[idx] ? false : allowed); } + if (allowed) { ({ allowed } = await plugins.hooks.fire('filter:privileges:isAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid })); return allowed; diff --git a/src/privileges/posts.js b/src/privileges/posts.js index e289cb8414..09a06aee26 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -86,7 +86,7 @@ privsPosts.filter = async function (privilege, pids, uid) { post.topic = tidToTopic[post.tid]; } return tidToTopic[post.tid] && tidToTopic[post.tid].cid; - }).filter(cid => parseInt(cid, 10)); + }).filter(cid => utils.isNumber(cid) ? parseInt(cid, 10) : cid); cids = _.uniq(cids); diff --git a/src/privileges/topics.js b/src/privileges/topics.js index a53f34bd98..5538e626d9 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -24,11 +24,16 @@ privsTopics.get = async function (tid, uid) { 'posts:delete', 'posts:view_deleted', 'read', 'purge', ]; const topicData = await topics.getTopicFields(tid, ['cid', 'uid', 'locked', 'deleted', 'scheduled']); - const [userPrivileges, isAdministrator, isModerator, disabled] = await Promise.all([ + const [userPrivileges, isAdministrator, isModerator, disabled, topicTools] = await Promise.all([ helpers.isAllowedTo(privs, uid, topicData.cid), user.isAdministrator(uid), user.isModerator(uid, topicData.cid), categories.getCategoryField(topicData.cid, 'disabled'), + plugins.hooks.fire('filter:topic.thread_tools', { + topic: topicData, + uid: uid, + tools: [], + }), ]); const privData = _.zipObject(privs, userPrivileges); const isOwner = uid > 0 && uid === topicData.uid; @@ -36,6 +41,7 @@ privsTopics.get = async function (tid, uid) { const editable = isAdminOrMod; const deletable = (privData['topics:delete'] && (isOwner || isModerator)) || isAdministrator; const mayReply = privsTopics.canViewDeletedScheduled(topicData, {}, false, privData['topics:schedule']); + const hasTools = topicTools.tools.length > 0; return await plugins.hooks.fire('filter:privileges.topics.get', { 'topics:reply': (privData['topics:reply'] && ((!topicData.locked && mayReply) || isModerator)) || isAdministrator, @@ -52,7 +58,7 @@ privsTopics.get = async function (tid, uid) { read: privData.read || isAdministrator, purge: (privData.purge && (isOwner || isModerator)) || isAdministrator, - view_thread_tools: editable || deletable, + view_thread_tools: editable || deletable || hasTools, editable: editable, deletable: deletable, view_deleted: isAdminOrMod || isOwner || privData['posts:view_deleted'], @@ -88,6 +94,7 @@ privsTopics.filterTids = async function (privilege, tids, uid) { const canViewScheduled = _.zipObject(cids, results.view_scheduled); tids = topicsData.filter(t => ( + t.tid && cidsSet.has(t.cid) && (results.isAdmin || privsTopics.canViewDeletedScheduled(t, {}, canViewDeleted[t.cid], canViewScheduled[t.cid])) )).map(t => t.tid); diff --git a/src/request.js b/src/request.js index b84b198914..3ffde3916e 100644 --- a/src/request.js +++ b/src/request.js @@ -1,18 +1,84 @@ 'use strict'; +const dns = require('dns').promises; +require('undici'); // keep this here, needed for SSRF (see `lookup()`) + const nconf = require('nconf'); +const ipaddr = require('ipaddr.js'); const { CookieJar } = require('tough-cookie'); const fetchCookie = require('fetch-cookie').default; const { version } = require('../package.json'); +const plugins = require('./plugins'); +const ttl = require('./cache/ttl'); +const checkCache = ttl({ + name: 'request-check', + max: 1000, + ttl: 1000 * 60 * 60, // 1 hour +}); +let allowList = new Set(); +let initialized = false; + exports.jar = function () { return new CookieJar(); }; const userAgent = `NodeBB/${version.split('.').shift()}.x (${nconf.get('url')})`; +async function init() { + if (initialized) { + return; + } + + allowList.add(nconf.get('url_parsed').host); + const { allowed } = await plugins.hooks.fire('filter:request.init', { allowed: allowList }); + if (allowed instanceof Set) { + allowList = allowed; + } + initialized = true; +} + +/** + * This method (alongside `check()`) guards against SSRF via DNS rebinding. + * + * - `check()` does a DNS lookup and ensures that all returned IPs do not belong to a reserved IP address space + * - `lookup()` provides additional logic that uses the cached DNS result from `check()` + * instead of doing another lookup (which is where DNS rebinding comes into play.) + * - For whatever reason `undici` needs to be required so that lookup can be overwritten properly. + */ +function lookup(hostname, options, callback) { + let { ok, lookup } = checkCache.get(hostname); + lookup = lookup && [...lookup]; + if (!ok) { + throw new Error('lookup-failed'); + } + + if (!lookup) { + // trusted, do regular lookup + dns.lookup(hostname, options).then((addresses) => { + callback(null, addresses); + }); + return; + } + + // Lookup needs to behave asynchronously — https://github.com/nodejs/node/issues/28664 + process.nextTick(() => { + if (options.all === true) { + callback(null, lookup); + } else { + const { address, family } = lookup.shift(); + callback(null, address, family); + } + }); +} + // Initialize fetch - somewhat hacky, but it's required for globalDispatcher to be available async function call(url, method, { body, timeout, jar, ...config } = {}) { + const { ok } = await check(url); + if (!ok) { + throw new Error('[[error:reserved-ip-address]]'); + } + let fetchImpl = fetch; if (jar) { fetchImpl = fetchCookie(fetch, jar); @@ -46,7 +112,9 @@ async function call(url, method, { body, timeout, jar, ...config } = {}) { return super.dispatch(opts, handler); } } - opts.dispatcher = new FetchAgent(); + opts.dispatcher = new FetchAgent({ + connect: { lookup }, + }); } const response = await fetchImpl(url, opts); @@ -75,6 +143,47 @@ async function call(url, method, { body, timeout, jar, ...config } = {}) { }; } +// Checks url to ensure it is not in reserved IP range (private, etc.) +async function check(url) { + await init(); + + const { host } = new URL(url); + const cached = checkCache.get(url); + if (cached !== undefined) { + return cached; + } + if (allowList.has(host)) { + const payload = { ok: true }; + checkCache.set(host, payload); + return payload; + } + + const addresses = new Set(); + let lookup; + if (ipaddr.isValid(url)) { + addresses.add(url); + } else { + lookup = await dns.lookup(host, { all: true }); + lookup.forEach(({ address, family }) => { + addresses.add({ address, family }); + }); + } + + if (addresses.size < 1) { + return { ok: false }; + } + + // Every IP address that the host resolves to should be a unicast address + const ok = Array.from(addresses).every(({ address: ip }) => { + const parsed = ipaddr.parse(ip); + return parsed.range() === 'unicast'; + }); + + const payload = { ok, lookup }; + checkCache.set(host, payload); + return payload; +} + /* const { body, response } = await request.get('someurl?foo=1&baz=2') */ diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js index d179c742be..daa65d1f69 100644 --- a/src/routes/activitypub.js +++ b/src/routes/activitypub.js @@ -3,8 +3,14 @@ const helpers = require('./helpers'); module.exports = function (app, middleware, controllers) { - helpers.setupPageRoute(app, '/world', [middleware.activitypub.enabled], controllers.activitypub.topics.list); - helpers.setupPageRoute(app, '/ap', [middleware.activitypub.enabled], controllers.activitypub.fetch); + helpers.setupPageRoute(app, '/world', [ + middleware.activitypub.enabled, + middleware.activitypub.pageview, + ], controllers.activitypub.topics.list); + helpers.setupPageRoute(app, '/ap', [ + middleware.activitypub.enabled, + middleware.activitypub.pageview, + ], controllers.activitypub.fetch); /** * The following controllers only respond if the sender is making an json+activitypub style call (i.e. S2S-only) @@ -14,6 +20,7 @@ module.exports = function (app, middleware, controllers) { const middlewares = [ middleware.activitypub.enabled, + middleware.activitypub.pageview, middleware.activitypub.assertS2S, middleware.activitypub.verify, middleware.activitypub.configureResponse, @@ -25,28 +32,28 @@ module.exports = function (app, middleware, controllers) { middleware.activitypub.normalize, ]; - app.get('/actor', middlewares, controllers.activitypub.actors.application); - app.post('/inbox', [...middlewares, ...inboxMiddlewares], controllers.activitypub.postInbox); + app.get('/actor', middlewares, helpers.tryRoute(controllers.activitypub.actors.application)); + app.post('/inbox', [...middlewares, ...inboxMiddlewares], helpers.tryRoute(controllers.activitypub.postInbox)); - app.get('/uid/:uid', [...middlewares, middleware.assert.user], controllers.activitypub.actors.user); - app.get('/user/:userslug', [...middlewares, middleware.exposeUid, middleware.assert.user], controllers.activitypub.actors.userBySlug); - app.get('/uid/:uid/inbox', [...middlewares, middleware.assert.user], controllers.activitypub.getInbox); - app.post('/uid/:uid/inbox', [...middlewares, middleware.assert.user, ...inboxMiddlewares], controllers.activitypub.postInbox); - app.get('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.getOutbox); - app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.postOutbox); - app.get('/uid/:uid/following', [...middlewares, middleware.assert.user], controllers.activitypub.getFollowing); - app.get('/uid/:uid/followers', [...middlewares, middleware.assert.user], controllers.activitypub.getFollowers); + app.get('/uid/:uid', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.actors.user)); + app.get('/user/:userslug', [...middlewares, middleware.exposeUid, middleware.assert.user], helpers.tryRoute(controllers.activitypub.actors.userBySlug)); + app.get('/uid/:uid/inbox', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getInbox)); + app.post('/uid/:uid/inbox', [...middlewares, middleware.assert.user, ...inboxMiddlewares], helpers.tryRoute(controllers.activitypub.postInbox)); + app.get('/uid/:uid/outbox', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getOutbox)); + app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.postOutbox)); + app.get('/uid/:uid/following', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getFollowing)); + app.get('/uid/:uid/followers', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getFollowers)); - app.get('/post/:pid', [...middlewares, middleware.assert.post], controllers.activitypub.actors.note); - app.get('/post/:pid/replies', [...middlewares, middleware.assert.post], controllers.activitypub.actors.replies); + app.get('/post/:pid', [...middlewares, middleware.assert.post], helpers.tryRoute(controllers.activitypub.actors.note)); + app.get('/post/:pid/replies', [...middlewares, middleware.assert.post], helpers.tryRoute(controllers.activitypub.actors.replies)); - app.get('/topic/:tid{/:slug}', [...middlewares, middleware.assert.topic], controllers.activitypub.actors.topic); - app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], controllers.activitypub.getInbox); - app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], controllers.activitypub.postInbox); - app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.getCategoryOutbox); - app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox); - app.get('/category/:cid{/:slug}', [...middlewares, middleware.assert.category], controllers.activitypub.actors.category); + app.get('/topic/:tid{/:slug}', [...middlewares, middleware.assert.topic], helpers.tryRoute(controllers.activitypub.actors.topic)); + app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.getInbox)); + app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], helpers.tryRoute(controllers.activitypub.postInbox)); + app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.getCategoryOutbox)); + app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.postOutbox)); + app.get('/category/:cid{/:slug}', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.actors.category)); - app.get('/message/:mid', [...middlewares, middleware.assert.message], controllers.activitypub.actors.message); + app.get('/message/:mid', [...middlewares, middleware.assert.message], helpers.tryRoute(controllers.activitypub.actors.message)); }; diff --git a/src/routes/admin.js b/src/routes/admin.js index fc89bf8413..ea973759c3 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -23,6 +23,7 @@ module.exports = function (app, name, middleware, controllers) { helpers.setupAdminPageRoute(app, `/${name}/manage/users`, middlewares, controllers.admin.users.index); helpers.setupAdminPageRoute(app, `/${name}/manage/users/custom-fields`, middlewares, controllers.admin.users.customFields); + helpers.setupAdminPageRoute(app, `/${name}/manage/users/custom-reasons`, middlewares, controllers.admin.users.banReasons); helpers.setupAdminPageRoute(app, `/${name}/manage/registration`, middlewares, controllers.admin.users.registrationQueue); helpers.setupAdminPageRoute(app, `/${name}/manage/admins-mods`, middlewares, controllers.admin.adminsMods.get); @@ -81,11 +82,18 @@ function apiRoutes(router, name, middleware, controllers) { router.get(`/api/${name}/groups/:groupname/csv`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.groups.getCSV)); router.get(`/api/${name}/analytics`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.dashboard.getAnalytics)); router.get(`/api/${name}/advanced/cache/dump`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.cache.dump)); + router.post(`/api/${name}/manage/categories`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.addRemote)); + router.post(`/api/${name}/manage/categories/:cid/name`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.renameRemote)); + router.delete(`/api/${name}/manage/categories/:cid`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.removeRemote)); - const multipart = require('connect-multiparty'); - const multipartMiddleware = multipart(); + const upload = require('../middleware/multer'); - const middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.ensureLoggedIn]; + const middlewares = [ + upload.array('files[]', 20), + middleware.validateFiles, + middleware.applyCSRF, + middleware.ensureLoggedIn, + ]; router.post(`/api/${name}/category/uploadpicture`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadCategoryPicture)); router.post(`/api/${name}/uploadfavicon`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadFavicon)); diff --git a/src/routes/api.js b/src/routes/api.js index 8b074d99bf..bb35c368f4 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -23,17 +23,18 @@ module.exports = function (app, middleware, controllers) { router.get('/topic/teaser/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.teaser)); router.get('/topic/pagination/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.pagination)); - const multipart = require('connect-multiparty'); - const multipartMiddleware = multipart(); + const upload = require('../middleware/multer'); + const postMiddlewares = [ middleware.maintenanceMode, - multipartMiddleware, + upload.array('files[]', 20), middleware.validateFiles, middleware.uploads.ratelimit, middleware.applyCSRF, ]; router.post('/post/upload', postMiddlewares, helpers.tryRoute(uploadsController.uploadPost)); + router.post('/topic/thumb/upload', postMiddlewares, helpers.tryRoute(uploadsController.uploadThumb)); router.post('/user/:userslug/uploadpicture', [ ...middlewares, ...postMiddlewares, diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 9d89df90e1..ab523d08e6 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -121,7 +121,7 @@ Auth.reloadRoutes = async function (params) { // passport seems to remove `req.session.returnTo` after it redirects req.session.registration.returnTo = req.session.next || req.session.returnTo; - passport.authenticate(strategy.name, (err, user) => { + passport.authenticate(strategy.name, (err, user, info) => { if (err) { if (req.session && req.session.registration) { delete req.session.registration; @@ -133,7 +133,10 @@ Auth.reloadRoutes = async function (params) { if (req.session && req.session.registration) { delete req.session.registration; } - return helpers.redirect(res, strategy.failureUrl !== undefined ? strategy.failureUrl : '/login'); + if (info && info.message) { + return helpers.redirect(res, `/?register=${encodeURIComponent(info.message)}`); + } + return helpers.redirect(res, strategy.failureUrl || '/login'); } res.locals.user = user; @@ -149,14 +152,17 @@ Auth.reloadRoutes = async function (params) { return next(err); } - helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/'); + helpers.redirect(res, strategy.successUrl || '/'); }); }); }); - const multipart = require('connect-multiparty'); - const multipartMiddleware = multipart(); - const middlewares = [multipartMiddleware, Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist]; + const upload = require('../middleware/multer'); + const middlewares = [ + upload.any(), + Auth.middleware.applyCSRF, + Auth.middleware.applyBlacklist, + ]; router.post('/register', middlewares, controllers.authentication.register); router.post('/register/complete', middlewares, controllers.authentication.registerComplete); diff --git a/src/routes/feeds.js b/src/routes/feeds.js index b913ca56da..0e4c066673 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -61,6 +61,11 @@ async function validateTokenIfRequiresLogin(requiresLogin, cid, req, res) { return true; } +function stripUnicodeControlChars(str) { + // eslint-disable-next-line no-control-regex + return str.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, ''); +} + async function generateForTopic(req, res, next) { if (meta.config['feeds:disableRSS']) { return next(); @@ -80,20 +85,21 @@ async function generateForTopic(req, res, next) { if (await validateTokenIfRequiresLogin(!userPrivileges['topics:read'], topic.cid, req, res)) { const topicData = await topics.getTopicWithPosts(topic, `tid:${tid}:posts`, req.uid || req.query.uid || 0, 0, 24, true); + const mainPost = topicData.posts[0]; topics.modifyPostsByPrivilege(topicData, userPrivileges); - + const title = stripUnicodeControlChars(topicData.title); const feed = new rss({ - title: utils.stripHTMLTags(topicData.title, utils.tags), - description: topicData.posts.length ? topicData.posts[0].content : '', + title: utils.stripHTMLTags(title, utils.tags), + description: topicData.posts.length ? stripUnicodeControlChars(mainPost.content) : '', feed_url: `${nconf.get('url')}/topic/${tid}.rss`, site_url: `${nconf.get('url')}/topic/${topicData.slug}`, - image_url: topicData.posts.length ? topicData.posts[0].picture : '', - author: topicData.posts.length ? topicData.posts[0].username : '', + image_url: topicData.posts.length ? mainPost.picture : '', + author: topicData.posts.length ? mainPost.username : '', ttl: 60, }); if (topicData.posts.length > 0) { - feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString(); + feed.pubDate = new Date(parseInt(mainPost.timestamp, 10)).toUTCString(); } const replies = topicData.posts.slice(1); replies.forEach((postData) => { @@ -103,8 +109,8 @@ async function generateForTopic(req, res, next) { ).toUTCString(); feed.item({ - title: `Reply to ${utils.stripHTMLTags(topicData.title, utils.tags)} on ${dateStamp}`, - description: postData.content, + title: `Reply to ${utils.stripHTMLTags(title, utils.tags)} on ${dateStamp}`, + description: stripUnicodeControlChars(postData.content), url: `${nconf.get('url')}/post/${postData.pid}`, author: postData.user ? postData.user.username : '', date: dateStamp, @@ -122,15 +128,20 @@ async function generateForCategory(req, res, next) { return next(); } const uid = req.uid || req.query.uid || 0; + async function getRecentlyCreatedTids() { + const [pinnedTids, tids] = await Promise.all([ + db.getSortedSetRevRange(`cid:${cid}:tids:pinned`, 0, -1), + db.getSortedSetRevRange(`cid:${cid}:tids:create`, 0, 24), + ]); + const allTids = Array.from(new Set([...pinnedTids, ...tids])); + const topicData = await topics.getTopicsFields(allTids, ['tid', 'timestamp']); + topicData.sort((a, b) => b.timestamp - a.timestamp); + return topicData.slice(0, 25).map(t => t.tid); + } const [userPrivileges, category, tids] = await Promise.all([ privileges.categories.get(cid, req.uid), categories.getCategoryData(cid), - db.getSortedSetRevIntersect({ - sets: ['topics:tid', `cid:${cid}:tids:lastposttime`], - start: 0, - stop: 24, - weights: [1, 0], - }), + getRecentlyCreatedTids(), ]); if (!category || !category.name) { @@ -252,7 +263,7 @@ async function generateTopicsFeed(feedOptions, feedTopics, timestampField) { feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url; feedOptions.site_url = nconf.get('url') + feedOptions.site_url; - feedTopics = feedTopics.filter(Boolean); + feedTopics = feedTopics.filter(t => t && !t.deleted); const feed = new rss(feedOptions); @@ -260,38 +271,39 @@ async function generateTopicsFeed(feedOptions, feedTopics, timestampField) { feed.pubDate = new Date(feedTopics[0][timestampField]).toUTCString(); } - async function addFeedItem(topicData) { + if (feedOptions.useMainPost) { + const tids = feedTopics.map(topic => topic.tid); + const mainPosts = await topics.getMainPosts(tids, feedOptions.uid); + feedTopics.forEach((topicData, index) => { + topicData.mainPost = mainPosts[index]; + }); + } + + function addFeedItem(topicData) { + const title = stripUnicodeControlChars(topicData.title); const feedItem = { - title: utils.stripHTMLTags(topicData.title, utils.tags), + title: utils.stripHTMLTags(title, utils.tags), url: `${nconf.get('url')}/topic/${topicData.slug}`, date: new Date(topicData[timestampField]).toUTCString(), }; - if (topicData.deleted) { - return; - } - if (topicData.teaser && topicData.teaser.user && !feedOptions.useMainPost) { - feedItem.description = topicData.teaser.content; + feedItem.description = stripUnicodeControlChars(topicData.teaser.content); feedItem.author = topicData.teaser.user.username; feed.item(feedItem); return; } - - const mainPost = await topics.getMainPost(topicData.tid, feedOptions.uid); + const { mainPost } = topicData; if (!mainPost) { feed.item(feedItem); return; } - feedItem.description = mainPost.content; + feedItem.description = stripUnicodeControlChars(mainPost.content); feedItem.author = mainPost.user && mainPost.user.username; feed.item(feedItem); } - for (const topicData of feedTopics) { - /* eslint-disable no-await-in-loop */ - await addFeedItem(topicData); - } + feedTopics.forEach(addFeedItem); return feed; } @@ -357,9 +369,10 @@ function generateForPostsFeed(feedOptions, posts) { } posts.forEach((postData) => { + const title = stripUnicodeControlChars(postData.topic ? postData.topic.title : ''); feed.item({ - title: postData.topic ? postData.topic.title : '', - description: postData.content, + title: title, + description: stripUnicodeControlChars(postData.content), url: `${nconf.get('url')}/post/${postData.pid}`, author: postData.user ? postData.user.username : '', date: new Date(parseInt(postData.timestamp, 10)).toUTCString(), @@ -394,7 +407,8 @@ async function generateForTag(req, res) { return controllers404.handle404(req, res); } const uid = await getUidFromToken(req); - const tag = validator.escape(String(req.params.tag)); + const set = `tag:${String(req.params.tag)}:topics`; + const tag = validator.escape(stripUnicodeControlChars(String(req.params.tag))); const page = parseInt(req.query.page, 10) || 1; const topicsPerPage = meta.config.topicsPerPage || 20; const start = Math.max(0, (page - 1) * topicsPerPage); @@ -407,7 +421,7 @@ async function generateForTag(req, res) { site_url: `/tags/${tag}`, start: start, stop: stop, - }, `tag:${tag}:topics`, res); + }, set, res); } async function getUidFromToken(req) { diff --git a/src/routes/helpers.js b/src/routes/helpers.js index 34a455076e..079eb36536 100644 --- a/src/routes/helpers.js +++ b/src/routes/helpers.js @@ -54,7 +54,7 @@ helpers.setupApiRoute = function (...args) { const [router, verb, name] = args; let middlewares = args.length > 4 ? args[args.length - 2] : []; const controller = args[args.length - 1]; - + const upload = require('../middleware/multer'); middlewares = [ middleware.autoLocale, middleware.applyBlacklist, @@ -63,7 +63,7 @@ helpers.setupApiRoute = function (...args) { middleware.registrationComplete, middleware.pluginHooks, middleware.logApiUsage, - middleware.handleMultipart, + upload.any(), ...middlewares, ]; diff --git a/src/routes/well-known.js b/src/routes/well-known.js index 83f8cb0895..efe397429e 100644 --- a/src/routes/well-known.js +++ b/src/routes/well-known.js @@ -72,6 +72,9 @@ module.exports = function (app, middleware, controllers) { metadata: { nodeName: meta.config.title || 'NodeBB', nodeDescription: meta.config.description || '', + federation: { + enabled: !!meta.config.activitypubEnabled, + }, }, }); })); diff --git a/src/routes/write/admin.js b/src/routes/write/admin.js index 4a70e48022..2121953efe 100644 --- a/src/routes/write/admin.js +++ b/src/routes/write/admin.js @@ -25,5 +25,11 @@ module.exports = function () { setupApiRoute(router, 'get', '/groups', [...middlewares], controllers.write.admin.listGroups); + setupApiRoute(router, 'post', '/activitypub/rules', [...middlewares, middleware.checkRequired.bind(null, ['cid', 'value', 'type'])], controllers.write.admin.activitypub.addRule); + setupApiRoute(router, 'delete', '/activitypub/rules/:rid', [...middlewares], controllers.write.admin.activitypub.deleteRule); + setupApiRoute(router, 'put', '/activitypub/rules/order', [...middlewares, middleware.checkRequired.bind(null, ['rids'])], controllers.write.admin.activitypub.reorderRules); + setupApiRoute(router, 'post', '/activitypub/relays', [...middlewares, middleware.checkRequired.bind(null, ['url'])], controllers.write.admin.activitypub.addRelay); + setupApiRoute(router, 'delete', '/activitypub/relays/:url', [...middlewares], controllers.write.admin.activitypub.removeRelay); + return router; }; diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index efbb52fc2e..7ff361aea3 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -41,6 +41,14 @@ module.exports = function () { setupApiRoute(router, 'get', '/:pid/replies', [middleware.assert.post], controllers.write.posts.getReplies); + setupApiRoute(router, 'post', '/queue/:id', controllers.write.posts.acceptQueuedPost); + setupApiRoute(router, 'delete', '/queue/:id', controllers.write.posts.removeQueuedPost); + setupApiRoute(router, 'put', '/queue/:id', controllers.write.posts.editQueuedPost); + setupApiRoute(router, 'post', '/queue/:id/notify', [middleware.checkRequired.bind(null, ['message'])], controllers.write.posts.notifyQueuedPostOwner); + + setupApiRoute(router, 'put', '/:pid/owner', [middleware.ensureLoggedIn, middleware.assert.post, middleware.checkRequired.bind(null, ['uid'])], controllers.write.posts.changeOwner); + setupApiRoute(router, 'post', '/owner', [middleware.ensureLoggedIn, middleware.checkRequired.bind(null, ['pids', 'uid'])], controllers.write.posts.changeOwner); + // Shorthand route to access post routes by topic index // TODO: upgrade to express 5 diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js index df10f66633..a1c7810558 100644 --- a/src/routes/write/topics.js +++ b/src/routes/write/topics.js @@ -10,9 +10,6 @@ const { setupApiRoute } = routeHelpers; module.exports = function () { const middlewares = [middleware.ensureLoggedIn]; - const multipart = require('connect-multiparty'); - const multipartMiddleware = multipart(); - setupApiRoute(router, 'post', '/', [middleware.checkRequired.bind(null, ['cid', 'title', 'content'])], controllers.write.topics.create); setupApiRoute(router, 'get', '/:tid', [], controllers.write.topics.get); setupApiRoute(router, 'post', '/:tid', [middleware.checkRequired.bind(null, ['content']), middleware.assert.topic], controllers.write.topics.reply); @@ -37,8 +34,14 @@ module.exports = function () { setupApiRoute(router, 'delete', '/:tid/tags', [...middlewares, middleware.assert.topic], controllers.write.topics.deleteTags); setupApiRoute(router, 'get', '/:tid/thumbs', [], controllers.write.topics.getThumbs); - setupApiRoute(router, 'post', '/:tid/thumbs', [multipartMiddleware, middleware.validateFiles, middleware.uploads.ratelimit, ...middlewares], controllers.write.topics.addThumb); - setupApiRoute(router, 'put', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['tid'])], controllers.write.topics.migrateThumbs); + + setupApiRoute(router, 'post', '/:tid/thumbs', [ + middleware.validateFiles, + middleware.uploads.ratelimit, + ...middlewares, + ], controllers.write.topics.addThumb); + + setupApiRoute(router, 'delete', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['path'])], controllers.write.topics.deleteThumb); setupApiRoute(router, 'put', '/:tid/thumbs/order', [...middlewares, middleware.checkRequired.bind(null, ['path', 'order'])], controllers.write.topics.reorderThumbs); @@ -51,5 +54,9 @@ module.exports = function () { setupApiRoute(router, 'put', '/:tid/move', [...middlewares, middleware.assert.topic], controllers.write.topics.move); + setupApiRoute(router, 'get', '/:tid/crossposts', [...middlewares, middleware.assert.topic], controllers.write.topics.getCrossposts); + setupApiRoute(router, 'post', '/:tid/crossposts', [...middlewares, middleware.assert.topic], controllers.write.topics.crosspost); + setupApiRoute(router, 'delete', '/:tid/crossposts', [...middlewares, middleware.assert.topic], controllers.write.topics.uncrosspost); + return router; }; diff --git a/src/search.js b/src/search.js index 8e9d947dae..e1a7fb4287 100644 --- a/src/search.js +++ b/src/search.js @@ -76,7 +76,7 @@ async function searchInContent(data) { } return []; } - let pids = []; + let pids; let tids = []; const inTopic = String(data.query || '').match(/^in:topic-([\d]+) /); if (inTopic) { @@ -146,7 +146,9 @@ async function searchInContent(data) { metadata.pids = metadata.pids.slice(start, start + itemsPerPage); } - returnData.posts = await posts.getPostSummaryByPids(metadata.pids, data.uid, {}); + returnData.posts = await posts.getPostSummaryByPids(metadata.pids, data.uid, { + extraFields: ['attachments'], + }); await plugins.hooks.fire('filter:search.contentGetResult', { result: returnData, data: data }); delete metadata.pids; delete metadata.data; @@ -374,9 +376,9 @@ function sortPosts(posts, data) { } else { posts.sort((p1, p2) => { if (p1[fields[0]][fields[1]] > p2[fields[0]][fields[1]]) { - return direction; - } else if (p1[fields[0]][fields[1]] < p2[fields[0]][fields[1]]) { return -direction; + } else if (p1[fields[0]][fields[1]] < p2[fields[0]][fields[1]]) { + return direction; } return 0; }); diff --git a/src/sitemap.js b/src/sitemap.js index 6e17514352..2260c9b466 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -60,6 +60,10 @@ async function getSitemapPages() { return data.urls; } +function getCacheExpireTimestamp() { + return Date.now() + (1000 * 60 * 60 * (meta.config.sitemapCacheDurationHours || 24)); +} + sitemap.getPages = async function () { if (sitemap.maps.pages && Date.now() < sitemap.maps.pagesCacheExpireTimestamp) { return sitemap.maps.pages; @@ -68,12 +72,12 @@ sitemap.getPages = async function () { const urls = await getSitemapPages(); if (!urls.length) { sitemap.maps.pages = ''; - sitemap.maps.pagesCacheExpireTimestamp = Date.now() + (1000 * 60 * 60 * 24); + sitemap.maps.pagesCacheExpireTimestamp = getCacheExpireTimestamp(); return sitemap.maps.pages; } sitemap.maps.pages = await urlsToSitemap(urls); - sitemap.maps.pagesCacheExpireTimestamp = Date.now() + (1000 * 60 * 60 * 24); + sitemap.maps.pagesCacheExpireTimestamp = getCacheExpireTimestamp(); return sitemap.maps.pages; }; @@ -105,12 +109,12 @@ sitemap.getCategories = async function () { if (!categoryUrls.length) { sitemap.maps.categories = ''; - sitemap.maps.categoriesCacheExpireTimestamp = Date.now() + (1000 * 60 * 60 * 24); + sitemap.maps.categoriesCacheExpireTimestamp = getCacheExpireTimestamp(); return sitemap.maps.categories; } sitemap.maps.categories = await urlsToSitemap(categoryUrls); - sitemap.maps.categoriesCacheExpireTimestamp = Date.now() + (1000 * 60 * 60 * 24); + sitemap.maps.categoriesCacheExpireTimestamp = getCacheExpireTimestamp(); return sitemap.maps.categories; }; @@ -140,7 +144,7 @@ sitemap.getTopicPage = async function (page) { if (!data.topics.length) { sitemap.maps.topics[page - 1] = { sm: '', - cacheExpireTimestamp: Date.now() + (1000 * 60 * 60 * 24), + cacheExpireTimestamp: getCacheExpireTimestamp(), }; return sitemap.maps.topics[page - 1].sm; } @@ -158,7 +162,7 @@ sitemap.getTopicPage = async function (page) { sitemap.maps.topics[page - 1] = { sm: await urlsToSitemap(topicUrls), - cacheExpireTimestamp: Date.now() + (1000 * 60 * 60 * 24), + cacheExpireTimestamp: getCacheExpireTimestamp(), }; return sitemap.maps.topics[page - 1].sm; diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 6e5093d9a1..db7438e681 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -9,6 +9,7 @@ const db = require('../database'); const privileges = require('../privileges'); const websockets = require('./index'); const batch = require('../batch'); +const plugins = require('../plugins'); const index = require('./index'); const getAdminSearchDict = require('../admin/search').getDictionary; @@ -126,4 +127,8 @@ SocketAdmin.clearSearchHistory = async function () { }); }; +SocketAdmin.parseRaw = async function (socket, text) { + return await plugins.hooks.fire('filter:parse.raw', text); +}; + require('../promisify')(SocketAdmin); diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js index bc084b14f5..03cffb6d20 100644 --- a/src/socket.io/admin/analytics.js +++ b/src/socket.io/admin/analytics.js @@ -18,14 +18,19 @@ Analytics.get = async function (socket, data) { data.amount = 24; } } - const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; + const getStats = data.units === 'days' ? + analytics.getDailyStatsForSet : + analytics.getHourlyStatsForSet; + if (data.graph === 'traffic') { + const until = data.until || Date.now(); const result = await utils.promiseParallel({ - uniqueVisitors: getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount), - pageviews: getStats('analytics:pageviews', data.until || Date.now(), data.amount), - pageviewsRegistered: getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount), - pageviewsGuest: getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount), - pageviewsBot: getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount), + uniqueVisitors: getStats('analytics:uniquevisitors', until, data.amount), + pageviews: getStats('analytics:pageviews', until, data.amount), + pageviewsRegistered: getStats('analytics:pageviews:registered', until, data.amount), + pageviewsGuest: getStats('analytics:pageviews:guest', until, data.amount), + pageviewsBot: getStats('analytics:pageviews:bot', until, data.amount), + appageviews: getStats('analytics:pageviews:ap', until, data.amount), summary: analytics.getSummary(), }); result.pastDay = result.pageviews.reduce((a, b) => parseInt(a, 10) + parseInt(b, 10)); diff --git a/src/socket.io/admin/cache.js b/src/socket.io/admin/cache.js index 65ddfbefe1..553e01022d 100644 --- a/src/socket.io/admin/cache.js +++ b/src/socket.io/admin/cache.js @@ -2,33 +2,18 @@ const SocketCache = module.exports; -const db = require('../../database'); -const plugins = require('../../plugins'); +const tracker = require('../../cache/tracker'); SocketCache.clear = async function (socket, data) { - let caches = { - post: require('../../posts/cache').getOrCreate(), - object: db.objectCache, - group: require('../../groups').cache, - local: require('../../cache'), - }; - caches = await plugins.hooks.fire('filter:admin.cache.get', caches); - if (!caches[data.name]) { - return; + const foundCache = await tracker.findCacheByName(data.name); + if (foundCache && foundCache.reset) { + foundCache.reset(); } - caches[data.name].reset(); }; SocketCache.toggle = async function (socket, data) { - let caches = { - post: require('../../posts/cache').getOrCreate(), - object: db.objectCache, - group: require('../../groups').cache, - local: require('../../cache'), - }; - caches = await plugins.hooks.fire('filter:admin.cache.get', caches); - if (!caches[data.name]) { - return; + const foundCache = await tracker.findCacheByName(data.name); + if (foundCache) { + foundCache.enabled = data.enabled; } - caches[data.name].enabled = data.enabled; }; diff --git a/src/socket.io/admin/email.js b/src/socket.io/admin/email.js index ed5bce7a60..9059520b2b 100644 --- a/src/socket.io/admin/email.js +++ b/src/socket.io/admin/email.js @@ -1,11 +1,15 @@ 'use strict'; +const nconf = require('nconf'); +const winston = require('winston'); + const meta = require('../../meta'); const userDigest = require('../../user/digest'); const userEmail = require('../../user/email'); const notifications = require('../../notifications'); const emailer = require('../../emailer'); const utils = require('../../utils'); +const user = require('../../user'); const Email = module.exports; @@ -14,55 +18,81 @@ Email.test = async function (socket, data) { ...(data.payload || {}), subject: '[[email:test-email.subject]]', }; + try { + switch (data.template) { + case 'digest': + await userDigest.execute({ + interval: 'month', + subscribers: [socket.uid], + }); + break; - switch (data.template) { - case 'digest': - await userDigest.execute({ - interval: 'month', - subscribers: [socket.uid], - }); - break; + case 'banned': + Object.assign(payload, { + username: 'test-user', + until: utils.toISOString(Date.now()), + reason: 'Test Reason', + }); + await emailer.send(data.template, socket.uid, payload); + break; - case 'banned': - Object.assign(payload, { - username: 'test-user', - until: utils.toISOString(Date.now()), - reason: 'Test Reason', - }); - await emailer.send(data.template, socket.uid, payload); - break; + case 'verify-email': + case 'welcome': + await userEmail.sendValidationEmail(socket.uid, { + force: 1, + template: data.template, + subject: data.template === 'welcome' ? `[[email:welcome-to, ${meta.config.title || meta.config.browserTitle || 'NodeBB'}]]` : undefined, + }); + break; - case 'verify-email': - case 'welcome': - await userEmail.sendValidationEmail(socket.uid, { - force: 1, - template: data.template, - subject: data.template === 'welcome' ? `[[email:welcome-to, ${meta.config.title || meta.config.browserTitle || 'NodeBB'}]]` : undefined, - }); - break; + case 'notification': { + const notification = await notifications.create({ + type: 'test', + bodyShort: '[[email:notif.test.short]]', + bodyLong: '[[email:notif.test.long]]', + nid: `uid:${socket.uid}:test`, + path: '/', + from: socket.uid, + }); + await emailer.send('notification', socket.uid, { + path: notification.path, + subject: utils.stripHTMLTags(notification.subject || '[[notifications:new-notification]]'), + intro: utils.stripHTMLTags(notification.bodyShort), + body: notification.bodyLong || '', + notification, + showUnsubscribe: true, + }); + break; + } - case 'notification': { - const notification = await notifications.create({ - type: 'test', - bodyShort: '[[email:notif.test.short]]', - bodyLong: '[[email:notif.test.long]]', - nid: `uid:${socket.uid}:test`, - path: '/', - from: socket.uid, - }); - await emailer.send('notification', socket.uid, { - path: notification.path, - subject: utils.stripHTMLTags(notification.subject || '[[notifications:new-notification]]'), - intro: utils.stripHTMLTags(notification.bodyShort), - body: notification.bodyLong || '', - notification, - showUnsubscribe: true, - }); - break; + default: + await emailer.send(data.template, socket.uid, payload); + break; } - - default: - await emailer.send(data.template, socket.uid, payload); - break; + } catch (err) { + winston.error(err.stack); + throw err; } }; + +Email.testSmtp = async (socket, data) => { + try { + const smtp = emailer.createSmtpTransport(data.smtp); + const content = 'This is a test email sent from NodeBB to verify your SMTP settings are correct.'; + const { hostname } = new URL(nconf.get('url')); + const toEmail = await user.getUserField(socket.uid, 'email'); + await smtp.sendMail({ + to: toEmail, + subject: `[${meta.config.title}] SMTP Settings Test Email`, + html: content, + text: content, + from: { + name: meta.config['email:from_name'] || 'NodeBB', + address: meta.config['email:from'] || `no-reply@${hostname}`, + }, + }); + } catch (err) { + winston.error(err.stack); + throw err; + } +}; \ No newline at end of file diff --git a/src/socket.io/admin/plugins.js b/src/socket.io/admin/plugins.js index 4489be42bd..32f5ff61d0 100644 --- a/src/socket.io/admin/plugins.js +++ b/src/socket.io/admin/plugins.js @@ -11,6 +11,9 @@ const { pluginNamePattern } = require('../../constants'); const Plugins = module.exports; Plugins.toggleActive = async function (socket, plugin_id) { + if (await plugins.isSystemPlugin(plugin_id)) { + throw new Error('[[error:cannot-toggle-system-plugin]]'); + } postsCache.reset(); const data = await plugins.toggleActive(plugin_id); await events.log({ @@ -22,6 +25,9 @@ Plugins.toggleActive = async function (socket, plugin_id) { }; Plugins.toggleInstall = async function (socket, data) { + if (await plugins.isSystemPlugin(data.id)) { + throw new Error('[[error:cannot-toggle-system-plugin]]'); + } const isInstalled = await plugins.isInstalled(data.id); const isStarterPlan = nconf.get('saas_plan') === 'starter'; if ((isStarterPlan || nconf.get('acpPluginInstallDisabled')) && !isInstalled) { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 9367ff9c53..0a981eacbb 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -2,6 +2,8 @@ const async = require('async'); const winston = require('winston'); +const nconf = require('nconf'); +const pubsub = require('../../pubsub'); const db = require('../../database'); const groups = require('../../groups'); @@ -129,8 +131,15 @@ User.forcePasswordReset = async function (socket, uids) { uids.forEach(uid => sockets.in(`uid_${uid}`).emit('event:logout')); }; +pubsub.on('admin.user.restartJobs', () => { + if (nconf.get('runJobs')) { + winston.verbose('[user/jobs] Restarting jobs...'); + user.startJobs(); + } +}); + User.restartJobs = async function () { - user.startJobs(); + pubsub.publish('admin.user.restartJobs', {}); }; User.loadGroups = async function (socket, uids) { @@ -210,3 +219,11 @@ User.saveCustomFields = async function (socket, fields) { await user.reloadCustomFieldWhitelist(); }; +User.saveCustomReasons = async function (socket, reasons) { + const keys = await db.getSortedSetRange('custom-reasons', 0, -1); + await db.delete('custom-reasons'); + await db.deleteAll(keys.map(k => `custom-reason:${k}`)); + const ids = reasons.map((f, i) => i); + await db.sortedSetAdd(`custom-reasons`, ids, ids); + await db.setObjectBulk(reasons.map((reason, i) => [`custom-reason:${i}`, reason])); +}; diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index d80638458e..844e2bd535 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -13,6 +13,7 @@ const notifications = require('../notifications'); const plugins = require('../plugins'); const utils = require('../utils'); const batch = require('../batch'); +const translator = require('../translator'); const SocketHelpers = module.exports; @@ -94,7 +95,10 @@ SocketHelpers.sendNotificationToPostOwner = async function (pid, fromuid, comman return; } fromuid = utils.isNumber(fromuid) ? parseInt(fromuid, 10) : fromuid; - const postData = await posts.getPostFields(pid, ['tid', 'uid', 'content']); + const [postData, fromCategory] = await Promise.all([ + posts.getPostFields(pid, ['tid', 'uid', 'content', 'sourceContent']), + !utils.isNumber(fromuid) && categories.exists(fromuid), + ]); const [canRead, isIgnoring] = await Promise.all([ privileges.posts.can('topics:read', pid, postData.uid), topics.isIgnoring([postData.tid], postData.uid), @@ -103,19 +107,17 @@ SocketHelpers.sendNotificationToPostOwner = async function (pid, fromuid, comman return; } const [userData, topicTitle, postObj] = await Promise.all([ - user.getUserFields(fromuid, ['username']), + fromCategory ? categories.getCategoryFields(fromuid, ['name']) : user.getUserFields(fromuid, ['username']), topics.getTopicField(postData.tid, 'title'), posts.parsePost(postData), ]); - const { displayname } = userData; - const title = utils.decodeHTMLEntities(topicTitle); - const titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + const bodyShort = translator.compile(notification, userData.displayname || userData.name, title); const notifObj = await notifications.create({ type: command, - bodyShort: `[[${notification}, ${displayname}, ${titleEscaped}]]`, + bodyShort: bodyShort, bodyLong: postObj.content, pid: pid, tid: postData.tid, @@ -150,10 +152,9 @@ SocketHelpers.sendNotificationToTopicOwner = async function (tid, fromuid, comma const ownerUid = topicData.uid; const title = utils.decodeHTMLEntities(topicData.title); - const titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); const notifObj = await notifications.create({ - bodyShort: `[[${notification}, ${displayname}, ${titleEscaped}]]`, + bodyShort: translator.compile(notification, displayname, title), path: `/topic/${topicData.slug}`, nid: `${command}:tid:${tid}:uid:${fromuid}`, from: fromuid, diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js index 2b0df88114..263193260a 100644 --- a/src/socket.io/notifications.js +++ b/src/socket.io/notifications.js @@ -34,8 +34,9 @@ SocketNotifs.markUnread = async function (socket, nid) { user.notifications.pushCount(socket.uid); }; -SocketNotifs.markAllRead = async function (socket) { - await notifications.markAllRead(socket.uid); +SocketNotifs.markAllRead = async function (socket, data) { + const filter = data && data.filter ? data.filter : ''; + await notifications.markAllRead(socket.uid, filter); user.notifications.pushCount(socket.uid); }; diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index d6833e363a..b20fbe9b97 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -1,18 +1,10 @@ 'use strict'; -const validator = require('validator'); - const db = require('../database'); const posts = require('../posts'); const privileges = require('../privileges'); -const plugins = require('../plugins'); -const meta = require('../meta'); const topics = require('../topics'); -const notifications = require('../notifications'); const utils = require('../utils'); -const events = require('../events'); -const translator = require('../translator'); - const api = require('../api'); const sockets = require('.'); @@ -99,90 +91,23 @@ SocketPosts.getReplies = async function (socket, pid) { }; SocketPosts.accept = async function (socket, data) { - await canEditQueue(socket, data, 'accept'); - const result = await posts.submitFromQueue(data.id); - if (result && socket.uid !== parseInt(result.uid, 10)) { - await sendQueueNotification('post-queue-accepted', result.uid, `/post/${result.pid}`); - } - await logQueueEvent(socket, result, 'accept'); + sockets.warnDeprecated(socket, 'POST /api/v3/posts/queue/:id'); + await api.posts.acceptQueuedPost(socket, data); }; SocketPosts.reject = async function (socket, data) { - await canEditQueue(socket, data, 'reject'); - const result = await posts.removeFromQueue(data.id); - if (result && socket.uid !== parseInt(result.uid, 10)) { - await sendQueueNotification('post-queue-rejected', result.uid, '/'); - } - await logQueueEvent(socket, result, 'reject'); + sockets.warnDeprecated(socket, 'DELETE /api/v3/posts/queue/:id'); + await api.posts.removeQueuedPost(socket, data); }; -async function logQueueEvent(socket, result, type) { - const eventData = { - type: `post-queue-${result.type}-${type}`, - uid: socket.uid, - ip: socket.ip, - content: result.data.content, - targetUid: result.uid, - }; - if (result.type === 'topic') { - eventData.cid = result.data.cid; - eventData.title = result.data.title; - } else { - eventData.tid = result.data.tid; - } - if (result.pid) { - eventData.pid = result.pid; - } - await events.log(eventData); -} - SocketPosts.notify = async function (socket, data) { - await canEditQueue(socket, data, 'notify'); - const result = await posts.getFromQueue(data.id); - if (result) { - await sendQueueNotification('post-queue-notify', result.uid, `/post-queue/${data.id}`, validator.escape(String(data.message))); - } + sockets.warnDeprecated(socket, 'POST /api/v3/posts/queue/:id/notify'); + await api.posts.notifyQueuedPostOwner(socket, data); }; -async function canEditQueue(socket, data, action) { - const [canEditQueue, queuedPost] = await Promise.all([ - posts.canEditQueue(socket.uid, data, action), - posts.getFromQueue(data.id), - ]); - if (!queuedPost) { - throw new Error('[[error:no-post]]'); - } - if (!canEditQueue) { - throw new Error('[[error:no-privileges]]'); - } -} - -async function sendQueueNotification(type, targetUid, path, notificationText) { - const bodyShort = notificationText ? - translator.compile(`notifications:${type}`, notificationText) : - translator.compile(`notifications:${type}`); - const notifData = { - type: type, - nid: `${type}-${targetUid}-${path}`, - bodyShort: bodyShort, - path: path, - }; - if (parseInt(meta.config.postQueueNotificationUid, 10) > 0) { - notifData.from = meta.config.postQueueNotificationUid; - } - const notifObj = await notifications.create(notifData); - await notifications.push(notifObj, [targetUid]); -} - SocketPosts.editQueuedContent = async function (socket, data) { - if (!data || !data.id || (!data.content && !data.title && !data.cid)) { - throw new Error('[[error:invalid-data]]'); - } - await posts.editQueuedContent(socket.uid, data); - if (data.content) { - return await plugins.hooks.fire('filter:parse.post', { postData: data }); - } - return { postData: data }; + sockets.warnDeprecated(socket, 'PUT /api/v3/posts/queue/:id'); + return await api.posts.editQueuedPost(socket, data); }; require('../promisify')(SocketPosts); diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index 397b6ef2a4..99a09580bd 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -5,12 +5,13 @@ const nconf = require('nconf'); const db = require('../../database'); const posts = require('../../posts'); const flags = require('../../flags'); -const events = require('../../events'); const privileges = require('../../privileges'); const plugins = require('../../plugins'); const social = require('../../social'); const user = require('../../user'); const utils = require('../../utils'); +const sockets = require('../index'); +const api = require('../../api'); module.exports = function (SocketPosts) { SocketPosts.loadPostTools = async function (socket, data) { @@ -77,23 +78,8 @@ module.exports = function (SocketPosts) { if (!data || !Array.isArray(data.pids) || !data.toUid) { throw new Error('[[error:invalid-data]]'); } - const isAdminOrGlobalMod = await user.isAdminOrGlobalMod(socket.uid); - if (!isAdminOrGlobalMod) { - throw new Error('[[error:no-privileges]]'); - } - - const postData = await posts.changeOwner(data.pids, data.toUid); - const logs = postData.map(({ pid, uid, cid }) => (events.log({ - type: 'post-change-owner', - uid: socket.uid, - ip: socket.ip, - targetUid: data.toUid, - pid: pid, - originalUid: uid, - cid: cid, - }))); - - await Promise.all(logs); + sockets.warnDeprecated(socket, 'PUT /api/v3/posts/owner'); + await api.posts.changeOwner(socket, { pids: data.pids, uid: data.toUid }); }; SocketPosts.getEditors = async function (socket, data) { diff --git a/src/socket.io/topics/move.js b/src/socket.io/topics/move.js index edda6b2c77..a3fd3a8076 100644 --- a/src/socket.io/topics/move.js +++ b/src/socket.io/topics/move.js @@ -11,7 +11,7 @@ const sockets = require('..'); module.exports = function (SocketTopics) { SocketTopics.move = async function (socket, data) { - sockets.warnDeprecated(socket, 'GET /api/v3/topics/:tid/move'); + sockets.warnDeprecated(socket, 'PUT /api/v3/topics/:tid/move'); if (!data || !Array.isArray(data.tids) || !data.cid) { throw new Error('[[error:invalid-data]]'); diff --git a/src/socket.io/topics/tags.js b/src/socket.io/topics/tags.js index ebcbfd405a..05e5293ae4 100644 --- a/src/socket.io/topics/tags.js +++ b/src/socket.io/topics/tags.js @@ -9,13 +9,13 @@ const utils = require('../../utils'); module.exports = function (SocketTopics) { SocketTopics.isTagAllowed = async function (socket, data) { - if (!data || !utils.isNumber(data.cid) || !data.tag) { + if (!data || !data.tag) { throw new Error('[[error:invalid-data]]'); } const systemTags = (meta.config.systemTags || '').split(','); const [tagWhitelist, isPrivileged] = await Promise.all([ - categories.getTagWhitelist([data.cid]), + utils.isNumber(data.cid) ? categories.getTagWhitelist([data.cid]) : [], user.isPrivileged(socket.uid), ]); return isPrivileged || @@ -74,7 +74,7 @@ module.exports = function (SocketTopics) { // used by tag filter search SocketTopics.tagFilterSearch = async function (socket, data) { - let cids = []; + let cids; if (Array.isArray(data.cids)) { cids = await privileges.categories.filterCids('topics:read', data.cids, socket.uid); } else { // if no cids passed in get all cids we can read @@ -82,7 +82,7 @@ module.exports = function (SocketTopics) { cids = cids.filter(cid => cid !== -1); } - let tags = []; + let tags; if (data.query) { const allowed = await privileges.global.can('search:tags', socket.uid); if (!allowed) { diff --git a/src/socket.io/topics/tools.js b/src/socket.io/topics/tools.js index baafd88d1a..e4044a5272 100644 --- a/src/socket.io/topics/tools.js +++ b/src/socket.io/topics/tools.js @@ -6,9 +6,6 @@ const plugins = require('../../plugins'); module.exports = function (SocketTopics) { SocketTopics.loadTopicTools = async function (socket, data) { - if (!socket.uid) { - throw new Error('[[error:no-privileges]]'); - } if (!data) { throw new Error('[[error:invalid-data]]'); } @@ -21,11 +18,15 @@ module.exports = function (SocketTopics) { if (!topicData) { throw new Error('[[error:no-topic]]'); } - if (!userPrivileges['topics:read']) { + if (!userPrivileges['topics:read'] || !userPrivileges.view_thread_tools) { throw new Error('[[error:no-privileges]]'); } topicData.privileges = userPrivileges; - const result = await plugins.hooks.fire('filter:topic.thread_tools', { topic: topicData, uid: socket.uid, tools: [] }); + const result = await plugins.hooks.fire('filter:topic.thread_tools', { + topic: topicData, + uid: socket.uid, + tools: [], + }); result.topic.thread_tools = result.tools; return result.topic; }; diff --git a/src/socket.io/topics/unread.js b/src/socket.io/topics/unread.js index e32ee59450..360e3feaa6 100644 --- a/src/socket.io/topics/unread.js +++ b/src/socket.io/topics/unread.js @@ -1,6 +1,7 @@ 'use strict'; const topics = require('../../topics'); +const categories = require('../../categories'); const api = require('../../api'); const sockets = require('..'); @@ -32,6 +33,10 @@ module.exports = function (SocketTopics) { }; SocketTopics.markCategoryTopicsRead = async function (socket, cid) { + const exists = await categories.exists(cid); + if (!exists) { + throw new Error('[[error:invalid-cid]]'); + } const tids = await topics.getUnreadTids({ cid: cid, uid: socket.uid, filter: '' }); await SocketTopics.markAsRead(socket, tids); }; diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 48b491ab43..7903d44014 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -174,6 +174,14 @@ SocketUser.editModerationNote = async function (socket, data) { return await user.getModerationNotesByIds(data.uid, [data.id]); }; +SocketUser.getCustomReasons = async function (socket, { type }) { + const canBan = await privileges.users.hasBanPrivilege(socket.uid); + if (!canBan) { + throw new Error('[[error:no-privileges]]'); + } + return await user.bans.getCustomReasons({ type }); +}; + SocketUser.deleteUpload = async function (socket, data) { if (!data || !data.name || !data.uid) { throw new Error('[[error:invalid-data]]'); diff --git a/src/start.js b/src/start.js index 99f3b662c5..2928ac6bb6 100644 --- a/src/start.js +++ b/src/start.js @@ -39,7 +39,7 @@ start.start = async function () { require('./user').startJobs(); require('./plugins').startJobs(); require('./topics').scheduled.startJobs(); - require('./activitypub').startJobs(); + require('./activitypub').jobs.start(); await db.delete('locks'); } @@ -107,13 +107,24 @@ function addProcessHandlers() { shutdown(1); }); process.on('message', (msg) => { - if (msg && Array.isArray(msg.compiling)) { - if (msg.compiling.includes('tpl')) { - const benchpressjs = require('benchpressjs'); - benchpressjs.flush(); - } else if (msg.compiling.includes('lang')) { - const translator = require('./translator'); - translator.flush(); + if (msg) { + if (Array.isArray(msg.compiling)) { + if (msg.compiling.includes('tpl')) { + const benchpressjs = require('benchpressjs'); + benchpressjs.flush(); + } else if (msg.compiling.includes('lang')) { + const translator = require('./translator'); + translator.flush(); + } + } + + if (msg.livereload) { + // Send livereload event to all connected clients via Socket.IO + const websockets = require('./socket.io'); + if (websockets.server) { + websockets.server.emit('event:livereload'); + winston.info('[livereload] Sent reload event to all clients'); + } } } }); diff --git a/src/topics/create.js b/src/topics/create.js index e6d97af77f..56fbe49830 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -41,6 +41,16 @@ module.exports = function (Topics) { topicData.tags = data.tags.join(','); } + if (Array.isArray(data.thumbs) && data.thumbs.length) { + const thumbs = Topics.thumbs.filterThumbs(data.thumbs); + topicData.thumbs = JSON.stringify(thumbs); + topicData.numThumbs = thumbs.length; + } + + if (data.generatedTitle && utils.isNumber(data.generatedTitle)) { + topicData.generatedTitle = data.generatedTitle; + } + const result = await plugins.hooks.fire('filter:topic.create', { topic: topicData, data: data }); topicData = result.topic; await db.setObject(`topic:${topicData.tid}`, topicData); @@ -67,7 +77,7 @@ module.exports = function (Topics) { db.sortedSetsAdd(timestampedSortedSetKeys, timestamp, topicData.tid), db.sortedSetsAdd(countedSortedSetKeys, 0, topicData.tid), user.addTopicIdToUser(topicData.uid, topicData.tid, timestamp), - db.incrObjectField(`category:${topicData.cid}`, 'topic_count'), + db.incrObjectField(`${utils.isNumber(topicData.cid) ? 'category' : 'categoryRemote'}:${topicData.cid}`, 'topic_count'), utils.isNumber(tid) ? db.incrObjectField('global', 'topicCount') : null, Topics.createTags(data.tags, topicData.tid, timestamp), scheduled ? Promise.resolve() : categories.updateRecentTid(topicData.cid, topicData.tid), @@ -83,11 +93,12 @@ module.exports = function (Topics) { Topics.post = async function (data) { data = await plugins.hooks.fire('filter:topic.post', data); const { uid } = data; + const remoteUid = activitypub.helpers.isUri(uid); const [categoryExists, canCreate, canTag, isAdmin] = await Promise.all([ parseInt(data.cid, 10) > 0 ? categories.exists(data.cid) : true, - privileges.categories.can('topics:create', data.cid, uid), - privileges.categories.can('topics:tag', data.cid, uid), + privileges.categories.can('topics:create', data.cid, remoteUid ? -2 : uid), + privileges.categories.can('topics:tag', data.cid, remoteUid ? -2 : uid), privileges.users.isAdministrator(uid), ]); @@ -241,7 +252,7 @@ module.exports = function (Topics) { async function onNewPost({ pid, tid, uid: postOwner }, { uid, handle }) { const [[postData], [userInfo]] = await Promise.all([ - posts.getPostSummaryByPids([pid], uid, {}), + posts.getPostSummaryByPids([pid], uid, { extraFields: ['attachments'] }), posts.getUserInfoForPosts([postOwner], uid), ]); await Promise.all([ diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js new file mode 100644 index 0000000000..2ed7193f5a --- /dev/null +++ b/src/topics/crossposts.js @@ -0,0 +1,173 @@ +'use strict'; + +const _ = require('lodash'); +const db = require('../database'); +const topics = require('.'); +const user = require('../user'); +const categories = require('../categories'); +const posts = require('../posts'); +const privileges = require('../privileges'); +const activitypub = require('../activitypub'); +const utils = require('../utils'); + +const Crossposts = module.exports; + +Crossposts.get = async function (tids) { + const isArray = Array.isArray(tids); + if (!isArray) { + tids = [tids]; + } + + const crosspostIds = await db.getSortedSetsMembers(tids.map(tid => `tid:${tid}:crossposts`)); + const allCrosspostIds = crosspostIds.flat(); + const allCrossposts = await db.getObjects(allCrosspostIds.map(id => `crosspost:${id}`)); + + const categoriesData = await categories.getCategoriesFields( + _.uniq(allCrossposts.map(c => c.cid)), ['cid', 'name', 'icon', 'bgColor', 'color', 'slug'] + ); + + const categoriesMap = categoriesData.reduce((map, category) => { + map.set(parseInt(category.cid, 10), category); + return map; + }, new Map()); + + const crosspostMap = allCrossposts.reduce((map, crosspost, index) => { + const id = allCrosspostIds[index]; + if (id && crosspost) { + map.set(id, crosspost); + crosspost.id = id; + crosspost.category = categoriesMap.get(parseInt(crosspost.cid, 10)); + crosspost.uid = utils.isNumber(crosspost.uid) ? parseInt(crosspost.uid, 10) : crosspost.uid; + crosspost.cid = utils.isNumber(crosspost.cid) ? parseInt(crosspost.cid, 10) : crosspost.cid; + } + return map; + }, new Map()); + + const crossposts = crosspostIds.map(ids => ids.map(id => crosspostMap.get(id))); + return isArray ? crossposts : crossposts[0]; +}; + +Crossposts.add = async function (tid, cid, uid) { + /** + * NOTE: If uid is 0, the assumption is that it is a "system" crosspost, not a guest! + * (Normally guest uid is 0) + */ + + // Target cid must exist + if (!utils.isNumber(cid)) { + await activitypub.actors.assert(cid); + } + const [exists, allowed] = await Promise.all([ + categories.exists(cid), + uid === 0 || privileges.categories.can('topics:crosspost', cid, uid), + ]); + if (!exists) { + throw new Error('[[error:invalid-cid]]'); + } + if (!allowed) { + throw new Error('[[error:not-allowed]]'); + } + if (uid < 0) { + throw new Error('[[error:invalid-uid]]'); + } + + const crossposts = await Crossposts.get(tid); + const crosspostedCids = crossposts.map(crosspost => String(crosspost.cid)); + const now = Date.now(); + const crosspostId = utils.generateUUID(); + if (!crosspostedCids.includes(String(cid))) { + const [topicData, pids] = await Promise.all([ + topics.getTopicFields(tid, ['uid', 'cid', 'timestamp']), + topics.getPids(tid), + ]); + let pidTimestamps = await posts.getPostsFields(pids, ['timestamp']); + pidTimestamps = pidTimestamps.map(({ timestamp }) => timestamp); + + if (cid === topicData.cid) { + throw new Error('[[error:invalid-cid]]'); + } + const zsets = [ + `cid:${topicData.cid}:tids`, + `cid:${topicData.cid}:tids:create`, + `cid:${topicData.cid}:tids:lastposttime`, + `cid:${topicData.cid}:uid:${topicData.uid}:tids`, + `cid:${topicData.cid}:tids:votes`, + `cid:${topicData.cid}:tids:posts`, + `cid:${topicData.cid}:tids:views`, + ]; + const scores = await db.sortedSetsScore(zsets, tid); + const bulkAdd = zsets.map((zset, idx) => { + return [zset.replace(`cid:${topicData.cid}`, `cid:${cid}`), scores[idx], tid]; + }); + await Promise.all([ + db.sortedSetAddBulk(bulkAdd), + db.sortedSetAdd(`cid:${cid}:pids`, pidTimestamps, pids), + db.setObject(`crosspost:${crosspostId}`, { uid, tid, cid, timestamp: now }), + db.sortedSetAdd(`tid:${tid}:crossposts`, now, crosspostId), + uid > 0 ? db.sortedSetAdd(`uid:${uid}:crossposts`, now, crosspostId) : false, + topics.events.log(tid, { uid, type: 'crosspost', toCid: cid }), + ]); + await categories.onTopicsMoved([cid]); // must be done after + } else { + throw new Error('[[error:topic-already-crossposted]]'); + } + + return [...crossposts, { id: crosspostId, uid, tid, cid, timestamp: now }]; +}; + +Crossposts.remove = async function (tid, cid, uid) { + let crossposts = await Crossposts.get(tid); + const isPrivileged = await user.isAdminOrGlobalMod(uid); + const isMod = await user.isModerator(uid, cid); + const crosspostId = crossposts.reduce((id, { id: _id, cid: _cid, uid: _uid }) => { + if (String(cid) === String(_cid) && (isPrivileged || isMod || String(uid) === String(_uid))) { + id = _id; + } + + return id; + }, null); + if (!crosspostId) { + throw new Error('[[error:invalid-data]]'); + } + + const [author, pids] = await Promise.all([ + topics.getTopicField(tid, 'uid'), + topics.getPids(tid), + ]); + let bulkRemove = [ + `cid:${cid}:tids`, + `cid:${cid}:tids:create`, + `cid:${cid}:tids:lastposttime`, + `cid:${cid}:uid:${author}:tids`, + `cid:${cid}:tids:votes`, + `cid:${cid}:tids:posts`, + `cid:${cid}:tids:views`, + ]; + bulkRemove = bulkRemove.map(zset => [zset, tid]); + + await Promise.all([ + db.sortedSetRemoveBulk(bulkRemove), + db.delete(`crosspost:${crosspostId}`), + db.sortedSetRemove(`tid:${tid}:crossposts`, crosspostId), + db.sortedSetRemove(`cid:${cid}:pids`, pids), + uid > 0 ? db.sortedSetRemove(`uid:${uid}:crossposts`, crosspostId) : false, + ]); + await categories.onTopicsMoved([cid]); + + topics.events.find(tid, { uid, toCid: cid, type: 'crosspost' }).then((eventIds) => { + topics.events.purge(tid, eventIds); + }); + + crossposts = await Crossposts.get(tid); + return crossposts; +}; + +Crossposts.removeAll = async function (tid) { + const crosspostIds = await db.getSortedSetMembers(`tid:${tid}:crossposts`); + const crossposts = await db.getObjects(crosspostIds.map(id => `crosspost:${id}`)); + await Promise.all(crossposts.map(async ({ tid, cid, uid }) => { + return Crossposts.remove(tid, cid, uid); + })); + + return []; +}; \ No newline at end of file diff --git a/src/topics/data.js b/src/topics/data.js index d411cbf329..03c48a08d9 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -10,9 +10,10 @@ const plugins = require('../plugins'); const intFields = [ 'tid', 'cid', 'uid', 'mainPid', 'postcount', - 'viewcount', 'postercount', 'deleted', 'locked', 'pinned', - 'pinExpiry', 'timestamp', 'upvotes', 'downvotes', 'lastposttime', - 'deleterUid', + 'viewcount', 'postercount', 'followercount', + 'deleted', 'locked', 'pinned', 'pinExpiry', + 'timestamp', 'upvotes', 'downvotes', + 'lastposttime', 'deleterUid', 'generatedTitle', ]; module.exports = function (Topics) { @@ -79,13 +80,11 @@ module.exports = function (Topics) { }; }; -function escapeTitle(topicData) { +function escapeTitle(topicData, hasField) { if (topicData) { - if (topicData.title) { + if (hasField('title')) { topicData.title = translator.escape(validator.escape(topicData.title)); - } - if (topicData.titleRaw) { - topicData.titleRaw = translator.escape(topicData.titleRaw); + topicData.titleRaw = translator.escape(topicData.titleRaw || ''); } } } @@ -95,39 +94,41 @@ function modifyTopic(topic, fields) { return; } + const hasField = utils.createFieldChecker(fields); + db.parseIntFields(topic, intFields, fields); - if (topic.hasOwnProperty('title')) { + if (hasField('title')) { topic.titleRaw = topic.title; topic.title = String(topic.title); } - escapeTitle(topic); + escapeTitle(topic, hasField); - if (topic.hasOwnProperty('timestamp')) { + if (hasField('timestamp')) { topic.timestampISO = utils.toISOString(topic.timestamp); - if (!fields.length || fields.includes('scheduled')) { + if (hasField('scheduled')) { topic.scheduled = topic.timestamp > Date.now(); } } - if (topic.hasOwnProperty('lastposttime')) { + if (hasField('lastposttime')) { topic.lastposttimeISO = utils.toISOString(topic.lastposttime); } - if (topic.hasOwnProperty('pinExpiry')) { + if (hasField('pinExpiry')) { topic.pinExpiryISO = utils.toISOString(topic.pinExpiry); } - if (topic.hasOwnProperty('upvotes') && topic.hasOwnProperty('downvotes')) { + if (hasField('upvotes') && hasField('downvotes')) { topic.votes = topic.upvotes - topic.downvotes; } - if (fields.includes('teaserPid') || !fields.length) { + if (hasField('teaserPid')) { topic.teaserPid = topic.teaserPid || null; } - if (fields.includes('tags') || !fields.length) { + if (hasField('tags')) { const tags = String(topic.tags || ''); topic.tags = tags.split(',').filter(Boolean).map((tag) => { const escaped = validator.escape(String(tag)); @@ -139,4 +140,12 @@ function modifyTopic(topic, fields) { }; }); } + + if (hasField('thumbs')) { + try { + topic.thumbs = topic.thumbs ? JSON.parse(String(topic.thumbs || '[]')) : []; + } catch (e) { + topic.thumbs = []; + } + } } diff --git a/src/topics/delete.js b/src/topics/delete.js index 903fba4ef5..9a6110fffc 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -8,6 +8,7 @@ const categories = require('../categories'); const flags = require('../flags'); const plugins = require('../plugins'); const batch = require('../batch'); +const activitypub = require('../activitypub'); const utils = require('../utils'); module.exports = function (Topics) { @@ -24,6 +25,7 @@ module.exports = function (Topics) { deleterUid: uid, deletedTimestamp: Date.now(), }), + activitypub.out.remove.context(uid, tid), ]); await categories.updateRecentTidForCid(cid); @@ -71,14 +73,11 @@ module.exports = function (Topics) { }; Topics.purge = async function (tid, uid) { - const [deletedTopic, tags] = await Promise.all([ - Topics.getTopicData(tid), - Topics.getTopicTags(tid), - ]); + const deletedTopic = await Topics.getTopicData(tid); if (!deletedTopic) { return; } - deletedTopic.tags = tags; + deletedTopic.tags = deletedTopic.tags.map(tag => tag.value); await deleteFromFollowersIgnorers(tid); await Promise.all([ @@ -100,6 +99,7 @@ module.exports = function (Topics) { Topics.deleteTopicTags(tid), Topics.events.purge(tid), Topics.thumbs.deleteAll(tid), + Topics.crossposts.removeAll(tid), reduceCounters(tid), ]); plugins.hooks.fire('action:topic.purge', { topic: deletedTopic, uid: uid }); @@ -145,8 +145,8 @@ module.exports = function (Topics) { const postCountChange = incr * topicData.postcount; await Promise.all([ db.incrObjectFieldBy('global', 'postCount', postCountChange), - db.incrObjectFieldBy(`category:${topicData.cid}`, 'post_count', postCountChange), - db.incrObjectFieldBy(`category:${topicData.cid}`, 'topic_count', incr), + db.incrObjectFieldBy(`${utils.isNumber(topicData.cid) ? 'category' : 'categoryRemote'}:${topicData.cid}`, 'post_count', postCountChange), + db.incrObjectFieldBy(`${utils.isNumber(topicData.cid) ? 'category' : 'categoryRemote'}:${topicData.cid}`, 'topic_count', incr), ]); } }; diff --git a/src/topics/events.js b/src/topics/events.js index 10ab9b8936..59284dc84a 100644 --- a/src/topics/events.js +++ b/src/topics/events.js @@ -1,5 +1,6 @@ 'use strict'; +const validator = require('validator'); const _ = require('lodash'); const nconf = require('nconf'); const db = require('../database'); @@ -72,6 +73,10 @@ Events._types = { icon: 'fa-code-fork', translation: async (event, language) => translateEventArgs(event, language, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)), }, + crosspost: { + icon: 'fa-square-arrow-up-right', + translation: async (event, language) => translateEventArgs(event, language, 'topic:user-crossposted-topic', renderUser(event), renderCategory(event.toCategory), renderTimeago(event)), + }, }; Events.init = async () => { @@ -107,7 +112,17 @@ function renderUser(event) { if (!event.user || event.user.system) { return '[[global:system-user]]'; } - return `${helpers.buildAvatar(event.user, '16px', true)} ${event.user.displayname}`; + + const user = { + ...event.user, + displayname: validator.escape(String(event.user.displayname)), + }; + + return `${helpers.buildAvatar(user, '16px', true)} ${user.displayname}`; +} + +function renderCategory(category) { + return `${helpers.buildCategoryLabel(category, 'a')}`; } function renderTimeago(event) { @@ -119,10 +134,10 @@ Events.get = async (tid, uid, reverse = false) => { return []; } - let eventIds = await db.getSortedSetRangeWithScores(`topic:${tid}:events`, 0, -1); + const eventIds = await db.getSortedSetRangeWithScores(`topic:${tid}:events`, 0, -1); const keys = eventIds.map(obj => `topicEvent:${obj.value}`); const timestamps = eventIds.map(obj => obj.score); - eventIds = eventIds.map(obj => obj.value); + let events = await db.getObjects(keys); events.forEach((e, idx) => { e.timestamp = timestamps[idx]; @@ -176,9 +191,10 @@ async function addEventsFromPostQueue(tid, uid, events) { } async function modifyEvent({ uid, events }) { - const [users, fromCategories, userSettings] = await Promise.all([ + const [users, fromCategories, toCategories, userSettings] = await Promise.all([ getUserInfo(events.map(event => event.uid).filter(Boolean)), getCategoryInfo(events.map(event => event.fromCid).filter(Boolean)), + getCategoryInfo(events.map(event => event.toCid).filter(Boolean)), user.getSettings(uid), ]); @@ -208,6 +224,9 @@ async function modifyEvent({ uid, events }) { if (event.hasOwnProperty('fromCid')) { event.fromCategory = fromCategories[event.fromCid]; } + if (event.hasOwnProperty('toCid')) { + event.toCategory = toCategories[event.toCid]; + } Object.assign(event, Events._types[event.type]); }); diff --git a/src/topics/follow.js b/src/topics/follow.js index a89dfa57c5..d1afb7025c 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -48,29 +48,30 @@ module.exports = function (Topics) { } async function follow(tid, uid) { - await addToSets(`tid:${tid}:followers`, `uid:${uid}:followed_tids`, tid, uid); + await db.setAdd(`tid:${tid}:followers`, uid); + await db.sortedSetAdd(`uid:${uid}:followed_tids`, Date.now(), tid); + await updateFollowerCount(tid); } async function unfollow(tid, uid) { - await removeFromSets(`tid:${tid}:followers`, `uid:${uid}:followed_tids`, tid, uid); + await db.setRemove(`tid:${tid}:followers`, uid); + await db.sortedSetRemove(`uid:${uid}:followed_tids`, tid); + await updateFollowerCount(tid); } async function ignore(tid, uid) { - await addToSets(`tid:${tid}:ignorers`, `uid:${uid}:ignored_tids`, tid, uid); + await db.setAdd(`tid:${tid}:ignorers`, uid); + await db.sortedSetAdd(`uid:${uid}:ignored_tids`, Date.now(), tid); } async function unignore(tid, uid) { - await removeFromSets(`tid:${tid}:ignorers`, `uid:${uid}:ignored_tids`, tid, uid); + await db.setRemove(`tid:${tid}:ignorers`, uid); + await db.sortedSetRemove(`uid:${uid}:ignored_tids`, tid); } - async function addToSets(set1, set2, tid, uid) { - await db.setAdd(set1, uid); - await db.sortedSetAdd(set2, Date.now(), tid); - } - - async function removeFromSets(set1, set2, tid, uid) { - await db.setRemove(set1, uid); - await db.sortedSetRemove(set2, tid); + async function updateFollowerCount(tid) { + const count = await db.setCount(`tid:${tid}:followers`); + await Topics.setTopicField(tid, 'followercount', count); } Topics.isFollowing = async function (tids, uid) { diff --git a/src/topics/index.js b/src/topics/index.js index 5717f26126..0f9c067535 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -35,6 +35,7 @@ Topics.thumbs = require('./thumbs'); require('./bookmarks')(Topics); require('./merge')(Topics); Topics.events = require('./events'); +Topics.crossposts = require('./crossposts'); Topics.exists = async function (tids) { return await db.exists( @@ -74,8 +75,7 @@ Topics.getTopicsByTids = async function (tids, options) { .map(t => t && t.uid && t.uid.toString()) .filter(v => utils.isNumber(v) || activitypub.helpers.isUri(v))); const cids = _.uniq(topics - .map(t => t && t.cid && t.cid.toString()) - .filter(v => utils.isNumber(v))); + .map(t => t && t.cid && t.cid.toString())); const guestTopics = topics.filter(t => t && t.uid === 0); async function loadGuestHandles() { diff --git a/src/topics/posts.js b/src/topics/posts.js index e32c18e727..535d53ff1d 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -209,7 +209,7 @@ module.exports = function (Topics) { parentPost.content = foundPost.content; return; } - parentPost = await posts.parsePost(parentPost); + await posts.parsePost(parentPost); })); const parents = {}; @@ -227,7 +227,7 @@ module.exports = function (Topics) { }); postData.forEach((post) => { - if (parents[post.toPid]) { + if (parents[post.toPid] && parents[post.toPid].content) { post.parent = parents[post.toPid]; } }); @@ -252,7 +252,7 @@ module.exports = function (Topics) { }; Topics.getLatestUndeletedReply = async function (tid) { - let isDeleted = false; + let isDeleted; let index = 0; do { /* eslint-disable no-await-in-loop */ @@ -312,13 +312,22 @@ module.exports = function (Topics) { }; Topics.increasePostCount = async function (tid) { - incrementFieldAndUpdateSortedSet(tid, 'postcount', 1, 'topics:posts'); + await incrementFieldAndUpdateSortedSet(tid, 'postcount', 1, 'topics:posts'); }; Topics.decreasePostCount = async function (tid) { - incrementFieldAndUpdateSortedSet(tid, 'postcount', -1, 'topics:posts'); + await incrementFieldAndUpdateSortedSet(tid, 'postcount', -1, 'topics:posts'); }; + async function incrementFieldAndUpdateSortedSet(tid, field, by, set) { + const cid = await Topics.getTopicField(tid, 'cid'); + const value = await db.incrObjectFieldBy(`topic:${tid}`, field, by); + const isRemoteCid = !utils.isNumber(cid) || cid === -1; + if (!isRemoteCid) { + await db.sortedSetAdd(set, value, tid); + } + } + Topics.increaseViewCount = async function (req, tid) { const allow = req.uid > 0 || (meta.config.guestsIncrementTopicViews && req.uid === 0); if (allow) { @@ -327,17 +336,16 @@ module.exports = function (Topics) { const interval = meta.config.incrementTopicViewsInterval * 60000; if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < now - interval) { const cid = await Topics.getTopicField(tid, 'cid'); - incrementFieldAndUpdateSortedSet(tid, 'viewcount', 1, ['topics:views', `cid:${cid}:tids:views`]); + const isRemoteCid = !utils.isNumber(cid) || cid === -1; + const value = await db.incrObjectFieldBy(`topic:${tid}`, 'viewcount', 1); + await db.sortedSetsAdd( + isRemoteCid ? [`cid:${cid}:tids:views`] : ['topics:views', `cid:${cid}:tids:views`], value, tid + ); req.session.tids_viewed[tid] = now; } } }; - async function incrementFieldAndUpdateSortedSet(tid, field, by, set) { - const value = await db.incrObjectFieldBy(`topic:${tid}`, field, by); - await db[Array.isArray(set) ? 'sortedSetsAdd' : 'sortedSetAdd'](set, value, tid); - } - Topics.getTitleByPid = async function (pid) { return await Topics.getTopicFieldByPid('title', pid); }; @@ -442,7 +450,7 @@ module.exports = function (Topics) { let { content } = postData; // ignore lines that start with `>` - content = content.split('\n').filter(line => !line.trim().startsWith('>')).join('\n'); + content = (content || '').split('\n').filter(line => !line.trim().startsWith('>')).join('\n'); // Scan post content for topic links const matches = [...content.matchAll(backlinkRegex)]; if (!matches) { diff --git a/src/topics/recent.js b/src/topics/recent.js index 8cc84c54c7..f31f8a3509 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -4,6 +4,7 @@ const db = require('../database'); const plugins = require('../plugins'); const posts = require('../posts'); +const utils = require('../utils'); module.exports = function (Topics) { const terms = { @@ -75,7 +76,7 @@ module.exports = function (Topics) { // Topics in /world are excluded from /recent const cid = await Topics.getTopicField(tid, 'cid'); - if (cid === -1) { + if (!utils.isNumber(cid) || cid === -1) { return await db.sortedSetRemove('topics:recent', data.tid); } diff --git a/src/topics/scheduled.js b/src/topics/scheduled.js index 1134ae1dfa..a20109a50d 100644 --- a/src/topics/scheduled.js +++ b/src/topics/scheduled.js @@ -11,7 +11,7 @@ const topics = require('./index'); const categories = require('../categories'); const groups = require('../groups'); const user = require('../user'); -const api = require('../api'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); const Scheduled = module.exports; @@ -175,7 +175,7 @@ function federatePosts(uids, topicData) { topicData.forEach(({ mainPid: pid }, idx) => { const uid = uids[idx]; - api.activitypub.create.note({ uid }, { pid }); + activitypub.out.create.note(uid, pid); }); } diff --git a/src/topics/sorted.js b/src/topics/sorted.js index 07a6215218..1d4da1f772 100644 --- a/src/topics/sorted.js +++ b/src/topics/sorted.js @@ -29,6 +29,7 @@ module.exports = function (Topics) { params.tags = [params.tags]; } data.tids = await getTids(params); + data.tids = await getInbox(data.tids, params); data.tids = await sortTids(data.tids, params); data.tids = await filterTids(data.tids.slice(0, meta.config.recentMaxTopics), params); data.topicCount = data.tids.length; @@ -42,7 +43,7 @@ module.exports = function (Topics) { const result = await plugins.hooks.fire('filter:topics.getSortedTids', { params: params, tids: [] }); return result.tids; } - let tids = []; + let tids; if (params.term !== 'alltime') { if (params.sort === 'posts') { tids = await getTidsWithMostPostsInTerm(params.cids, params.uid, params.term); @@ -72,6 +73,33 @@ module.exports = function (Topics) { return tids; } + async function getInbox(tids, params) { + if (!Array.isArray(params.cids) || !params.cids.includes('-1')) { + return tids; + } + + let inbox; + if (params.term !== 'alltime') { + const method = params.sort === 'old' ? + 'getSortedSetRangeByScore' : + 'getSortedSetRevRangeByScore'; + inbox = await db[method]( + `uid:${params.uid}:inbox`, + 0, + 1000, + '+inf', + Date.now() - Topics.getSinceFromTerm(params.term) + ); + } else { + const method = params.sort === 'old' ? + 'getSortedSetRange' : + 'getSortedSetRevRange'; + inbox = await db[method](`uid:${params.uid}:inbox`, 0, meta.config.recentMaxTopics - 1); + } + + return _.uniq(tids.concat(inbox)); + } + function sortToSet(sort) { const map = { recent: 'topics:recent', @@ -243,7 +271,11 @@ module.exports = function (Topics) { } async function filterTids(tids, params) { - const { filter, uid } = params; + let { filter, uid, cids } = params; + cids = cids && cids.map(String); + if (cids && cids.length === 1 && cids.includes('-1')) { + cids = undefined; + } if (filter === 'new') { tids = await Topics.filterNewTids(tids, uid); @@ -271,18 +303,19 @@ module.exports = function (Topics) { const isCidIgnored = _.zipObject(topicCids, ignoredCids); topicData = filtered; - const cids = params.cids && params.cids.map(String); const { tags } = params; tids = topicData.filter(t => ( t && t.cid && !isCidIgnored[t.cid] && - (cids || parseInt(t.cid, 10) !== -1) && (!cids || cids.includes(String(t.cid))) && (!tags.length || tags.every(tag => t.tags.find(topicTag => topicTag.value === tag))) )).map(t => t.tid); - const result = await plugins.hooks.fire('filter:topics.filterSortedTids', { tids: tids, params: params }); + const result = await plugins.hooks.fire('filter:topics.filterSortedTids', { + tids, + params, + }); return result.tids; } diff --git a/src/topics/tags.js b/src/topics/tags.js index 0df806c34d..3cbc29a801 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -205,7 +205,7 @@ module.exports = function (Topics) { }; Topics.getTagTopicCount = async function (tag, cids = []) { - let count = 0; + let count; if (cids.length) { count = await db.sortedSetsCardSum( cids.map(cid => `cid:${cid}:tag:${tag}:topics`) @@ -476,7 +476,7 @@ module.exports = function (Topics) { if (parseInt(data.cid, 10)) { tagWhitelist = await categories.getTagWhitelist([data.cid]); } - let tags = []; + let tags; if (Array.isArray(tagWhitelist[0]) && tagWhitelist[0].length) { const scores = await db.sortedSetScores(`cid:${data.cid}:tags`, tagWhitelist[0]); tags = tagWhitelist[0].map((tag, index) => ({ value: tag, score: scores[index] })); diff --git a/src/topics/teaser.js b/src/topics/teaser.js index 477d2891b8..f729afb4ec 100644 --- a/src/topics/teaser.js +++ b/src/topics/teaser.js @@ -110,7 +110,7 @@ module.exports = function (Topics) { } async function getPreviousNonBlockedPost(postData, blockedUids) { - let isBlocked = false; + let isBlocked; let prevPost = postData; const postsPerIteration = 5; let start = 0; diff --git a/src/topics/thumbs.js b/src/topics/thumbs.js index be2916a05d..3239ab0fdc 100644 --- a/src/topics/thumbs.js +++ b/src/topics/thumbs.js @@ -5,29 +5,26 @@ const _ = require('lodash'); const nconf = require('nconf'); const path = require('path'); const mime = require('mime'); - -const db = require('../database'); -const file = require('../file'); const plugins = require('../plugins'); const posts = require('../posts'); const meta = require('../meta'); -const cache = require('../cache'); const topics = module.parent.exports; const Thumbs = module.exports; -Thumbs.exists = async function (id, path) { - const isDraft = !await topics.exists(id); - const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`; +const upload_url = nconf.get('relative_path') + nconf.get('upload_url'); +const upload_path = nconf.get('upload_path'); - return db.isSortedSetMember(set, path); +Thumbs.exists = async function (tid, path) { + const thumbs = await topics.getTopicField(tid, 'thumbs'); + return thumbs.includes(path); }; Thumbs.load = async function (topicData) { const mainPids = topicData.filter(Boolean).map(t => t.mainPid); - let hashes = await posts.getPostsFields(mainPids, ['attachments']); - const hasUploads = await db.exists(mainPids.map(pid => `post:${pid}:uploads`)); - hashes = hashes.map(o => o.attachments); + const mainPostData = await posts.getPostsFields(mainPids, ['attachments', 'uploads']); + const hasUploads = mainPostData.map(p => Array.isArray(p.uploads) && p.uploads.length > 0); + const hashes = mainPostData.map(o => o.attachments); let hasThumbs = topicData.map((t, idx) => t && (parseInt(t.numThumbs, 10) > 0 || !!(hashes[idx] && hashes[idx].length) || @@ -36,11 +33,71 @@ Thumbs.load = async function (topicData) { const topicsWithThumbs = topicData.filter((tid, idx) => hasThumbs[idx]); const tidsWithThumbs = topicsWithThumbs.map(t => t.tid); - const thumbs = await Thumbs.get(tidsWithThumbs); + const thumbs = await loadFromTopicData(topicsWithThumbs, { + thumbsOnly: meta.config.showPostUploadsAsThumbnails !== 1, + }); + const tidToThumbs = _.zipObject(tidsWithThumbs, thumbs); return topicData.map(t => (t && t.tid ? (tidToThumbs[t.tid] || []) : [])); }; +async function loadFromTopicData(topicData, options = {}) { + const tids = topicData.map(t => t && t.tid); + const thumbs = topicData.map(t => t && Array.isArray(t.thumbs) ? t.thumbs : []); + + if (!options.thumbsOnly) { + const mainPids = topicData.map(t => t.mainPid); + const [mainPidUploads, mainPidAttachments] = await Promise.all([ + posts.uploads.list(mainPids), + posts.attachments.get(mainPids), + ]); + + // Add uploaded media to thumb sets + mainPidUploads.forEach((uploads, idx) => { + uploads = uploads.filter((upload) => { + const type = mime.getType(upload); + return !thumbs[idx].includes(upload) && type && type.startsWith('image/'); + }); + + if (uploads.length) { + thumbs[idx].push(...uploads); + } + }); + + // Add attachments to thumb sets + mainPidAttachments.forEach((attachments, idx) => { + attachments = attachments.filter( + attachment => !thumbs[idx].includes(attachment.url) && (attachment.mediaType && attachment.mediaType.startsWith('image/')) + ); + + if (attachments.length) { + thumbs[idx].push(...attachments.map(attachment => attachment.url)); + } + }); + } + + const hasTimestampPrefix = /^\d+-/; + + let response = thumbs.map((thumbSet, idx) => thumbSet.map(thumb => ({ + id: String(tids[idx]), + name: (() => { + const name = path.basename(thumb); + return hasTimestampPrefix.test(name) ? name.slice(14) : name; + })(), + path: thumb, + url: thumb.startsWith('http') ? + thumb : + path.posix.join(upload_url, thumb.replace(/\\/g, '/')), + }))); + + ({ thumbs: response } = await plugins.hooks.fire('filter:topics.getThumbs', { + tids, + thumbsOnly: options.thumbsOnly, + thumbs: response, + })); + return response; +}; + Thumbs.get = async function (tids, options) { // Allow singular or plural usage let singular = false; @@ -54,118 +111,77 @@ Thumbs.get = async function (tids, options) { thumbsOnly: false, }; } - - const isDraft = (await topics.exists(tids)).map(exists => !exists); - if (!meta.config.allowTopicsThumbnail || !tids.length) { return singular ? [] : tids.map(() => []); } - const hasTimestampPrefix = /^\d+-/; - const upload_url = nconf.get('relative_path') + nconf.get('upload_url'); - const sets = tids.map((tid, idx) => `${isDraft[idx] ? 'draft' : 'topic'}:${tid}:thumbs`); - const thumbs = await Promise.all(sets.map(getThumbs)); - - let mainPids = await topics.getTopicsFields(tids, ['mainPid']); - mainPids = mainPids.map(o => o.mainPid); - - if (!options.thumbsOnly) { - // Add uploaded media to thumb sets - const mainPidUploads = await Promise.all(mainPids.map(posts.uploads.list)); - mainPidUploads.forEach((uploads, idx) => { - uploads = uploads.filter((upload) => { - const type = mime.getType(upload); - return !thumbs[idx].includes(upload) && type && type.startsWith('image/'); - }); - - if (uploads.length) { - thumbs[idx].push(...uploads); - } - }); - - // Add attachments to thumb sets - const mainPidAttachments = await posts.attachments.get(mainPids); - mainPidAttachments.forEach((attachments, idx) => { - attachments = attachments.filter( - attachment => !thumbs[idx].includes(attachment.url) && (attachment.mediaType && attachment.mediaType.startsWith('image/')) - ); - - if (attachments.length) { - thumbs[idx].push(...attachments.map(attachment => attachment.url)); - } - }); - } - - let response = thumbs.map((thumbSet, idx) => thumbSet.map(thumb => ({ - id: tids[idx], - name: (() => { - const name = path.basename(thumb); - return hasTimestampPrefix.test(name) ? name.slice(14) : name; - })(), - path: thumb, - url: thumb.startsWith('http') ? thumb : path.posix.join(upload_url, thumb.replace(/\\/g, '/')), - }))); - - ({ thumbs: response } = await plugins.hooks.fire('filter:topics.getThumbs', { - tids, - thumbsOnly: options.thumbsOnly, - thumbs: response, - })); - return singular ? response.pop() : response; + const topicData = await topics.getTopicsFields(tids, ['tid', 'mainPid', 'thumbs']); + const response = await loadFromTopicData(topicData, options); + return singular ? response[0] : response; }; -async function getThumbs(set) { - const cached = cache.get(set); - if (cached !== undefined) { - return cached.slice(); - } - const thumbs = await db.getSortedSetRange(set, 0, -1); - cache.set(set, thumbs); - return thumbs.slice(); -} Thumbs.associate = async function ({ id, path, score }) { - // Associates a newly uploaded file as a thumb to the passed-in draft or topic - const isDraft = !await topics.exists(id); + // Associates a newly uploaded file as a thumb to the passed-in topic + const topicData = await topics.getTopicData(id); + if (!topicData) { + return; + } const isLocal = !path.startsWith('http'); - const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`; - const numThumbs = await db.sortedSetCard(set); // Normalize the path to allow for changes in upload_path (and so upload_url can be appended if needed) if (isLocal) { path = path.replace(nconf.get('relative_path'), ''); path = path.replace(nconf.get('upload_url'), ''); } - await db.sortedSetAdd(set, isFinite(score) ? score : numThumbs, path); - if (!isDraft) { - const numThumbs = await db.sortedSetCard(set); - await topics.setTopicField(id, 'numThumbs', numThumbs); - } - cache.del(set); - // Associate thumbnails with the main pid (only on local upload) - if (!isDraft && isLocal) { - const mainPid = (await topics.getMainPids([id]))[0]; - await posts.uploads.associate(mainPid, path); + if (Array.isArray(topicData.thumbs)) { + const currentIdx = topicData.thumbs.indexOf(path); + const insertIndex = (typeof score === 'number' && score >= 0 && score < topicData.thumbs.length) ? + score : + topicData.thumbs.length; + + if (currentIdx !== -1) { + // Remove from current position + topicData.thumbs.splice(currentIdx, 1); + // Adjust insertIndex if needed + const adjustedIndex = currentIdx < insertIndex ? insertIndex - 1 : insertIndex; + topicData.thumbs.splice(adjustedIndex, 0, path); + } else { + topicData.thumbs.splice(insertIndex, 0, path); + } + + await topics.setTopicFields(id, { + thumbs: JSON.stringify(topicData.thumbs), + numThumbs: topicData.thumbs.length, + }); + // Associate thumbnails with the main pid (only on local upload) + if (isLocal && currentIdx === -1) { + await posts.uploads.associate(topicData.mainPid, path); + } } }; -Thumbs.migrate = async function (uuid, id) { - // Converts the draft thumb zset to the topic zset (combines thumbs if applicable) - const set = `draft:${uuid}:thumbs`; - const thumbs = await db.getSortedSetRangeWithScores(set, 0, -1); - await Promise.all(thumbs.map(async thumb => await Thumbs.associate({ - id, - path: thumb.value, - score: thumb.score, - }))); - await db.delete(set); - cache.del(set); +Thumbs.filterThumbs = function (thumbs) { + if (!Array.isArray(thumbs)) { + return []; + } + thumbs = thumbs.filter((thumb) => { + if (thumb.startsWith('http')) { + return true; + } + // ensure it is in upload path + const fullPath = path.join(upload_path, thumb); + return fullPath.startsWith(upload_path); + }); + return thumbs; }; -Thumbs.delete = async function (id, relativePaths) { - const isDraft = !await topics.exists(id); - const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`; +Thumbs.delete = async function (tid, relativePaths) { + const topicData = await topics.getTopicData(tid); + if (!topicData) { + return; + } if (typeof relativePaths === 'string') { relativePaths = [relativePaths]; @@ -173,48 +189,28 @@ Thumbs.delete = async function (id, relativePaths) { throw new Error('[[error:invalid-data]]'); } - const absolutePaths = relativePaths.map(relativePath => path.join(nconf.get('upload_path'), relativePath)); - const [associated, existsOnDisk] = await Promise.all([ - db.isSortedSetMembers(set, relativePaths), - Promise.all(absolutePaths.map(async absolutePath => file.exists(absolutePath))), - ]); + const toRemove = relativePaths.map( + relativePath => topicData.thumbs.includes(relativePath) ? relativePath : null + ).filter(Boolean); - const toRemove = []; - const toDelete = []; - relativePaths.forEach((relativePath, idx) => { - if (associated[idx]) { - toRemove.push(relativePath); - } - - if (existsOnDisk[idx]) { - toDelete.push(absolutePaths[idx]); - } - }); - - await db.sortedSetRemove(set, toRemove); - - if (isDraft && toDelete.length) { // drafts only; post upload dissociation handles disk deletion for topics - await Promise.all(toDelete.map(path => file.delete(path))); - } - - if (toRemove.length && !isDraft) { - const topics = require('.'); - const mainPid = (await topics.getMainPids([id]))[0]; + if (toRemove.length) { + const { mainPid } = topicData.mainPid; + topicData.thumbs = topicData.thumbs.filter(thumb => !toRemove.includes(thumb)); await Promise.all([ - db.incrObjectFieldBy(`topic:${id}`, 'numThumbs', -toRemove.length), + topics.setTopicFields(tid, { + thumbs: JSON.stringify(topicData.thumbs), + numThumbs: topicData.thumbs.length, + }), Promise.all(toRemove.map(async relativePath => posts.uploads.dissociate(mainPid, relativePath))), ]); } - if (toRemove.length) { - cache.del(set); +}; + +Thumbs.deleteAll = async (tid) => { + const topicData = await topics.getTopicData(tid); + if (!topicData) { + return; } -}; - -Thumbs.deleteAll = async (id) => { - const isDraft = !await topics.exists(id); - const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`; - - const thumbs = await db.getSortedSetRange(set, 0, -1); - await Thumbs.delete(id, thumbs); + await Thumbs.delete(tid, topicData.thumbs); }; diff --git a/src/topics/tools.js b/src/topics/tools.js index 13bbd5ece7..f5e9b13dc7 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -241,6 +241,10 @@ module.exports = function (Topics) { if (cid === topicData.cid) { throw new Error('[[error:cant-move-topic-to-same-category]]'); } + if (!utils.isNumber(cid) || !utils.isNumber(topicData.cid)) { + throw new Error('[[error:cant-move-topic-to-from-remote-categories]]'); + } + const tags = await Topics.getTopicTags(tid); await db.sortedSetsRemove([ `cid:${topicData.cid}:tids`, @@ -283,9 +287,7 @@ module.exports = function (Topics) { oldCid: oldCid, }), Topics.updateCategoryTagsCount([oldCid, cid], tags), - oldCid !== -1 ? - Topics.events.log(tid, { type: 'move', uid: data.uid, fromCid: oldCid }) : - topicTools.share(tid, data.uid), + Topics.events.log(tid, { type: 'move', uid: data.uid, fromCid: oldCid }), ]); // Update entry in recent topics zset — must come after hash update diff --git a/src/topics/unread.js b/src/topics/unread.js index 676bb0647e..afdb3fc155 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -141,14 +141,14 @@ module.exports = function (Topics) { }); tids = await privileges.topics.filterTids('topics:read', tids, params.uid); - const topicData = (await Topics.getTopicsFields(tids, ['tid', 'cid', 'uid', 'postcount', 'deleted', 'scheduled', 'tags'])) - .filter(t => t.scheduled || !t.deleted); + const topicData = (await Topics.getTopicsFields(tids, ['tid', 'cid', 'uid', 'postcount', 'deleted', 'tags'])) + .filter(t => !t.deleted); const topicCids = _.uniq(topicData.map(topic => topic.cid)).filter(Boolean); const categoryWatchState = await categories.getWatchState(topicCids, params.uid); const userCidState = _.zipObject(topicCids, categoryWatchState); - const filterCids = params.cid && params.cid.map(cid => parseInt(cid, 10)); + const filterCids = params.cid && params.cid.map(cid => utils.isNumber(cid) ? parseInt(cid, 10) : cid); const filterTags = params.tag && params.tag.map(tag => String(tag)); topicData.forEach((topic) => { @@ -290,7 +290,7 @@ module.exports = function (Topics) { }; Topics.markAsRead = async function (tids, uid) { - if (!Array.isArray(tids) || !tids.length) { + if (!Array.isArray(tids) || !tids.length || !utils.isNumber(uid)) { return false; } @@ -325,9 +325,7 @@ module.exports = function (Topics) { }; Topics.markAllRead = async function (uid) { - const cutoff = await Topics.unreadCutoff(uid); - let tids = await db.getSortedSetRevRangeByScore('topics:recent', 0, -1, '+inf', cutoff); - tids = await privileges.topics.filterTids('topics:read', tids, uid); + const tids = await Topics.getUnreadTids({ uid }); Topics.markTopicNotificationsRead(tids, uid); await Topics.markAsRead(tids, uid); await db.delete(`uid:${uid}:tids_unread`); @@ -353,8 +351,23 @@ module.exports = function (Topics) { if (!(parseInt(uid, 10) > 0)) { return tids.map(() => false); } - const [topicScores, userScores, tids_unread, blockedUids] = await Promise.all([ + + // Remote tids do not get slotted into topics:recent; separate calculation follows + async function getRemoteTopicScores(tids) { + let cids = await Topics.getTopicsFields(tids, ['cid']); + cids = cids.map(({ cid }) => cid); + return await Promise.all(tids.map(async (tid, idx) => { + const cid = cids[idx]; + if (utils.isNumber(tid) || !cid) { + return null; + } + return await db.sortedSetScore(`cid:${cid}:tids`, tid); + })); + } + + const [topicScores, remoteTopicScores, userScores, tids_unread, blockedUids] = await Promise.all([ db.sortedSetScores('topics:recent', tids), + getRemoteTopicScores(tids), db.sortedSetScores(`uid:${uid}:tids_read`, tids), db.sortedSetScores(`uid:${uid}:tids_unread`, tids), user.blocks.list(uid), @@ -363,7 +376,7 @@ module.exports = function (Topics) { const cutoff = await Topics.unreadCutoff(uid); const result = tids.map((tid, index) => { const read = !tids_unread[index] && - (topicScores[index] < cutoff || + ((topicScores[index] || remoteTopicScores[index]) < cutoff || !!(userScores[index] && userScores[index] >= topicScores[index])); return { tid: tid, read: read, index: index }; }); diff --git a/src/translator.js b/src/translator.js index 8584686056..a9f9e888e0 100644 --- a/src/translator.js +++ b/src/translator.js @@ -3,7 +3,7 @@ const winston = require('winston'); function warn(msg) { - if (global.env === 'development') { + if (process.env.NODE_ENV === 'development') { winston.warn(msg); } } diff --git a/src/upgrades/1.10.0/view_deleted_privilege.js b/src/upgrades/1.10.0/view_deleted_privilege.js index a483bcf417..3b65f2d5b7 100644 --- a/src/upgrades/1.10.0/view_deleted_privilege.js +++ b/src/upgrades/1.10.0/view_deleted_privilege.js @@ -11,6 +11,7 @@ module.exports = { method: async function () { const { progress } = this; const cids = await db.getSortedSetRange('categories:cid', 0, -1); + progress.total = cids.length; for (const cid of cids) { const uids = await db.getSortedSetRange(`group:cid:${cid}:privileges:moderate:members`, 0, -1); for (const uid of uids) { diff --git a/src/upgrades/1.10.2/fix_category_topic_zsets.js b/src/upgrades/1.10.2/fix_category_topic_zsets.js index 999383feac..83b4d7b27f 100644 --- a/src/upgrades/1.10.2/fix_category_topic_zsets.js +++ b/src/upgrades/1.10.2/fix_category_topic_zsets.js @@ -1,5 +1,3 @@ -/* eslint-disable no-await-in-loop */ - 'use strict'; const db = require('../../database'); @@ -13,18 +11,24 @@ module.exports = { const { progress } = this; const topics = require('../../topics'); + progress.total = await db.sortedSetCard('topics:tid'); await batch.processSortedSet('topics:tid', async (tids) => { - for (const tid of tids) { - progress.incr(); - const topicData = await db.getObjectFields(`topic:${tid}`, ['cid', 'pinned', 'postcount']); - if (parseInt(topicData.pinned, 10) !== 1) { + progress.incr(tids.length); + const topicData = await db.getObjectFields( + tids.map(tid => `topic:${tid}`), + ['tid', 'cid', 'pinned', 'postcount'], + ); + const bulkAdd = []; + topicData.forEach((topic) => { + if (topic && parseInt(topic.pinned, 10) !== 1) { topicData.postcount = parseInt(topicData.postcount, 10) || 0; - await db.sortedSetAdd(`cid:${topicData.cid}:tids:posts`, topicData.postcount, tid); + bulkAdd.push([`cid:${topicData.cid}:tids:posts`, topicData.postcount, topicData.tid]); } - await topics.updateLastPostTimeFromLastPid(tid); - } + }); + await db.sortedSetAddBulk(bulkAdd); + await Promise.all(tids.map(tid => topics.updateLastPostTimeFromLastPid(tid))); }, { - progress: progress, + batch: 500, }); }, }; diff --git a/src/upgrades/1.10.2/upgrade_bans_to_hashes.js b/src/upgrades/1.10.2/upgrade_bans_to_hashes.js index 84c7a0ed4d..2bc55b4667 100644 --- a/src/upgrades/1.10.2/upgrade_bans_to_hashes.js +++ b/src/upgrades/1.10.2/upgrade_bans_to_hashes.js @@ -11,14 +11,23 @@ module.exports = { method: async function () { const { progress } = this; + progress.total = await db.sortedSetCard('users:joindate'); + await batch.processSortedSet('users:joindate', async (uids) => { - for (const uid of uids) { - progress.incr(); - const [bans, reasons, userData] = await Promise.all([ - db.getSortedSetRevRangeWithScores(`uid:${uid}:bans`, 0, -1), - db.getSortedSetRevRangeWithScores(`banned:${uid}:reasons`, 0, -1), - db.getObjectFields(`user:${uid}`, ['banned', 'banned:expire', 'joindate', 'lastposttime', 'lastonline']), - ]); + progress.incr(uids.length); + const [allUserData, allBans] = await Promise.all([ + db.getObjectsFields( + uids.map(uid => `user:${uid}`), + ['banned', 'banned:expire', 'joindate', 'lastposttime', 'lastonline'], + ), + db.getSortedSetsMembersWithScores( + uids.map(uid => `uid:${uid}:bans`) + ), + ]); + + await Promise.all(uids.map(async (uid, index) => { + const userData = allUserData[index]; + const bans = allBans[index] || []; // has no history, but is banned, create plain object with just uid and timestmap if (!bans.length && parseInt(userData.banned, 10)) { @@ -31,6 +40,7 @@ module.exports = { const banKey = `uid:${uid}:ban:${banTimestamp}`; await addBan(uid, banKey, { uid: uid, timestamp: banTimestamp }); } else if (bans.length) { + const reasons = await db.getSortedSetRevRangeWithScores(`banned:${uid}:reasons`, 0, -1); // process ban history for (const ban of bans) { const reasonData = reasons.find(reasonData => reasonData.score === ban.score); @@ -46,14 +56,16 @@ module.exports = { await addBan(uid, banKey, data); } } - } + })); }, { - progress: this.progress, + batch: 500, }); }, }; async function addBan(uid, key, data) { - await db.setObject(key, data); - await db.sortedSetAdd(`uid:${uid}:bans:timestamp`, data.timestamp, key); + await Promise.all([ + db.setObject(key, data), + db.sortedSetAdd(`uid:${uid}:bans:timestamp`, data.timestamp, key), + ]); } diff --git a/src/upgrades/1.10.2/username_email_history.js b/src/upgrades/1.10.2/username_email_history.js index 3b03568a69..8ee4306e3d 100644 --- a/src/upgrades/1.10.2/username_email_history.js +++ b/src/upgrades/1.10.2/username_email_history.js @@ -11,27 +11,34 @@ module.exports = { method: async function () { const { progress } = this; - await batch.processSortedSet('users:joindate', async (uids) => { - async function updateHistory(uid, set, fieldName) { - const count = await db.sortedSetCard(set); - if (count <= 0) { - // User has not changed their username/email before, record original username - const userData = await user.getUserFields(uid, [fieldName, 'joindate']); - if (userData && userData.joindate && userData[fieldName]) { - await db.sortedSetAdd(set, userData.joindate, [userData[fieldName], userData.joindate].join(':')); - } - } - } + progress.total = await db.sortedSetCard('users:joindate'); - await Promise.all(uids.map(async (uid) => { - await Promise.all([ - updateHistory(uid, `user:${uid}:usernames`, 'username'), - updateHistory(uid, `user:${uid}:emails`, 'email'), - ]); - progress.incr(); - })); + await batch.processSortedSet('users:joindate', async (uids) => { + const [usernameHistory, emailHistory, userData] = await Promise.all([ + db.sortedSetsCard(uids.map(uid => `user:${uid}:usernames`)), + db.sortedSetsCard(uids.map(uid => `user:${uid}:emails`)), + user.getUsersFields(uids, ['uid', 'username', 'email', 'joindate']), + ]); + + const bulkAdd = []; + userData.forEach((data, index) => { + const thisUsernameHistory = usernameHistory[index]; + const thisEmailHistory = emailHistory[index]; + if (thisUsernameHistory <= 0 && data && data.joindate && data.username) { + bulkAdd.push([ + `user:${data.uid}:usernames`, data.joindate, [data.username, data.joindate].join(':'), + ]); + } + if (thisEmailHistory <= 0 && data && data.joindate && data.email) { + bulkAdd.push([ + `user:${data.uid}:emails`, data.joindate, [data.email, data.joindate].join(':'), + ]); + } + }); + await db.sortedSetAddBulk(bulkAdd); + progress.incr(uids.length); }, { - progress: this.progress, + batch: 500, }); }, }; diff --git a/src/upgrades/1.12.1/clear_username_email_history.js b/src/upgrades/1.12.1/clear_username_email_history.js index 822b500884..0d36534502 100644 --- a/src/upgrades/1.12.1/clear_username_email_history.js +++ b/src/upgrades/1.12.1/clear_username_email_history.js @@ -1,45 +1,32 @@ 'use strict'; -const async = require('async'); + const db = require('../../database'); const user = require('../../user'); +const batch = require('../../batch'); module.exports = { name: 'Delete username email history for deleted users', timestamp: Date.UTC(2019, 2, 25), - method: function (callback) { + method: async function () { const { progress } = this; - let currentUid = 1; - db.getObjectField('global', 'nextUid', (err, nextUid) => { - if (err) { - return callback(err); - } - progress.total = nextUid; - async.whilst((next) => { - next(null, currentUid < nextUid); - }, - (next) => { - progress.incr(); - user.exists(currentUid, (err, exists) => { - if (err) { - return next(err); - } - if (exists) { - currentUid += 1; - return next(); - } - db.deleteAll([`user:${currentUid}:usernames`, `user:${currentUid}:emails`], (err) => { - if (err) { - return next(err); - } - currentUid += 1; - next(); - }); - }); - }, - (err) => { - callback(err); - }); + + progress.total = await db.getObjectField('global', 'nextUid'); + const allUids = []; + for (let i = 1; i < progress.total; i += 1) { + allUids.push(i); + } + await batch.processArray(allUids, async (uids) => { + const exists = await user.exists(uids); + const missingUids = uids.filter((uid, index) => !exists[index]); + const keysToDelete = [ + ...missingUids.map(uid => `user:${uid}:usernames`), + ...missingUids.map(uid => `user:${uid}:emails`), + ]; + await db.deleteAll(keysToDelete); + progress.incr(uids.length); + }, { + batch: 500, }); }, }; diff --git a/src/upgrades/1.12.1/moderation_notes_refactor.js b/src/upgrades/1.12.1/moderation_notes_refactor.js index 390273d74a..85118a9a0c 100644 --- a/src/upgrades/1.12.1/moderation_notes_refactor.js +++ b/src/upgrades/1.12.1/moderation_notes_refactor.js @@ -12,10 +12,12 @@ module.exports = { const { progress } = this; await batch.processSortedSet('users:joindate', async (uids) => { - await Promise.all(uids.map(async (uid) => { - progress.incr(); - - const notes = await db.getSortedSetRevRange(`uid:${uid}:moderation:notes`, 0, -1); + progress.incr(uids.length); + const allNotes = await db.getSortedSetsMembers( + uids.map(uid => `uid:${uid}:moderation:notes`) + ); + await Promise.all(uids.map(async (uid, index) => { + const notes = allNotes[index]; for (const note of notes) { const noteData = JSON.parse(note); noteData.timestamp = noteData.timestamp || Date.now(); diff --git a/src/upgrades/1.13.0/clean_post_topic_hash.js b/src/upgrades/1.13.0/clean_post_topic_hash.js index caa6dbd8f6..20cfd78c22 100644 --- a/src/upgrades/1.13.0/clean_post_topic_hash.js +++ b/src/upgrades/1.13.0/clean_post_topic_hash.js @@ -8,6 +8,7 @@ module.exports = { timestamp: Date.UTC(2019, 9, 7), method: async function () { const { progress } = this; + progress.total = await db.sortedSetCard('posts:pid') + await db.sortedSetCard('topics:tid'); await cleanPost(progress); await cleanTopic(progress); }, @@ -51,7 +52,6 @@ async function cleanPost(progress) { })); }, { batch: 500, - progress: progress, }); } @@ -90,6 +90,5 @@ async function cleanTopic(progress) { })); }, { batch: 500, - progress: progress, }); } diff --git a/src/upgrades/1.6.2/topics_lastposttime_zset.js b/src/upgrades/1.6.2/topics_lastposttime_zset.js index 1dee9feb1a..f299b19c01 100644 --- a/src/upgrades/1.6.2/topics_lastposttime_zset.js +++ b/src/upgrades/1.6.2/topics_lastposttime_zset.js @@ -1,29 +1,30 @@ 'use strict'; -const async = require('async'); - const db = require('../../database'); +const batch = require('../../batch'); module.exports = { name: 'New sorted set cid::tids:lastposttime', timestamp: Date.UTC(2017, 9, 30), - method: function (callback) { + method: async function () { const { progress } = this; + progress.total = await db.sortedSetCard('topics:tid'); - require('../../batch').processSortedSet('topics:tid', (tids, next) => { - async.eachSeries(tids, (tid, next) => { - db.getObjectFields(`topic:${tid}`, ['cid', 'timestamp', 'lastposttime'], (err, topicData) => { - if (err || !topicData) { - return next(err); - } - progress.incr(); - - const timestamp = topicData.lastposttime || topicData.timestamp || Date.now(); - db.sortedSetAdd(`cid:${topicData.cid}:tids:lastposttime`, timestamp, tid, next); - }, next); - }, next); + await batch.processSortedSet('topics:tid', async (tids) => { + const topicData = await db.getObjectsFields( + tids.map(tid => `topic:${tid}`), ['tid', 'cid', 'timestamp', 'lastposttime'] + ); + const bulkAdd = []; + topicData.forEach((data) => { + if (data && data.cid && data.tid) { + const timestamp = data.lastposttime || data.timestamp || Date.now(); + bulkAdd.push([`cid:${data.cid}:tids:lastposttime`, timestamp, data.tid]); + } + }); + await db.sortedSetAddBulk(bulkAdd); + progress.incr(tids.length); }, { - progress: this.progress, - }, callback); + batch: 500, + }); }, }; diff --git a/src/upgrades/1.7.1/notification-settings.js b/src/upgrades/1.7.1/notification-settings.js index fed592effb..e3693d4f04 100644 --- a/src/upgrades/1.7.1/notification-settings.js +++ b/src/upgrades/1.7.1/notification-settings.js @@ -8,23 +8,38 @@ module.exports = { timestamp: Date.UTC(2017, 10, 15), method: async function () { const { progress } = this; - + progress.total = await db.sortedSetCard('users:joindate'); await batch.processSortedSet('users:joindate', async (uids) => { - await Promise.all(uids.map(async (uid) => { - progress.incr(); - const userSettings = await db.getObjectFields(`user:${uid}:settings`, ['sendChatNotifications', 'sendPostNotifications']); - if (userSettings) { + + const userSettings = await db.getObjectsFields( + uids.map(uid => `user:${uid}:settings`), + ['sendChatNotifications', 'sendPostNotifications'], + ); + + const bulkSet = []; + userSettings.forEach((settings, index) => { + const set = {}; + if (settings) { if (parseInt(userSettings.sendChatNotifications, 10) === 1) { - await db.setObjectField(`user:${uid}:settings`, 'notificationType_new-chat', 'notificationemail'); + set['notificationType_new-chat'] = 'notificationemail'; } if (parseInt(userSettings.sendPostNotifications, 10) === 1) { - await db.setObjectField(`user:${uid}:settings`, 'notificationType_new-reply', 'notificationemail'); + set['notificationType_new-reply'] = 'notificationemail'; + } + if (Object.keys(set).length) { + bulkSet.push([`user:${uids[index]}:settings`, set]); } } - await db.deleteObjectFields(`user:${uid}:settings`, ['sendChatNotifications', 'sendPostNotifications']); - })); + }); + await db.setObjectBulk(bulkSet); + + await db.deleteObjectFields( + uids.map(uid => `user:${uid}:settings`), + ['sendChatNotifications', 'sendPostNotifications'], + ); + + progress.incr(uids.length); }, { - progress: progress, batch: 500, }); }, diff --git a/src/upgrades/1.7.3/topic_votes.js b/src/upgrades/1.7.3/topic_votes.js index 008aaece0a..d5f6b9fd57 100644 --- a/src/upgrades/1.7.3/topic_votes.js +++ b/src/upgrades/1.7.3/topic_votes.js @@ -10,32 +10,42 @@ module.exports = { method: async function () { const { progress } = this; - batch.processSortedSet('topics:tid', async (tids) => { - await Promise.all(tids.map(async (tid) => { - progress.incr(); - const topicData = await db.getObjectFields(`topic:${tid}`, ['mainPid', 'cid', 'pinned']); - if (topicData.mainPid && topicData.cid) { - const postData = await db.getObject(`post:${topicData.mainPid}`); - if (postData) { - const upvotes = parseInt(postData.upvotes, 10) || 0; - const downvotes = parseInt(postData.downvotes, 10) || 0; - const data = { - upvotes: upvotes, - downvotes: downvotes, - }; - const votes = upvotes - downvotes; - await Promise.all([ - db.setObject(`topic:${tid}`, data), - db.sortedSetAdd('topics:votes', votes, tid), - ]); - if (parseInt(topicData.pinned, 10) !== 1) { - await db.sortedSetAdd(`cid:${topicData.cid}:tids:votes`, votes, tid); - } + progress.total = await db.sortedSetCard('topics:tid'); + + await batch.processSortedSet('topics:tid', async (tids) => { + const topicsData = await db.getObjectsFields( + tids.map(tid => `topic:${tid}`), + ['tid', 'mainPid', 'cid', 'pinned'], + ); + const mainPids = topicsData.map(topicData => topicData && topicData.mainPid); + const mainPosts = await db.getObjects(mainPids.map(pid => `post:${pid}`)); + + const bulkSet = []; + const bulkAdd = []; + + topicsData.forEach((topicData, index) => { + const mainPost = mainPosts[index]; + if (mainPost && topicData && topicData.cid) { + const upvotes = parseInt(mainPost.upvotes, 10) || 0; + const downvotes = parseInt(mainPost.downvotes, 10) || 0; + const data = { + upvotes: upvotes, + downvotes: downvotes, + }; + const votes = upvotes - downvotes; + bulkSet.push([`topic:${topicData.tid}`, data]); + bulkAdd.push(['topics:votes', votes, topicData.tid]); + if (parseInt(topicData.pinned, 10) !== 1) { + bulkAdd.push([`cid:${topicData.cid}:tids:votes`, votes, topicData.tid]); } } - })); + }); + + await db.setObjectBulk(bulkSet); + await db.sortedSetAddBulk('topics:votes', bulkAdd); + + progress.incr(tids.length); }, { - progress: progress, batch: 500, }); }, diff --git a/src/upgrades/1.8.1/diffs_zset_to_listhash.js b/src/upgrades/1.8.1/diffs_zset_to_listhash.js index 370242fba1..277418a79e 100644 --- a/src/upgrades/1.8.1/diffs_zset_to_listhash.js +++ b/src/upgrades/1.8.1/diffs_zset_to_listhash.js @@ -1,57 +1,40 @@ 'use strict'; -const async = require('async'); const db = require('../../database'); const batch = require('../../batch'); - module.exports = { name: 'Reformatting post diffs to be stored in lists and hash instead of single zset', timestamp: Date.UTC(2018, 2, 15), - method: function (callback) { + method: async function () { const { progress } = this; - batch.processSortedSet('posts:pid', (pids, next) => { - async.each(pids, (pid, next) => { - db.getSortedSetRangeWithScores(`post:${pid}:diffs`, 0, -1, (err, diffs) => { - if (err) { - return next(err); - } + progress.total = await db.sortedSetCard('posts:pid'); - if (!diffs || !diffs.length) { - progress.incr(); - return next(); - } + await batch.processSortedSet('posts:pid', async (pids) => { + const postDiffs = await db.getSortedSetsMembersWithScores( + pids.map(pid => `post:${pid}:diffs`), + ); - // For each diff, push to list - async.each(diffs, (diff, next) => { - async.series([ - async.apply(db.delete.bind(db), `post:${pid}:diffs`), - async.apply(db.listPrepend.bind(db), `post:${pid}:diffs`, diff.score), - async.apply(db.setObject.bind(db), `diff:${pid}.${diff.score}`, { - pid: pid, - patch: diff.value, - }), - ], next); - }, (err) => { - if (err) { - return next(err); - } + await db.deleteAll(pids.map(pid => `post:${pid}:diffs`)); - progress.incr(); - return next(); - }); - }); - }, (err) => { - if (err) { - // Probably type error, ok to incr and continue - progress.incr(); + await Promise.all(postDiffs.map(async (diffs, index) => { + if (!diffs || !diffs.length) { + return; } - - return next(); - }); + diffs.reverse(); + const pid = pids[index]; + await db.listAppend(`post:${pid}:diffs`, diffs.map(d => d.score)); + await db.setObjectBulk( + diffs.map(d => ([`diff:${pid}.${d.score}`, { + pid: pid, + patch: d.value, + }])) + ); + })); + progress.incr(pids.length); }, { - progress: progress, - }, callback); + batch: 500, + }); }, }; diff --git a/src/upgrades/1.9.0/refresh_post_upload_associations.js b/src/upgrades/1.9.0/refresh_post_upload_associations.js index 44acfc079f..6183529641 100644 --- a/src/upgrades/1.9.0/refresh_post_upload_associations.js +++ b/src/upgrades/1.9.0/refresh_post_upload_associations.js @@ -1,21 +1,20 @@ 'use strict'; -const async = require('async'); +const db = require('../../database'); const posts = require('../../posts'); +const batch = require('../../batch'); module.exports = { name: 'Refresh post-upload associations', timestamp: Date.UTC(2018, 3, 16), - method: function (callback) { + method: async function () { const { progress } = this; - - require('../../batch').processSortedSet('posts:pid', (pids, next) => { - async.each(pids, (pid, next) => { - posts.uploads.sync(pid, next); - progress.incr(); - }, next); + progress.total = await db.sortedSetCard('posts:pid'); + await batch.processSortedSet('posts:pid', async (pids) => { + await Promise.all(pids.map(pid => posts.uploads.sync(pid))); + progress.incr(pids.length); }, { - progress: this.progress, - }, callback); + batch: 500, + }); }, }; diff --git a/src/upgrades/2.8.7/fix-email-sorted-sets.js b/src/upgrades/2.8.7/fix-email-sorted-sets.js index fcab69a8f4..84919e6774 100644 --- a/src/upgrades/2.8.7/fix-email-sorted-sets.js +++ b/src/upgrades/2.8.7/fix-email-sorted-sets.js @@ -26,7 +26,7 @@ module.exports = { } // user has email but doesn't match whats stored in user hash, gh#11259 - if (userData.email && userData.email.toLowerCase() !== email.toLowerCase()) { + if (userData.email && email && String(userData.email).toLowerCase() !== email.toLowerCase()) { bulkRemove.push(['email:uid', email]); bulkRemove.push(['email:sorted', `${email.toLowerCase()}:${uid}`]); } diff --git a/src/upgrades/3.7.0/category-read-by-uid.js b/src/upgrades/3.7.0/category-read-by-uid.js index 4ef564f53a..971620613e 100644 --- a/src/upgrades/3.7.0/category-read-by-uid.js +++ b/src/upgrades/3.7.0/category-read-by-uid.js @@ -9,6 +9,7 @@ module.exports = { method: async function () { const { progress } = this; const nextCid = await db.getObjectField('global', 'nextCid'); + progress.total = nextCid; const allCids = []; for (let i = 1; i <= nextCid; i++) { allCids.push(i); @@ -18,7 +19,6 @@ module.exports = { progress.incr(cids.length); }, { batch: 500, - progress, }); }, }; diff --git a/src/upgrades/4.3.0/chat_allow_list.js b/src/upgrades/4.3.0/chat_allow_list.js new file mode 100644 index 0000000000..0136c527ef --- /dev/null +++ b/src/upgrades/4.3.0/chat_allow_list.js @@ -0,0 +1,44 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + + +module.exports = { + name: 'Set user chat allow list to the users following if they turned on restrict chat', + timestamp: Date.UTC(2025, 3, 25), + method: async function () { + const { progress } = this; + + progress.total = await db.sortedSetCard('users:joindate'); + + await batch.processSortedSet('users:joindate', async (uids) => { + const keys = uids.map(uid => `user:${uid}:settings`); + const [userSettings, followingUids] = await Promise.all([ + db.getObjects(keys), + db.getSortedSetsMembers(uids.map(uid => `following:${uid}`)), + ]); + + const bulkSet = []; + + userSettings.forEach((settings, idx) => { + if (settings) { + const uid = uids[idx]; + const followingUidsOfThisUser = followingUids[idx] || []; + + if (parseInt(settings.restrictChat, 10) === 1 && followingUidsOfThisUser.length > 0) { + bulkSet.push([ + `user:${uid}:settings`, { chatAllowList: JSON.stringify(followingUidsOfThisUser) }, + ]); + } + } + }); + + await db.setObjectBulk(bulkSet); + + progress.incr(uids.length); + }, { + batch: 500, + }); + }, +}; diff --git a/src/upgrades/4.3.0/fix_duplicate_handles.js b/src/upgrades/4.3.0/fix_duplicate_handles.js new file mode 100644 index 0000000000..d3c62f712f --- /dev/null +++ b/src/upgrades/4.3.0/fix_duplicate_handles.js @@ -0,0 +1,89 @@ +'use strict'; + +const user = require('../../user'); +const categories = require('../../categories'); +const activitypub = require('../../activitypub'); + +const db = require('../../database'); + +module.exports = { + // you should use spaces + // the underscores are there so you can double click to select the whole thing + name: 'Fix duplicate accounts sharing identical handles', + // remember, month is zero-indexed (so January is 0, December is 11) + timestamp: Date.UTC(2025, 3, 29), + method: async function () { + const { progress } = this; + + // Build list of duplicate remote user handles + let handles = await db.getSortedSetMembers('ap.preferredUsername:sorted'); + handles = handles.map(handle => handle.split(':https://')[0]); + const duplicateUsers = new Set(); + handles.forEach((handle, idx) => { + if (handles.indexOf(handle) !== idx) { + duplicateUsers.add(handle); + } + }); + + // Build list of duplicate remote category handles + handles = await db.getSortedSetMembers('categories:name'); + handles = handles + .filter(handle => handle.indexOf('@') !== -1) // zset contains category names too + .map(handle => handle.split(':https://')[0]); + const duplicateCategories = new Set(); + handles.forEach((handle, idx) => { + if (handles.indexOf(handle) !== idx) { + duplicateCategories.add(handle); + } + }); + + progress.total = duplicateUsers.size + duplicateCategories.size; + + // Find real user, fix handle reference, delete the rest + await Promise.all(Array.from(duplicateUsers).map(async (handle) => { + const max = '(' + handle.substr(0, handle.length - 1) + String.fromCharCode(handle.charCodeAt(handle.length - 1) + 1); + let ids = await db.getSortedSetRangeByLex('ap.preferredUsername:sorted', `[${handle}`, max); + ids = ids.map(id => id.slice(handle.length + 1)); + const { actorUri: canonicalId } = await activitypub.helpers.query(handle); + + await Promise.all(ids.map(async (id) => { + if (id !== canonicalId) { + try { + await user.deleteAccount(id); + } catch (e) { + // User doesn't exist, maybe never did. + await db.sortedSetRemove('ap.preferredUsername:sorted', `${handle}:${id}`); + } + } + })); + + // Fix handle:uid backreference or delete + if (canonicalId) { + await db.setObjectField('handle:uid', handle, canonicalId); + } + + progress.incr(); + })); + + // Find real category, fix handle reference, delete the rest + await Promise.all(Array.from(duplicateCategories).map(async (handle) => { + const max = '(' + handle.substr(0, handle.length - 1) + String.fromCharCode(handle.charCodeAt(handle.length - 1) + 1); + let ids = await db.getSortedSetRangeByLex('categories:name', `[${handle}`, max); + ids = ids.map(id => id.slice(handle.length + 1)); + const { actorUri: canonicalId } = await activitypub.helpers.query(handle); + + await Promise.all(ids.map(async (id) => { + if (id !== canonicalId) { + await categories.purge(id, 0); + } + })); + + // Fix handle:uid backreference or delete + if (canonicalId) { + await db.setObjectField('handle:cid', handle, canonicalId); + } + + progress.incr(); + })); + }, +}; diff --git a/src/upgrades/4.3.0/normalize_thumbs_uploads.js b/src/upgrades/4.3.0/normalize_thumbs_uploads.js index d5fc15820a..3a33daee9f 100644 --- a/src/upgrades/4.3.0/normalize_thumbs_uploads.js +++ b/src/upgrades/4.3.0/normalize_thumbs_uploads.js @@ -7,7 +7,7 @@ const crypto = require('crypto'); module.exports = { name: 'Normalize topic thumbnails, post & user uploads to same format', - timestamp: Date.UTC(2021, 1, 7), + timestamp: Date.UTC(2025, 3, 4), method: async function () { const { progress } = this; @@ -89,34 +89,36 @@ module.exports = { const keys = uids.map(uid => `uid:${uid}:uploads`); const userUploadData = await db.getSortedSetsMembersWithScores(keys); - const bulkAdd = []; - const bulkRemove = []; - const promises = []; - userUploadData.forEach((userUploads, idx) => { + await Promise.all(userUploadData.map(async (allUserUploads, idx) => { const uid = uids[idx]; - if (Array.isArray(userUploads)) { - userUploads.forEach((userUpload) => { - const normalizedPath = normalizePath(userUpload.value); - if (normalizedPath !== userUpload.value) { - bulkAdd.push([`uid:${uid}:uploads`, userUpload.score, normalizedPath]); - promises.push(db.setObjectField(`upload:${md5(normalizedPath)}`, 'uid', uid)); + if (Array.isArray(allUserUploads)) { + await batch.processArray(allUserUploads, async (userUploads) => { + const bulkAdd = []; + const bulkRemove = []; + const promises = []; + userUploads.forEach((userUpload) => { + const normalizedPath = normalizePath(userUpload.value); + if (normalizedPath !== userUpload.value) { + bulkAdd.push([`uid:${uid}:uploads`, userUpload.score, normalizedPath]); + promises.push(db.setObjectField(`upload:${md5(normalizedPath)}`, 'uid', uid)); - bulkRemove.push([`uid:${uid}:uploads`, userUpload.value]); - promises.push(db.delete(`upload:${md5(userUpload.value)}`)); - } + bulkRemove.push([`uid:${uid}:uploads`, userUpload.value]); + promises.push(db.delete(`upload:${md5(userUpload.value)}`)); + } + }); + await Promise.all(promises); + await db.sortedSetRemoveBulk(bulkRemove); + await db.sortedSetAddBulk(bulkAdd); + }, { + batch: 500, }); - } - }); - - await Promise.all(promises); - await db.sortedSetRemoveBulk(bulkRemove); - await db.sortedSetAddBulk(bulkAdd); + })); progress.incr(uids.length); }, { - batch: 500, + batch: 100, }); }, }; diff --git a/src/upgrades/4.3.0/topic_follower_counts.js b/src/upgrades/4.3.0/topic_follower_counts.js new file mode 100644 index 0000000000..81beff21fe --- /dev/null +++ b/src/upgrades/4.3.0/topic_follower_counts.js @@ -0,0 +1,35 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + + +module.exports = { + name: 'Set "followercount" on each topic object', + timestamp: Date.UTC(2025, 3, 15), + method: async function () { + const { progress } = this; + + progress.total = await db.sortedSetCard('topics:tid'); + + await batch.processSortedSet('topics:tid', async (tids) => { + const keys = tids.map(tid => `tid:${tid}:followers`); + const followerCounts = await db.setsCount(keys); + + const bulkSet = []; + + followerCounts.forEach((count, idx) => { + const tid = tids[idx]; + if (count > 0) { + bulkSet.push([`topic:${tid}`, {followercount: count}]); + } + }); + + await db.setObjectBulk(bulkSet); + + progress.incr(tids.length); + }, { + batch: 500, + }); + }, +}; diff --git a/src/upgrades/4.3.2/fix_category_sync_null_values.js b/src/upgrades/4.3.2/fix_category_sync_null_values.js new file mode 100644 index 0000000000..e81590cf27 --- /dev/null +++ b/src/upgrades/4.3.2/fix_category_sync_null_values.js @@ -0,0 +1,12 @@ +'use strict'; + +const db = require('../../database'); + +module.exports = { + name: 'Fix null values in category synchronization list', + timestamp: Date.UTC(2025, 4, 8), + method: async () => { + const cids = await db.getSortedSetMembers('categories:cid'); + await db.sortedSetsRemove(cids.map(cid => `followRequests:cid.${cid}`), 'null'); + }, +}; diff --git a/src/upgrades/4.5.0/post-uploads-to-hash.js b/src/upgrades/4.5.0/post-uploads-to-hash.js new file mode 100644 index 0000000000..bef3b72df7 --- /dev/null +++ b/src/upgrades/4.5.0/post-uploads-to-hash.js @@ -0,0 +1,39 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Move post::uploads to post hash', + timestamp: Date.UTC(2025, 6, 5), + method: async function () { + const { progress } = this; + + const postCount = await db.sortedSetCard('posts:pid'); + progress.total = postCount; + + await batch.processSortedSet('posts:pid', async (pids) => { + const keys = pids.map(pid => `post:${pid}:uploads`); + + const postUploadData = await db.getSortedSetsMembersWithScores(keys); + + const bulkSet = []; + postUploadData.forEach((postUploads, idx) => { + const pid = pids[idx]; + if (Array.isArray(postUploads) && postUploads.length > 0) { + bulkSet.push([ + `post:${pid}`, + { uploads: JSON.stringify(postUploads.map(upload => upload.value)) }, + ]); + } + }); + + await db.setObjectBulk(bulkSet); + await db.deleteAll(keys); + + progress.incr(pids.length); + }, { + batch: 500, + }); + }, +}; diff --git a/src/upgrades/4.5.0/topic-thumbs-to-hash.js b/src/upgrades/4.5.0/topic-thumbs-to-hash.js new file mode 100644 index 0000000000..3385052c90 --- /dev/null +++ b/src/upgrades/4.5.0/topic-thumbs-to-hash.js @@ -0,0 +1,39 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Move topic::thumbs to topic hash', + timestamp: Date.UTC(2025, 6, 5), + method: async function () { + const { progress } = this; + + const topicCount = await db.sortedSetCard('topics:tid'); + progress.total = topicCount; + + await batch.processSortedSet('topics:tid', async (tids) => { + const keys = tids.map(tid => `topic:${tid}:thumbs`); + + const topicThumbData = await db.getSortedSetsMembersWithScores(keys); + + const bulkSet = []; + topicThumbData.forEach((topicThumbs, idx) => { + const tid = tids[idx]; + if (Array.isArray(topicThumbs) && topicThumbs.length > 0) { + bulkSet.push([ + `topic:${tid}`, + { thumbs: JSON.stringify(topicThumbs.map(thumb => thumb.value)) }, + ]); + } + }); + + await db.setObjectBulk(bulkSet); + await db.deleteAll(keys); + + progress.incr(tids.length); + }, { + batch: 500, + }); + }, +}; diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js new file mode 100644 index 0000000000..e32590cb01 --- /dev/null +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -0,0 +1,24 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Remove extraneous upvote and tids_read data for remote users', + timestamp: Date.UTC(2025, 11, 11), + method: async function () { + const { progress } = this; + await batch.processSortedSet('usersRemote:lastCrawled', async (uids) => { + const readKeys = uids.map(uid => `uid:${uid}:tids_read`); + const voteKeys = uids.map(uid => `uid:${uid}:upvote`); + + const combined = readKeys.concat(voteKeys); + + await db.deleteAll(combined); + progress.incr(uids.length); + }, { + batch: 500, + progress, + }); + }, +}; diff --git a/src/upgrades/4.8.1/clean_ap_tids_from_topic_zsets.js b/src/upgrades/4.8.1/clean_ap_tids_from_topic_zsets.js new file mode 100644 index 0000000000..547dd14157 --- /dev/null +++ b/src/upgrades/4.8.1/clean_ap_tids_from_topic_zsets.js @@ -0,0 +1,48 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); +const utils = require('../../utils'); + +module.exports = { + name: 'Remove AP tids from topics:recent, topics:views, topics:posts, topics:votes zsets', + timestamp: Date.UTC(2026, 0, 25), + method: async function () { + const { progress } = this; + const [recent, views, posts, votes] = await db.sortedSetsCard([ + 'topics:recent', 'topics:views', 'topics:posts', 'topics:votes', + ]); + progress.total = recent + views + posts + votes; + + async function cleanupSet(setName) { + const tidsToRemove = []; + await batch.processSortedSet(setName, async (tids) => { + const topicData = await db.getObjectsFields(tids.map(tid => `topic:${tid}`), ['cid']); + topicData.forEach((t, index) => { + if (t) { + t.tid = tids[index]; + } + }); + const batchTids = topicData.filter( + t => t && (!t.cid || !utils.isNumber(t.cid) || t.cid === -1) + ).map(t => t.tid); + + tidsToRemove.push(...batchTids); + progress.incr(tids.length); + }, { + batch: 500, + }); + + await batch.processArray(tidsToRemove, async (batchTids) => { + await db.sortedSetRemove(setName, batchTids); + }, { + batch: 500, + }); + + } + await cleanupSet('topics:recent'); + await cleanupSet('topics:views'); + await cleanupSet('topics:posts'); + await cleanupSet('topics:votes'); + }, +}; diff --git a/src/upgrades/4.9.0/crosspost_privilege.js b/src/upgrades/4.9.0/crosspost_privilege.js new file mode 100644 index 0000000000..5cd55e997b --- /dev/null +++ b/src/upgrades/4.9.0/crosspost_privilege.js @@ -0,0 +1,18 @@ +'use strict'; + +const privileges = require('../../privileges'); +const db = require('../../database'); + +module.exports = { + name: 'Give topic:crosspost privilege to registered-users on all categories', + timestamp: Date.UTC(2026, 1, 12), + method: async () => { + const cids = await db.getSortedSetMembers('categories:cid'); + await Promise.all(cids.map(async (cid) => { + const can = await privileges.categories.can('topics:create', cid, 'registered-users'); + if (can) { + await privileges.categories.give(['groups:topics:crosspost'], cid, 'registered-users'); + } + })); + }, +}; diff --git a/src/user/admin.js b/src/user/admin.js index 35598bbbd9..b629e46628 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -55,7 +55,8 @@ module.exports = function (User) { fields: fieldsToExport, showIps: fieldsToExport.includes('ip'), }); - + const customUserFields = await db.getSortedSetRange('user-custom-fields', 0, -1); + const fieldsToWrapInQuotes = ['fullname', 'signature', 'aboutme', ...customUserFields]; if (!showIps && fields.includes('ip')) { fields.splice(fields.indexOf('ip'), 1); } @@ -63,7 +64,7 @@ module.exports = function (User) { path.join(baseDir, 'build/export', 'users.csv'), 'w' ); - fs.promises.appendFile(fd, `${fields.map(f => `"${f}"`).join(',')}\n`); + await fs.promises.appendFile(fd, `${fields.map(f => `"${f}"`).join(',')}\n`); await batch.processSortedSet('users:joindate', async (uids) => { const userFieldsToLoad = fields.filter(field => field !== 'ip' && field !== 'password'); const usersData = await User.getUsersFields(uids, userFieldsToLoad); @@ -76,6 +77,11 @@ module.exports = function (User) { if (Array.isArray(userIps[index])) { user.ip = userIps[index].join(','); } + fieldsToWrapInQuotes.forEach((field) => { + if (user[field]) { + user[field] = `"${String(user[field])}"`; + } + }); }); const opts = { fields, header: false }; diff --git a/src/user/approval.js b/src/user/approval.js index 5067239d8a..c0e654438c 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -22,6 +22,19 @@ module.exports = function (User) { } }), null, true); + User.createOrQueue = async function (req, userData, opts = {}) { + User.checkUsernameLength(userData.username); + const queue = await User.shouldQueueUser(req.ip); + const result = await plugins.hooks.fire('filter:register.shouldQueue', { req, userData, queue }); + if (result.queue) { + await User.addToApprovalQueue({ ...userData, ip: req.ip, _opts: JSON.stringify(opts) }); + return { queued: true, message: await getRegistrationQueuedMessage() }; + } + + const uid = await User.create(userData, opts); + return { queued: false, uid }; + }; + User.addToApprovalQueue = async function (userData) { userData.username = userData.username.trim(); userData.userslug = slugify(userData.username); @@ -31,9 +44,12 @@ module.exports = function (User) { username: userData.username, email: userData.email, ip: userData.ip, - hashedPassword: hashedPassword, + _opts: userData._opts || '{}', }; - const results = await plugins.hooks.fire('filter:user.addToApprovalQueue', { data: data, userData: userData }); + if (hashedPassword) { + data.hashedPassword = hashedPassword; + } + const results = await plugins.hooks.fire('filter:user.addToApprovalQueue', { data, userData }); await db.setObject(`registration:queue:name:${userData.username}`, results.data); await db.sortedSetAdd('registration:queue', Date.now(), userData.username); await sendNotificationToAdmins(userData.username); @@ -69,12 +85,15 @@ module.exports = function (User) { if (!userData) { throw new Error('[[error:invalid-data]]'); } + const opts = parseCreateOptions(userData); const creation_time = await db.sortedSetScore('registration:queue', username); - const uid = await User.create(userData); - await User.setUserFields(uid, { - password: userData.hashedPassword, - 'password:shaWrapped': 1, - }); + const uid = await User.create(userData, opts); + if (userData.hashedPassword) { + await User.setUserFields(uid, { + password: userData.hashedPassword, + 'password:shaWrapped': 1, + }); + } await removeFromQueue(username); await markNotificationRead(username); await plugins.hooks.fire('filter:register.complete', { uid: uid }); @@ -90,6 +109,17 @@ module.exports = function (User) { return uid; }; + function parseCreateOptions(userData) { + try { + const opts = JSON.parse(userData._opts || '{}'); + delete userData._opts; + return opts; + } catch (err) { + winston.error(`[user.acceptRegistration] Failed to parse create options for queued user ${userData.username}: ${err.stack}`); + return {}; + } + } + async function markNotificationRead(username) { const nid = `new-register:${username}`; const uids = await groups.getMembers('administrators', 0, -1); @@ -120,6 +150,20 @@ module.exports = function (User) { return false; }; + async function getRegistrationQueuedMessage() { + let message = '[[register:registration-added-to-queue]]'; + if (meta.config.showAverageApprovalTime) { + const average_time = await db.getObjectField('registration:queue:approval:times', 'average'); + if (average_time > 0) { + message += ` [[register:registration-queue-average-time, ${Math.floor(average_time / 60)}, ${Math.floor(average_time % 60)}]]`; + } + } + if (meta.config.autoApproveTime > 0) { + message += ` [[register:registration-queue-auto-approve-time, ${meta.config.autoApproveTime}]]`; + } + return message; + }; + User.getRegistrationQueue = async function (start, stop) { const data = await db.getSortedSetRevRangeWithScores('registration:queue', start, stop); const keys = data.filter(Boolean).map(user => `registration:queue:name:${user.value}`); diff --git a/src/user/bans.js b/src/user/bans.js index c52a24db6b..417f0c3e7d 100644 --- a/src/user/bans.js +++ b/src/user/bans.js @@ -7,6 +7,8 @@ const emailer = require('../emailer'); const db = require('../database'); const groups = require('../groups'); const privileges = require('../privileges'); +const plugins = require('../plugins'); +const translator = require('../translator'); module.exports = function (User) { User.bans = {}; @@ -50,20 +52,27 @@ module.exports = function (User) { } // Email notification of ban - const username = await User.getUserField(uid, 'username'); + const [username, settings] = await Promise.all([ + User.getUserField(uid, 'username'), + User.getSettings(uid), + ]); const siteTitle = meta.config.title || 'NodeBB'; - const data = { + await emailer.send('banned', uid, { subject: `[[email:banned.subject, ${siteTitle}]]`, username: username, until: until ? (new Date(until)).toUTCString().replace(/,/g, '\\,') : false, - reason: reason, - }; - await emailer.send('banned', uid, data).catch(err => winston.error(`[emailer.send] ${err.stack}`)); + reason: await parseReason(reason, settings.userLang), + }).catch(err => winston.error(`[emailer.send] ${err.stack}`)); return banData; }; + async function parseReason(reason, lang) { + const parsed = await plugins.hooks.fire('filter:parse.raw', reason); + return await translator.translate(parsed, lang); + } + User.bans.unban = async function (uids, reason = '') { const isArray = Array.isArray(uids); uids = isArray ? uids : [uids]; @@ -155,4 +164,19 @@ module.exports = function (User) { const banObj = await db.getObject(keys[0]); return banObj && banObj.reason ? banObj.reason : ''; }; + + User.bans.getCustomReasons = async function ({ type = '' } = {}) { + const keys = await db.getSortedSetRange('custom-reasons', 0, -1); + type = type || ''; + const reasons = (await db.getObjects(keys.map(k => `custom-reason:${k}`))).filter(Boolean); + await Promise.all(reasons.map(async (reason, i) => { + reason.key = i; + reason.parsedBody = translator.escape(await plugins.hooks.fire('filter:parse.raw', reason.body || '')); + reason.body = translator.escape(reason.body); + })); + if (type !== '') { + return reasons.filter(reason => reason.type === type || reason.type === ''); + } + return reasons; + }; }; diff --git a/src/user/blocks.js b/src/user/blocks.js index aa8425fe96..723ace7628 100644 --- a/src/user/blocks.js +++ b/src/user/blocks.js @@ -8,7 +8,7 @@ const cacheCreate = require('../cache/lru'); module.exports = function (User) { User.blocks = { _cache: cacheCreate({ - name: 'user:blocks', + name: 'user-blocks', max: 100, ttl: 0, }), diff --git a/src/user/categories.js b/src/user/categories.js index 38770138a9..4248def8bd 100644 --- a/src/user/categories.js +++ b/src/user/categories.js @@ -3,7 +3,9 @@ const _ = require('lodash'); const db = require('../database'); +const meta = require('../meta'); const categories = require('../categories'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); const utils = require('../utils'); @@ -27,7 +29,14 @@ module.exports = function (User) { if (exists.includes(false)) { throw new Error('[[error:no-category]]'); } - await db.sortedSetsAdd(cids.map(cid => `cid:${cid}:uid:watch:state`), state, uid); + + const apiMethod = state >= categories.watchStates.tracking ? activitypub.out.follow : activitypub.out.undo.follow; + const follows = cids.filter(cid => !utils.isNumber(cid)).map(cid => apiMethod('uid', uid, cid)); // returns promises + + await Promise.all([ + db.sortedSetsAdd(cids.map(cid => `cid:${cid}:uid:watch:state`), state, uid), + ...follows, + ]); }; User.getCategoryWatchState = async function (uid) { @@ -67,7 +76,11 @@ module.exports = function (User) { }; User.getCategoriesByStates = async function (uid, states) { - const cids = await categories.getAllCidsFromSet('categories:cid'); + const [localCids, remoteCids] = await Promise.all([ + categories.getAllCidsFromSet('categories:cid'), + meta.config.activitypubEnabled ? db.getObjectValues('handle:cid') : [], + ]); + const cids = localCids.concat(remoteCids); if (!(parseInt(uid, 10) > 0)) { return cids; } diff --git a/src/user/create.js b/src/user/create.js index 1b18281722..00e5fc69cc 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -12,7 +12,8 @@ const meta = require('../meta'); const analytics = require('../analytics'); module.exports = function (User) { - User.create = async function (data) { + User.create = async function (data, opts = {}) { + opts = { emailVerification: 'send', ...opts }; data.username = data.username.trim(); data.userslug = slugify(data.username); if (data.email !== undefined) { @@ -27,7 +28,7 @@ module.exports = function (User) { } try { - return await create(data); + return await create(data, opts); } finally { await db.deleteObjectFields('locks', [data.username, data.email]); } @@ -40,7 +41,7 @@ module.exports = function (User) { } } - async function create(data) { + async function create(data, opts) { const timestamp = data.timestamp || Date.now(); let userData = { @@ -73,7 +74,6 @@ module.exports = function (User) { userData = results.user; const uid = await db.incrObjectField('global', 'nextUid'); - const isFirstUser = uid === 1; userData.uid = uid; await db.setObject(`user:${uid}`, userData); @@ -103,18 +103,8 @@ module.exports = function (User) { User.updateDigestSetting(userData.uid, meta.config.dailyDigestFreq), ]); - if (data.email && isFirstUser) { - await User.setUserField(uid, 'email', data.email); - await User.email.confirmByUid(userData.uid); - } + await handleEmailVerification(uid, data.email, data.token, opts.emailVerification); - if (data.email && userData.uid > 1) { - await User.email.sendValidationEmail(userData.uid, { - email: data.email, - template: 'welcome', - subject: `[[email:welcome-to, ${meta.config.title || meta.config.browserTitle || 'NodeBB'}]]`, - }).catch(err => winston.error(`[user.create] Validation email failed to send\n[emailer.send] ${err.stack}`)); - } if (userNameChanged) { await User.notifications.sendNameChangeNotification(userData.uid, userData.username); } @@ -136,13 +126,38 @@ module.exports = function (User) { ]); } + async function handleEmailVerification(uid, email, token, method) { + if (email) { + switch (method) { + case 'verify': + await User.setUserField(uid, 'email', email); + await User.email.confirmByUid(uid); + break; + case 'send': + if (!token || !await User.isInviteTokenValid(token, email)) { + await User.email.sendValidationEmail(uid, { + email: email, + template: 'welcome', + subject: `[[email:welcome-to, ${meta.config.title || meta.config.browserTitle || 'NodeBB'}]]`, + }).catch(err => winston.error(`[user.create] Validation email failed to send\n[emailer.send] ${err.stack}`)); + } + break; + case 'skip': + // explicitly do nothing + break; + default: + throw new Error(`Invalid email verification mode: ${method}`); + } + } + } + User.isDataValid = async function (userData) { if (userData.email && !utils.isEmailValid(userData.email)) { throw new Error('[[error:invalid-email]]'); } - if (!utils.isUserNameValid(userData.username) || !userData.userslug) { - throw new Error(`[[error:invalid-username, ${userData.username}]]`); + if (!utils.isUserNameValid(userData.username) || !utils.isSlugValid(userData.userslug)) { + throw new Error(`[[error:invalid-username]]`); } if (userData.password) { diff --git a/src/user/data.js b/src/user/data.js index cd2326281d..d45e4a6259 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -9,9 +9,12 @@ const meta = require('../meta'); const plugins = require('../plugins'); const activitypub = require('../activitypub'); const utils = require('../utils'); +const coverPhoto = require('../coverPhoto'); const relative_path = nconf.get('relative_path'); +const prependRelativePath = url => url.startsWith('http') ? url : relative_path + url; + const intFields = [ 'uid', 'postcount', 'topiccount', 'reputation', 'profileviews', 'banned', 'banned:expire', 'email:confirmed', 'joindate', 'lastonline', @@ -115,31 +118,22 @@ module.exports = function (User) { }; function ensureRequiredFields(fields, fieldsToRemove) { - function addField(field) { - if (!fields.includes(field)) { - fields.push(field); - fieldsToRemove.push(field); - } - } - if (fields.length && !fields.includes('uid')) { fields.push('uid'); } - if (fields.includes('picture')) { - addField('uploadedpicture'); - } - - if (fields.includes('status')) { - addField('lastonline'); - } - - if (fields.includes('banned') && !fields.includes('banned:expire')) { - addField('banned:expire'); - } - - if (fields.includes('username') && !fields.includes('fullname')) { - addField('fullname'); + const requiredFields = { + picture: 'uploadedpicture', + status: 'lastonline', + banned: 'banned:expire', + 'banned:expire': 'banned', + username: 'fullname', + }; + for (const [key, field] of Object.entries(requiredFields)) { + if (fields.includes(key) && !fields.includes(field)) { + fields.push(field); + fieldsToRemove.push(field); + } } } @@ -243,7 +237,7 @@ module.exports = function (User) { } if (user.hasOwnProperty('email')) { - user.email = validator.escape(user.email ? user.email.toString() : ''); + user.email = validator.escape(String(user.email || '')); } if (!user.uid && !activitypub.helpers.isUri(user.uid)) { @@ -256,13 +250,19 @@ module.exports = function (User) { if (user.hasOwnProperty('groupTitle')) { parseGroupTitle(user); } - - if (user.picture && user.picture === user.uploadedpicture) { - user.uploadedpicture = user.picture.startsWith('http') ? user.picture : relative_path + user.picture; - user.picture = user.uploadedpicture; - } else if (user.uploadedpicture) { - user.uploadedpicture = user.uploadedpicture.startsWith('http') ? user.uploadedpicture : relative_path + user.uploadedpicture; + const isUsingUploadedPicture = user.picture && user.picture === user.uploadedpicture; + if (isUsingUploadedPicture || user.uploadedpicture) { + user.uploadedpicture = prependRelativePath(user.uploadedpicture); + if (isUsingUploadedPicture) { + user.picture = user.uploadedpicture; + } } + if (user.hasOwnProperty('cover:url')) { + user['cover:url'] = user['cover:url'] ? + prependRelativePath(user['cover:url']) : + coverPhoto.getDefaultProfileCover(user.uid); + } + if (meta.config.defaultAvatar && !user.picture) { user.picture = User.getDefaultAvatar(); } @@ -271,10 +271,20 @@ module.exports = function (User) { user.status = User.getStatus(user); } - for (let i = 0; i < fieldsToRemove.length; i += 1) { - user[fieldsToRemove[i]] = undefined; + if (user.hasOwnProperty('joindate')) { + user.joindateISO = utils.toISOString(user.joindate); } + if (user.hasOwnProperty('lastonline') && (!requestedFields.length || requestedFields.includes('lastonline')) && !fieldsToRemove.includes('lastonline')) { + user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO; + } + + if (user.hasOwnProperty('mutedUntil')) { + user.muted = user.mutedUntil > Date.now(); + } + + user.isLocal = utils.isNumber(user.uid); + // User Icons if (requestedFields.includes('picture') && user.username && user.uid !== 0 && !meta.config.defaultAvatar) { if (!iconBackgrounds.includes(user['icon:bgColor'])) { @@ -284,19 +294,7 @@ module.exports = function (User) { user['icon:text'] = (user.username[0] || '').toUpperCase(); } - if (user.hasOwnProperty('joindate')) { - user.joindateISO = utils.toISOString(user.joindate); - } - - if (user.hasOwnProperty('lastonline')) { - user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO; - } - - if (user.hasOwnProperty('mutedUntil')) { - user.muted = user.mutedUntil > Date.now(); - } - - if (user.hasOwnProperty('banned') || user.hasOwnProperty('banned:expire')) { + if (user.hasOwnProperty('banned') && user.hasOwnProperty('banned:expire')) { const result = User.bans.calcExpiredFromUserData(user); user.banned = result.banned; const unban = result.banned && result.banExpired; @@ -307,9 +305,17 @@ module.exports = function (User) { user.banned = false; } } - - user.isLocal = utils.isNumber(user.uid); }); + + // remove fields that were added just for processing + fieldsToRemove.forEach((field) => { + users.forEach((user) => { + if (user) { + user[field] = undefined; + } + }); + }); + if (unbanUids.length) { await User.bans.unban(unbanUids, '[[user:info.ban-expired]]'); } @@ -333,7 +339,7 @@ module.exports = function (User) { user.displayname = validator.escape(String( meta.config.showFullnameAsDisplayName && showfullname && user.fullname ? - user.fullname : + utils.stripBidiControls(user.fullname) : user.username )); } diff --git a/src/user/delete.js b/src/user/delete.js index a4aac56c9f..65d8ccea65 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -43,15 +43,17 @@ module.exports = function (User) { async function deletePosts(callerUid, uid) { await batch.processSortedSet(`uid:${uid}:posts`, async (pids) => { await posts.purge(pids, callerUid); + await db.sortedSetRemove(`uid:${uid}:posts`, pids); }, { alwaysStartAt: 0, batch: 500 }); } async function deleteTopics(callerUid, uid) { - await batch.processSortedSet(`uid:${uid}:topics`, async (ids) => { - await async.eachSeries(ids, async (tid) => { + await batch.processSortedSet(`uid:${uid}:topics`, async (tids) => { + await async.eachSeries(tids, async (tid) => { await topics.purge(tid, callerUid); }); - }, { alwaysStartAt: 0 }); + await db.sortedSetRemove(`uid:${uid}:topics`, tids); + }, { alwaysStartAt: 0, batch: 100 }); } async function deleteUploads(callerUid, uid) { @@ -122,6 +124,7 @@ module.exports = function (User) { `uid:${uid}:upvote`, `uid:${uid}:downvote`, `uid:${uid}:flag:pids`, `uid:${uid}:sessions`, + `uid:${uid}:shares`, `invitation:uid:${uid}`, ]; @@ -158,9 +161,10 @@ module.exports = function (User) { activitypub.actors.remove(uid), ]); await db.deleteAll([ - `followers:${uid}`, `following:${uid}`, `user:${uid}`, + `followers:${uid}`, `following:${uid}`, `uid:${uid}:followed_tags`, `uid:${uid}:followed_tids`, `uid:${uid}:ignored_tids`, + `${utils.isNumber(uid) ? 'user' : 'userRemote'}:${uid}`, ]); delete deletesInProgress[uid]; return userData; diff --git a/src/user/digest.js b/src/user/digest.js index 61f4b2f12f..89301dde85 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -84,17 +84,26 @@ Digest.getSubscribers = async function (interval) { Digest.send = async function (data) { let emailsSent = 0; + let emailsFailed = 0; if (!data || !data.subscribers || !data.subscribers.length) { return emailsSent; } let errorLogged = false; + const date = new Date(); await batch.processArray(data.subscribers, async (uids) => { - let userData = await user.getUsersFields(uids, ['uid', 'email', 'email:confirmed', 'username', 'userslug', 'lastonline']); - userData = userData.filter(u => u && u.email && (meta.config.includeUnverifiedEmails || u['email:confirmed'])); + let userData = await user.getUsersFields(uids, [ + 'uid', 'email', 'email:confirmed', 'username', 'userslug', 'lastonline', + ]); + userData = userData.filter( + u => u && u.email && (meta.config.includeUnverifiedEmails || u['email:confirmed']) + ); if (!userData.length) { return; } - await Promise.all(userData.map(async (userObj) => { + const userSettings = await user.getMultipleUserSettings(userData.map(u => u.uid)); + const successfullUids = []; + await Promise.all(userData.map(async (userObj, index) => { + const userSetting = userSettings[index]; const [publicRooms, notifications, topics] = await Promise.all([ getUnreadPublicRooms(userObj.uid), user.notifications.getUnreadInterval(userObj.uid, data.interval), @@ -117,58 +126,62 @@ Digest.send = async function (data) { } }); - emailsSent += 1; - const now = new Date(); - await emailer.send('digest', userObj.uid, { - subject: `[[email:digest.subject, ${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}]]`, - username: userObj.username, - userslug: userObj.userslug, - notifications: unreadNotifs, - publicRooms: publicRooms, - recent: topics.recent, - topTopics: topics.top, - popularTopics: topics.popular, - interval: data.interval, - showUnsubscribe: true, - }).catch((err) => { + try { + await emailer.send('digest', userObj.uid, { + subject: `[[email:digest.subject, ${date.toLocaleDateString(userSetting.userLang)}]]`, + username: userObj.username, + userslug: userObj.userslug, + notifications: unreadNotifs, + publicRooms: publicRooms, + recent: topics.recent, + topTopics: topics.top, + popularTopics: topics.popular, + interval: data.interval, + showUnsubscribe: true, + }); + emailsSent += 1; + successfullUids.push(userObj.uid); + } catch (err) { + emailsFailed += 1; if (!errorLogged) { winston.error(`[user/jobs] Could not send digest email\n[emailer.send] ${err.stack}`); errorLogged = true; } - }); + } })); - if (data.interval !== 'alltime') { + + if (data.interval !== 'alltime' && successfullUids.length) { const now = Date.now(); - await db.sortedSetAdd('digest:delivery', userData.map(() => now), userData.map(u => u.uid)); + await db.sortedSetAdd('digest:delivery', successfullUids.map(() => now), successfullUids); } }, { interval: 1000, batch: 100, }); - winston.info(`[user/jobs] Digest (${data.interval}) sending completed. ${emailsSent} emails sent.`); + winston.info(`[user/jobs] Digest (${data.interval}) sending completed. ${emailsSent} emails sent. ${emailsFailed} failures.`); return emailsSent; }; Digest.getDeliveryTimes = async (start, stop) => { - const count = await db.sortedSetCard('users:joindate'); - const uids = await user.getUidsFromSet('users:joindate', start, stop); + const [count, uids] = await Promise.all([ + db.sortedSetCard('users:joindate'), + user.getUidsFromSet('users:joindate', start, stop), + ]); if (!uids.length) { - return []; + return { users: [], count }; } - const [scores, settings] = await Promise.all([ + const [scores, settings, userData] = await Promise.all([ // Grab the last time a digest was successfully delivered to these uids db.sortedSetScores('digest:delivery', uids), // Get users' digest settings Digest.getUsersInterval(uids), + user.getUsersFields(uids, ['username', 'picture']), ]); - // Populate user data - let userData = await user.getUsersFields(uids, ['username', 'picture']); - userData = userData.map((user, idx) => { + userData.forEach((user, idx) => { user.lastDelivery = scores[idx] ? new Date(scores[idx]).toISOString() : '[[admin/manage/digest:null]]'; user.setting = settings[idx]; - return user; }); return { diff --git a/src/user/email.js b/src/user/email.js index b485081713..16ec7e04bc 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -145,7 +145,7 @@ UserEmail.sendValidationEmail = async function (uid, options) { uid, username, confirm_link, - confirm_code: await plugins.hooks.fire('filter:user.verify.code', confirm_code), + confirm_code, email: options.email, subject: options.subject || '[[email:email.verify-your-email.subject]]', diff --git a/src/user/follow.js b/src/user/follow.js index c89f90b979..7e561d07e5 100644 --- a/src/user/follow.js +++ b/src/user/follow.js @@ -58,11 +58,8 @@ module.exports = function (User) { ]); } - const [followingCount, followingRemoteCount, followerCount, followerRemoteCount] = await Promise.all([ - db.sortedSetCard(`following:${uid}`), - db.sortedSetCard(`followingRemote:${uid}`), - db.sortedSetCard(`followers:${theiruid}`), - db.sortedSetCard(`followersRemote:${theiruid}`), + const [followingCount, followingRemoteCount, followerCount, followerRemoteCount] = await db.sortedSetsCard([ + `following:${uid}`, `followingRemote:${uid}`, `followers:${theiruid}`, `followersRemote:${theiruid}`, ]); await Promise.all([ User.setUserField(uid, 'followingCount', followingCount + followingRemoteCount), @@ -82,11 +79,15 @@ module.exports = function (User) { if (parseInt(uid, 10) <= 0) { return []; } - const uids = await db.getSortedSetRevRange([ + let uids = await db.getSortedSetRevRange([ `${type}:${uid}`, `${type}Remote:${uid}`, ], start, stop); + // Filter out remote categories + const isCategory = await db.exists(uids.map(uid => `categoryRemote:${uid}`)); + uids = uids.filter((uid, idx) => !isCategory[idx]); + const data = await plugins.hooks.fire(`filter:user.${type}`, { uids: uids, uid: uid, diff --git a/src/user/index.js b/src/user/index.js index 35c3e67f3e..df0fa13616 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -46,9 +46,11 @@ User.exists = async function (uids) { const singular = !Array.isArray(uids); uids = singular ? [uids] : uids; - let results = await Promise.all(uids.map(async uid => await db.isMemberOfSortedSets(['users:joindate', 'usersRemote:lastCrawled'], uid))); - results = results.map(set => set.some(Boolean)); - + const [localExists, remoteExists] = await Promise.all([ + db.isSortedSetMembers('users:joindate', uids), + meta.config.activitypubEnabled ? db.exists(uids.map(uid => `userRemote:${uid}`)) : uids.map(() => false), + ]); + const results = localExists.map((local, idx) => local || remoteExists[idx]); return singular ? results.pop() : results; }; @@ -126,8 +128,9 @@ User.getUidByUserslug = async function (userslug) { }; User.getUidsByUserslugs = async function (userslugs) { - const apSlugs = userslugs.filter(slug => slug.includes('@')); - const normalSlugs = userslugs.filter(slug => !slug.includes('@')); + const uniqueSlugs = _.uniq(userslugs); + const apSlugs = uniqueSlugs.filter(slug => slug.includes('@')); + const normalSlugs = uniqueSlugs.filter(slug => !slug.includes('@')); const slugToUid = Object.create(null); async function getApSlugs() { await Promise.all(apSlugs.map(slug => activitypub.actors.assert(slug))); diff --git a/src/user/invite.js b/src/user/invite.js index a9a5368bb1..344e09fd6b 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -72,6 +72,12 @@ module.exports = function (User) { } }; + User.isInviteTokenValid = async function (token, enteredEmail) { + if (!token) return false; + const email = await db.getObjectField(`invitation:token:${token}`, 'email'); + return email && email === enteredEmail; + }; + User.confirmIfInviteEmailIsUsed = async function (token, enteredEmail, uid) { if (!enteredEmail) { return; diff --git a/src/user/profile.js b/src/user/profile.js index 3009d0a3d5..2a0c187ebc 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -11,7 +11,7 @@ const meta = require('../meta'); const db = require('../database'); const groups = require('../groups'); const plugins = require('../plugins'); -const api = require('../api'); +const activitypub = require('../activitypub'); const tx = require('../translator'); module.exports = function (User) { @@ -68,7 +68,7 @@ module.exports = function (User) { fields: fields, oldData: oldData, }); - api.activitypub.update.profile({ uid }, { uid: updateUid }); + activitypub.out.update.profile(updateUid, uid); return await User.getUserFields(updateUid, [ 'email', 'username', 'userslug', @@ -170,16 +170,10 @@ module.exports = function (User) { } } - if (data.username.length < meta.config.minimumUsernameLength) { - throw new Error('[[error:username-too-short]]'); - } - - if (data.username.length > meta.config.maximumUsernameLength) { - throw new Error('[[error:username-too-long]]'); - } + User.checkUsernameLength(data.username); const userslug = slugify(data.username); - if (!utils.isUserNameValid(data.username) || !userslug) { + if (!utils.isUserNameValid(data.username) || !utils.isSlugValid(userslug)) { throw new Error('[[error:invalid-username]]'); } @@ -201,6 +195,20 @@ module.exports = function (User) { } User.checkUsername = async username => isUsernameAvailable({ username }); + User.checkUsernameLength = function (username) { + if ( + !username || + username.length < meta.config.minimumUsernameLength || + slugify(username).length < meta.config.minimumUsernameLength + ) { + throw new Error('[[error:username-too-short]]'); + } + + if (username.length > meta.config.maximumUsernameLength) { + throw new Error('[[error:username-too-long]]'); + } + }; + async function isAboutMeValid(callerUid, data) { if (!data.aboutme) { return; @@ -249,7 +257,7 @@ module.exports = function (User) { if (!data.groupTitle) { return; } - let groupTitles = []; + let groupTitles; if (validator.isJSON(data.groupTitle)) { groupTitles = JSON.parse(data.groupTitle); if (!Array.isArray(groupTitles)) { diff --git a/src/user/search.js b/src/user/search.js index 17df4b68f1..419dd36b3f 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -60,15 +60,18 @@ module.exports = function (User) { if (!uids.length) { const searchMethod = data.findUids || findUids; - uids = await searchMethod(query, searchBy, data.hardCap); + const promises = [ + searchMethod(query, searchBy, data.hardCap), + ]; const mapping = { username: 'ap.preferredUsername', fullname: 'ap.name', }; - if (meta.config.activitypubEnabled && mapping.hasOwnProperty(searchBy)) { - uids = uids.concat(await searchMethod(query, mapping[searchBy], data.hardCap)); + if (meta.config.activitypubEnabled && Object.hasOwn(mapping, searchBy)) { + promises.push(searchMethod(query, mapping[searchBy], data.hardCap)); } + uids = (await Promise.all(promises)).flat(); } } @@ -77,7 +80,7 @@ module.exports = function (User) { uids.length = data.hardCap; } - const result = await plugins.hooks.fire('filter:users.search', { uids: uids, uid: uid }); + const result = await plugins.hooks.fire('filter:users.search', { uids, uid }); uids = result.uids; const searchResult = { @@ -106,8 +109,9 @@ module.exports = function (User) { } searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2); - searchResult.users = userData.filter(user => (user && - utils.isNumber(user.uid) ? user.uid > 0 : activitypub.helpers.isUri(user.uid))); + searchResult.users = userData.filter( + user => user && (utils.isNumber(user.uid) ? user.uid > 0 : activitypub.helpers.isUri(user.uid)) + ); return searchResult; }; @@ -119,11 +123,9 @@ module.exports = function (User) { const min = query; const max = query.substr(0, query.length - 1) + String.fromCharCode(query.charCodeAt(query.length - 1) + 1); - const resultsPerPage = meta.config.userSearchResultsPerPage; - hardCap = hardCap || resultsPerPage * 10; + hardCap = hardCap || 500; const data = await db.getSortedSetRangeByLex(`${searchBy}:sorted`, min, max, 0, hardCap); - // const uids = data.map(data => data.split(':').pop()); const uids = data.map((data) => { if (data.includes(':https:')) { return data.substring(data.indexOf(':https:') + 1); diff --git a/src/user/settings.js b/src/user/settings.js index 5390f37580..3d37d3fa28 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -59,16 +59,18 @@ module.exports = function (User) { settings.openOutgoingLinksInNewTab = parseInt(getSetting(settings, 'openOutgoingLinksInNewTab', 0), 10) === 1; settings.dailyDigestFreq = getSetting(settings, 'dailyDigestFreq', 'off'); settings.usePagination = parseInt(getSetting(settings, 'usePagination', 0), 10) === 1; - settings.topicsPerPage = Math.min( + settings.topicsPerPage = Math.max(1, Math.min( meta.config.maxTopicsPerPage, - settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : defaultTopicsPerPage, - defaultTopicsPerPage - ); - settings.postsPerPage = Math.min( + settings.topicsPerPage ? + parseInt(settings.topicsPerPage, 10) : + defaultTopicsPerPage, + )); + settings.postsPerPage = Math.max(1, Math.min( meta.config.maxPostsPerPage, - settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : defaultPostsPerPage, - defaultPostsPerPage - ); + settings.postsPerPage ? + parseInt(settings.postsPerPage, 10) : + defaultPostsPerPage, + )); settings.userLang = settings.userLang || meta.config.defaultLang || 'en-GB'; settings.acpLang = settings.acpLang || settings.userLang; settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest'); @@ -76,7 +78,7 @@ module.exports = function (User) { settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1; settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1; settings.upvoteNotifFreq = getSetting(settings, 'upvoteNotifFreq', 'all'); - settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; + settings.disableIncomingChats = parseInt(getSetting(settings, 'disableIncomingChats', 0), 10) === 1; settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; settings.updateUrlWithPostIndex = parseInt(getSetting(settings, 'updateUrlWithPostIndex', 1), 10) === 1; settings.bootswatchSkin = validator.escape(String(settings.bootswatchSkin || '')); @@ -89,9 +91,19 @@ module.exports = function (User) { settings[notificationType] = getSetting(settings, notificationType, 'notification'); }); + settings.chatAllowList = parseJSONSetting(settings.chatAllowList || '[]', []).map(String); + settings.chatDenyList = parseJSONSetting(settings.chatDenyList || '[]', []).map(String); return settings; } + function parseJSONSetting(value, defaultValue) { + try { + return JSON.parse(value); + } catch (err) { + return defaultValue; + } + } + function getSetting(settings, key, defaultValue) { if (settings[key] || settings[key] === 0) { return settings[key]; @@ -145,7 +157,7 @@ module.exports = function (User) { acpLang: data.acpLang || meta.config.defaultLang, followTopicsOnCreate: data.followTopicsOnCreate, followTopicsOnReply: data.followTopicsOnReply, - restrictChat: data.restrictChat, + disableIncomingChats: data.disableIncomingChats, topicSearchEnabled: data.topicSearchEnabled, updateUrlWithPostIndex: data.updateUrlWithPostIndex, homePageRoute: ((data.homePageRoute === 'custom' ? data.homePageCustom : data.homePageRoute) || '').replace(/^\//, ''), @@ -155,6 +167,8 @@ module.exports = function (User) { categoryWatchState: data.categoryWatchState, categoryTopicSort: data.categoryTopicSort, topicPostSort: data.topicPostSort, + chatAllowList: data.chatAllowList, + chatDenyList: data.chatDenyList, }; const notificationTypes = await notifications.getAllNotificationTypes(); notificationTypes.forEach((notificationType) => { diff --git a/src/utils.js b/src/utils.js index bcacaa565e..abc7a92e2a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -42,11 +42,25 @@ utils.secureRandom = function (low, high) { }; utils.getSass = function () { + // https://github.com/NodeBB/NodeBB/issues/11606 + function isMusl() { + if (process.platform !== 'linux') { + return false; + } + + try { + return !process.report.getReport().header.glibcVersionRuntime; + } catch { + return true; + } + } + if (process.platform === 'freebsd' || isMusl()) { + return require('sass'); + } try { - const sass = require('sass-embedded'); - return sass; + return require('sass-embedded'); } catch (err) { - console.error(err.message) + console.error(err.message); return require('sass'); } }; diff --git a/src/views/admin/advanced/cache.tpl b/src/views/admin/advanced/cache.tpl index a4964c742b..17e226665e 100644 --- a/src/views/admin/advanced/cache.tpl +++ b/src/views/admin/advanced/cache.tpl @@ -9,66 +9,62 @@ - -
- {{{ each caches }}} -
-
-
-
-
-
- +
+
+ + + + + + + + + + + + + + + + {{{ each caches }}} + + + + + + + + + + + + {{{ end }}} - {{{ if (@key == "post") }}} -
-
- - -
- {{{ end }}} - - + +
name capacity count size hits misses hit ratio hits/sec ttl +
+
+
+ +
+ {./name}
- [[admin/advanced/cache:{@key}-cache]] - -
- - -
- - -
-
-
- [[admin/advanced/cache:percent-full, {./percentFull}]] -
-
- -
- {{{if ./length}}}{./length}{{{else}}}{./itemCount}{{{end}}} / {{{if ./max}}}{./max}{{{else}}}{./maxSize}{{{end}}} -
- -
- {./hits} -
-
- {./misses} -
-
- {./hitRatio} -
-
- {./hitsPerSecond} -
- - {{{ if ./ttl }}} -
- {./ttl} -
+
{./percentFull}%{{{if ./length}}}{./length}{{{else}}}{./itemCount}{{{end}}} + {{{ if (./name == "post") }}} +
+ + +
+ {{{ else }}} + {{{if ./max}}}{./max}{{{else}}}{./maxSize}{{{end}}} + {{{ end }}} +
{./hits}{./misses}{./hitRatio}{./hitsPerSecond}{./ttl} +
+ + +
+
-
- {{{ end }}}
diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl index 493e16ac93..536826487c 100644 --- a/src/views/admin/development/info.tpl +++ b/src/views/admin/development/info.tpl @@ -40,11 +40,11 @@ {info.git.branch}@{info.git.hashShort} {info.process.cpuUsage}% - {info.process.memoryUsage.humanReadable} gb + {info.process.memoryUsage.rssReadable}gb / {info.process.memoryUsage.heapUsedReadable}gb - {info.os.usedmem} gb / - {info.os.totalmem} gb + {info.os.usedmem}gb / + {info.os.totalmem}gb {info.os.load} {info.process.uptimeHumanReadable} diff --git a/src/views/admin/manage/categories.tpl b/src/views/admin/manage/categories.tpl index bed5850325..0519c429d3 100644 --- a/src/views/admin/manage/categories.tpl +++ b/src/views/admin/manage/categories.tpl @@ -8,7 +8,16 @@ - +
diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index 5d3b84c639..19b0efa116 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -37,12 +37,12 @@
-
diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl index 04d4e3b66e..f08142547c 100644 --- a/src/views/admin/manage/group.tpl +++ b/src/views/admin/manage/group.tpl @@ -5,7 +5,7 @@

[[admin/manage/groups:edit-group]]

-
+