From ab6ed111554c617bd0a556794b7b0c64aaab0af8 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 15 May 2025 09:19:48 +0000 Subject: [PATCH 001/828] Latest translations and fallbacks --- public/language/he/admin/settings/user.json | 2 +- public/language/nb/category.json | 6 ++--- public/language/nb/global.json | 2 +- public/language/nb/modules.json | 4 +-- public/language/nb/notifications.json | 8 +++--- public/language/nb/tags.json | 4 +-- public/language/nb/topic.json | 22 ++++++++-------- public/language/nb/unread.json | 2 +- public/language/nb/user.json | 2 +- .../language/nn-NO/admin/settings/user.json | 2 +- public/language/nn-NO/category.json | 6 ++--- public/language/nn-NO/global.json | 2 +- public/language/nn-NO/modules.json | 4 +-- public/language/nn-NO/notifications.json | 10 +++---- public/language/nn-NO/tags.json | 4 +-- public/language/nn-NO/topic.json | 26 +++++++++---------- public/language/nn-NO/unread.json | 2 +- public/language/nn-NO/user.json | 2 +- 18 files changed, 55 insertions(+), 55 deletions(-) diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json index 0c0d227b39..3f1b6958b6 100644 --- a/public/language/he/admin/settings/user.json +++ b/public/language/he/admin/settings/user.json @@ -67,7 +67,7 @@ "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/nb/category.json b/public/language/nb/category.json index 2f274001a6..e114c8a083 100644 --- a/public/language/nb/category.json +++ b/public/language/nb/category.json @@ -4,10 +4,10 @@ "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", - "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": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", + "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.", @@ -17,7 +17,7 @@ "tracking": "Følg", "not-watching": "Følger ikke", "ignoring": "Ignorerer", - "watching.description": "Varsle meg om nye emner.
Vis emner i ulest og nylig", + "watching.description": "Varsle meg om nye innlegg.
Vis innlegg 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", diff --git a/public/language/nb/global.json b/public/language/nb/global.json index edb8c97735..fe49ecbeaf 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -82,7 +82,7 @@ "downvoted": "Nedstemt", "views": "Visninger", "posters": "Innlegg", - "watching": "Watching", + "watching": "Følger", "reputation": "Omdømme", "lastpost": "Siste innlegg", "firstpost": "Første innlegg", diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index 08e42a051e..bd0436e435 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -8,7 +8,7 @@ "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.send": "Publiser", "chat.no-active": "Du har ingen aktive chatter.", "chat.user-typing-1": "%1 skriver ...", "chat.user-typing-2": "%1 og %2 skriver ...", @@ -121,7 +121,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 757e983c88..6c56f4b626 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -50,7 +50,7 @@ "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-posted-topic": "%1 har skrevet et nytt innlegg: %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)", @@ -83,9 +83,9 @@ "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-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-group-chat": "Når du mottar en gruppemelding i chat", 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/topic.json b/public/language/nb/topic.json index 48dc204ac2..3e8489d703 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -9,7 +9,7 @@ "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", @@ -87,15 +87,15 @@ "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", @@ -144,8 +144,8 @@ "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": "Click the posts you want to fork, enter a title for the new topic and click fork topic", "fork-no-pids": "Ingen innlegg valgt!", "no-posts-selected": "Ingen innlegg valgt.", "x-posts-selected": "%1 innlegg valgt", @@ -157,7 +157,7 @@ "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", @@ -172,7 +172,7 @@ "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.uploading": "laster opp...", "composer.thumb-url-label": "Lim inn som tråd-minatyr URL", @@ -193,9 +193,9 @@ "most-votes": "Flest tilrådinger", "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", 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 590afb957a..efcbc0c1d7 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -110,7 +110,7 @@ "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å 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", diff --git a/public/language/nn-NO/admin/settings/user.json b/public/language/nn-NO/admin/settings/user.json index 5dc36d2d55..76c93d24c6 100644 --- a/public/language/nn-NO/admin/settings/user.json +++ b/public/language/nn-NO/admin/settings/user.json @@ -81,7 +81,7 @@ "default-notification-settings": "Standard varslingsinnstillingar", "categoryWatchState": "Kategori-overvåking", "categoryWatchState.tracking": "Sporing", - "categoryWatchState.notwatching": "Ikkje overvåking", + "categoryWatchState.notwatching": "Følgjer ikkje", "categoryWatchState.ignoring": "Ignorerer", "restrictions-new": "Nye restriksjonar", "restrictions.rep-threshold": "Omdømmegrense", diff --git a/public/language/nn-NO/category.json b/public/language/nn-NO/category.json index 22f6ffeaa3..152c69d345 100644 --- a/public/language/nn-NO/category.json +++ b/public/language/nn-NO/category.json @@ -4,10 +4,10 @@ "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", - "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": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.", + "no-followers": "Ingen følgjer denne kategorien enno. Følg den for å få oppdateringer.", "browsing": "blar gjennom", "no-replies": "Ingen har svart", "no-new-posts": "Ingen nye innlegg.", @@ -17,7 +17,7 @@ "tracking": "Følgjer med", "not-watching": "Følgjer ikkje", "ignoring": "Ignorerer", - "watching.description": "Varsle meg om nye emne.
Vis emne i Uleste og nye", + "watching.description": "Varsle meg om nye innlegg.
Vis innlegg 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", diff --git a/public/language/nn-NO/global.json b/public/language/nn-NO/global.json index 4f4ba51768..d6801a9670 100644 --- a/public/language/nn-NO/global.json +++ b/public/language/nn-NO/global.json @@ -82,7 +82,7 @@ "downvoted": "Stemte ned", "views": "Visningar", "posters": "Innleggsskrivarar", - "watching": "Watching", + "watching": "Følgjer", "reputation": "Omdømme", "lastpost": "Siste innlegg", "firstpost": "Fyrste innlegg", diff --git a/public/language/nn-NO/modules.json b/public/language/nn-NO/modules.json index 353020cc78..061d785238 100644 --- a/public/language/nn-NO/modules.json +++ b/public/language/nn-NO/modules.json @@ -8,7 +8,7 @@ "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.send": "Publiser", "chat.no-active": "Du har ingen aktive chattar", "chat.user-typing-1": "%1 skriv ...", "chat.user-typing-2": "%1 og %2 skriv ...", @@ -121,7 +121,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 b08b16179f..5677ae316f 100644 --- a/public/language/nn-NO/notifications.json +++ b/public/language/nn-NO/notifications.json @@ -50,13 +50,13 @@ "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-posted-topic": "%1 har skrive eit nytt innlegg: %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-posted-topic-in-category": "%1 har skrive eit nytt innlegg 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.", @@ -83,9 +83,9 @@ "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-new-topic-in-category": "Når eit innlegg vert oppretta i ein kategori du følgjer", + "notificationType-new-reply": "Når eit nytt svar vert posta i eit innlegg du følgjer", + "notificationType-post-edit": "Når eit svar blir redigert i eit innlegg 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", diff --git a/public/language/nn-NO/tags.json b/public/language/nn-NO/tags.json index fd1087d24c..19f4a2b929 100644 --- a/public/language/nn-NO/tags.json +++ b/public/language/nn-NO/tags.json @@ -10,8 +10,8 @@ "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.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/topic.json b/public/language/nn-NO/topic.json index 48f429a550..ee67d56f48 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", @@ -87,15 +87,15 @@ "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ølgje dette innlegget", "share-this-post": "Del dette innlegget", "watching": "Følgjer", "not-watching": "Følgjer 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", @@ -144,8 +144,8 @@ "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": "Click the posts you want to fork, enter a title for the new topic and click fork topic", "fork-no-pids": "Ingen innlegg vald!", "no-posts-selected": "Ingen innlegg vald!", "x-posts-selected": "%1 innlegg vald", @@ -157,7 +157,7 @@ "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", @@ -172,7 +172,7 @@ "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.uploading": "laster opp...", "composer.thumb-url-label": "Lim inn ein emne-miniatyr-URL", @@ -193,9 +193,9 @@ "most-votes": "Fleire stemmer", "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", diff --git a/public/language/nn-NO/unread.json b/public/language/nn-NO/unread.json index 37583696e6..71e51f34dd 100644 --- a/public/language/nn-NO/unread.json +++ b/public/language/nn-NO/unread.json @@ -9,7 +9,7 @@ "all-categories": "Alle kategoriar", "topics-marked-as-read.success": "Emne merka som lest!", "all-topics": "Alle emne", - "new-topics": "Nye emne", + "new-topics": "Nye innlegg", "watched-topics": "Emne du følgjer", "unreplied-topics": "Emne utan svar", "multiple-categories-selected": "Fleire vald" diff --git a/public/language/nn-NO/user.json b/public/language/nn-NO/user.json index 9f36c196ea..cd615d95be 100644 --- a/public/language/nn-NO/user.json +++ b/public/language/nn-NO/user.json @@ -110,7 +110,7 @@ "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", From 8f933459cded56711406f5008cd812294188e38c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 15 May 2025 15:38:57 -0400 Subject: [PATCH 002/828] fix: bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in `attributedTo` --- src/activitypub/mocks.js | 7 ++++++- src/activitypub/notes.js | 21 ++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 76cc8af01b..e5a8e8e363 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -42,7 +42,7 @@ const sanitizeConfig = { Mocks._normalize = async (object) => { // Normalized incoming AP objects into expected types for easier mocking - let { type, attributedTo, url, image, mediaType, content, source, attachment } = object; + let { type, attributedTo, url, image, mediaType, content, source, attachment, cc } = object; switch (true) { // non-string attributedTo handling case Array.isArray(attributedTo): { @@ -52,6 +52,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); } } @@ -148,6 +152,7 @@ Mocks._normalize = async (object) => { return { ...object, + cc, attributedTo, content, sourceContent, diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 31ca249d3d..616fc3a44f 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -115,18 +115,33 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (hasTid) { mainPid = await topics.getTopicField(tid, 'mainPid'); } else { - // Check recipients/audience for local category + // 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); - if (recipientCids.length) { + // 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); + return assertedGroups[idx] && cidHostname === hostname; + }).shift(); + } catch (e) { + // noop + } + + if (remoteCid || recipientCids.length) { // Overrides passed-in value, respect addressing from main post over booster - options.cid = recipientCids.shift(); + options.cid = remoteCid || recipientCids.shift(); } // mainPid ok to leave as-is From 4602b6b7c8f58993c1b22d6ce174910587d91a31 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 16 May 2025 09:20:24 +0000 Subject: [PATCH 003/828] Latest translations and fallbacks --- public/language/ru/topic.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index 86c8306ecf..af877fb070 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -61,8 +61,8 @@ "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", From 0fe1e53cf9f52b4060359d44ed215597c3fa1cdf Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 18 May 2025 09:19:19 +0000 Subject: [PATCH 004/828] Latest translations and fallbacks --- public/language/nb/topic.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 3e8489d703..15db4e3016 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -67,8 +67,8 @@ "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 innlegget dette innlegget på %3", - "user-forked-topic-ago": "%1 gaflet dette emnet %3", - "user-forked-topic-on": "%1 gaflet dette emnet på %3", + "user-forked-topic-ago": "%1 forgrenet dette emnet %3", + "user-forked-topic-on": "%1 forgrenet dette emnet på %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", @@ -145,7 +145,7 @@ "post-moved": "Innlegg flyttet!", "fork-topic": "Forgren tråd", "enter-new-topic-title": "Skriv tittel på innlegg", - "fork-topic-instruction": "Click the posts you want to fork, enter a title for the new topic and click fork topic", + "fork-topic-instruction": "Velg innleggene du vil flytte til ny 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", From 475b0704b9f5b950a435a74eb1b3dc0d15d249d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 13:14:48 -0400 Subject: [PATCH 005/828] chore(deps): update dependency @eslint/js to v9.27.0 (#13429) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e6500e7908..cf4bc84c5d 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.26.0", + "@eslint/js": "9.27.0", "@stylistic/eslint-plugin-js": "4.2.0", "eslint-config-nodebb": "1.1.4", "eslint-plugin-import": "2.31.0", From 2417a79b5fa8acc07f605db44f53a50078cbd024 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 13:15:06 -0400 Subject: [PATCH 006/828] fix(deps): update dependency sass to v1.89.0 (#13427) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index cf4bc84c5d..1f813463aa 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.88.0", + "sass": "1.89.0", "satori": "0.13.1", "semver": "7.7.2", "serve-favicon": "2.5.0", From 650eeac9087c24117bf0ca98514d0a50a57a8126 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 13:15:23 -0400 Subject: [PATCH 007/828] chore(deps): update dependency mocha to v11.3.0 (#13426) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1f813463aa..6e6f15ce63 100644 --- a/install/package.json +++ b/install/package.json @@ -170,7 +170,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.0.0", - "mocha": "11.2.2", + "mocha": "11.3.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From 42f16da501dcfd74c5f7874e4bc73e11b9dedf4c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 13:43:21 -0400 Subject: [PATCH 008/828] fix(deps): update dependency nodebb-plugin-dbsearch to v6.2.17 (#13432) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6e6f15ce63..a87883043a 100644 --- a/install/package.json +++ b/install/package.json @@ -99,7 +99,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.2.50", - "nodebb-plugin-dbsearch": "6.2.16", + "nodebb-plugin-dbsearch": "6.2.17", "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.2.1", From 5d017710bda84ca268c9ddb93b44dcdb46de43af Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 17:10:26 -0400 Subject: [PATCH 009/828] chore(deps): update dependency mocha to v11.4.0 (#13435) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a87883043a..f54667b509 100644 --- a/install/package.json +++ b/install/package.json @@ -170,7 +170,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.0.0", - "mocha": "11.3.0", + "mocha": "11.4.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From aa9772822afb260df4fa9cb22274e61dcebc412e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 17:11:40 -0400 Subject: [PATCH 010/828] chore(deps): update dependency sass-embedded to v1.89.0 (#13425) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f54667b509..95438ff373 100644 --- a/install/package.json +++ b/install/package.json @@ -177,7 +177,7 @@ "smtp-server": "3.13.6" }, "optionalDependencies": { - "sass-embedded": "1.88.0" + "sass-embedded": "1.89.0" }, "resolutions": { "*/jquery": "3.7.1" From ee8e223f2032fd3d0aa788615c69bd88e4ffb9a3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 17:11:51 -0400 Subject: [PATCH 011/828] fix(deps): update dependency connect-redis to v8.1.0 (#13433) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 95438ff373..6695ff083e 100644 --- a/install/package.json +++ b/install/package.json @@ -60,7 +60,7 @@ "connect-mongo": "5.1.0", "connect-multiparty": "2.2.0", "connect-pg-simple": "10.0.0", - "connect-redis": "8.0.3", + "connect-redis": "8.1.0", "cookie-parser": "1.4.7", "cron": "4.3.0", "cropperjs": "1.6.2", From 2e02d3f6730ebfb1a891ebf9a468fa407d8fffe2 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 20 May 2025 09:19:53 +0000 Subject: [PATCH 012/828] Latest translations and fallbacks --- public/language/nb/topic.json | 4 ++-- public/language/nn-NO/topic.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 15db4e3016..54fc6d6007 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -76,7 +76,7 @@ "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", + "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.", @@ -145,7 +145,7 @@ "post-moved": "Innlegg flyttet!", "fork-topic": "Forgren tråd", "enter-new-topic-title": "Skriv tittel på innlegg", - "fork-topic-instruction": "Velg innleggene du vil flytte til ny forgrenet emne, skriv tittel for det nye emnet og klikk forgren", + "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", diff --git a/public/language/nn-NO/topic.json b/public/language/nn-NO/topic.json index ee67d56f48..4e6c94845e 100644 --- a/public/language/nn-NO/topic.json +++ b/public/language/nn-NO/topic.json @@ -145,7 +145,7 @@ "post-moved": "Innlegg flytta!", "fork-topic": "Kopier emne", "enter-new-topic-title": "Skriv tittel på innlegg", - "fork-topic-instruction": "Click the posts you want to fork, enter a title for the new topic and click fork topic", + "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", From 385f4f12be4dbd41832bebbe043288ee524a1bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20U=C5=9Fakl=C4=B1?= Date: Tue, 20 May 2025 10:45:56 -0400 Subject: [PATCH 013/828] 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 --- install/package.json | 3 +-- src/controllers/accounts/edit.js | 2 +- src/controllers/admin/uploads.js | 16 +++++++------ src/controllers/uploads.js | 11 ++++----- src/middleware/index.js | 41 ++++++++++++++++---------------- src/middleware/uploads.js | 2 +- src/routes/admin.js | 14 +++++++---- src/routes/api.js | 8 ++++--- src/routes/authentication.js | 12 +++++++--- src/routes/helpers.js | 6 +++-- src/routes/write/topics.js | 11 +++++---- test/helpers/index.js | 2 +- 12 files changed, 74 insertions(+), 54 deletions(-) diff --git a/install/package.json b/install/package.json index b3dca2f562..e56c9e056c 100644 --- a/install/package.json +++ b/install/package.json @@ -58,7 +58,6 @@ "compression": "1.8.0", "connect-flash": "0.1.1", "connect-mongo": "5.1.0", - "connect-multiparty": "2.2.0", "connect-pg-simple": "10.0.0", "connect-redis": "8.1.0", "cookie-parser": "1.4.7", @@ -95,7 +94,7 @@ "mongodb": "6.16.0", "morgan": "1.10.0", "mousetrap": "1.6.5", - "multiparty": "4.2.3", + "multer": "2.0.0", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.2.50", 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/admin/uploads.js b/src/controllers/admin/uploads.js index 56d64674cf..433edd07ce 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -13,7 +13,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; @@ -147,7 +149,7 @@ async function getFileData(currentDir, file) { } uploadsController.uploadCategoryPicture = async function (req, res, next) { - const uploadedFile = req.files.files[0]; + const uploadedFile = req.files[0]; let params = null; try { @@ -202,7 +204,7 @@ async function sanitizeSvg(filePath) { } 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); @@ -217,7 +219,7 @@ uploadsController.uploadFavicon = async function (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 +246,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); @@ -263,7 +265,7 @@ uploadsController.uploadLogo = async function (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); @@ -294,7 +296,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); diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index d2e392b5d3..d2cfb48107 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 = []; @@ -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]]')); } @@ -201,7 +198,9 @@ async function saveFileToLocal(uid, folder, uploadedFile) { } 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/middleware/index.js b/src/middleware/index.js index 5a0e69842f..417d8309bc 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -5,7 +5,6 @@ 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'); @@ -27,7 +26,7 @@ const delayCache = cacheCreate({ ttl: 1000 * 60, max: 200, }); -const multipartMiddleware = multipart(); + const middleware = module.exports; @@ -101,17 +100,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]]'])); @@ -291,14 +303,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/uploads.js b/src/middleware/uploads.js index d1ce5b09b2..9b434b3286 100644 --- a/src/middleware/uploads.js +++ b/src/middleware/uploads.js @@ -23,7 +23,7 @@ exports.ratelimit = helpers.try(async (req, res, next) => { 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/routes/admin.js b/src/routes/admin.js index 89a3050ee6..b7e751695c 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -82,10 +82,16 @@ function apiRoutes(router, name, middleware, controllers) { 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)); - const multipart = require('connect-multiparty'); - const multipartMiddleware = multipart(); - - const middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.ensureLoggedIn]; + const multer = require('multer'); + const storage = multer.diskStorage({}); + const upload = multer({ storage }); + + 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 0fe575a326..e374e242a4 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -23,11 +23,13 @@ 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 multer = require('multer'); + const storage = multer.diskStorage({}); + const upload = multer({ storage }); + const postMiddlewares = [ middleware.maintenanceMode, - multipartMiddleware, + upload.array('files[]', 20), middleware.validateFiles, middleware.uploads.ratelimit, middleware.applyCSRF, diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 9d89df90e1..720675b29d 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -154,9 +154,15 @@ Auth.reloadRoutes = async function (params) { }); }); - const multipart = require('connect-multiparty'); - const multipartMiddleware = multipart(); - const middlewares = [multipartMiddleware, Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist]; + + const multer = require('multer'); + const storage = multer.diskStorage({}); + const upload = multer({ storage }); + 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/helpers.js b/src/routes/helpers.js index 34a455076e..2109a0bd9c 100644 --- a/src/routes/helpers.js +++ b/src/routes/helpers.js @@ -54,7 +54,9 @@ 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 multer = require('multer'); + const storage = multer.diskStorage({}); + const upload = multer({ storage }); middlewares = [ middleware.autoLocale, middleware.applyBlacklist, @@ -63,7 +65,7 @@ helpers.setupApiRoute = function (...args) { middleware.registrationComplete, middleware.pluginHooks, middleware.logApiUsage, - middleware.handleMultipart, + upload.any(), ...middlewares, ]; diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js index df10f66633..eb56cdaf42 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,7 +34,13 @@ 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, 'post', '/:tid/thumbs', [ + 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, '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); diff --git a/test/helpers/index.js b/test/helpers/index.js index e71a05edaa..89de878ce8 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -95,7 +95,7 @@ helpers.uploadFile = async function (uploadEndPoint, filePath, data, jar, csrf_t const file = await fs.promises.readFile(filePath); const blob = new Blob([file], { type: mime.getType(filePath) }); - form.append('files', blob, path.basename(filePath)); + form.append('files[]', blob, path.basename(filePath)); if (data && data.params) { form.append('params', data.params); From 314a4ff047cdf1a3e1e2b681f35719ec95c41bcc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 10:56:59 -0400 Subject: [PATCH 014/828] fix(deps): update dependency webpack to v5.99.9 (#13438) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e56c9e056c..2f51844f91 100644 --- a/install/package.json +++ b/install/package.json @@ -146,7 +146,7 @@ "toobusy-js": "0.5.1", "tough-cookie": "5.1.2", "validator": "13.15.0", - "webpack": "5.99.8", + "webpack": "5.99.9", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.2.0", From 136e88140f689a4133894fdd2771f911e4e212fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 10:57:09 -0400 Subject: [PATCH 015/828] chore(deps): update dependency smtp-server to v3.13.7 (#13437) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2f51844f91..5ea7e1476b 100644 --- a/install/package.json +++ b/install/package.json @@ -173,7 +173,7 @@ "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.13.6" + "smtp-server": "3.13.7" }, "optionalDependencies": { "sass-embedded": "1.89.0" From 1d624aadbe8aefe54badaf48602f2a09956259fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 10:57:23 -0400 Subject: [PATCH 016/828] fix(deps): update dependency commander to v14 (#13434) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5ea7e1476b..24e7f42ec7 100644 --- a/install/package.json +++ b/install/package.json @@ -53,7 +53,7 @@ "chart.js": "4.4.9", "cli-graph": "3.2.2", "clipboard": "2.0.11", - "commander": "13.1.0", + "commander": "14.0.0", "compare-versions": "6.1.1", "compression": "1.8.0", "connect-flash": "0.1.1", From 76a624b9ca2004f76f101300922ca6dfa17f4fee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 11:31:52 -0400 Subject: [PATCH 017/828] fix(deps): update dependency diff to v8.0.2 (#13440) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8236c389f8..35d41c8db3 100644 --- a/install/package.json +++ b/install/package.json @@ -65,7 +65,7 @@ "cropperjs": "1.6.2", "csrf-sync": "4.2.1", "daemon": "1.1.0", - "diff": "8.0.1", + "diff": "8.0.2", "esbuild": "0.25.4", "express": "4.21.2", "express-session": "1.18.1", From 777ecdf2c14b7882f550cb9e4140b6ccda566c00 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 23 May 2025 09:20:20 +0000 Subject: [PATCH 018/828] Latest translations and fallbacks --- public/language/bg/modules.json | 2 +- public/language/he/global.json | 6 +++--- public/language/ru/admin/advanced/events.json | 8 ++++---- public/language/ru/category.json | 2 +- public/language/ru/error.json | 2 +- public/language/ru/global.json | 10 +++++----- public/language/ru/user.json | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index ba3da12344..f6485e5213 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -120,7 +120,7 @@ "bootbox.ok": "Добре", "bootbox.cancel": "Отказ", "bootbox.confirm": "Потвърждаване", - "bootbox.submit": "Публикуване", + "bootbox.submit": "Изпращане", "bootbox.send": "Изпращане", "cover.dragging-title": "Наместване на снимката", "cover.dragging-message": "Преместете снимката на желаното положение и натиснете „Запазване“", diff --git a/public/language/he/global.json b/public/language/he/global.json index 428cd81ac4..7ec0571727 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": "פוסטים אחרונים", @@ -134,8 +134,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/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/category.json b/public/language/ru/category.json index 4739a2510f..79ef86d73a 100644 --- a/public/language/ru/category.json +++ b/public/language/ru/category.json @@ -7,7 +7,7 @@ "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.", + "no-followers": "Никто на этом сайте не отслеживает эту категорию. Начните отслеживать или наблюдать за этой категорией, чтобы получать обновления.", "browsing": "просматривают", "no-replies": "Нет ответов", "no-new-posts": "Нет новых сообщений", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index c419fcb3c8..89555f0367 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -38,7 +38,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 мин.", diff --git a/public/language/ru/global.json b/public/language/ru/global.json index 01765d3374..d4381e5558 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": "Категории", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 60982acf7a..2942cb492a 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -108,7 +108,7 @@ "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", + "chat-list-add-user": "Добавить участника", "digest-label": "Подписка на дайджест", "digest-description": "Подписаться на рассылку уведомлений о событиях и новых темах на форуме с указанной периодичностью", "digest-off": "Отключена", From c18464757885aed98ccf481808b84c8c6b59dc39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 19:51:45 -0400 Subject: [PATCH 019/828] chore(deps): update dependency mocha to v11.5.0 (#13442) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 35d41c8db3..d98b9d2cbf 100644 --- a/install/package.json +++ b/install/package.json @@ -169,7 +169,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.0.0", - "mocha": "11.4.0", + "mocha": "11.5.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From e3a7fb5ccb05b95cc82eaee209e0b35f57a9c146 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 06:11:41 -0400 Subject: [PATCH 020/828] fix(deps): update dependency bootbox to v6.0.4 (#13443) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d98b9d2cbf..2b96813e70 100644 --- a/install/package.json +++ b/install/package.json @@ -46,7 +46,7 @@ "bcryptjs": "3.0.2", "benchpressjs": "2.5.5", "body-parser": "2.2.0", - "bootbox": "6.0.3", + "bootbox": "6.0.4", "bootstrap": "5.3.6", "bootswatch": "5.3.6", "chalk": "4.1.2", From 3ca6a9bcfa89a36069ea506857f1d3e41b38400c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 11:54:27 -0400 Subject: [PATCH 021/828] fix(deps): update dependency nodebb-plugin-dbsearch to v6.2.18 (#13445) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2b96813e70..5df8baa302 100644 --- a/install/package.json +++ b/install/package.json @@ -98,7 +98,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.2.50", - "nodebb-plugin-dbsearch": "6.2.17", + "nodebb-plugin-dbsearch": "6.2.18", "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.2.1", From aeeda7c3be86c0941b94323df274d6af64ee2041 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 25 May 2025 09:19:33 +0000 Subject: [PATCH 022/828] Latest translations and fallbacks --- public/language/vi/category.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/vi/category.json b/public/language/vi/category.json index 4abc703c44..ccddbfda2b 100644 --- a/public/language/vi/category.json +++ b/public/language/vi/category.json @@ -1,7 +1,7 @@ { "category": "Danh mục", "subcategories": "Danh mục phụ", - "uncategorized": "Chưa có danh mục", + "uncategorized": "Chưa phân loại", "uncategorized.description": "Các chủ đề không phù hợp với bất kỳ danh mục hiện có nào", "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", From 6a5bbe9204092434ba446521cbde74ae0f12cef1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 09:09:21 -0400 Subject: [PATCH 023/828] fix(deps): update dependency esbuild to v0.25.5 (#13447) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 580f3c992b..7ad282fd29 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.4", + "esbuild": "0.25.5", "express": "4.21.2", "express-session": "1.18.1", "express-useragent": "1.0.15", From 6efe3fdd02f9359d77c97eddf86179737b5792d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 17:36:42 -0400 Subject: [PATCH 024/828] chore(deps): update dependency lint-staged to v16.1.0 (#13449) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7ad282fd29..e5bebd24ef 100644 --- a/install/package.json +++ b/install/package.json @@ -168,7 +168,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "26.1.0", - "lint-staged": "16.0.0", + "lint-staged": "16.1.0", "mocha": "11.5.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 36f0cf250fc2c5e04d13a4709d2131e5232e3878 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 09:04:04 -0400 Subject: [PATCH 025/828] fix(deps): update dependency validator to v13.15.15 (#13451) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e5bebd24ef..ecd3950cb8 100644 --- a/install/package.json +++ b/install/package.json @@ -145,7 +145,7 @@ "tinycon": "0.6.8", "toobusy-js": "0.5.1", "tough-cookie": "5.1.2", - "validator": "13.15.0", + "validator": "13.15.15", "webpack": "5.99.9", "webpack-merge": "6.0.1", "winston": "3.17.0", From a80edfa1f11823bd6e0047b7b51ebbc0493d83c3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 29 May 2025 15:15:06 -0400 Subject: [PATCH 026/828] fix: patch ap .probe() so that it does not execute on requests for its own resources --- src/activitypub/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 4067405080..80ec4a40f5 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -511,8 +511,8 @@ ActivityPub.probe = async ({ uid, url }) => { // Disable on config setting; restrict lookups to HTTPS-enabled URLs only const { activitypubProbe } = meta.config; - const { protocol } = new URL(url); - if (!activitypubProbe || protocol !== 'https:') { + const { protocol, host } = new URL(url); + if (!activitypubProbe || protocol !== 'https:' || host === nconf.get('url_parsed').host) { return false; } From 28c021a01bf86abaf8404261a9d161ad99328a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 30 May 2025 11:11:45 -0400 Subject: [PATCH 027/828] fix: remove null categories --- src/categories/search.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/categories/search.js b/src/categories/search.js index bd6a96435c..9e3d869974 100644 --- a/src/categories/search.js +++ b/src/categories/search.js @@ -44,7 +44,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); From 57a5de26827693f86b5804b2e389930dac16c0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 30 May 2025 11:15:02 -0400 Subject: [PATCH 028/828] refactor: use strings for cids --- src/categories/search.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/categories/search.js b/src/categories/search.js index 9e3d869974..50521d882a 100644 --- a/src/categories/search.js +++ b/src/categories/search.js @@ -5,7 +5,6 @@ const _ = require('lodash'); const privileges = require('../privileges'); const activitypub = require('../activitypub'); const plugins = require('../plugins'); -const utils = require('../utils'); const db = require('../database'); module.exports = function (Categories) { @@ -65,7 +64,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; }; @@ -82,7 +81,7 @@ module.exports = function (Categories) { const split = data.split(':'); split.shift(); const cid = split.join(':'); - return utils.isNumber(cid) ? parseInt(cid, 10) : cid; + return cid; }); } From 0d595008b0bc08dfe52449fe43015257c0f71fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 30 May 2025 17:12:54 -0400 Subject: [PATCH 029/828] chore: eslint config --- eslint.config.mjs | 2 +- install/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index dd39fc1544..47cfa158f5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,7 @@ import publicConfig from 'eslint-config-nodebb/public'; import commonRules from 'eslint-config-nodebb/common'; import { defineConfig } from 'eslint/config'; -import stylisticJs from '@stylistic/eslint-plugin-js' +import stylisticJs from '@stylistic/eslint-plugin' import js from '@eslint/js'; import globals from 'globals'; diff --git a/install/package.json b/install/package.json index ecd3950cb8..1126e4fbc7 100644 --- a/install/package.json +++ b/install/package.json @@ -161,8 +161,8 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.27.0", - "@stylistic/eslint-plugin-js": "4.4.0", - "eslint-config-nodebb": "1.1.5", + "@stylistic/eslint-plugin": "4.4.0", + "eslint-config-nodebb": "1.1.6", "eslint-plugin-import": "2.31.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", From ff00829b3f9cad8ee726eab84b231e9dcc10c953 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 1 Jun 2025 09:19:27 +0000 Subject: [PATCH 030/828] Latest translations and fallbacks --- public/language/hr/category.json | 2 +- public/language/hr/themes/harmony.json | 2 +- public/language/hr/topic.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/language/hr/category.json b/public/language/hr/category.json index ff5b1cb9c5..a02822d8d7 100644 --- a/public/language/hr/category.json +++ b/public/language/hr/category.json @@ -5,7 +5,7 @@ "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", "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", diff --git a/public/language/hr/themes/harmony.json b/public/language/hr/themes/harmony.json index 727a1b0553..3d91fe5a4f 100644 --- a/public/language/hr/themes/harmony.json +++ b/public/language/hr/themes/harmony.json @@ -6,7 +6,7 @@ "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..fdf37e5614 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", @@ -215,7 +215,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", From fcb3bfbc35221f207a83b197766edc06d0f05cdb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 1 Jun 2025 12:40:37 -0400 Subject: [PATCH 031/828] fix: return 200 for non-implemented activities instead of 501 --- src/controllers/activitypub/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index 0e7112ddfd..de478a6021 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -145,7 +145,7 @@ Controller.postInbox = async (req, res) => { 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); + return res.sendStatus(200); } try { From 9d3b8c3abcd60aa5a6d85ff804008e7d8345a95b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 22 May 2025 14:14:53 -0400 Subject: [PATCH 032/828] feat: add protection mechanism to request lib so that network requests to reserved IP ranges throw an error --- public/language/en-GB/error.json | 2 ++ src/request.js | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 82a623ea32..2ca10a839f 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.", diff --git a/src/request.js b/src/request.js index b84b198914..4806eabc41 100644 --- a/src/request.js +++ b/src/request.js @@ -1,10 +1,18 @@ 'use strict'; +const dns = require('dns').promises; + 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 ttl = require('./cache/ttl'); +const checkCache = ttl({ + ttl: 1000 * 60 * 60, // 1 hour +}); + exports.jar = function () { return new CookieJar(); }; @@ -13,6 +21,11 @@ const userAgent = `NodeBB/${version.split('.').shift()}.x (${nconf.get('url')})` // 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); @@ -75,6 +88,40 @@ 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) { + const cached = checkCache.get(url); + if (cached) { + return cached; + } + + const addresses = new Set(); + if (ipaddr.isValid(url)) { + addresses.add(url); + } else { + const { host } = new URL(url); + const [v4, v6] = await Promise.all([ + dns.resolve4(host), + dns.resolve6(host), + ]); + v4.forEach((ip) => { + addresses.add(ip); + }); + v6.forEach((ip) => { + addresses.add(ip); + }); + } + + // Every IP address that the host resolves to should be a unicast address + const ok = Array.from(addresses).every((ip) => { + const parsed = ipaddr.parse(ip); + return parsed.range() === 'unicast'; + }); + + checkCache.set(url, ok); + return ok; +} + /* const { body, response } = await request.get('someurl?foo=1&baz=2') */ From df36021628c47f584d94b88f69dbcd6e3fdba29a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 22 May 2025 15:36:22 -0400 Subject: [PATCH 033/828] fix: simplify dns to use .lookup instead of .resolve4 and .resolve6, automatically allow requests to own hostname --- src/request.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/request.js b/src/request.js index 4806eabc41..86ad8c5fc7 100644 --- a/src/request.js +++ b/src/request.js @@ -90,6 +90,11 @@ 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) { + const { host } = new URL(url); + if (host === nconf.get('url_parsed').host) { + return true; + } + const cached = checkCache.get(url); if (cached) { return cached; @@ -99,16 +104,9 @@ async function check(url) { if (ipaddr.isValid(url)) { addresses.add(url); } else { - const { host } = new URL(url); - const [v4, v6] = await Promise.all([ - dns.resolve4(host), - dns.resolve6(host), - ]); - v4.forEach((ip) => { - addresses.add(ip); - }); - v6.forEach((ip) => { - addresses.add(ip); + const lookup = await dns.lookup(host, { all: true }); + lookup.forEach(({ address }) => { + addresses.add(address); }); } @@ -118,7 +116,7 @@ async function check(url) { return parsed.range() === 'unicast'; }); - checkCache.set(url, ok); + checkCache.set(host, ok); return ok; } From 70c04f0cb25e15f150084b1d87d3d8af3efb44a1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 23 May 2025 13:57:25 -0400 Subject: [PATCH 034/828] fix: undefined check, allow plugins to append to allow list --- install/package.json | 1 + src/request.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 1126e4fbc7..5d454bf7ed 100644 --- a/install/package.json +++ b/install/package.json @@ -145,6 +145,7 @@ "tinycon": "0.6.8", "toobusy-js": "0.5.1", "tough-cookie": "5.1.2", + "undici": "^7.10.0", "validator": "13.15.15", "webpack": "5.99.9", "webpack-merge": "6.0.1", diff --git a/src/request.js b/src/request.js index 86ad8c5fc7..3709aba2f6 100644 --- a/src/request.js +++ b/src/request.js @@ -8,10 +8,13 @@ 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({ ttl: 1000 * 60 * 60, // 1 hour }); +let allowList = new Set(); +let initialized = false; exports.jar = function () { return new CookieJar(); @@ -19,6 +22,19 @@ exports.jar = function () { 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; +} + // 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); @@ -90,13 +106,15 @@ 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); - if (host === nconf.get('url_parsed').host) { + if (allowList.has(host)) { return true; } const cached = checkCache.get(url); - if (cached) { + if (cached !== undefined) { return cached; } From a8e613e13ac6f3eb8cc048bc085beb31f2594270 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 24 May 2025 22:12:48 -0400 Subject: [PATCH 035/828] fix: further guard against DNS rebinding attack --- src/request.js | 60 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/request.js b/src/request.js index 3709aba2f6..aaf0d55b32 100644 --- a/src/request.js +++ b/src/request.js @@ -1,6 +1,7 @@ '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'); @@ -35,9 +36,39 @@ async function init() { 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) { + const { ok, lookup } = checkCache.get(hostname); + if (!ok) { + throw new Error('lookup-failed'); + } + + if (!lookup) { + // trusted, do regular lookup + dns.lookup(hostname, options).then((addresses) => { + callback(null, addresses); + }); + return; + } + + 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); + const { ok } = await check(url); if (!ok) { throw new Error('[[error:reserved-ip-address]]'); } @@ -75,7 +106,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); @@ -109,33 +142,36 @@ async function check(url) { await init(); const { host } = new URL(url); - if (allowList.has(host)) { - return true; - } - 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 { - const lookup = await dns.lookup(host, { all: true }); - lookup.forEach(({ address }) => { - addresses.add(address); + lookup = await dns.lookup(host, { all: true }); + lookup.forEach(({ address, family }) => { + addresses.add({ address, family }); }); } // Every IP address that the host resolves to should be a unicast address - const ok = Array.from(addresses).every((ip) => { + const ok = Array.from(addresses).every(({ address: ip }) => { const parsed = ipaddr.parse(ip); return parsed.range() === 'unicast'; }); - checkCache.set(host, ok); - return ok; + const payload = { ok, lookup }; + checkCache.set(host, payload); + return payload; } /* From e1eb76feba9ff1aba8ba2a032651c5de9e79e371 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 2 Jun 2025 15:06:01 +0000 Subject: [PATCH 036/828] chore(i18n): fallback strings for new resources: nodebb.error --- public/language/ar/error.json | 1 + public/language/az/error.json | 1 + public/language/bg/error.json | 1 + public/language/bn/error.json | 1 + public/language/cs/error.json | 1 + public/language/da/error.json | 1 + public/language/de/error.json | 1 + public/language/el/error.json | 1 + public/language/en-US/error.json | 1 + public/language/en-x-pirate/error.json | 1 + public/language/es/error.json | 1 + public/language/et/error.json | 1 + public/language/fa-IR/error.json | 1 + public/language/fi/error.json | 1 + public/language/fr/error.json | 1 + public/language/gl/error.json | 1 + public/language/he/error.json | 1 + public/language/hr/error.json | 1 + public/language/hu/error.json | 1 + public/language/hy/error.json | 1 + public/language/id/error.json | 1 + public/language/it/error.json | 1 + public/language/ja/error.json | 1 + public/language/ko/error.json | 1 + public/language/lt/error.json | 1 + public/language/lv/error.json | 1 + public/language/ms/error.json | 1 + public/language/nb/error.json | 1 + public/language/nl/error.json | 1 + public/language/nn-NO/error.json | 1 + public/language/pl/error.json | 1 + public/language/pt-BR/error.json | 1 + public/language/pt-PT/error.json | 1 + public/language/ro/error.json | 1 + public/language/ru/error.json | 1 + public/language/rw/error.json | 1 + public/language/sc/error.json | 1 + public/language/sk/error.json | 1 + public/language/sl/error.json | 1 + public/language/sq-AL/error.json | 1 + public/language/sr/error.json | 1 + public/language/sv/error.json | 1 + public/language/th/error.json | 1 + public/language/tr/error.json | 1 + public/language/uk/error.json | 1 + public/language/vi/error.json | 1 + public/language/zh-CN/error.json | 1 + public/language/zh-TW/error.json | 1 + 48 files changed, 48 insertions(+) diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 4c9d77e775..7c7c5263bd 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": "البحث في المنتدى يتطلب حساب - الرجاء تسجيل الدخول أو التسجيل", diff --git a/public/language/az/error.json b/public/language/az/error.json index 4c364364fb..b166b6697d 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.", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 78566e8bf1..ef2e08eb2e 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": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "Изглежда не сте се вписали в системата.", "account-locked": "Вашият акаунт беше заключен временно", "search-requires-login": "Търсенето изисква регистриран акаунт! Моля, впишете се или се регистрирайте!", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 19157e3724..20d1ba7460 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.", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index be1a799f5b..20e0f99402 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": "Network requests to reserved IP ranges are not allowed.", "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.", diff --git a/public/language/da/error.json b/public/language/da/error.json index 657c4a479d..74c2493e70 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.", diff --git a/public/language/de/error.json b/public/language/de/error.json index 8c44aceccd..df536bf3b8 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": "Network requests to reserved IP ranges are not allowed.", "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.", diff --git a/public/language/el/error.json b/public/language/el/error.json index 9043105827..eacb718b80 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.", diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index 535a568d1e..ac1d640df8 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.", diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json index 535a568d1e..ac1d640df8 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.", diff --git a/public/language/es/error.json b/public/language/es/error.json index b1e53c4474..0eb92764ec 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.", diff --git a/public/language/et/error.json b/public/language/et/error.json index caffd57a57..fed9ae8c6a 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.", diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json index 827290a155..c3a0d69b51 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": "استفاده از جستجو نیازمند ورود با نام‌کاربری و رمز‌عبور است. لطفا ابتدا وارد شوید.", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 905a03415a..bf7ac10acb 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.", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 94ea74f7ab..6850f40cfa 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.", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 40b34fe8be..90a746455a 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.", diff --git a/public/language/he/error.json b/public/language/he/error.json index 82af4336b3..8e8701f593 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": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "נראה שאינכם מחוברים למערכת.", "account-locked": "חשבונכם נחסם באופן זמני", "search-requires-login": "חיפוש מצריך חשבון - אנא הירשמו או התחברו.", diff --git a/public/language/hr/error.json b/public/language/hr/error.json index 9160c18653..64e727f74e 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.", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 63e9d7f465..d57301a8f8 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.", diff --git a/public/language/hy/error.json b/public/language/hy/error.json index 553a5f00b4..9f7ada1b52 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": "Որոնումը պահանջում է հաշիվ. խնդրում ենք մուտք գործել կամ գրանցվել:", diff --git a/public/language/id/error.json b/public/language/id/error.json index 7cc7b20821..b531cf59a7 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.", diff --git a/public/language/it/error.json b/public/language/it/error.json index 1b862ddede..b71b169fd9 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -3,6 +3,7 @@ "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": "Network requests to reserved IP ranges are not allowed.", "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!", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index ce848d8b34..711ba3f8d7 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": "検索するにはアカウントが必要です - ログインするかアカウントを作成してください。", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index b81d681962..4efa32a96d 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": "검색에는 계정이 필요합니다. 로그인하거나 등록하세요.", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index 1b1c92380d..0c12b909ff 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", diff --git a/public/language/lv/error.json b/public/language/lv/error.json index e902e1e6b7..e61d791583 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.", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index a1c4660876..7a62ec9697 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.", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index f7e2f4a8f6..ce8b49bedc 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": "Network requests to reserved IP ranges are not allowed.", "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.", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index 83670b19ba..38fc553a01 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.", diff --git a/public/language/nn-NO/error.json b/public/language/nn-NO/error.json index 3325de8889..2f8a10bbec 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.", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 135608bb95..5f7f39d7e7 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": "Network requests to reserved IP ranges are not allowed.", "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.", diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json index 111b323f42..7c3299664d 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.", diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json index 95fa2edfa4..cef7efc5e3 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.", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index 472aa0fa53..be141aba93 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.", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 89555f0367..4b428d82b9 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": "Поиск доступен только для зарегистрированных участников. Пожалуйста, войдите или зарегистрируйтесь.", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 7c0dd10704..a086230d94 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.", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 535a568d1e..ac1d640df8 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.", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 8015c6c2b6..90c552fff9 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.", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index d87f7af441..1f793c9618 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.", diff --git a/public/language/sq-AL/error.json b/public/language/sq-AL/error.json index 28c75032ae..7bff3fb57a 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.", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 8200be23b1..3bec40bc5b 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": "Претраживање захтева налог — пријавите се или се региструјте.", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 6aad430f33..0fdd062e90 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.", diff --git a/public/language/th/error.json b/public/language/th/error.json index 16672afaef..062d51afa9 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": "\"ฟังก์ชั่นการค้นหา\" ต้องการบัญชีผู้ใช้ กรุณาเข้าสู่ระบบหรือสมัครสมาชิก", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index a31b16828a..1b4093991a 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.", diff --git a/public/language/uk/error.json b/public/language/uk/error.json index f5ead51c58..33ea4560f3 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": "Для пошуку потрібен акаунт — будь ласка, увійдіть чи зареєструйтесь.", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index c95f985908..5d84e951e9 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": "Network requests to reserved IP ranges are not allowed.", "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ý.", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index dd760f4ad1..186940d8a8 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": "Network requests to reserved IP ranges are not allowed.", "not-logged-in": "您还没有登录。", "account-locked": "您的帐号已被临时锁定", "search-requires-login": "搜索功能仅限会员使用 - 请先登录或者注册。", diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index f7b64e8001..3b78a51fbb 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": "搜尋功能僅限成員使用 - 請先登入或者註冊。", From 6411c19765050f12864bfb04678adb8fbe271be1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 2 Jun 2025 11:58:54 -0400 Subject: [PATCH 037/828] fix: #13459, unread indicators for remote categories --- src/topics/unread.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index db0e9c0f9e..4dd5a94435 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -351,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), @@ -361,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 }; }); From 0ccfe1dfe9a5c11bbfac6156ec67adee781fdead Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 3 Jun 2025 09:20:10 +0000 Subject: [PATCH 038/828] Latest translations and fallbacks --- public/language/bg/error.json | 2 +- public/language/pl/error.json | 2 +- public/language/vi/error.json | 2 +- public/language/zh-CN/error.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/bg/error.json b/public/language/bg/error.json index ef2e08eb2e..ab85561c34 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -3,7 +3,7 @@ "invalid-json": "Неправилен JSON", "wrong-parameter-type": "За свойството `%1` се очакваше стойност от тип %3, но вместо това беше получено %2", "required-parameters-missing": "Липсват задължителни параметри от това извикване към ППИ: %1", - "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.", + "reserved-ip-address": "Мрежовите заявки до IP адреси от резервирани области не са позволени.", "not-logged-in": "Изглежда не сте се вписали в системата.", "account-locked": "Вашият акаунт беше заключен временно", "search-requires-login": "Търсенето изисква регистриран акаунт! Моля, впишете се или се регистрирайте!", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 5f7f39d7e7..4e9c0cc58a 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -3,7 +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": "Network requests to reserved IP ranges are not allowed.", + "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.", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 5d84e951e9..cbd7c3798a 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -3,7 +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": "Network requests to reserved IP ranges are not allowed.", + "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ý.", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index 186940d8a8..42a7901772 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -3,7 +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.", + "reserved-ip-address": "不允许向保留 IP 地址发送网络请求。", "not-logged-in": "您还没有登录。", "account-locked": "您的帐号已被临时锁定", "search-requires-login": "搜索功能仅限会员使用 - 请先登录或者注册。", From ea91dc00cd7359a0c81ed7af29e990ada4a6b989 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 4 Jun 2025 09:20:16 +0000 Subject: [PATCH 039/828] Latest translations and fallbacks --- public/language/it/error.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/it/error.json b/public/language/it/error.json index b71b169fd9..abf63e8df8 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -3,7 +3,7 @@ "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": "Network requests to reserved IP ranges are not allowed.", + "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!", From 010113a9a00280663822b17696c56f3f8d1227ec Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 4 Jun 2025 13:19:29 -0400 Subject: [PATCH 040/828] fix: wrap cached returns for dns lookups in nextTick --- src/request.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/request.js b/src/request.js index aaf0d55b32..fe8de851c1 100644 --- a/src/request.js +++ b/src/request.js @@ -58,12 +58,15 @@ function lookup(hostname, options, callback) { return; } - if (options.all === true) { - callback(null, lookup); - } else { - const { address, family } = lookup.shift(); - callback(null, address, family); - } + // 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 From 3694f6555bca1ce0f16c75096eaced0260f83e33 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:54:37 -0400 Subject: [PATCH 041/828] fix(deps): update dependency cron to v4.3.1 (#13457) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5d454bf7ed..ddd5520400 100644 --- a/install/package.json +++ b/install/package.json @@ -61,7 +61,7 @@ "connect-pg-simple": "10.0.0", "connect-redis": "8.1.0", "cookie-parser": "1.4.7", - "cron": "4.3.0", + "cron": "4.3.1", "cropperjs": "1.6.2", "csrf-sync": "4.2.1", "daemon": "1.1.0", From efb14ead1d4d2e2ba7370eea179b044ee192686f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 5 Jun 2025 11:16:26 +0000 Subject: [PATCH 042/828] chore(i18n): fallback strings for new resources: nodebb.error --- public/language/ar/error.json | 1 + public/language/az/error.json | 1 + public/language/bg/error.json | 1 + public/language/bn/error.json | 1 + public/language/cs/error.json | 1 + public/language/da/error.json | 1 + public/language/de/error.json | 1 + public/language/el/error.json | 1 + public/language/en-US/error.json | 1 + public/language/en-x-pirate/error.json | 1 + public/language/es/error.json | 1 + public/language/et/error.json | 1 + public/language/fa-IR/error.json | 1 + public/language/fi/error.json | 1 + public/language/fr/error.json | 1 + public/language/gl/error.json | 1 + public/language/he/error.json | 1 + public/language/hr/error.json | 1 + public/language/hu/error.json | 1 + public/language/hy/error.json | 1 + public/language/id/error.json | 1 + public/language/it/error.json | 1 + public/language/ja/error.json | 1 + public/language/ko/error.json | 1 + public/language/lt/error.json | 1 + public/language/lv/error.json | 1 + public/language/ms/error.json | 1 + public/language/nb/error.json | 1 + public/language/nl/error.json | 1 + public/language/nn-NO/error.json | 1 + public/language/pl/error.json | 1 + public/language/pt-BR/error.json | 1 + public/language/pt-PT/error.json | 1 + public/language/ro/error.json | 1 + public/language/ru/error.json | 1 + public/language/rw/error.json | 1 + public/language/sc/error.json | 1 + public/language/sk/error.json | 1 + public/language/sl/error.json | 1 + public/language/sq-AL/error.json | 1 + public/language/sr/error.json | 1 + public/language/sv/error.json | 1 + public/language/th/error.json | 1 + public/language/tr/error.json | 1 + public/language/uk/error.json | 1 + public/language/vi/error.json | 1 + public/language/zh-CN/error.json | 1 + public/language/zh-TW/error.json | 1 + 48 files changed, 48 insertions(+) diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 7c7c5263bd..4d2ea94cba 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -237,6 +237,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", diff --git a/public/language/az/error.json b/public/language/az/error.json index b166b6697d..62ca9934ff 100644 --- a/public/language/az/error.json +++ b/public/language/az/error.json @@ -237,6 +237,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.", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index ab85561c34..122239e27c 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -237,6 +237,7 @@ "socket-reconnect-failed": "В момента сървърът е недостъпен. Натиснете тук, за да опитате отново, или опитайте пак по-късно.", "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": "Инсталирането на добавки чрез ACP е изключено", "plugins-set-in-configuration": "Не можете да променяте състоянието на добавката, тъй като то се определя по време на работата ѝ (чрез config.json, променливи на средата или аргументи при изпълнение). Вместо това може да промените конфигурацията.", "theme-not-set-in-configuration": "Когато определяте активните добавки в конфигурацията, промяната на темите изисква да се добави новата тема към активните добавки, преди актуализирането ѝ в ACP", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 20d1ba7460..3dfb852227 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -237,6 +237,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", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 20e0f99402..4295b7d97a 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -237,6 +237,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", diff --git a/public/language/da/error.json b/public/language/da/error.json index 74c2493e70..9418e8663c 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -237,6 +237,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", diff --git a/public/language/de/error.json b/public/language/de/error.json index df536bf3b8..dfb5a8da31 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -237,6 +237,7 @@ "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", "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", + "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 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.", diff --git a/public/language/el/error.json b/public/language/el/error.json index eacb718b80..70bedd30c8 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -237,6 +237,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", diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index ac1d640df8..c3bb2dc892 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -237,6 +237,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", diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json index ac1d640df8..c3bb2dc892 100644 --- a/public/language/en-x-pirate/error.json +++ b/public/language/en-x-pirate/error.json @@ -237,6 +237,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", diff --git a/public/language/es/error.json b/public/language/es/error.json index 0eb92764ec..27a8776929 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -237,6 +237,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", diff --git a/public/language/et/error.json b/public/language/et/error.json index fed9ae8c6a..7fd4027cec 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -237,6 +237,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", diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json index c3a0d69b51..7660c66770 100644 --- a/public/language/fa-IR/error.json +++ b/public/language/fa-IR/error.json @@ -237,6 +237,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", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index bf7ac10acb..d7d1af89ea 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -237,6 +237,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", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 6850f40cfa..d22c59af08 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -237,6 +237,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", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 90a746455a..7669c41b8f 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -237,6 +237,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", diff --git a/public/language/he/error.json b/public/language/he/error.json index 8e8701f593..a8e7a433d5 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -237,6 +237,7 @@ "socket-reconnect-failed": "לא ניתן להגיע לשרת בשלב זה. לחצו כאן כדי לנסות שוב, או נסו שוב במועד מאוחר יותר", "invalid-plugin-id": "מזהה תוסף לא תקין", "plugin-not-whitelisted": "לא ניתן להתקין את התוסף – ניתן להתקין דרך הניהול רק תוספים שנמצאים ברשימה הלבנה של מנהל החבילות של NodeBB.", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "plugin-installation-via-acp-disabled": "התקנת תוסף באמצעות ACP מושבתת", "plugins-set-in-configuration": "לא ניתן לשנות את מצב התוסף כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), שנו את התצורה במקום זאת.", "theme-not-set-in-configuration": "כאשר מגדירים תוספים פעילים בתצורה, שינוי ערכות נושא מחייב הוספת ערכת הנושא החדשה לרשימת התוספים הפעילים לפני עדכון שלו ב-ACP", diff --git a/public/language/hr/error.json b/public/language/hr/error.json index 64e727f74e..4e2d576d5c 100644 --- a/public/language/hr/error.json +++ b/public/language/hr/error.json @@ -237,6 +237,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", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index d57301a8f8..e337533e57 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -237,6 +237,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", diff --git a/public/language/hy/error.json b/public/language/hy/error.json index 9f7ada1b52..e6c4a3c18b 100644 --- a/public/language/hy/error.json +++ b/public/language/hy/error.json @@ -237,6 +237,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-ում:", diff --git a/public/language/id/error.json b/public/language/id/error.json index b531cf59a7..21d344ce72 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -237,6 +237,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", diff --git a/public/language/it/error.json b/public/language/it/error.json index abf63e8df8..bddf939887 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -237,6 +237,7 @@ "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", + "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", "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", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index 711ba3f8d7..be0b4396d1 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -237,6 +237,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", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 4efa32a96d..80ee9f08e5 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -237,6 +237,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에서 테마를 업데이트해야 합니다", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index 0c12b909ff..5fbf1188c4 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -237,6 +237,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", diff --git a/public/language/lv/error.json b/public/language/lv/error.json index e61d791583..6195541875 100644 --- a/public/language/lv/error.json +++ b/public/language/lv/error.json @@ -237,6 +237,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", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 7a62ec9697..26e1b5a310 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -237,6 +237,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", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index ce8b49bedc..74d4ffbdac 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -237,6 +237,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.", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index 38fc553a01..662a161197 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -237,6 +237,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", diff --git a/public/language/nn-NO/error.json b/public/language/nn-NO/error.json index 2f8a10bbec..1268223a30 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -237,6 +237,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", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 4e9c0cc58a..acaf834286 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -237,6 +237,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": "You cannot toggle the state of a system plugin", "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ć.", diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json index 7c3299664d..2fc1039862 100644 --- a/public/language/pt-BR/error.json +++ b/public/language/pt-BR/error.json @@ -237,6 +237,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", diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json index cef7efc5e3..fa0418c41c 100644 --- a/public/language/pt-PT/error.json +++ b/public/language/pt-PT/error.json @@ -237,6 +237,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", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index be141aba93..abc63f3b06 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -237,6 +237,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", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 4b428d82b9..d741a098a1 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -237,6 +237,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", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index a086230d94..0452b4fc11 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -237,6 +237,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", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index ac1d640df8..c3bb2dc892 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -237,6 +237,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", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 90c552fff9..cd21ec05a3 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -237,6 +237,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", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index 1f793c9618..ac9775d30b 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -237,6 +237,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", diff --git a/public/language/sq-AL/error.json b/public/language/sq-AL/error.json index 7bff3fb57a..b1aab456a0 100644 --- a/public/language/sq-AL/error.json +++ b/public/language/sq-AL/error.json @@ -237,6 +237,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", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 3bec40bc5b..ec8ef67b20 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -237,6 +237,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", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 0fdd062e90..19a3580440 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -237,6 +237,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", diff --git a/public/language/th/error.json b/public/language/th/error.json index 062d51afa9..529a74d404 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -237,6 +237,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": "เมื่อกำหนดปลั๊กอันที่กำลังทำงานในส่วนตั้งค่า การเปลี่ยนธีมต้องเพิ่มทีมในรายการปลั๊กอินที่กำลังใช้งานก่อนที่จะเปลี่ยนในแผงควบคุมผู้ดูแล", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 1b4093991a..ea93e9abe6 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -237,6 +237,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", diff --git a/public/language/uk/error.json b/public/language/uk/error.json index 33ea4560f3..002b6a8471 100644 --- a/public/language/uk/error.json +++ b/public/language/uk/error.json @@ -237,6 +237,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", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index cbd7c3798a..a60add62f9 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -237,6 +237,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": "You cannot toggle the state of a system plugin", "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", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index 42a7901772..48103d6188 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -237,6 +237,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": "ACP 安装插件已被禁用", "plugins-set-in-configuration": "您不能修改插件状态因为它们在运行时中被定义(config.json,环境变量或终端选项),请转而修改配置。", "theme-not-set-in-configuration": "在配置中定义活跃的插件时,需要先将新主题加入活跃插件的列表,才能在管理员控制面板中修改主题", diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index 3b78a51fbb..14a9bcdbbb 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -237,6 +237,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", From a3cc99a2f07de51d33231dff75ded8504b833f89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:17:11 -0400 Subject: [PATCH 043/828] fix(deps): update dependency mongodb to v6.17.0 (#13471) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3a6dc38e22..70a231b0e4 100644 --- a/install/package.json +++ b/install/package.json @@ -91,7 +91,7 @@ "lru-cache": "11.1.0", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.16.0", + "mongodb": "6.17.0", "morgan": "1.10.0", "mousetrap": "1.6.5", "multer": "2.0.0", From c363b84e90bbad19ae33356dfbc119a20a5d857d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:17:34 -0400 Subject: [PATCH 044/828] fix(deps): update dependency ace-builds to v1.42.0 (#13470) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 70a231b0e4..c326eb69c7 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.41.0", + "ace-builds": "1.42.0", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", From 602417d0f91526e41d9d15625db09c41b45ba4a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:17:56 -0400 Subject: [PATCH 045/828] fix(deps): update dependency sass to v1.89.1 (#13467) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c326eb69c7..14516f7473 100644 --- a/install/package.json +++ b/install/package.json @@ -127,7 +127,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.89.0", + "sass": "1.89.1", "satori": "0.13.1", "semver": "7.7.2", "serve-favicon": "2.5.0", From d0060e5d7119f53ecb5600a50e61ec8cfe425e83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:19:13 -0400 Subject: [PATCH 046/828] fix(deps): update dependency multer to v2.0.1 (#13466) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 14516f7473..8c196c6567 100644 --- a/install/package.json +++ b/install/package.json @@ -94,7 +94,7 @@ "mongodb": "6.17.0", "morgan": "1.10.0", "mousetrap": "1.6.5", - "multer": "2.0.0", + "multer": "2.0.1", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.2.50", From 1c432925cdd83476e2e196f401a56cac31ef161c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:19:30 -0400 Subject: [PATCH 047/828] fix(deps): update dependency postcss to v8.5.4 (#13453) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8c196c6567..b248cbe0c5 100644 --- a/install/package.json +++ b/install/package.json @@ -118,7 +118,7 @@ "passport-local": "1.0.0", "pg": "8.16.0", "pg-cursor": "2.15.0", - "postcss": "8.5.3", + "postcss": "8.5.4", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", From 32f13162dc65cf1e49378ca9413a9b8a114012f9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:19:56 -0400 Subject: [PATCH 048/828] chore(deps): update dependency sass-embedded to v1.89.1 (#13463) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b248cbe0c5..17c2ecec06 100644 --- a/install/package.json +++ b/install/package.json @@ -177,7 +177,7 @@ "smtp-server": "3.13.7" }, "optionalDependencies": { - "sass-embedded": "1.89.0" + "sass-embedded": "1.89.1" }, "resolutions": { "*/jquery": "3.7.1" From 6478532bf5e8e02050f31147f9f4665dd0e882d5 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 5 Jun 2025 11:28:47 -0400 Subject: [PATCH 049/828] fix: ensure check returns false if no addresses are looked up, fix bug where cached value got changed accidentally --- src/request.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/request.js b/src/request.js index fe8de851c1..ce5e95d5bb 100644 --- a/src/request.js +++ b/src/request.js @@ -45,7 +45,8 @@ async function init() { * - For whatever reason `undici` needs to be required so that lookup can be overwritten properly. */ function lookup(hostname, options, callback) { - const { ok, lookup } = checkCache.get(hostname); + let { ok, lookup } = checkCache.get(hostname); + lookup = [...lookup]; if (!ok) { throw new Error('lookup-failed'); } @@ -166,6 +167,10 @@ async function check(url) { }); } + 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); From 44d1a17bc59a15eb74d35055b85b3a1774e16995 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:46:25 -0400 Subject: [PATCH 050/828] fix(deps): update dependency satori to v0.13.2 (#13468) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 17c2ecec06..0ae491693d 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.89.1", - "satori": "0.13.1", + "satori": "0.13.2", "semver": "7.7.2", "serve-favicon": "2.5.0", "sharp": "0.32.6", From 01b10170aafd03961e9c6a5b950587901f7e00aa Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 6 Jun 2025 09:20:17 +0000 Subject: [PATCH 051/828] Latest translations and fallbacks --- public/language/bg/error.json | 2 +- public/language/it/error.json | 2 +- public/language/pl/error.json | 2 +- public/language/zh-CN/error.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 122239e27c..94d12a36b1 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -237,7 +237,7 @@ "socket-reconnect-failed": "В момента сървърът е недостъпен. Натиснете тук, за да опитате отново, или опитайте пак по-късно.", "invalid-plugin-id": "Грешен идентификатор на добавка", "plugin-not-whitelisted": "Добавката не може да бъде инсталирана – само добавки, одобрени от пакетния мениджър на NodeBB могат да бъдат инсталирани чрез ACP", - "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", + "cannot-toggle-system-plugin": "Не можете да превключите състоянието на системна добавка", "plugin-installation-via-acp-disabled": "Инсталирането на добавки чрез ACP е изключено", "plugins-set-in-configuration": "Не можете да променяте състоянието на добавката, тъй като то се определя по време на работата ѝ (чрез config.json, променливи на средата или аргументи при изпълнение). Вместо това може да промените конфигурацията.", "theme-not-set-in-configuration": "Когато определяте активните добавки в конфигурацията, промяната на темите изисква да се добави новата тема към активните добавки, преди актуализирането ѝ в ACP", diff --git a/public/language/it/error.json b/public/language/it/error.json index bddf939887..0fb310b196 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -237,7 +237,7 @@ "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", - "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", + "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", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index acaf834286..27cb6c9d42 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -237,7 +237,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": "You cannot toggle the state of a system plugin", + "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ć.", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index 48103d6188..e91848b78d 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -237,7 +237,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", + "cannot-toggle-system-plugin": "您不能切换系统插件的状态", "plugin-installation-via-acp-disabled": "ACP 安装插件已被禁用", "plugins-set-in-configuration": "您不能修改插件状态因为它们在运行时中被定义(config.json,环境变量或终端选项),请转而修改配置。", "theme-not-set-in-configuration": "在配置中定义活跃的插件时,需要先将新主题加入活跃插件的列表,才能在管理员控制面板中修改主题", From b3170c9c8ba4172f2401839179731050ebb7b503 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:08:13 -0400 Subject: [PATCH 052/828] chore(deps): update dependency @eslint/js to v9.28.0 (#13469) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0ae491693d..fd02304c27 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.27.0", + "@eslint/js": "9.28.0", "@stylistic/eslint-plugin": "4.4.0", "eslint-config-nodebb": "1.1.6", "eslint-plugin-import": "2.31.0", From 166aaa7ab9379fc2ac2fb9ad6b908e21aceacaa4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:08:25 -0400 Subject: [PATCH 053/828] chore(deps): update redis docker tag to v8.0.2 (#13465) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 43fdf33611..cf72066b92 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.0.1' + image: 'redis:8.0.2' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 9011d0f92a..8a67d5964d 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.0.1-alpine + image: redis:8.0.2-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 da31cd6886..d8855ccac4 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:8.0.1-alpine + image: redis:8.0.2-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 637cecb0cd..a53acdfef2 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:8.0.1-alpine + image: redis:8.0.2-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 From 6b33b1f457a06416881437a814c1df2999daa080 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:08:44 -0400 Subject: [PATCH 054/828] fix(deps): update dependency workerpool to v9.3.2 (#13452) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fd02304c27..bcab3b1d66 100644 --- a/install/package.json +++ b/install/package.json @@ -150,7 +150,7 @@ "webpack": "5.99.9", "webpack-merge": "6.0.1", "winston": "3.17.0", - "workerpool": "9.2.0", + "workerpool": "9.3.2", "xml": "1.0.1", "xregexp": "5.1.2", "yargs": "17.7.2", From d239125f438e6ba8a514524f0278898896002fc5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:08:54 -0400 Subject: [PATCH 055/828] chore(deps): update dependency smtp-server to v3.13.8 (#13464) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index bcab3b1d66..e9a67cd2c0 100644 --- a/install/package.json +++ b/install/package.json @@ -174,7 +174,7 @@ "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.13.7" + "smtp-server": "3.13.8" }, "optionalDependencies": { "sass-embedded": "1.89.1" From 536ae9d6a577926aef93c3bf601c4914f6f9f3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 6 Jun 2025 11:26:02 -0400 Subject: [PATCH 056/828] chore: up eslint --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index e9a67cd2c0..117a6d013d 100644 --- a/install/package.json +++ b/install/package.json @@ -162,8 +162,8 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.28.0", - "@stylistic/eslint-plugin": "4.4.0", - "eslint-config-nodebb": "1.1.6", + "@stylistic/eslint-plugin": "4.4.1", + "eslint-config-nodebb": "1.1.7", "eslint-plugin-import": "2.31.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", From 29afcd36b51946f6267f4ba3a1e5f49c89bf3a39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:18:57 -0400 Subject: [PATCH 057/828] fix(deps): update dependency satori to v0.14.0 (#13476) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 117a6d013d..58d09b18cf 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.89.1", - "satori": "0.13.2", + "satori": "0.14.0", "semver": "7.7.2", "serve-favicon": "2.5.0", "sharp": "0.32.6", From f157cfa7e8225dd6fd904cd1c2c6ff4160a3dae1 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 8 Jun 2025 09:19:19 +0000 Subject: [PATCH 058/828] Latest translations and fallbacks --- public/language/vi/error.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/vi/error.json b/public/language/vi/error.json index a60add62f9..467b78742c 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -237,7 +237,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": "You cannot toggle the state of a system plugin", + "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", From 9b4082dcfbec77e72437975e76b2ba2de4e3572b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:54:01 -0400 Subject: [PATCH 059/828] chore(deps): update dependency mocha to v11.6.0 (#13479) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 58d09b18cf..a1b3026c7b 100644 --- a/install/package.json +++ b/install/package.json @@ -170,7 +170,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.1.0", - "mocha": "11.5.0", + "mocha": "11.6.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From 78ebe2988bb6ed76c95120116f2319ea986469e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:55:37 -0400 Subject: [PATCH 060/828] fix(deps): update dependency satori to v0.15.2 (#13481) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 897109b8e2..1e31e33e2e 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.89.1", - "satori": "0.14.0", + "satori": "0.15.2", "semver": "7.7.2", "serve-favicon": "2.5.0", "sharp": "0.32.6", From 2280ea88f24ff82a8d1ec1347dade0c525b41f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 10 Jun 2025 12:46:07 -0400 Subject: [PATCH 061/828] fix: typo --- public/src/client/topic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 3bb50a5da5..f332a807be 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -363,7 +363,7 @@ define('forum/topic', [ const { top } = link.get(0).getBoundingClientRect(); const dropup = top > window.innerHeight / 2; tooltip.on('mouseenter', function () { - onPreviewTooltip = true; + cursorOnPreviewTooltip = true; }); tooltip.one('mouseleave', destroyTooltip); $(window).off('click', onClickOutside).one('click', onClickOutside); From 95ae8b5f1a109996f2122ae7a51a9fbadd670279 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 11 Jun 2025 09:19:40 +0000 Subject: [PATCH 062/828] Latest translations and fallbacks --- public/language/nb/category.json | 4 ++-- public/language/nb/error.json | 6 +++--- public/language/nb/user.json | 10 +++++----- public/language/nb/world.json | 14 +++++++------- public/language/nn-NO/category.json | 4 ++-- public/language/nn-NO/user.json | 4 ++-- public/language/nn-NO/world.json | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/public/language/nb/category.json b/public/language/nb/category.json index e114c8a083..5da2e6af1f 100644 --- a/public/language/nb/category.json +++ b/public/language/nb/category.json @@ -1,8 +1,8 @@ { "category": "Kategori", "subcategories": "Underkategorier", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "Ukategorisert", + "uncategorized.description": "Innlegg som ikke passer inn i noen av de eksisterende kategoriene.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nytt innlegg", "guest-login-post": "Logg inn for å publisere innlegg", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 74d4ffbdac..e7fbac1aaa 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -3,7 +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": "Network requests to reserved IP ranges are not allowed.", + "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.", @@ -68,8 +68,8 @@ "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", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index efcbc0c1d7..10f16695ae 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -59,7 +59,7 @@ "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", "follow": "Følg", @@ -105,10 +105,10 @@ "show-email": "Vis min e-post", "show-fullname": "Vis mitt fulle navn", "restrict-chats": "Bare tillat chat-meldinger fra brukere 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", + "disable-incoming-chats": "Slå av meldinger fra chat ", + "chat-allow-list": "Bare tillat chat-meldinger fra følgende brukere", + "chat-deny-list": "Ikke tillat chat-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 innlegg) i samsvar med valgte tidspunkt", "digest-off": "Av", diff --git a/public/language/nb/world.json b/public/language/nb/world.json index d61c766c85..c2d9bb4bc2 100644 --- a/public/language/nb/world.json +++ b/public/language/nb/world.json @@ -7,15 +7,15 @@ "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!", - "show-categories": "Show categories", - "hide-categories": "Hide categories" + "show-categories": "Vis kategorier", + "hide-categories": "Skjul kategorier" } \ No newline at end of file diff --git a/public/language/nn-NO/category.json b/public/language/nn-NO/category.json index 152c69d345..2ef1b7bff5 100644 --- a/public/language/nn-NO/category.json +++ b/public/language/nn-NO/category.json @@ -1,8 +1,8 @@ { "category": "Kategori", "subcategories": "Underkategoriar", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "Ukategorisert", + "uncategorized.description": "Innlegg som ikkje passar helt inn i nokon av dei eksisterande kategoriane.", "handle.description": "This category can be followed from the open social web via the handle %1", "new-topic-button": "Nytt innlegg", "guest-login-post": "Logg inn for å legge inn innlegg", diff --git a/public/language/nn-NO/user.json b/public/language/nn-NO/user.json index cd615d95be..702ba8b92a 100644 --- a/public/language/nn-NO/user.json +++ b/public/language/nn-NO/user.json @@ -59,7 +59,7 @@ "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", "follow": "Følg", @@ -105,7 +105,7 @@ "show-email": "Vis e-posten min", "show-fullname": "Vis fullt namn", "restrict-chats": "Tillat berre chatmeldingar frå brukarar eg følgjer", - "disable-incoming-chats": "Disable incoming chat messages ", + "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", diff --git a/public/language/nn-NO/world.json b/public/language/nn-NO/world.json index 71be5a0749..978f5841bf 100644 --- a/public/language/nn-NO/world.json +++ b/public/language/nn-NO/world.json @@ -14,7 +14,7 @@ "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.how": "I mellomtida kan du klikke på snarvegsknappane øvst for å sjå kva anna dette forumet inneheld, og begynne å oppdage nytt innhald!", "show-categories": "Show categories", "hide-categories": "Hide categories" From f56517878288cac09b12f27c354e4a4bf2002c97 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:37:52 -0400 Subject: [PATCH 063/828] chore(deps): update dependency sass-embedded to v1.89.2 (#13482) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1e31e33e2e..a17eb36a4c 100644 --- a/install/package.json +++ b/install/package.json @@ -177,7 +177,7 @@ "smtp-server": "3.13.8" }, "optionalDependencies": { - "sass-embedded": "1.89.1" + "sass-embedded": "1.89.2" }, "resolutions": { "*/jquery": "3.7.1" From c04bd7cc6e6569440b82f86218e1f3b3ff559248 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:38:12 -0400 Subject: [PATCH 064/828] fix(deps): update dependency @fontsource/inter to v5.2.6 (#13477) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a17eb36a4c..c8f0dca264 100644 --- a/install/package.json +++ b/install/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@adactive/bootstrap-tagsinput": "0.8.2", - "@fontsource/inter": "5.2.5", + "@fontsource/inter": "5.2.6", "@fontsource/poppins": "5.2.6", "@fortawesome/fontawesome-free": "6.7.2", "@isaacs/ttlcache": "1.4.1", From d2a7eecb28ec267b6ec9abb2dd150328d98ad202 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:02:25 -0400 Subject: [PATCH 065/828] fix(deps): update dependency serve-favicon to v2.5.1 (#13488) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c8f0dca264..17e51cfc99 100644 --- a/install/package.json +++ b/install/package.json @@ -130,7 +130,7 @@ "sass": "1.89.1", "satori": "0.15.2", "semver": "7.7.2", - "serve-favicon": "2.5.0", + "serve-favicon": "2.5.1", "sharp": "0.32.6", "sitemap": "8.0.0", "socket.io": "4.8.1", From efcbbf29d161290ab4f14b40ecac34279eb94500 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:02:43 -0400 Subject: [PATCH 066/828] fix(deps): update dependency nodebb-plugin-emoji to v6.0.3 (#13486) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 17e51cfc99..5df2e774ab 100644 --- a/install/package.json +++ b/install/package.json @@ -99,7 +99,7 @@ "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.2.51", "nodebb-plugin-dbsearch": "6.2.19", - "nodebb-plugin-emoji": "6.0.2", + "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.2.1", "nodebb-plugin-mentions": "4.7.6", From 442c6e71c0d0fb9c0b575be6bf6a6c4e721b1f96 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:04:53 -0400 Subject: [PATCH 067/828] fix(deps): update dependency sass to v1.89.2 (#13487) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5df2e774ab..1ec373b8a9 100644 --- a/install/package.json +++ b/install/package.json @@ -127,7 +127,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.89.1", + "sass": "1.89.2", "satori": "0.15.2", "semver": "7.7.2", "serve-favicon": "2.5.1", From c101d0d5afa70cab727f76f31a3f0c0faf678901 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:59:25 -0400 Subject: [PATCH 068/828] fix(deps): update dependency postcss to v8.5.5 (#13490) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1ec373b8a9..3c4d7c11e1 100644 --- a/install/package.json +++ b/install/package.json @@ -118,7 +118,7 @@ "passport-local": "1.0.0", "pg": "8.16.0", "pg-cursor": "2.15.0", - "postcss": "8.5.4", + "postcss": "8.5.5", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", From 703fcbbf36bac82f0cc0e2ef8a92fce92a445a5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:02:18 -0400 Subject: [PATCH 069/828] fix(deps): update dependency postcss to v8.5.6 (#13494) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3c4d7c11e1..d6eb0b1466 100644 --- a/install/package.json +++ b/install/package.json @@ -118,7 +118,7 @@ "passport-local": "1.0.0", "pg": "8.16.0", "pg-cursor": "2.15.0", - "postcss": "8.5.5", + "postcss": "8.5.6", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", From d6ba79302df38990b258c0aa9b734dc95cbc8177 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:09:05 -0400 Subject: [PATCH 070/828] chore(deps): update dependency lint-staged to v16.1.2 (#13492) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d6eb0b1466..43f7637970 100644 --- a/install/package.json +++ b/install/package.json @@ -169,7 +169,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "26.1.0", - "lint-staged": "16.1.0", + "lint-staged": "16.1.2", "mocha": "11.6.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From f36a5ac8928f1dbf33ca8aa86cb56a24f144607b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:10:55 -0400 Subject: [PATCH 071/828] fix(deps): update dependency chart.js to v4.5.0 (#13495) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 43f7637970..df6c0fd5fb 100644 --- a/install/package.json +++ b/install/package.json @@ -50,7 +50,7 @@ "bootstrap": "5.3.6", "bootswatch": "5.3.6", "chalk": "4.1.2", - "chart.js": "4.4.9", + "chart.js": "4.5.0", "cli-graph": "3.2.2", "clipboard": "2.0.11", "commander": "14.0.0", From 2046ca724ad3617e27fcf68d8cecb576a0309335 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 09:36:26 -0400 Subject: [PATCH 072/828] chore(deps): update dependency @eslint/js to v9.29.0 (#13491) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index df6c0fd5fb..d817989349 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@stylistic/eslint-plugin": "4.4.1", "eslint-config-nodebb": "1.1.7", "eslint-plugin-import": "2.31.0", From 14043ab0fd6efc3070cdca0b2c4f5cbfef3d7f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20U=C5=9Fakl=C4=B1?= Date: Wed, 18 Jun 2025 13:04:57 -0400 Subject: [PATCH 073/828] 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 --- install/package.json | 4 +- src/activitypub/index.js | 1 - src/controllers/activitypub/index.js | 5 +- src/controllers/helpers.js | 4 + src/database/redis.js | 2 +- src/database/redis/connection.js | 37 ++-- src/database/redis/hash.js | 48 +++-- src/database/redis/helpers.js | 38 ++-- src/database/redis/list.js | 22 ++- src/database/redis/main.js | 22 +-- src/database/redis/sets.js | 24 +-- src/database/redis/sorted.js | 237 ++++++++++++------------- 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/topics/posts.js | 2 +- test/activitypub/actors.js | 3 +- test/activitypub/notes.js | 4 +- test/database/sorted.js | 64 +++---- test/mocks/databasemock.js | 1 - 21 files changed, 304 insertions(+), 302 deletions(-) diff --git a/install/package.json b/install/package.json index d817989349..a42eb52182 100644 --- a/install/package.json +++ b/install/package.json @@ -98,7 +98,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.2.51", - "nodebb-plugin-dbsearch": "6.2.19", + "nodebb-plugin-dbsearch": "6.3.0", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.2.1", @@ -122,7 +122,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "ioredis": "5.6.1", + "redis": "5.5.6", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 80ec4a40f5..78cb3f26f6 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -132,7 +132,6 @@ ActivityPub.resolveInboxes = async (ids) => { }, [[], []]); 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)]; diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index de478a6021..6751cb30cd 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -145,14 +145,15 @@ Controller.postInbox = async (req, res) => { 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.`); + console.log('[activitypub/inbox] method not found', method, req.body); 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/helpers.js b/src/controllers/helpers.js index a6ade8c73b..e11692867e 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -448,6 +448,10 @@ helpers.getHomePageRoutes = async function (uid) { }; helpers.formatApiResponse = async (statusCode, res, payload) => { + if (!res.hasOwnProperty('req')) { + console.log('formatApiResponse', statusCode, payload); + } + if (res.req.method === 'HEAD') { return res.sendStatus(statusCode); } diff --git a/src/database/redis.js b/src/database/redis.js index f73ee79313..472dae8de6 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(); } diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index a4ba757ef6..2a38cf1a79 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,40 @@ connection.connect = async function (options) { let cxn; if (options.cluster) { - cxn = new Redis.Cluster(options.cluster, options.options); + const rootNodes = options.cluster.map(node => ({ url : `redis://${node.host}:${node.port}` })); + cxn = createCluster({ + ...options.options, + rootNodes: rootNodes, + }); } else if (options.sentinels) { - cxn = new Redis({ - sentinels: options.sentinels, + const sentinelRootNodes = options.sentinels.map(sentinel => ({ host: sentinel.host, port: sentinel.port })); + cxn = createSentinel({ ...options.options, + name: 'sentinel-db', + 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: { + reconnectStrategy: 3000, + }, }); } @@ -49,9 +61,14 @@ connection.connect = async function (options) { }); cxn.on('ready', () => { // back-compat with node_redis - cxn.batch = cxn.pipeline; + cxn.batch = cxn.multi; resolve(cxn); }); + cxn.connect().then(() => { + winston.info('Connected to Redis successfully'); + }).catch((err) => { + winston.error('Error connecting to Redis:', err); + }); if (options.password) { cxn.auth(options.password); diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 4c6e7b374f..c03bff055b 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); @@ -92,9 +102,9 @@ module.exports = function (module) { 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]) }; +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 objects; + 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/sets.js b/src/database/redis/sets.js index b2b390598b..dd7e484325 100644 --- a/src/database/redis/sets.js +++ b/src/database/redis/sets.js @@ -10,7 +10,7 @@ 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) { @@ -18,7 +18,7 @@ module.exports = function (module) { return; } const batch = module.client.batch(); - keys.forEach(k => batch.sadd(String(k), String(value))); + keys.forEach(k => batch.sAdd(String(k), String(value))); await helpers.execBatch(batch); }; @@ -34,57 +34,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..d8613a3a68 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))); - 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); + 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; + 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); - 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); + let data; + if (byScore) { + data = await module.client[cmd](key, min, max, opts); + } else { + data = await module.client[cmd](key, start, stop, opts); } - 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 []; + if (!withScores) { + return data; } - 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,65 +250,52 @@ 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'; @@ -318,20 +304,19 @@ module.exports = function (module) { 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/topics/posts.js b/src/topics/posts.js index e32c18e727..8201bcad02 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -442,7 +442,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/test/activitypub/actors.js b/test/activitypub/actors.js index 37fc74280d..ac2d11e5da 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -449,7 +449,8 @@ describe('Inbox resolution', () => { await activitypub.actors.assert(id); const inboxes = await activitypub.resolveInboxes([id]); - + console.log('inboxes', inboxes); + console.log('actor', actor); assert(inboxes && Array.isArray(inboxes)); assert.strictEqual(inboxes.length, 1); assert.strictEqual(inboxes[0], actor.inbox); diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index fbbf0c59ec..57ffce4259 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -650,7 +650,7 @@ describe('Notes', () => { it('should upvote an asserted remote post', async () => { const { id } = helpers.mocks.note(); - await activitypub.notes.assert(0, [id], { skipChecks: true }); + await activitypub.notes.assert(0, id, { skipChecks: true }); const { activity: like } = helpers.mocks.like({ object: id, }); @@ -672,7 +672,7 @@ describe('Notes', () => { it('should update a note\'s content', async () => { const { id: actor } = helpers.mocks.person(); const { id, note } = helpers.mocks.note({ attributedTo: actor }); - await activitypub.notes.assert(0, [id], { skipChecks: true }); + await activitypub.notes.assert(0, id, { skipChecks: true }); note.content = utils.generateUUID(); const { activity: update } = helpers.mocks.update({ object: note }); const { activity } = helpers.mocks.announce({ object: update }); diff --git a/test/database/sorted.js b/test/database/sorted.js index b98d969730..a375b2ec48 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -501,7 +501,9 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea ['byScoreWithScoresKeys1', 1, 'value1'], ['byScoreWithScoresKeys2', 2, 'value2'], ]); - const data = await db.getSortedSetRevRangeByScoreWithScores(['byScoreWithScoresKeys1', 'byScoreWithScoresKeys2'], 0, -1, 5, -5); + const data = await db.getSortedSetRevRangeByScoreWithScores([ + 'byScoreWithScoresKeys1', 'byScoreWithScoresKeys2', + ], 0, -1, 5, -5); assert.deepStrictEqual(data, [{ value: 'value2', score: 2 }, { value: 'value1', score: 1 }]); }); }); @@ -1144,23 +1146,17 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea assert.strictEqual(await db.exists('sorted3'), false); }); - it('should remove multiple values from multiple keys', (done) => { - db.sortedSetAdd('multiTest1', [1, 2, 3, 4], ['one', 'two', 'three', 'four'], (err) => { - assert.ifError(err); - db.sortedSetAdd('multiTest2', [3, 4, 5, 6], ['three', 'four', 'five', 'six'], (err) => { - assert.ifError(err); - db.sortedSetRemove(['multiTest1', 'multiTest2'], ['two', 'three', 'four', 'five', 'doesnt exist'], (err) => { - assert.ifError(err); - db.getSortedSetsMembers(['multiTest1', 'multiTest2'], (err, members) => { - assert.ifError(err); - assert.equal(members[0].length, 1); - assert.equal(members[1].length, 1); - assert.deepEqual(members, [['one'], ['six']]); - done(); - }); - }); - }); - }); + it('should remove multiple values from multiple keys', async () => { + await db.sortedSetAdd('multiTest1', [1, 2, 3, 4], ['one', 'two', 'three', 'four']); + await db.sortedSetAdd('multiTest2', [3, 4, 5, 6], ['three', 'four', 'five', 'six']); + + await db.sortedSetRemove(['multiTest1', 'multiTest2'], ['two', 'three', 'four', 'five', 'doesnt exist']); + + const members = await db.getSortedSetsMembers(['multiTest1', 'multiTest2']); + + assert.equal(members[0].length, 1); + assert.equal(members[1].length, 1); + assert.deepEqual(members, [['one'], ['six']]); }); it('should remove value from multiple keys', async () => { @@ -1171,24 +1167,15 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea assert.deepStrictEqual(await db.getSortedSetRange('multiTest4', 0, -1), ['four', 'five', 'six']); }); - it('should remove multiple values from multiple keys', (done) => { - db.sortedSetAdd('multiTest5', [1], ['one'], (err) => { - assert.ifError(err); - db.sortedSetAdd('multiTest6', [2], ['two'], (err) => { - assert.ifError(err); - db.sortedSetAdd('multiTest7', [3], [333], (err) => { - assert.ifError(err); - db.sortedSetRemove(['multiTest5', 'multiTest6', 'multiTest7'], ['one', 'two', 333], (err) => { - assert.ifError(err); - db.getSortedSetsMembers(['multiTest5', 'multiTest6', 'multiTest7'], (err, members) => { - assert.ifError(err); - assert.deepEqual(members, [[], [], []]); - done(); - }); - }); - }); - }); - }); + it('should remove multiple values from multiple keys', async () => { + await db.sortedSetAdd('multiTest5', [1], ['one']); + await db.sortedSetAdd('multiTest6', [2], ['two']); + await db.sortedSetAdd('multiTest7', [3], [333]); + + await db.sortedSetRemove(['multiTest5', 'multiTest6', 'multiTest7'], ['one', 'two', 333]); + + const members = await db.getSortedSetsMembers(['multiTest5', 'multiTest6', 'multiTest7']); + assert.deepEqual(members, [[], [], []]); }); it('should not remove anything if values is empty array', (done) => { @@ -1379,7 +1366,10 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea weights: [1, 0.5], }, (err, data) => { assert.ifError(err); - assert.deepEqual([{ value: 'value2', score: 4 }, { value: 'value3', score: 5.5 }], data); + assert.deepEqual([ + { value: 'value2', score: 4 }, + { value: 'value3', score: 5.5 }, + ], data); done(); }); }); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 6a568109b7..9416962b07 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -171,7 +171,6 @@ before(async function () { require('../../src/user').startJobs(); await webserver.listen(); - // Iterate over all of the test suites/contexts this.test.parent.suites.forEach((suite) => { // Attach an afterAll listener that resets the defaults From f7f70468fd5dda007adff3b7d2f604b4ee342d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 18 Jun 2025 13:17:29 -0400 Subject: [PATCH 074/828] fix: pubsub on node-redis --- src/database/redis/pubsub.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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); From d3faff3680d19c8a634569d3d072702f49e88e65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:18:36 -0400 Subject: [PATCH 075/828] fix(deps): update dependency connect-redis to v9 (#13497) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a42eb52182..2444e00d91 100644 --- a/install/package.json +++ b/install/package.json @@ -59,7 +59,7 @@ "connect-flash": "0.1.1", "connect-mongo": "5.1.0", "connect-pg-simple": "10.0.0", - "connect-redis": "8.1.0", + "connect-redis": "9.0.0", "cookie-parser": "1.4.7", "cron": "4.3.1", "cropperjs": "1.6.2", From e84fc7393915438e88b9436f8b26bdc6f2f9a331 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:18:50 -0400 Subject: [PATCH 076/828] fix(deps): update dependency bootstrap to v5.3.7 (#13499) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2444e00d91..4f9f9845f8 100644 --- a/install/package.json +++ b/install/package.json @@ -47,7 +47,7 @@ "benchpressjs": "2.5.5", "body-parser": "2.2.0", "bootbox": "6.0.4", - "bootstrap": "5.3.6", + "bootstrap": "5.3.7", "bootswatch": "5.3.6", "chalk": "4.1.2", "chart.js": "4.5.0", From 0a0dd1c14dbce63c8360bee5cbe1472b1a6c87c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:19:01 -0400 Subject: [PATCH 077/828] chore(deps): update dependency mocha to v11.7.0 (#13502) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4f9f9845f8..244486ef13 100644 --- a/install/package.json +++ b/install/package.json @@ -170,7 +170,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.1.2", - "mocha": "11.6.0", + "mocha": "11.7.0", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From 1fc91d5e751d36bd10eb9fea49ab0c6119315efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 18 Jun 2025 13:21:18 -0400 Subject: [PATCH 078/828] test: add a null field test --- test/database/hash.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/database/hash.js b/test/database/hash.js index 571cf8bb95..55b75c0df6 100644 --- a/test/database/hash.js +++ b/test/database/hash.js @@ -117,6 +117,12 @@ describe('Hash methods', () => { const result = await db.getObject('emptykey'); assert.deepStrictEqual(result, null); }); + + it('should return null if a field is set to null', async () => { + await db.setObject('nullFieldTest', { baz: 'baz', foo: null }); + const data = await db.getObjectFields('nullFieldTest', ['baz', 'foo']); + assert.deepStrictEqual(data, { baz: 'baz', foo: null }); + }); }); describe('setObjectField()', () => { From 0315e369411c3f18c3bdaac10f6817a8a8f23f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 18 Jun 2025 13:34:55 -0400 Subject: [PATCH 079/828] chore: remove logs --- test/activitypub/actors.js | 2 -- test/mocks/databasemock.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index ac2d11e5da..8271fdb335 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -449,8 +449,6 @@ describe('Inbox resolution', () => { await activitypub.actors.assert(id); const inboxes = await activitypub.resolveInboxes([id]); - console.log('inboxes', inboxes); - console.log('actor', actor); assert(inboxes && Array.isArray(inboxes)); assert.strictEqual(inboxes.length, 1); assert.strictEqual(inboxes[0], actor.inbox); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 9416962b07..a252bc69d8 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -14,7 +14,7 @@ const util = require('util'); process.env.NODE_ENV = process.env.TEST_ENV || 'production'; global.env = process.env.NODE_ENV || 'production'; - +process.env.CI = 'true'; const winston = require('winston'); const packageInfo = require('../../package.json'); From 819e28052aa672f565e0196c7036f5b778007ae5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:35:38 -0400 Subject: [PATCH 080/828] fix(deps): update dependency pg to v8.16.1 (#13503) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 244486ef13..04d03164d8 100644 --- a/install/package.json +++ b/install/package.json @@ -116,7 +116,7 @@ "passport": "0.7.0", "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", - "pg": "8.16.0", + "pg": "8.16.1", "pg-cursor": "2.15.0", "postcss": "8.5.6", "postcss-clean": "1.2.0", From 39d243b04f63a44670ac69211604bbbfba37caf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 18 Jun 2025 13:42:19 -0400 Subject: [PATCH 081/828] test: remove ci env --- test/mocks/databasemock.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index a252bc69d8..41b01b2c72 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -14,7 +14,6 @@ const util = require('util'); process.env.NODE_ENV = process.env.TEST_ENV || 'production'; global.env = process.env.NODE_ENV || 'production'; -process.env.CI = 'true'; const winston = require('winston'); const packageInfo = require('../../package.json'); From 0b9bfc1ce1bf381aba0170c1c860b8dd007357b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 18 Jun 2025 16:59:57 -0400 Subject: [PATCH 082/828] refactor: parallel socket.io adapter --- src/database/redis.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/database/redis.js b/src/database/redis.js index 472dae8de6..d2118aa925 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -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`, }); From 3b364ba12046a2bffbc366367f19bd2af8c0c931 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:13:33 -0400 Subject: [PATCH 083/828] fix(deps): update dependency pg-cursor to v2.15.1 (#13504) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 85aea01d55..356efb7ae7 100644 --- a/install/package.json +++ b/install/package.json @@ -117,7 +117,7 @@ "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", "pg": "8.16.1", - "pg-cursor": "2.15.0", + "pg-cursor": "2.15.1", "postcss": "8.5.6", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", From f9c6d24c73dd012835adbf2c5f80e5014a794fba Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sat, 21 Jun 2025 09:19:27 +0000 Subject: [PATCH 084/828] Latest translations and fallbacks --- .../es/admin/manage/user-custom-fields.json | 44 +++++++++---------- public/language/es/user.json | 4 +- 2 files changed, 24 insertions(+), 24 deletions(-) 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/user.json b/public/language/es/user.json index 66dfc6c8dd..76e9c3c181 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -105,10 +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": "Disable incoming chat messages ", + "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": "Add user", + "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", From e360f649b3f6dc8215ae841da7650458b0eb597b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:19:33 -0400 Subject: [PATCH 085/828] fix(deps): update dependency ace-builds to v1.43.0 (#13507) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 356efb7ae7..fd1f0f7c0a 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.42.0", + "ace-builds": "1.43.0", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", From 10f7b49be8502460491def251af3a13bdb63a7c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:20:37 -0400 Subject: [PATCH 086/828] fix(deps): update dependency pg-cursor to v2.15.2 (#13506) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fd1f0f7c0a..24bdcffffe 100644 --- a/install/package.json +++ b/install/package.json @@ -117,7 +117,7 @@ "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", "pg": "8.16.1", - "pg-cursor": "2.15.1", + "pg-cursor": "2.15.2", "postcss": "8.5.6", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", From 1eefaf5cd819b30d58f2612a2dba8f1014265bc1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:43:41 -0400 Subject: [PATCH 087/828] fix(deps): update dependency bootswatch to v5.3.7 (#13510) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 24bdcffffe..e71f85ca34 100644 --- a/install/package.json +++ b/install/package.json @@ -48,7 +48,7 @@ "body-parser": "2.2.0", "bootbox": "6.0.4", "bootstrap": "5.3.7", - "bootswatch": "5.3.6", + "bootswatch": "5.3.7", "chalk": "4.1.2", "chart.js": "4.5.0", "cli-graph": "3.2.2", From 82c8034cfbf475e9f3ce6af7f41fba2ca2d1978d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 26 Jun 2025 12:55:31 -0400 Subject: [PATCH 088/828] test: testing timeout on failing test --- test/api.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/api.js b/test/api.js index d10433a88b..53699d0a20 100644 --- a/test/api.js +++ b/test/api.js @@ -487,6 +487,7 @@ describe('API', async () => { }); it('should not error out when called', async () => { + this.timeout(0); await setupData(); if (csrfToken) { @@ -513,6 +514,7 @@ describe('API', async () => { redirect: 'manual', headers: headers, body: body, + timeout: 10000, }); } else if (type === 'form') { result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken); From 1a85fafbaf7d5db4f900f1955add4abc9a244fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 26 Jun 2025 13:01:28 -0400 Subject: [PATCH 089/828] test: on more --- test/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api.js b/test/api.js index 53699d0a20..70868ba489 100644 --- a/test/api.js +++ b/test/api.js @@ -486,7 +486,7 @@ describe('API', async () => { } }); - it('should not error out when called', async () => { + it('should not error out when called', async function () { this.timeout(0); await setupData(); From fa31ba0560b9f16452b1cbbc10b64bf67ff20456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 26 Jun 2025 13:10:11 -0400 Subject: [PATCH 090/828] test: increase timeout --- test/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api.js b/test/api.js index 70868ba489..f7616904c8 100644 --- a/test/api.js +++ b/test/api.js @@ -514,7 +514,7 @@ describe('API', async () => { redirect: 'manual', headers: headers, body: body, - timeout: 10000, + timeout: 30000, }); } else if (type === 'form') { result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken); From 4be2e82b5afe0e02769104dfa246e9402ea6806d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:20:02 -0400 Subject: [PATCH 091/828] fix(deps): update dependency nodebb-theme-harmony to v2.1.16 (#13513) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e71f85ca34..1b3fcf6e81 100644 --- a/install/package.json +++ b/install/package.json @@ -106,7 +106,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.4", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.15", + "nodebb-theme-harmony": "2.1.16", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.43", "nodebb-theme-persona": "14.1.12", From 59090931039e25f6dcb9ee15a0e1b9bc6ab9f24a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:20:12 -0400 Subject: [PATCH 092/828] fix(deps): update dependency nodebb-theme-peace to v2.2.44 (#13514) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1b3fcf6e81..31be2dd161 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-rewards-essentials": "1.0.2", "nodebb-theme-harmony": "2.1.16", "nodebb-theme-lavender": "7.1.19", - "nodebb-theme-peace": "2.2.43", + "nodebb-theme-peace": "2.2.44", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.38", "nodemailer": "7.0.3", From bbacd8f6e420c1b8ddf0d216f854b3eda3e87156 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:29:07 -0400 Subject: [PATCH 093/828] chore(deps): update dependency mocha to v11.7.1 (#13509) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 31be2dd161..4dbc75762f 100644 --- a/install/package.json +++ b/install/package.json @@ -170,7 +170,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.1.2", - "mocha": "11.7.0", + "mocha": "11.7.1", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From d2f0944eabd274967d6333fa855376eedbf6e2bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:29:27 -0400 Subject: [PATCH 094/828] fix(deps): update dependency pg to v8.16.2 (#13505) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4dbc75762f..48dcbdc61d 100644 --- a/install/package.json +++ b/install/package.json @@ -116,7 +116,7 @@ "passport": "0.7.0", "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", - "pg": "8.16.1", + "pg": "8.16.2", "pg-cursor": "2.15.2", "postcss": "8.5.6", "postcss-clean": "1.2.0", From a41d2c0b1a9e60c2765c883b7175a58a04138568 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 09:43:26 -0400 Subject: [PATCH 095/828] chore(deps): update dependency smtp-server to v3.14.0 (#13515) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 48dcbdc61d..07981f8855 100644 --- a/install/package.json +++ b/install/package.json @@ -174,7 +174,7 @@ "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.13.8" + "smtp-server": "3.14.0" }, "optionalDependencies": { "sass-embedded": "1.89.2" From 92a3859f7bdd68b52e44551b3ce62fd55ce6e834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 27 Jun 2025 14:18:53 -0400 Subject: [PATCH 096/828] feat: add option to toggle chat join/leave message closes #13508 --- public/language/en-GB/modules.json | 1 + public/openapi/components/schemas/Chats.yaml | 4 ++ .../read/user/userslug/chats/roomid.yaml | 4 ++ .../write/chats/roomId/messages/mid.yaml | 6 +-- .../write/chats/roomId/messages/mid/ip.yaml | 2 +- public/src/client/chats/manage.js | 4 ++ src/api/chats.js | 12 +++++- src/messaging/rooms.js | 37 ++++++++++++++----- src/views/modals/manage-room.tpl | 8 +++- 9 files changed, 62 insertions(+), 16 deletions(-) diff --git a/public/language/en-GB/modules.json b/public/language/en-GB/modules.json index 29ba02726f..f4b473992a 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", diff --git a/public/openapi/components/schemas/Chats.yaml b/public/openapi/components/schemas/Chats.yaml index dc84aca4ef..036b937158 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: diff --git a/public/openapi/read/user/userslug/chats/roomid.yaml b/public/openapi/read/user/userslug/chats/roomid.yaml index 5c5fd1c296..73c4a62da9 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: @@ -360,6 +362,8 @@ get: type: string notificationSetting: type: number + joinLeaveMessages: + type: number publicRooms: type: array items: diff --git a/public/openapi/write/chats/roomId/messages/mid.yaml b/public/openapi/write/chats/roomId/messages/mid.yaml index 5053f1546d..dfa06e7811 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: 3 requestBody: required: true content: @@ -92,7 +92,7 @@ delete: type: number required: true description: a valid message id - example: 5 + example: 3 responses: '200': description: Message successfully deleted @@ -125,7 +125,7 @@ post: type: number required: true description: a valid message id - example: 5 + example: 3 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..1730542213 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: 3 responses: '200': description: Chat message ip address retrieved 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/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/messaging/rooms.js b/src/messaging/rooms.js index 8b57b81da7..4bf4deed2c 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -22,7 +22,7 @@ const roomUidCache = cacheCreate({ }); 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: 0, }; if (data.hasOwnProperty('roomName') && data.roomName) { @@ -280,12 +281,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 +330,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 +347,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 +373,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/views/modals/manage-room.tpl b/src/views/modals/manage-room.tpl index 08c96ccb0b..55f9390de7 100644 --- a/src/views/modals/manage-room.tpl +++ b/src/views/modals/manage-room.tpl @@ -1,6 +1,6 @@
{{{ if user.isAdmin }}} -
+
+
+ +
+ +
+

{{{ end }}} From f5aca1144d776478f6b7a0821fe0684e9816a5c3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 27 Jun 2025 18:19:19 +0000 Subject: [PATCH 097/828] chore(i18n): fallback strings for new resources: nodebb.modules --- public/language/ar/modules.json | 1 + public/language/az/modules.json | 1 + public/language/bg/modules.json | 1 + public/language/bn/modules.json | 1 + public/language/cs/modules.json | 1 + public/language/da/modules.json | 1 + public/language/de/modules.json | 1 + public/language/el/modules.json | 1 + public/language/en-US/modules.json | 1 + public/language/en-x-pirate/modules.json | 1 + public/language/es/modules.json | 1 + public/language/et/modules.json | 1 + public/language/fa-IR/modules.json | 1 + public/language/fi/modules.json | 1 + public/language/fr/modules.json | 1 + public/language/gl/modules.json | 1 + public/language/he/modules.json | 1 + public/language/hr/modules.json | 1 + public/language/hu/modules.json | 1 + public/language/hy/modules.json | 1 + public/language/id/modules.json | 1 + public/language/it/modules.json | 1 + public/language/ja/modules.json | 1 + public/language/ko/modules.json | 1 + public/language/lt/modules.json | 1 + public/language/lv/modules.json | 1 + public/language/ms/modules.json | 1 + public/language/nb/modules.json | 1 + public/language/nl/modules.json | 1 + public/language/nn-NO/modules.json | 1 + public/language/pl/modules.json | 1 + public/language/pt-BR/modules.json | 1 + public/language/pt-PT/modules.json | 1 + public/language/ro/modules.json | 1 + public/language/ru/modules.json | 1 + public/language/rw/modules.json | 1 + public/language/sc/modules.json | 1 + public/language/sk/modules.json | 1 + public/language/sl/modules.json | 1 + public/language/sq-AL/modules.json | 1 + public/language/sr/modules.json | 1 + public/language/sv/modules.json | 1 + public/language/th/modules.json | 1 + public/language/tr/modules.json | 1 + public/language/uk/modules.json | 1 + public/language/vi/modules.json | 1 + public/language/zh-CN/modules.json | 1 + public/language/zh-TW/modules.json | 1 + 48 files changed, 48 insertions(+) diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json index 1bf14cdc27..cb15105037 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", diff --git a/public/language/az/modules.json b/public/language/az/modules.json index dad624a47f..b82661a7e1 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", diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index f6485e5213..864c179ad7 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": "Join/Leave Messages", "chat.notification-setting-room-default": "По подразбиране за стаята", "chat.notification-setting-none": "Без известия", "chat.notification-setting-at-mention-only": "Само @споменавания", diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json index d010f1ad37..45ce1f0e8c 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", diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index f8d89bde37..7ba9a7a2a3 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/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", diff --git a/public/language/da/modules.json b/public/language/da/modules.json index 85a9e8fdfa..e8ceb340f2 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", diff --git a/public/language/de/modules.json b/public/language/de/modules.json index 5edc6169e8..cf5c776242 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", diff --git a/public/language/el/modules.json b/public/language/el/modules.json index a1d1259471..2768fec8a4 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", diff --git a/public/language/en-US/modules.json b/public/language/en-US/modules.json index a1d1259471..2768fec8a4 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", diff --git a/public/language/en-x-pirate/modules.json b/public/language/en-x-pirate/modules.json index c78a052be8..fb7c802ccb 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", diff --git a/public/language/es/modules.json b/public/language/es/modules.json index 1fd0eda552..be35e9c442 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", diff --git a/public/language/et/modules.json b/public/language/et/modules.json index 1aa6c0d427..8a8c22cc93 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", diff --git a/public/language/fa-IR/modules.json b/public/language/fa-IR/modules.json index ddd073ddbf..0115f29ae5 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", diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index d68c1bbaef..dcf1fcea47 100644 --- a/public/language/fi/modules.json +++ b/public/language/fi/modules.json @@ -48,6 +48,7 @@ "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", diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index 475fd1d297..2b54930f35 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", diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index 437ec89919..02ae4bd868 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", diff --git a/public/language/he/modules.json b/public/language/he/modules.json index fb546965c1..12e592b6e2 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": "Join/Leave Messages", "chat.notification-setting-room-default": "ברירת המחדל של החדר", "chat.notification-setting-none": "ללא התראות", "chat.notification-setting-at-mention-only": "@אזכור בלבד", diff --git a/public/language/hr/modules.json b/public/language/hr/modules.json index ae32d6f0d7..c3d8f47ad4 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", diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json index 8b50449c1d..a3544a3bf1 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", diff --git a/public/language/hy/modules.json b/public/language/hy/modules.json index 2de14aeb1e..381dcdfa51 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": "@նշում միայն", diff --git a/public/language/id/modules.json b/public/language/id/modules.json index 5b15156ff3..86afe16b09 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", diff --git a/public/language/it/modules.json b/public/language/it/modules.json index 2646ad5d5f..8cba8bc989 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": "Join/Leave Messages", "chat.notification-setting-room-default": "Stanza predefinita", "chat.notification-setting-none": "Nessuna notifica", "chat.notification-setting-at-mention-only": "@solo menzione", diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json index e177131774..19b23706f7 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", diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index 090984bfd0..b79e2f7028 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": "@언급만", diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json index 6a1a9c3c7a..6f9cb47059 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", diff --git a/public/language/lv/modules.json b/public/language/lv/modules.json index 0c1a36c576..940d9e52a1 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", diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json index a2de6c8f89..d3f710ccaa 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", diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index bd0436e435..4a3d1439fa 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -48,6 +48,7 @@ "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", diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index cbe6bc5603..1e9e509560 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", diff --git a/public/language/nn-NO/modules.json b/public/language/nn-NO/modules.json index 061d785238..e2e97396c5 100644 --- a/public/language/nn-NO/modules.json +++ b/public/language/nn-NO/modules.json @@ -48,6 +48,7 @@ "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", diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index 65f5d143de..09578d0f71 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": "Join/Leave Messages", "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 @", diff --git a/public/language/pt-BR/modules.json b/public/language/pt-BR/modules.json index 64a27be0d1..823391370f 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", diff --git a/public/language/pt-PT/modules.json b/public/language/pt-PT/modules.json index 5b3018d4fa..6e918bcc11 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", diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json index e45d834abc..980870abbc 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", diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index d59776b42d..20e28da7b5 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": "только @упоминание", diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json index 340d16cac4..8440752ccf 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", diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json index 7145e11029..981e4a6bc4 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", diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json index 51c3df2339..1335f8c9c5 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", diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json index 5571ad4c35..9768079262 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", diff --git a/public/language/sq-AL/modules.json b/public/language/sq-AL/modules.json index e715ed133c..dfeb88b7f5 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", diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index f10f3e5d83..62ac69ee64 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": "@помињање само", diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json index 14b07ee511..14d3713957 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", diff --git a/public/language/th/modules.json b/public/language/th/modules.json index c8198f1d99..f2a1a40e5e 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": "เฉพาะเมื่อ @ถูกพูดถึง", diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index d85dbd2821..929c0cf962 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", diff --git a/public/language/uk/modules.json b/public/language/uk/modules.json index 476a18f3f0..dd7a740981 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", diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index 2d5b2e4b92..37a40832a5 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -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": "Join/Leave Messages", "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", diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index 184fc4dec0..dddb4a8de1 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/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": "仅@提及", diff --git a/public/language/zh-TW/modules.json b/public/language/zh-TW/modules.json index a0d0f953d3..0262c03fb0 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", From 7acd63c2a0ab8f4535fbbd868d90869c49281273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 27 Jun 2025 15:03:23 -0400 Subject: [PATCH 098/828] test: fix test, add joinLeaveMessages to newRoom --- src/messaging/rooms.js | 4 ++-- test/messaging.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js index 4bf4deed2c..934bf4e40d 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -88,7 +88,7 @@ module.exports = function (Messaging) { timestamp: now, notificationSetting: data.notificationSetting, messageCount: 0, - joinLeaveMessages: 0, + joinLeaveMessages: data.joinLeaveMessages || 0, }; if (data.hasOwnProperty('roomName') && data.roomName) { @@ -127,7 +127,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); } diff --git a/test/messaging.js b/test/messaging.js index 4429fd6cd7..fbde5d72f6 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -184,6 +184,7 @@ describe('Messaging Library', () => { await User.setSetting(mocks.users.baz.uid, 'disableIncomingMessages', '0'); const { body } = await callv3API('post', `/chats`, { uids: [mocks.users.baz.uid], + joinLeaveMessages: 1, }, 'foo'); await User.setSetting(mocks.users.baz.uid, 'disableIncomingMessages', '1'); @@ -803,7 +804,7 @@ describe('Messaging Library', () => { assert.equal(response.statusCode, 200); assert(Array.isArray(body.rooms)); - assert.equal(body.rooms.length, 3); + assert.equal(body.rooms.length, 2); assert.equal(body.title, '[[pages:chats]]'); }); From 22d1972f83f87adc72baf3cb4b134fdb68ddf66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 27 Jun 2025 15:13:16 -0400 Subject: [PATCH 099/828] test: one more test fix --- public/openapi/write/chats/roomId/messages/mid.yaml | 6 +++--- public/openapi/write/chats/roomId/messages/mid/ip.yaml | 2 +- test/api.js | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/public/openapi/write/chats/roomId/messages/mid.yaml b/public/openapi/write/chats/roomId/messages/mid.yaml index dfa06e7811..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: 3 + example: 2 requestBody: required: true content: @@ -92,7 +92,7 @@ delete: type: number required: true description: a valid message id - example: 3 + example: 2 responses: '200': description: Message successfully deleted @@ -125,7 +125,7 @@ post: type: number required: true description: a valid message id - example: 3 + 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 1730542213..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: 3 + example: 2 responses: '200': description: Chat message ip address retrieved diff --git a/test/api.js b/test/api.js index f7616904c8..fbb36b24b8 100644 --- a/test/api.js +++ b/test/api.js @@ -282,8 +282,13 @@ describe('API', async () => { await flags.appendNote(flagId, 1, 'test note', 1626446956652); await flags.create('post', 2, unprivUid, 'sample reasons', Date.now()); // for testing flag notes (since flag 1 deleted) - // Create a new chat room - await messaging.newRoom(adminUid, { uids: [unprivUid] }); + // Create a new chat room & send a message + const roomId = await messaging.newRoom(adminUid, { uids: [unprivUid] }); + await messaging.sendMessage({ + roomId, + uid: adminUid, + content: 'this is a chat message', + }); // Create an empty file to test DELETE /files and thumb deletion fs.closeSync(fs.openSync(path.resolve(nconf.get('upload_path'), 'files/test.txt'), 'w')); From 6e5083c263318d4437d94c0fd2f001c26c3a3689 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:20:28 -0400 Subject: [PATCH 100/828] fix(deps): update dependency pg-cursor to v2.15.3 (#13516) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 07981f8855..a234787dff 100644 --- a/install/package.json +++ b/install/package.json @@ -117,7 +117,7 @@ "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", "pg": "8.16.2", - "pg-cursor": "2.15.2", + "pg-cursor": "2.15.3", "postcss": "8.5.6", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", From c056bf5618004db28c661860074b9785c0ef0ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 27 Jun 2025 15:22:39 -0400 Subject: [PATCH 101/828] chore: up eslint --- install/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/package.json b/install/package.json index 07981f8855..87b9719041 100644 --- a/install/package.json +++ b/install/package.json @@ -162,9 +162,9 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.29.0", - "@stylistic/eslint-plugin": "4.4.1", - "eslint-config-nodebb": "1.1.7", - "eslint-plugin-import": "2.31.0", + "@stylistic/eslint-plugin": "5.0.0", + "eslint-config-nodebb": "1.1.8", + "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", From 655a3bd3a305bf87175852341152c791dd472d8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:41:45 -0400 Subject: [PATCH 102/828] fix(deps): update dependency workerpool to v9.3.3 (#13518) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 610e5a2a2f..7460d55b53 100644 --- a/install/package.json +++ b/install/package.json @@ -150,7 +150,7 @@ "webpack": "5.99.9", "webpack-merge": "6.0.1", "winston": "3.17.0", - "workerpool": "9.3.2", + "workerpool": "9.3.3", "xml": "1.0.1", "xregexp": "5.1.2", "yargs": "17.7.2", From fd82919e5ab1c7acedbb3f9a71776e0fd4919d41 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:41:54 -0400 Subject: [PATCH 103/828] fix(deps): update dependency pg to v8.16.3 (#13517) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7460d55b53..89da143141 100644 --- a/install/package.json +++ b/install/package.json @@ -116,7 +116,7 @@ "passport": "0.7.0", "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", - "pg": "8.16.2", + "pg": "8.16.3", "pg-cursor": "2.15.3", "postcss": "8.5.6", "postcss-clean": "1.2.0", From 85e2d7d338009fa3fea8ba16e795bedfb8dba899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 27 Jun 2025 16:08:51 -0400 Subject: [PATCH 104/828] test: psql fix --- test/activitypub/feps.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/test/activitypub/feps.js b/test/activitypub/feps.js index 65520f0f4e..adcf2bb2c6 100644 --- a/test/activitypub/feps.js +++ b/test/activitypub/feps.js @@ -269,20 +269,18 @@ describe('FEPs', () => { }); it('should be called when a post is moved to another topic', async () => { - const [{ topicData: topic1 }, { topicData: topic2 }] = await Promise.all([ - topics.post({ - uid, - cid, - title: utils.generateUUID(), - content: utils.generateUUID(), - }), - topics.post({ - uid, - cid, - title: utils.generateUUID(), - content: utils.generateUUID(), - }), - ]); + const topic1 = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + const topic2 = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); assert(topic1 && topic2); From 22005b9ccf83f37498d6bf02afdfb1b7f60f6ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 27 Jun 2025 16:17:06 -0400 Subject: [PATCH 105/828] assign correct data --- test/activitypub/feps.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/activitypub/feps.js b/test/activitypub/feps.js index adcf2bb2c6..705df788e1 100644 --- a/test/activitypub/feps.js +++ b/test/activitypub/feps.js @@ -269,13 +269,13 @@ describe('FEPs', () => { }); it('should be called when a post is moved to another topic', async () => { - const topic1 = await topics.post({ + const { topicData: topic1 } = await topics.post({ uid, cid, title: utils.generateUUID(), content: utils.generateUUID(), }); - const topic2 = await topics.post({ + const { topicData: topic2 } = await topics.post({ uid, cid, title: utils.generateUUID(), From 15ea123382fd529752a7b2c8e2180edbd6e21b78 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:04:00 -0400 Subject: [PATCH 106/828] chore(deps): update dependency @eslint/js to v9.30.0 (#13519) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 89da143141..d3cf811015 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.30.0", "@stylistic/eslint-plugin": "5.0.0", "eslint-config-nodebb": "1.1.8", "eslint-plugin-import": "2.32.0", From 48071ebbb576cf4368af2f716de240e21fc53fd8 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 29 Jun 2025 09:19:19 +0000 Subject: [PATCH 107/828] Latest translations and fallbacks --- public/language/bg/modules.json | 2 +- public/language/it/modules.json | 2 +- public/language/pl/modules.json | 2 +- public/language/zh-CN/modules.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index 864c179ad7..170141d17d 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -48,7 +48,7 @@ "chat.add-user": "Добавяне на потребител", "chat.notification-settings": "Настройки за известията", "chat.default-notification-setting": "Стандартни настройки за известията", - "chat.join-leave-messages": "Join/Leave Messages", + "chat.join-leave-messages": "Съобщения за присъединяване/напускане", "chat.notification-setting-room-default": "По подразбиране за стаята", "chat.notification-setting-none": "Без известия", "chat.notification-setting-at-mention-only": "Само @споменавания", diff --git a/public/language/it/modules.json b/public/language/it/modules.json index 8cba8bc989..c5b60f5c4b 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -48,7 +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": "Join/Leave Messages", + "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", diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index 09578d0f71..3e52bcddc8 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -48,7 +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": "Join/Leave Messages", + "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 @", diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index dddb4a8de1..d9ae48b598 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -48,7 +48,7 @@ "chat.add-user": "添加用户", "chat.notification-settings": "通知设置", "chat.default-notification-setting": "默认通知设置", - "chat.join-leave-messages": "Join/Leave Messages", + "chat.join-leave-messages": "加入/退出 消息", "chat.notification-setting-room-default": "默认房间", "chat.notification-setting-none": "无通知", "chat.notification-setting-at-mention-only": "仅@提及", From f1fbea7b28fbd029602ced0add72d55a38a15049 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:25:03 -0400 Subject: [PATCH 108/828] fix(deps): update dependency nodemailer to v7.0.4 (#13522) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d3cf811015..847cb84807 100644 --- a/install/package.json +++ b/install/package.json @@ -111,7 +111,7 @@ "nodebb-theme-peace": "2.2.44", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.38", - "nodemailer": "7.0.3", + "nodemailer": "7.0.4", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", From 18d6e5e1d64bb921415a331530a2540221f6a50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 30 Jun 2025 20:33:16 -0400 Subject: [PATCH 109/828] chore: up eslint-plugin --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 847cb84807..afc0a88162 100644 --- a/install/package.json +++ b/install/package.json @@ -162,8 +162,8 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.30.0", - "@stylistic/eslint-plugin": "5.0.0", - "eslint-config-nodebb": "1.1.8", + "@stylistic/eslint-plugin": "5.1.0", + "eslint-config-nodebb": "1.1.9", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", From 37f0fa961ea4b39ebbe4b388f1a02d672559da39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 1 Jul 2025 10:01:10 -0400 Subject: [PATCH 110/828] Refactor hook call for filterSortedTids --- src/topics/sorted.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/topics/sorted.js b/src/topics/sorted.js index 07a6215218..2112fe3ad1 100644 --- a/src/topics/sorted.js +++ b/src/topics/sorted.js @@ -282,7 +282,10 @@ module.exports = function (Topics) { (!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; } From aba2ddad949ac52879fbafa8592b18ab1d5e034d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:18:56 -0400 Subject: [PATCH 111/828] fix(deps): update dependency ace-builds to v1.43.1 (#13525) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index afc0a88162..4e4098989f 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.43.0", + "ace-builds": "1.43.1", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", From 6d7df13fdb899e5d129a73a1c2bbfd73298a5b70 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:19:07 -0400 Subject: [PATCH 112/828] chore(deps): update dependency @eslint/js to v9.30.1 (#13524) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4e4098989f..5ae8666505 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.30.0", + "@eslint/js": "9.30.1", "@stylistic/eslint-plugin": "5.1.0", "eslint-config-nodebb": "1.1.9", "eslint-plugin-import": "2.32.0", From ceae2aa1a81342ecb470a8a0bbbaa5f0ae541965 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:19:16 -0400 Subject: [PATCH 113/828] 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> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5ae8666505..acd710b639 100644 --- a/install/package.json +++ b/install/package.json @@ -104,7 +104,7 @@ "nodebb-plugin-markdown": "13.2.1", "nodebb-plugin-mentions": "4.7.6", "nodebb-plugin-spam-be-gone": "2.3.2", - "nodebb-plugin-web-push": "0.7.4", + "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", "nodebb-theme-harmony": "2.1.16", "nodebb-theme-lavender": "7.1.19", From 5a5ca8a5fb3d80e536f19cc52efa8145c5ae1247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 2 Jul 2025 17:38:35 -0400 Subject: [PATCH 114/828] fix: closes #13526, dont send multiple emails when user is invited --- src/user/create.js | 12 +++++++----- src/user/invite.js | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/user/create.js b/src/user/create.js index 1b18281722..ee4c3f27e7 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -109,11 +109,13 @@ module.exports = function (User) { } 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 (!data.token || !await User.isInviteTokenValid(data.token, data.email)) { + 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); 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; From 80fabdcb33bc6ae1be7624f5f01ea45f90e5b5d3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 3 Jul 2025 09:20:10 +0000 Subject: [PATCH 115/828] Latest translations and fallbacks --- public/language/vi/modules.json | 2 +- public/language/vi/topic.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index 37a40832a5..c4d8c24c78 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -48,7 +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": "Join/Leave Messages", + "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", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 59db71b7a6..12f0394c98 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -214,7 +214,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", From 991f518e2f615d654484cbe2444ef08ad378dbd1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:47:50 -0400 Subject: [PATCH 116/828] fix(deps): update dependency nodebb-theme-peace to v2.2.45 (#13529) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index acd710b639..8bfd634780 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-rewards-essentials": "1.0.2", "nodebb-theme-harmony": "2.1.16", "nodebb-theme-lavender": "7.1.19", - "nodebb-theme-peace": "2.2.44", + "nodebb-theme-peace": "2.2.45", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.38", "nodemailer": "7.0.4", From bfcc36f7cbcdb297e6151f67a0b0fefdae6d3e83 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 6 Jul 2025 09:19:12 +0000 Subject: [PATCH 117/828] Latest translations and fallbacks --- public/language/vi/admin/manage/users.json | 2 +- public/language/vi/error.json | 10 +++++----- public/language/vi/global.json | 10 +++++----- public/language/vi/groups.json | 2 +- public/language/vi/modules.json | 4 ++-- public/language/vi/pages.json | 10 +++++----- public/language/vi/search.json | 2 +- public/language/vi/topic.json | 8 ++++---- public/language/vi/user.json | 4 ++-- public/language/vi/users.json | 2 +- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/public/language/vi/admin/manage/users.json b/public/language/vi/admin/manage/users.json index 8a0a2b0806..d3136effd4 100644 --- a/public/language/vi/admin/manage/users.json +++ b/public/language/vi/admin/manage/users.json @@ -119,7 +119,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/error.json b/public/language/vi/error.json index 467b78742c..09cb43c110 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -84,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ự.", @@ -127,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", @@ -137,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", @@ -159,7 +159,7 @@ "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", diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 3ac87d5132..5a066d9b0e 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -134,11 +134,11 @@ "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.accept": "Hiểu rồi!", "cookies.learn-more": "Tìm Hiểu Thêm", "edited": "Đã Sửa", "disabled": "Đã tắt", @@ -146,7 +146,7 @@ "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 df781930e5..d4c6e34b97 100644 --- a/public/language/vi/groups.json +++ b/public/language/vi/groups.json @@ -29,7 +29,7 @@ "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", diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index c4d8c24c78..c238cc8d51 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -19,7 +19,7 @@ "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", @@ -101,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", diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json index 851486de52..44579ce5f7 100644 --- a/public/language/vi/pages.json +++ b/public/language/vi/pages.json @@ -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/search.json b/public/language/vi/search.json index 56361bb4d5..63ba203e7d 100644 --- a/public/language/vi/search.json +++ b/public/language/vi/search.json @@ -33,7 +33,7 @@ "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", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 12f0394c98..a8960d63db 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -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", @@ -132,7 +132,7 @@ "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-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.", @@ -194,7 +194,7 @@ "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.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 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)", diff --git a/public/language/vi/user.json b/public/language/vi/user.json index 9584fe0e5d..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", @@ -188,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", From 24e7cf4a0078dd91d637f449501df5e1fd1f3372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20U=C5=9Fakl=C4=B1?= Date: Mon, 7 Jul 2025 10:22:24 -0400 Subject: [PATCH 118/828] 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 --- install/package.json | 2 +- .../components/schemas/PostObject.yaml | 2 + public/openapi/write/topics/tid/thumbs.yaml | 49 ---- .../write/topics/tid/thumbs/order.yaml | 4 +- public/src/client/topic/events.js | 6 + public/src/modules/helpers.common.js | 7 + public/src/modules/topicThumbs.js | 78 +++--- src/activitypub/mocks.js | 8 +- src/api/posts.js | 4 +- src/api/topics.js | 22 +- src/controllers/write/topics.js | 15 +- src/posts/data.js | 8 + src/posts/edit.js | 12 +- src/posts/uploads.js | 42 ++-- src/routes/api.js | 1 + src/routes/write/topics.js | 2 +- src/topics/create.js | 6 + src/topics/data.js | 8 + src/topics/thumbs.js | 227 +++++++++--------- src/upgrades/4.5.0/post-uploads-to-hash.js | 39 +++ src/upgrades/4.5.0/topic-thumbs-to-hash.js | 39 +++ src/views/modals/topic-thumbs.tpl | 6 +- test/posts/uploads.js | 22 +- test/topics/thumbs.js | 165 +++---------- 24 files changed, 369 insertions(+), 405 deletions(-) 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 diff --git a/install/package.json b/install/package.json index 8bfd634780..5ffca3c819 100644 --- a/install/package.json +++ b/install/package.json @@ -97,7 +97,7 @@ "multer": "2.0.1", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", - "nodebb-plugin-composer-default": "10.2.51", + "nodebb-plugin-composer-default": "10.3.0", "nodebb-plugin-dbsearch": "6.3.0", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml index bcb2f79e53..1904cded51 100644 --- a/public/openapi/components/schemas/PostObject.yaml +++ b/public/openapi/components/schemas/PostObject.yaml @@ -300,6 +300,8 @@ PostDataObject: type: boolean attachments: type: array + uploads: + type: array replies: type: object properties: 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/src/client/topic/events.js b/public/src/client/topic/events.js index 4a8a36a768..c52f8beefe 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -176,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/modules/helpers.common.js b/public/src/modules/helpers.common.js index 183c1bbb70..896d0b485b 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -34,6 +34,7 @@ module.exports = function (utils, Benchpress, relative_path) { humanReadableNumber, formattedNumber, txEscape, + uploadBasename, generatePlaceholderWave, register, __escape: identity, @@ -379,6 +380,12 @@ module.exports = function (utils, Benchpress, relative_path) { 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/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/src/activitypub/mocks.js b/src/activitypub/mocks.js index e5a8e8e363..f9995b3b72 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -715,8 +715,12 @@ Mocks.notes.public = async (post) => { // Special handling for main posts (as:Article w/ as:Note preview) const noteAttachment = isMainPost ? [...attachment] : null; - const uploads = await posts.uploads.listWithSizes(post.pid); - const isThumb = await db.isSortedSetMembers(`topic:${post.tid}:thumbs`, uploads.map(u => u.name)); + const [uploads, thumbs] = await Promise.all([ + posts.uploads.listWithSizes(post.pid), + topics.getTopicField(post.tid, 'thumbs'), + ]); + const isThumb = uploads.map(u => Array.isArray(thumbs) ? thumbs.includes(u.name) : false); + uploads.forEach(({ name, width, height }, idx) => { const mediaType = mime.getType(name); const url = `${nconf.get('url') + nconf.get('upload_url')}/${name}`; diff --git a/src/api/posts.js b/src/api/posts.js index b39c173eb6..d8971796b3 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -120,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({ diff --git a/src/api/topics.js b/src/api/topics.js index 0155429ecc..38e3cefbf8 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'); @@ -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]]'); } }; @@ -80,7 +74,6 @@ 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]); @@ -233,17 +226,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); diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index b46002bb65..06aded5913 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -138,25 +138,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')) { diff --git a/src/posts/data.js b/src/posts/data.js index d74a22e69d..28e6c24aaa 100644 --- a/src/posts/data.js +++ b/src/posts/data.js @@ -70,5 +70,13 @@ function modifyPost(post, fields) { if (!fields.length || fields.includes('attachments')) { post.attachments = (post.attachments || '').split(',').filter(Boolean); } + + if (!fields.length || fields.includes('uploads')) { + try { + post.uploads = post.uploads ? JSON.parse(post.uploads) : []; + } catch (err) { + post.uploads = []; + } + } } } diff --git a/src/posts/edit.js b/src/posts/edit.js index 077616b29e..b18bf99078 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -29,7 +29,7 @@ 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); @@ -142,6 +142,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 +181,7 @@ module.exports = function (Posts) { renamed: renamed, tagsupdated: tagsupdated, tags: tags, + thumbsupdated: thumbsupdated, oldTags: topicData.tags, rescheduled: rescheduling(data, topicData), }; 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/routes/api.js b/src/routes/api.js index e374e242a4..4424d9a979 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -36,6 +36,7 @@ module.exports = function (app, middleware, controllers) { ]; 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/write/topics.js b/src/routes/write/topics.js index eb56cdaf42..2b159ee3c0 100644 --- a/src/routes/write/topics.js +++ b/src/routes/write/topics.js @@ -41,7 +41,7 @@ module.exports = function () { ...middlewares, ], controllers.write.topics.addThumb); - setupApiRoute(router, 'put', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['tid'])], controllers.write.topics.migrateThumbs); + 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); diff --git a/src/topics/create.js b/src/topics/create.js index 352823a202..2f41c822b1 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -41,6 +41,12 @@ 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; + } + const result = await plugins.hooks.fire('filter:topic.create', { topic: topicData, data: data }); topicData = result.topic; await db.setObject(`topic:${topicData.tid}`, topicData); diff --git a/src/topics/data.js b/src/topics/data.js index 76c027121d..a5801e0475 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -140,4 +140,12 @@ function modifyTopic(topic, fields) { }; }); } + + if (fields.includes('thumbs') || !fields.length) { + try { + topic.thumbs = topic.thumbs ? JSON.parse(String(topic.thumbs || '[]')) : []; + } catch (e) { + topic.thumbs = []; + } + } } diff --git a/src/topics/thumbs.js b/src/topics/thumbs.js index be2916a05d..2c3482664d 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,42 +33,25 @@ 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); + const tidToThumbs = _.zipObject(tidsWithThumbs, thumbs); return topicData.map(t => (t && t.tid ? (tidToThumbs[t.tid] || []) : [])); }; -Thumbs.get = async function (tids, options) { - // Allow singular or plural usage - let singular = false; - if (!Array.isArray(tids)) { - tids = [tids]; - singular = true; - } - - if (!options) { - 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); +async function loadFromTopicData(topicData, options = {}) { + const tids = topicData.map(t => t.tid); + const thumbs = topicData.map(t => t ? 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 - const mainPidUploads = await Promise.all(mainPids.map(posts.uploads.list)); mainPidUploads.forEach((uploads, idx) => { uploads = uploads.filter((upload) => { const type = mime.getType(upload); @@ -84,7 +64,6 @@ Thumbs.get = async function (tids, options) { }); // 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/')) @@ -96,14 +75,18 @@ Thumbs.get = async function (tids, options) { }); } + const hasTimestampPrefix = /^\d+-/; + let response = thumbs.map((thumbSet, idx) => thumbSet.map(thumb => ({ - id: tids[idx], + 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, '/')), + url: thumb.startsWith('http') ? + thumb : + path.posix.join(upload_url, thumb.replace(/\\/g, '/')), }))); ({ thumbs: response } = await plugins.hooks.fire('filter:topics.getThumbs', { @@ -111,61 +94,93 @@ Thumbs.get = async function (tids, options) { thumbsOnly: options.thumbsOnly, thumbs: response, })); - return singular ? response.pop() : response; + return response; }; -async function getThumbs(set) { - const cached = cache.get(set); - if (cached !== undefined) { - return cached.slice(); +Thumbs.get = async function (tids, options) { + // Allow singular or plural usage + let singular = false; + if (!Array.isArray(tids)) { + tids = [tids]; + singular = true; } - const thumbs = await db.getSortedSetRange(set, 0, -1); - cache.set(set, thumbs); - return thumbs.slice(); -} + + if (!options) { + options = { + thumbsOnly: false, + }; + } + if (!meta.config.allowTopicsThumbnail || !tids.length) { + return singular ? [] : tids.map(() => []); + } + + const topicData = await topics.getTopicsFields(tids, ['tid', 'mainPid', 'thumbs']); + const response = await loadFromTopicData(topicData, options); + return singular ? response[0] : response; +}; + 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 +188,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 = []; - const toDelete = []; - relativePaths.forEach((relativePath, idx) => { - if (associated[idx]) { - toRemove.push(relativePath); - } - - if (existsOnDisk[idx]) { - toDelete.push(absolutePaths[idx]); - } - }); + const toRemove = relativePaths.map( + relativePath => topicData.thumbs.includes(relativePath) ? relativePath : null + ).filter(Boolean); - 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 (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); +Thumbs.deleteAll = async (tid) => { + const topicData = await topics.getTopicData(tid); + if (!topicData) { + return; + } + await Thumbs.delete(tid, topicData.thumbs); }; 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/views/modals/topic-thumbs.tpl b/src/views/modals/topic-thumbs.tpl index ead862457c..d59f189be1 100644 --- a/src/views/modals/topic-thumbs.tpl +++ b/src/views/modals/topic-thumbs.tpl @@ -3,13 +3,13 @@
[[modules:thumbs.modal.no-thumbs]]
{{{ end }}} {{{ each thumbs }}} -
+
- +

- {./name} + {uploadBasename(@value)}

diff --git a/test/posts/uploads.js b/test/posts/uploads.js index 8f22f3d1f9..45ea10411f 100644 --- a/test/posts/uploads.js +++ b/test/posts/uploads.js @@ -62,13 +62,13 @@ describe('upload methods', () => { }); describe('.sync()', () => { - it('should properly add new images to the post\'s zset', (done) => { + it('should properly add new images to the post\'s hash', (done) => { posts.uploads.sync(pid, (err) => { assert.ifError(err); - db.sortedSetCard(`post:${pid}:uploads`, (err, length) => { + posts.uploads.list(pid, (err, uploads) => { assert.ifError(err); - assert.strictEqual(length, 2); + assert.strictEqual(uploads.length, 2); done(); }); }); @@ -81,8 +81,8 @@ describe('upload methods', () => { content: 'here is an image [alt text](/assets/uploads/files/abracadabra.png)... AND NO MORE!', }); await posts.uploads.sync(pid); - const length = await db.sortedSetCard(`post:${pid}:uploads`); - assert.strictEqual(1, length); + const uploads = await posts.uploads.list(pid); + assert.strictEqual(1, uploads.length); }); }); @@ -345,13 +345,11 @@ describe('post uploads management', () => { reply = replyData; }); - it('should automatically sync uploads on topic create and reply', (done) => { - db.sortedSetsCard([`post:${topic.topicData.mainPid}:uploads`, `post:${reply.pid}:uploads`], (err, lengths) => { - assert.ifError(err); - assert.strictEqual(lengths[0], 1); - assert.strictEqual(lengths[1], 1); - done(); - }); + it('should automatically sync uploads on topic create and reply', async () => { + const uploads1 = await posts.uploads.list(topic.topicData.mainPid); + const uploads2 = await posts.uploads.list(reply.pid); + assert.strictEqual(uploads1.length, 1); + assert.strictEqual(uploads2.length, 1); }); it('should automatically sync uploads on post edit', async () => { diff --git a/test/topics/thumbs.js b/test/topics/thumbs.js index afc13e5b22..b740596c74 100644 --- a/test/topics/thumbs.js +++ b/test/topics/thumbs.js @@ -37,8 +37,6 @@ describe('Topic thumbs', () => { const relativeThumbPaths = thumbPaths.map(path => path.replace(nconf.get('upload_path'), '')); - const uuid = utils.generateUUID(); - function createFiles() { fs.closeSync(fs.openSync(path.resolve(__dirname, '../uploads', thumbPaths[0]), 'w')); fs.closeSync(fs.openSync(path.resolve(__dirname, '../uploads', thumbPaths[1]), 'w')); @@ -70,7 +68,11 @@ describe('Topic thumbs', () => { // Touch a couple files and associate it to a topic createFiles(); - await db.sortedSetAdd(`topic:${topicObj.topicData.tid}:thumbs`, 0, `${relativeThumbPaths[0]}`); + + await topics.setTopicFields(topicObj.topicData.tid, { + numThumbs: 1, + thumbs: JSON.stringify([relativeThumbPaths[0]]), + }); }); it('should return bool for whether a thumb exists', async () => { @@ -80,10 +82,9 @@ describe('Topic thumbs', () => { describe('.get()', () => { it('should return an array of thumbs', async () => { - require('../../src/cache').del(`topic:${topicObj.topicData.tid}:thumbs`); const thumbs = await topics.thumbs.get(topicObj.topicData.tid); assert.deepStrictEqual(thumbs, [{ - id: topicObj.topicData.tid, + id: String(topicObj.topicData.tid), name: 'test.png', path: `${relativeThumbPaths[0]}`, url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`, @@ -94,7 +95,7 @@ describe('Topic thumbs', () => { const thumbs = await topics.thumbs.get([topicObj.topicData.tid, topicObj.topicData.tid + 1]); assert.deepStrictEqual(thumbs, [ [{ - id: topicObj.topicData.tid, + id: String(topicObj.topicData.tid), name: 'test.png', path: `${relativeThumbPaths[0]}`, url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`, @@ -119,25 +120,13 @@ describe('Topic thumbs', () => { mainPid = topicObj.postData.pid; }); - it('should add an uploaded file to a zset', async () => { + it('should add an uploaded file to the topic hash', async () => { await topics.thumbs.associate({ id: tid, path: relativeThumbPaths[0], }); - - const exists = await db.isSortedSetMember(`topic:${tid}:thumbs`, relativeThumbPaths[0]); - assert(exists); - }); - - it('should also work with UUIDs', async () => { - await topics.thumbs.associate({ - id: uuid, - path: relativeThumbPaths[1], - score: 5, - }); - - const exists = await db.isSortedSetMember(`draft:${uuid}:thumbs`, relativeThumbPaths[1]); - assert(exists); + const topicData = await topics.getTopicData(tid); + assert(topicData.thumbs.includes(relativeThumbPaths[0])); }); it('should also work with a URL', async () => { @@ -145,14 +134,8 @@ describe('Topic thumbs', () => { id: tid, path: relativeThumbPaths[2], }); - - const exists = await db.isSortedSetMember(`topic:${tid}:thumbs`, relativeThumbPaths[2]); - assert(exists); - }); - - it('should have a score equal to the number of thumbs prior to addition', async () => { - const scores = await db.sortedSetScores(`topic:${tid}:thumbs`, [relativeThumbPaths[0], relativeThumbPaths[2]]); - assert.deepStrictEqual(scores, [0, 1]); + const topicData = await topics.getTopicData(tid); + assert(topicData.thumbs.includes(relativeThumbPaths[2])); }); it('should update the relevant topic hash with the number of thumbnails', async () => { @@ -166,23 +149,19 @@ describe('Topic thumbs', () => { path: relativeThumbPaths[0], }); - const score = await db.sortedSetScore(`topic:${tid}:thumbs`, relativeThumbPaths[0]); - - assert(isFinite(score)); // exists in set - assert.strictEqual(score, 2); + const topicData = await topics.getTopicData(tid); + assert.strictEqual(topicData.thumbs.indexOf(relativeThumbPaths[0]), 1); }); - it('should update the score to be passed in as the third argument', async () => { + it('should update the index to be passed in as the third argument', async () => { await topics.thumbs.associate({ id: tid, path: relativeThumbPaths[0], score: 0, }); - const score = await db.sortedSetScore(`topic:${tid}:thumbs`, relativeThumbPaths[0]); - - assert(isFinite(score)); // exists in set - assert.strictEqual(score, 0); + const topicData = await topics.getTopicData(tid); + assert.strictEqual(topicData.thumbs.indexOf(relativeThumbPaths[0]), 0); }); it('should associate the thumbnail with that topic\'s main pid\'s uploads', async () => { @@ -195,33 +174,6 @@ describe('Topic thumbs', () => { const uploads = await posts.uploads.list(mainPid); assert(uploads.includes(relativeThumbPaths[0])); }); - - it('should combine the thumbs uploaded to a UUID zset and combine it with a topic\'s thumb zset', async () => { - await topics.thumbs.migrate(uuid, tid); - - const thumbs = await topics.thumbs.get(tid); - assert.strictEqual(thumbs.length, 3); - assert.deepStrictEqual(thumbs, [ - { - id: tid, - name: 'test.png', - path: relativeThumbPaths[0], - url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`, - }, - { - id: tid, - name: 'example.org', - path: 'https://example.org', - url: 'https://example.org', - }, - { - id: tid, - name: 'test2.png', - path: relativeThumbPaths[1], - url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[1]}`, - }, - ]); - }); }); describe(`.delete()`, () => { @@ -231,8 +183,8 @@ describe('Topic thumbs', () => { path: `/files/test.png`, }); await topics.thumbs.delete(1, `/files/test.png`); - - assert.strictEqual(await db.isSortedSetMember('topic:1:thumbs', '/files/test.png'), false); + const thumbs = await topics.getTopicField(1, 'thumbs'); + assert.strictEqual(thumbs.includes(`/files/test.png`), false); }); it('should no longer be associated with that topic\'s main pid\'s uploads', async () => { @@ -241,40 +193,12 @@ describe('Topic thumbs', () => { assert(!uploads.includes(path.basename(relativeThumbPaths[0]))); }); - it('should also work with UUIDs', async () => { - await topics.thumbs.associate({ - id: uuid, - path: `/files/test.png`, - }); - await topics.thumbs.delete(uuid, '/files/test.png'); - - assert.strictEqual(await db.isSortedSetMember(`draft:${uuid}:thumbs`, '/files/test.png'), false); - assert.strictEqual(await file.exists(path.join(`${nconf.get('upload_path')}`, '/files/test.png')), false); - }); - - it('should also work with URLs', async () => { - await topics.thumbs.associate({ - id: uuid, - path: thumbPaths[2], - }); - await topics.thumbs.delete(uuid, relativeThumbPaths[2]); - - assert.strictEqual(await db.isSortedSetMember(`draft:${uuid}:thumbs`, relativeThumbPaths[2]), false); - }); - - it('should not delete the file from disk if not associated with the tid', async () => { - createFiles(); - await topics.thumbs.delete(uuid, thumbPaths[0]); - assert.strictEqual(await file.exists(thumbPaths[0]), true); - }); - it('should have no more thumbs left', async () => { - const associated = await db.isSortedSetMembers(`topic:1:thumbs`, [relativeThumbPaths[0], relativeThumbPaths[1]]); - assert.strictEqual(associated.some(Boolean), false); + const thumbs = await topics.getTopicField(1, 'thumbs'); + assert.strictEqual(thumbs.length, 0); }); it('should decrement numThumbs if dissociated one by one', async () => { - console.log('before', await db.getSortedSetRange(`topic:1:thumbs`, 0, -1)); await topics.thumbs.associate({ id: 1, path: `${nconf.get('relative_path')}${nconf.get('upload_url')}/files/test.png` }); await topics.thumbs.associate({ id: 1, path: `${nconf.get('relative_path')}${nconf.get('upload_url')}/files/test2.png` }); @@ -290,18 +214,14 @@ describe('Topic thumbs', () => { describe('.deleteAll()', () => { before(async () => { - await Promise.all([ - topics.thumbs.associate({ id: 1, path: '/files/test.png' }), - topics.thumbs.associate({ id: 1, path: '/files/test2.png' }), - ]); + await topics.thumbs.associate({ id: 1, path: '/files/test.png' }); + await topics.thumbs.associate({ id: 1, path: '/files/test2.png' }); createFiles(); }); it('should have thumbs prior to tests', async () => { - const associated = await db.isSortedSetMembers( - `topic:1:thumbs`, ['/files/test.png', '/files/test2.png'] - ); - assert.strictEqual(associated.every(Boolean), true); + const thumbs = await topics.getTopicField(1, 'thumbs'); + assert.deepStrictEqual(thumbs, ['/files/test.png', '/files/test2.png']); }); it('should not error out', async () => { @@ -309,14 +229,8 @@ describe('Topic thumbs', () => { }); it('should remove all associated thumbs with that topic', async () => { - const associated = await db.isSortedSetMembers( - `topic:1:thumbs`, ['/files/test.png', '/files/test2.png'] - ); - assert.strictEqual(associated.some(Boolean), false); - }); - - it('should no longer have a :thumbs zset', async () => { - assert.strictEqual(await db.exists('topic:1:thumbs'), false); + const thumbs = await topics.getTopicField(1, 'thumbs'); + assert.deepStrictEqual(thumbs, []); }); }); @@ -330,11 +244,6 @@ describe('Topic thumbs', () => { assert.strictEqual(response.statusCode, 200); }); - it('should succeed with a uuid', async () => { - const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF); - assert.strictEqual(response.statusCode, 200); - }); - it('should succeed with uploader plugins', async () => { const hookMethod = async () => ({ name: 'test.png', @@ -346,7 +255,7 @@ describe('Topic thumbs', () => { }); const { response } = await helpers.uploadFile( - `${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, + `${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, @@ -375,7 +284,7 @@ describe('Topic thumbs', () => { it('should fail if thumbnails are not enabled', async () => { meta.config.allowTopicsThumbnail = 0; - const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF); + const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF); assert.strictEqual(response.statusCode, 503); assert(body && body.status); assert.strictEqual(body.status.message, 'Topic thumbnails are disabled.'); @@ -384,7 +293,7 @@ describe('Topic thumbs', () => { it('should fail if file is not image', async () => { meta.config.allowTopicsThumbnail = 1; - const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/503.html'), {}, adminJar, adminCSRF); + const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/503.html'), {}, adminJar, adminCSRF); assert.strictEqual(response.statusCode, 500); assert(body && body.status); assert.strictEqual(body.status.message, 'Invalid File'); @@ -402,21 +311,17 @@ describe('Topic thumbs', () => { content: 'The content of test topic', }); - await Promise.all([ - topics.thumbs.associate({ id: topicObj.tid, path: thumbPaths[0] }), - topics.thumbs.associate({ id: topicObj.tid, path: thumbPaths[1] }), - ]); + + await topics.thumbs.associate({ id: topicObj.tid, path: thumbPaths[0] }); + await topics.thumbs.associate({ id: topicObj.tid, path: thumbPaths[1] }); + createFiles(); await topics.purge(topicObj.tid, adminUid); }); - it('should no longer have a :thumbs zset', async () => { - assert.strictEqual(await db.exists(`topic:${topicObj.tid}:thumbs`), false); - }); - it('should not leave post upload associations behind', async () => { - const uploads = await db.getSortedSetMembers(`post:${topicObj.postData.pid}:uploads`); + const uploads = await posts.uploads.list(topicObj.postData.pid); assert.strictEqual(uploads.length, 0); }); }); From 72fec565c21d8bf03752e6fa2763d2609a8fd849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 7 Jul 2025 11:28:22 -0400 Subject: [PATCH 119/828] fix: check topic and thumbs --- src/topics/thumbs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/topics/thumbs.js b/src/topics/thumbs.js index 2c3482664d..22fa2c48bd 100644 --- a/src/topics/thumbs.js +++ b/src/topics/thumbs.js @@ -41,8 +41,8 @@ Thumbs.load = async function (topicData) { }; async function loadFromTopicData(topicData, options = {}) { - const tids = topicData.map(t => t.tid); - const thumbs = topicData.map(t => t ? t.thumbs : []); + 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); From 329f98d5db866be9a624774e7c1430ed45d71cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 7 Jul 2025 12:16:08 -0400 Subject: [PATCH 120/828] fix: for attribute, remove upload trigger when click inputs user can input an absolute url in the inputs --- src/views/admin/settings/general.tpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index 0d75c9d601..5dccda3f0a 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -120,9 +120,9 @@
- +
- + @@ -132,7 +132,7 @@
- +
@@ -144,7 +144,7 @@
- + From 113607829f77b20b7841cbbdcc1c1b8146092bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 7 Jul 2025 17:09:42 -0400 Subject: [PATCH 121/828] remove log --- src/controllers/activitypub/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index 6751cb30cd..b3bc85aab6 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -145,7 +145,6 @@ Controller.postInbox = async (req, res) => { 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.`); - console.log('[activitypub/inbox] method not found', method, req.body); return res.sendStatus(200); } From c6f4148b214409fab97ec0e916062c6d3c253dc3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:38:43 -0400 Subject: [PATCH 122/828] fix(deps): update dependency nodemailer to v7.0.5 (#13537) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5ffca3c819..285fc971d5 100644 --- a/install/package.json +++ b/install/package.json @@ -111,7 +111,7 @@ "nodebb-theme-peace": "2.2.45", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.38", - "nodemailer": "7.0.4", + "nodemailer": "7.0.5", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", From 8960fdb3a5f85c5d28100bae1e74a202a753f1b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:39:03 -0400 Subject: [PATCH 123/828] fix(deps): update dependency esbuild to v0.25.6 (#13538) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 285fc971d5..98d0fd038e 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.5", + "esbuild": "0.25.6", "express": "4.21.2", "express-session": "1.18.1", "express-useragent": "1.0.15", From dbed2db992e80d12e031790923ee2892819596f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 8 Jul 2025 11:03:02 -0400 Subject: [PATCH 124/828] fix: make clickable element anchor add rounded corners --- public/src/client/topic.js | 2 +- src/views/modals/topic-thumbs-view.tpl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 43a64a9fa3..cce6f1f99a 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -202,7 +202,7 @@ 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')); }); } }); diff --git a/src/views/modals/topic-thumbs-view.tpl b/src/views/modals/topic-thumbs-view.tpl index 9603e9bc36..a0ee682f80 100644 --- a/src/views/modals/topic-thumbs-view.tpl +++ b/src/views/modals/topic-thumbs-view.tpl @@ -1,14 +1,14 @@
- +
{{{ if (thumbs.length != "1") }}}
{{{ each thumbs }}} -
- -
+ + + {{{ end }}}
{{{ end }}} From 0ef98ec495c4d91f00fc5963cd7f4878039faa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 8 Jul 2025 13:34:41 -0400 Subject: [PATCH 125/828] fix: set to empty string if undefined --- src/controllers/admin/events.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/admin/events.js b/src/controllers/admin/events.js index 72d9b4c3e1..9b4d493071 100644 --- a/src/controllers/admin/events.js +++ b/src/controllers/admin/events.js @@ -60,11 +60,11 @@ eventsController.get = async function (req, res) { pagination: pagination.create(page, pageCount, req.query), types: types, 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)), + 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 || '')), }, }); }; From a6cb933bac58220c60ccafd2ae2c13d3ffc8e28a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:55:29 -0400 Subject: [PATCH 126/828] fix(deps): update dependency redis to v5.6.0 (#13540) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 98d0fd038e..626fb01958 100644 --- a/install/package.json +++ b/install/package.json @@ -122,7 +122,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.5.6", + "redis": "5.6.0", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From dae81b76fbe76acd4b669789903eb9d57068527b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 8 Jul 2025 14:03:56 -0400 Subject: [PATCH 127/828] chore: up dbsearch --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 98d0fd038e..580cf6095a 100644 --- a/install/package.json +++ b/install/package.json @@ -98,7 +98,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.3.0", - "nodebb-plugin-dbsearch": "6.3.0", + "nodebb-plugin-dbsearch": "6.3.1", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.2.1", From 1b80910e806ea2a7348abcef71a57dbeaff89f4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:09:15 -0400 Subject: [PATCH 128/828] chore(deps): update redis docker tag to v8.0.3 (#13539) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cf72066b92..e3bdaf2ebd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.0.2' + image: 'redis:8.0.3' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 8a67d5964d..0cfee0de3a 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.0.2-alpine + image: redis:8.0.3-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 d8855ccac4..dd415cc5fb 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:8.0.2-alpine + image: redis:8.0.3-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 a53acdfef2..06273610b7 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:8.0.2-alpine + image: redis:8.0.3-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 From 4a5a4fe6bd387dbea4f1d0b9f42d7ce74740c5a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:03:23 -0400 Subject: [PATCH 129/828] fix(deps): update dependency webpack to v5.100.0 (#13541) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b59d90be8c..4fab30daf1 100644 --- a/install/package.json +++ b/install/package.json @@ -147,7 +147,7 @@ "tough-cookie": "5.1.2", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.99.9", + "webpack": "5.100.0", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From e4f56e8392e68e40076a3b2f577cd1529b479186 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:10:00 -0400 Subject: [PATCH 130/828] fix(deps): update dependency nodebb-theme-peace to v2.2.46 (#13542) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4fab30daf1..b3c3f78258 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-rewards-essentials": "1.0.2", "nodebb-theme-harmony": "2.1.16", "nodebb-theme-lavender": "7.1.19", - "nodebb-theme-peace": "2.2.45", + "nodebb-theme-peace": "2.2.46", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.38", "nodemailer": "7.0.5", From f88329dbbe9a6ab99eec0eaee3a4b80efbcfe820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 08:50:53 -0400 Subject: [PATCH 131/828] feat: add heap snapshot --- .../language/en-GB/admin/development/info.json | 3 ++- public/openapi/read.yaml | 2 ++ .../openapi/read/admin/advanced/heap/dump.yaml | 18 ++++++++++++++++++ src/controllers/admin/info.js | 18 ++++++++++++++++++ src/routes/admin.js | 1 + src/views/admin/development/info.tpl | 7 ++++++- 6 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 public/openapi/read/admin/advanced/heap/dump.yaml diff --git a/public/language/en-GB/admin/development/info.json b/public/language/en-GB/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/en-GB/admin/development/info.json +++ b/public/language/en-GB/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml index 3ec355bd95..9228ba9c7d 100644 --- a/public/openapi/read.yaml +++ b/public/openapi/read.yaml @@ -174,6 +174,8 @@ paths: $ref: 'read/admin/advanced/cache.yaml' /api/admin/advanced/cache/dump: $ref: 'read/admin/advanced/cache/dump.yaml' + /api/admin/advanced/heap/dump: + $ref: 'read/admin/advanced/heap/dump.yaml' /api/admin/development/logger: $ref: 'read/admin/development/logger.yaml' /api/admin/development/info: diff --git a/public/openapi/read/admin/advanced/heap/dump.yaml b/public/openapi/read/admin/advanced/heap/dump.yaml new file mode 100644 index 0000000000..2c0465eed4 --- /dev/null +++ b/public/openapi/read/admin/advanced/heap/dump.yaml @@ -0,0 +1,18 @@ +get: + tags: + - admin + summary: Get nodejs heap snapshot + description: Downloads a Node.js heap snapshot for memory analysis. + parameters: [] + responses: + "200": + description: Heap snapshot file (in .heapsnapshot format) + content: + application/octet-stream: + schema: + type: string + format: binary + examples: + heapSnapshot: + summary: Example Heap Snapshot Download + description: A binary heap snapshot file. diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 6f63faf8a9..d50af63ebf 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -143,3 +143,21 @@ async function getGitInfo() { ]); return { hash: hash, hashShort: hash.slice(0, 6), branch: branch }; } + +infoController.getHeapdump = async function (req, res) { + const v8 = require('v8'); + const path = require('path'); + const fs = require('fs'); + const filename = path.join(nconf.get('upload_path'), `heapdump-${Date.now()}.heapsnapshot`); + const stored = v8.writeHeapSnapshot(filename, {}); + res.download(stored, 'heapdump.heapsnapshot', (err) => { + if (err) { + winston.error(err.stack); + } + fs.unlink(stored, (unlinkErr) => { + if (unlinkErr) { + winston.error(unlinkErr.stack); + } + }); + }); +}; \ No newline at end of file diff --git a/src/routes/admin.js b/src/routes/admin.js index b7e751695c..94ba8e9e02 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -81,6 +81,7 @@ 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.get(`/api/${name}/advanced/heap/dump`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.info.getHeapdump)); const multer = require('multer'); const storage = multer.diskStorage({}); diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl index 493e16ac93..49301f3543 100644 --- a/src/views/admin/development/info.tpl +++ b/src/views/admin/development/info.tpl @@ -5,7 +5,12 @@
- [[admin/development/info:nodes-responded, {nodeCount}, {timeout}]] +
+ [[admin/development/info:nodes-responded, {nodeCount}, {timeout}]] + + [[admin/development/info:heap-dump]] + +
From 5b54e926f7402a7a3548e863732e23d52eaee79d Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 11 Jul 2025 12:51:18 +0000 Subject: [PATCH 132/828] chore(i18n): fallback strings for new resources: nodebb.admin-development-info --- public/language/ar/admin/development/info.json | 3 ++- public/language/az/admin/development/info.json | 3 ++- public/language/bg/admin/development/info.json | 3 ++- public/language/bn/admin/development/info.json | 3 ++- public/language/cs/admin/development/info.json | 3 ++- public/language/da/admin/development/info.json | 3 ++- public/language/de/admin/development/info.json | 3 ++- public/language/el/admin/development/info.json | 3 ++- public/language/en-US/admin/development/info.json | 3 ++- public/language/en-x-pirate/admin/development/info.json | 3 ++- public/language/es/admin/development/info.json | 3 ++- public/language/et/admin/development/info.json | 3 ++- public/language/fa-IR/admin/development/info.json | 3 ++- public/language/fi/admin/development/info.json | 3 ++- public/language/fr/admin/development/info.json | 3 ++- public/language/gl/admin/development/info.json | 3 ++- public/language/he/admin/development/info.json | 3 ++- public/language/hr/admin/development/info.json | 3 ++- public/language/hu/admin/development/info.json | 3 ++- public/language/hy/admin/development/info.json | 3 ++- public/language/id/admin/development/info.json | 3 ++- public/language/it/admin/development/info.json | 3 ++- public/language/ja/admin/development/info.json | 3 ++- public/language/ko/admin/development/info.json | 3 ++- public/language/lt/admin/development/info.json | 3 ++- public/language/lv/admin/development/info.json | 3 ++- public/language/ms/admin/development/info.json | 3 ++- public/language/nb/admin/development/info.json | 3 ++- public/language/nl/admin/development/info.json | 3 ++- public/language/nn-NO/admin/development/info.json | 3 ++- public/language/pl/admin/development/info.json | 3 ++- public/language/pt-BR/admin/development/info.json | 3 ++- public/language/pt-PT/admin/development/info.json | 3 ++- public/language/ro/admin/development/info.json | 3 ++- public/language/ru/admin/development/info.json | 3 ++- public/language/rw/admin/development/info.json | 3 ++- public/language/sc/admin/development/info.json | 3 ++- public/language/sk/admin/development/info.json | 3 ++- public/language/sl/admin/development/info.json | 3 ++- public/language/sq-AL/admin/development/info.json | 3 ++- public/language/sr/admin/development/info.json | 3 ++- public/language/sv/admin/development/info.json | 3 ++- public/language/th/admin/development/info.json | 3 ++- public/language/tr/admin/development/info.json | 3 ++- public/language/uk/admin/development/info.json | 3 ++- public/language/vi/admin/development/info.json | 3 ++- public/language/zh-CN/admin/development/info.json | 3 ++- public/language/zh-TW/admin/development/info.json | 3 ++- 48 files changed, 96 insertions(+), 48 deletions(-) diff --git a/public/language/ar/admin/development/info.json b/public/language/ar/admin/development/info.json index 4c97beee13..386c47e050 100644 --- a/public/language/ar/admin/development/info.json +++ b/public/language/ar/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/az/admin/development/info.json b/public/language/az/admin/development/info.json index a61eab10d3..22b43c2bc7 100644 --- a/public/language/az/admin/development/info.json +++ b/public/language/az/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Bağlantı sayı", "guests": "Qonaqlar", - "info": "Məlumat" + "info": "Məlumat", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/bg/admin/development/info.json b/public/language/bg/admin/development/info.json index 71f1e63c4b..8caea710f7 100644 --- a/public/language/bg/admin/development/info.json +++ b/public/language/bg/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Брой връзки", "guests": "Гости", - "info": "Информация" + "info": "Информация", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/bn/admin/development/info.json b/public/language/bn/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/bn/admin/development/info.json +++ b/public/language/bn/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/cs/admin/development/info.json b/public/language/cs/admin/development/info.json index 19fb830e9d..2a5e981a74 100644 --- a/public/language/cs/admin/development/info.json +++ b/public/language/cs/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Hosté", - "info": "Informace" + "info": "Informace", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/da/admin/development/info.json b/public/language/da/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/da/admin/development/info.json +++ b/public/language/da/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/de/admin/development/info.json b/public/language/de/admin/development/info.json index b5dd27dfe5..296aa0af43 100644 --- a/public/language/de/admin/development/info.json +++ b/public/language/de/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Verbindungsanzahl", "guests": "Gäste", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/el/admin/development/info.json b/public/language/el/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/el/admin/development/info.json +++ b/public/language/el/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/en-US/admin/development/info.json b/public/language/en-US/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/en-US/admin/development/info.json +++ b/public/language/en-US/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/development/info.json b/public/language/en-x-pirate/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/en-x-pirate/admin/development/info.json +++ b/public/language/en-x-pirate/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/es/admin/development/info.json b/public/language/es/admin/development/info.json index 809d0c85bc..92821fcf36 100644 --- a/public/language/es/admin/development/info.json +++ b/public/language/es/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Número de conexiones", "guests": "Invitados", - "info": "Información" + "info": "Información", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/et/admin/development/info.json b/public/language/et/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/et/admin/development/info.json +++ b/public/language/et/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/fa-IR/admin/development/info.json b/public/language/fa-IR/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/fa-IR/admin/development/info.json +++ b/public/language/fa-IR/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/fi/admin/development/info.json b/public/language/fi/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/fi/admin/development/info.json +++ b/public/language/fi/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/fr/admin/development/info.json b/public/language/fr/admin/development/info.json index afcbe555aa..3a17f92ad9 100644 --- a/public/language/fr/admin/development/info.json +++ b/public/language/fr/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "nombre de connexions", "guests": "Invités", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/gl/admin/development/info.json b/public/language/gl/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/gl/admin/development/info.json +++ b/public/language/gl/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/he/admin/development/info.json b/public/language/he/admin/development/info.json index d20aa255f5..0532e8febf 100644 --- a/public/language/he/admin/development/info.json +++ b/public/language/he/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "כמות חיבורים", "guests": "אורחים", - "info": "מידע" + "info": "מידע", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/hr/admin/development/info.json b/public/language/hr/admin/development/info.json index 3b21db6c9a..ebc7612366 100644 --- a/public/language/hr/admin/development/info.json +++ b/public/language/hr/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Gosti", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/hu/admin/development/info.json b/public/language/hu/admin/development/info.json index 48bf12d34b..130d0efdda 100644 --- a/public/language/hu/admin/development/info.json +++ b/public/language/hu/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Vendégek", - "info": "Információ" + "info": "Információ", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/hy/admin/development/info.json b/public/language/hy/admin/development/info.json index ca2bdf9f66..cf5128c6fc 100644 --- a/public/language/hy/admin/development/info.json +++ b/public/language/hy/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Հյուրեր", - "info": "տեղեկատվություն" + "info": "տեղեկատվություն", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/id/admin/development/info.json b/public/language/id/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/id/admin/development/info.json +++ b/public/language/id/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/it/admin/development/info.json b/public/language/it/admin/development/info.json index d5bb340bc5..7e2ef613ef 100644 --- a/public/language/it/admin/development/info.json +++ b/public/language/it/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Conteggio connessioni", "guests": "Ospiti", - "info": "Informazioni" + "info": "Informazioni", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/ja/admin/development/info.json b/public/language/ja/admin/development/info.json index 857c419a66..1214f8a023 100644 --- a/public/language/ja/admin/development/info.json +++ b/public/language/ja/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "ゲスト数", - "info": "情報" + "info": "情報", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/ko/admin/development/info.json b/public/language/ko/admin/development/info.json index 08cb8a739b..7184d8060f 100644 --- a/public/language/ko/admin/development/info.json +++ b/public/language/ko/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "비회원", - "info": "정보" + "info": "정보", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/lt/admin/development/info.json b/public/language/lt/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/lt/admin/development/info.json +++ b/public/language/lt/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/lv/admin/development/info.json b/public/language/lv/admin/development/info.json index 87cb89d8cf..d1798f4254 100644 --- a/public/language/lv/admin/development/info.json +++ b/public/language/lv/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Viesi", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/ms/admin/development/info.json b/public/language/ms/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/ms/admin/development/info.json +++ b/public/language/ms/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/nb/admin/development/info.json b/public/language/nb/admin/development/info.json index deb1975761..fc3af734be 100644 --- a/public/language/nb/admin/development/info.json +++ b/public/language/nb/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Antall tilkoblinger", "guests": "Gjester", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/nl/admin/development/info.json b/public/language/nl/admin/development/info.json index b5ee1a8838..19fa4d7cfd 100644 --- a/public/language/nl/admin/development/info.json +++ b/public/language/nl/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Gasten", - "info": "Informatie" + "info": "Informatie", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/nn-NO/admin/development/info.json b/public/language/nn-NO/admin/development/info.json index 2d28c9fcfb..30c27cddb3 100644 --- a/public/language/nn-NO/admin/development/info.json +++ b/public/language/nn-NO/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Tilkoplingar", "guests": "Gjestar", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/pl/admin/development/info.json b/public/language/pl/admin/development/info.json index a56206ad41..3a90dc57cb 100644 --- a/public/language/pl/admin/development/info.json +++ b/public/language/pl/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Liczba połączeń", "guests": "Goście", - "info": "Informacja" + "info": "Informacja", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/pt-BR/admin/development/info.json b/public/language/pt-BR/admin/development/info.json index e0fabb3ddb..bc6c6aeecd 100644 --- a/public/language/pt-BR/admin/development/info.json +++ b/public/language/pt-BR/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Visitantes", - "info": "Informação" + "info": "Informação", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/pt-PT/admin/development/info.json b/public/language/pt-PT/admin/development/info.json index 9dc6b6665e..5f7c5e7d6c 100644 --- a/public/language/pt-PT/admin/development/info.json +++ b/public/language/pt-PT/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Convidados", - "info": "Informação" + "info": "Informação", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/ro/admin/development/info.json b/public/language/ro/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/ro/admin/development/info.json +++ b/public/language/ro/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/ru/admin/development/info.json b/public/language/ru/admin/development/info.json index a7f1481f88..748d45e9ed 100644 --- a/public/language/ru/admin/development/info.json +++ b/public/language/ru/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Гостей", - "info": "Сырые данные" + "info": "Сырые данные", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/rw/admin/development/info.json b/public/language/rw/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/rw/admin/development/info.json +++ b/public/language/rw/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/sc/admin/development/info.json b/public/language/sc/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/sc/admin/development/info.json +++ b/public/language/sc/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/sk/admin/development/info.json b/public/language/sk/admin/development/info.json index c6dbfd349e..eb4ce931a7 100644 --- a/public/language/sk/admin/development/info.json +++ b/public/language/sk/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Hostia", - "info": "Informácie" + "info": "Informácie", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/sl/admin/development/info.json b/public/language/sl/admin/development/info.json index f53089170f..36dbf4ba32 100644 --- a/public/language/sl/admin/development/info.json +++ b/public/language/sl/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Gostje", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/sq-AL/admin/development/info.json b/public/language/sq-AL/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/sq-AL/admin/development/info.json +++ b/public/language/sq-AL/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/sr/admin/development/info.json b/public/language/sr/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/sr/admin/development/info.json +++ b/public/language/sr/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/sv/admin/development/info.json b/public/language/sv/admin/development/info.json index 9834719daf..40e8fded15 100644 --- a/public/language/sv/admin/development/info.json +++ b/public/language/sv/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info" + "info": "Info", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/th/admin/development/info.json b/public/language/th/admin/development/info.json index b78747dc04..f161f88b83 100644 --- a/public/language/th/admin/development/info.json +++ b/public/language/th/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "จำนวนการเชื่อมต่อ", "guests": "ผู้เยี่ยมเยียน", - "info": "ข้อมูล" + "info": "ข้อมูล", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/tr/admin/development/info.json b/public/language/tr/admin/development/info.json index d1bccbc674..cc0eaf9242 100644 --- a/public/language/tr/admin/development/info.json +++ b/public/language/tr/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Ziyaretçiler", - "info": "Bilgi" + "info": "Bilgi", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/uk/admin/development/info.json b/public/language/uk/admin/development/info.json index 42f90c8282..0db8f04563 100644 --- a/public/language/uk/admin/development/info.json +++ b/public/language/uk/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Connection Count", "guests": "Гостей", - "info": "Інфо" + "info": "Інфо", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/vi/admin/development/info.json b/public/language/vi/admin/development/info.json index f8e2480b94..088d0fb147 100644 --- a/public/language/vi/admin/development/info.json +++ b/public/language/vi/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "Số Lượng Kết Nối", "guests": "Khách", - "info": "Thông tin" + "info": "Thông tin", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/development/info.json b/public/language/zh-CN/admin/development/info.json index ac389d848e..2a7845e5ca 100644 --- a/public/language/zh-CN/admin/development/info.json +++ b/public/language/zh-CN/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "连接数", "guests": "游客", - "info": "信息" + "info": "信息", + "heap-dump": "Heap Dump" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/development/info.json b/public/language/zh-TW/admin/development/info.json index df3bc6cd12..d57fd95abd 100644 --- a/public/language/zh-TW/admin/development/info.json +++ b/public/language/zh-TW/admin/development/info.json @@ -22,5 +22,6 @@ "connection-count": "連線次數", "guests": "訪客", - "info": "資訊" + "info": "資訊", + "heap-dump": "Heap Dump" } \ No newline at end of file From 930ff21f335de42d015985ae438bb96b009baef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 09:01:33 -0400 Subject: [PATCH 133/828] test: disable timeout --- src/controllers/admin/info.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index d50af63ebf..4e9e95df9e 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -145,6 +145,7 @@ async function getGitInfo() { } infoController.getHeapdump = async function (req, res) { + req.timeout(0); // Disable timeout for this request const v8 = require('v8'); const path = require('path'); const fs = require('fs'); From 27aab921910f575e06a94a1805907f1c902d48db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 09:05:43 -0400 Subject: [PATCH 134/828] test: try timeout again --- src/controllers/admin/info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 4e9e95df9e..e244003a1a 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -145,7 +145,7 @@ async function getGitInfo() { } infoController.getHeapdump = async function (req, res) { - req.timeout(0); // Disable timeout for this request + req.setTimeout(0); const v8 = require('v8'); const path = require('path'); const fs = require('fs'); From e74996fbb950765795bb58687a6b9f0398262ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 10:22:37 -0400 Subject: [PATCH 135/828] revert: remove heapdump --- .../en-GB/admin/development/info.json | 3 +-- public/openapi/read.yaml | 2 -- .../read/admin/advanced/heap/dump.yaml | 18 ------------------ src/controllers/admin/info.js | 19 ------------------- src/routes/admin.js | 1 - src/views/admin/development/info.tpl | 7 +------ 6 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 public/openapi/read/admin/advanced/heap/dump.yaml diff --git a/public/language/en-GB/admin/development/info.json b/public/language/en-GB/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/en-GB/admin/development/info.json +++ b/public/language/en-GB/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml index 9228ba9c7d..3ec355bd95 100644 --- a/public/openapi/read.yaml +++ b/public/openapi/read.yaml @@ -174,8 +174,6 @@ paths: $ref: 'read/admin/advanced/cache.yaml' /api/admin/advanced/cache/dump: $ref: 'read/admin/advanced/cache/dump.yaml' - /api/admin/advanced/heap/dump: - $ref: 'read/admin/advanced/heap/dump.yaml' /api/admin/development/logger: $ref: 'read/admin/development/logger.yaml' /api/admin/development/info: diff --git a/public/openapi/read/admin/advanced/heap/dump.yaml b/public/openapi/read/admin/advanced/heap/dump.yaml deleted file mode 100644 index 2c0465eed4..0000000000 --- a/public/openapi/read/admin/advanced/heap/dump.yaml +++ /dev/null @@ -1,18 +0,0 @@ -get: - tags: - - admin - summary: Get nodejs heap snapshot - description: Downloads a Node.js heap snapshot for memory analysis. - parameters: [] - responses: - "200": - description: Heap snapshot file (in .heapsnapshot format) - content: - application/octet-stream: - schema: - type: string - format: binary - examples: - heapSnapshot: - summary: Example Heap Snapshot Download - description: A binary heap snapshot file. diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index e244003a1a..6f63faf8a9 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -143,22 +143,3 @@ async function getGitInfo() { ]); return { hash: hash, hashShort: hash.slice(0, 6), branch: branch }; } - -infoController.getHeapdump = async function (req, res) { - req.setTimeout(0); - const v8 = require('v8'); - const path = require('path'); - const fs = require('fs'); - const filename = path.join(nconf.get('upload_path'), `heapdump-${Date.now()}.heapsnapshot`); - const stored = v8.writeHeapSnapshot(filename, {}); - res.download(stored, 'heapdump.heapsnapshot', (err) => { - if (err) { - winston.error(err.stack); - } - fs.unlink(stored, (unlinkErr) => { - if (unlinkErr) { - winston.error(unlinkErr.stack); - } - }); - }); -}; \ No newline at end of file diff --git a/src/routes/admin.js b/src/routes/admin.js index 94ba8e9e02..b7e751695c 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -81,7 +81,6 @@ 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.get(`/api/${name}/advanced/heap/dump`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.info.getHeapdump)); const multer = require('multer'); const storage = multer.diskStorage({}); diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl index 49301f3543..493e16ac93 100644 --- a/src/views/admin/development/info.tpl +++ b/src/views/admin/development/info.tpl @@ -5,12 +5,7 @@
-
- [[admin/development/info:nodes-responded, {nodeCount}, {timeout}]] - - [[admin/development/info:heap-dump]] - -
+ [[admin/development/info:nodes-responded, {nodeCount}, {timeout}]]
From 59c1ce853f905c2f4a9af799a24b2fb76fc3bdf9 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 11 Jul 2025 14:23:04 +0000 Subject: [PATCH 136/828] chore(i18n): fallback strings for new resources: nodebb.admin-development-info --- public/language/ar/admin/development/info.json | 3 +-- public/language/az/admin/development/info.json | 3 +-- public/language/bg/admin/development/info.json | 3 +-- public/language/bn/admin/development/info.json | 3 +-- public/language/cs/admin/development/info.json | 3 +-- public/language/da/admin/development/info.json | 3 +-- public/language/de/admin/development/info.json | 3 +-- public/language/el/admin/development/info.json | 3 +-- public/language/en-US/admin/development/info.json | 3 +-- public/language/en-x-pirate/admin/development/info.json | 3 +-- public/language/es/admin/development/info.json | 3 +-- public/language/et/admin/development/info.json | 3 +-- public/language/fa-IR/admin/development/info.json | 3 +-- public/language/fi/admin/development/info.json | 3 +-- public/language/fr/admin/development/info.json | 3 +-- public/language/gl/admin/development/info.json | 3 +-- public/language/he/admin/development/info.json | 3 +-- public/language/hr/admin/development/info.json | 3 +-- public/language/hu/admin/development/info.json | 3 +-- public/language/hy/admin/development/info.json | 3 +-- public/language/id/admin/development/info.json | 3 +-- public/language/it/admin/development/info.json | 3 +-- public/language/ja/admin/development/info.json | 3 +-- public/language/ko/admin/development/info.json | 3 +-- public/language/lt/admin/development/info.json | 3 +-- public/language/lv/admin/development/info.json | 3 +-- public/language/ms/admin/development/info.json | 3 +-- public/language/nb/admin/development/info.json | 3 +-- public/language/nl/admin/development/info.json | 3 +-- public/language/nn-NO/admin/development/info.json | 3 +-- public/language/pl/admin/development/info.json | 3 +-- public/language/pt-BR/admin/development/info.json | 3 +-- public/language/pt-PT/admin/development/info.json | 3 +-- public/language/ro/admin/development/info.json | 3 +-- public/language/ru/admin/development/info.json | 3 +-- public/language/rw/admin/development/info.json | 3 +-- public/language/sc/admin/development/info.json | 3 +-- public/language/sk/admin/development/info.json | 3 +-- public/language/sl/admin/development/info.json | 3 +-- public/language/sq-AL/admin/development/info.json | 3 +-- public/language/sr/admin/development/info.json | 3 +-- public/language/sv/admin/development/info.json | 3 +-- public/language/th/admin/development/info.json | 3 +-- public/language/tr/admin/development/info.json | 3 +-- public/language/uk/admin/development/info.json | 3 +-- public/language/vi/admin/development/info.json | 3 +-- public/language/zh-CN/admin/development/info.json | 3 +-- public/language/zh-TW/admin/development/info.json | 3 +-- 48 files changed, 48 insertions(+), 96 deletions(-) diff --git a/public/language/ar/admin/development/info.json b/public/language/ar/admin/development/info.json index 386c47e050..4c97beee13 100644 --- a/public/language/ar/admin/development/info.json +++ b/public/language/ar/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/az/admin/development/info.json b/public/language/az/admin/development/info.json index 22b43c2bc7..a61eab10d3 100644 --- a/public/language/az/admin/development/info.json +++ b/public/language/az/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Bağlantı sayı", "guests": "Qonaqlar", - "info": "Məlumat", - "heap-dump": "Heap Dump" + "info": "Məlumat" } \ No newline at end of file diff --git a/public/language/bg/admin/development/info.json b/public/language/bg/admin/development/info.json index 8caea710f7..71f1e63c4b 100644 --- a/public/language/bg/admin/development/info.json +++ b/public/language/bg/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Брой връзки", "guests": "Гости", - "info": "Информация", - "heap-dump": "Heap Dump" + "info": "Информация" } \ No newline at end of file diff --git a/public/language/bn/admin/development/info.json b/public/language/bn/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/bn/admin/development/info.json +++ b/public/language/bn/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/cs/admin/development/info.json b/public/language/cs/admin/development/info.json index 2a5e981a74..19fb830e9d 100644 --- a/public/language/cs/admin/development/info.json +++ b/public/language/cs/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Hosté", - "info": "Informace", - "heap-dump": "Heap Dump" + "info": "Informace" } \ No newline at end of file diff --git a/public/language/da/admin/development/info.json b/public/language/da/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/da/admin/development/info.json +++ b/public/language/da/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/de/admin/development/info.json b/public/language/de/admin/development/info.json index 296aa0af43..b5dd27dfe5 100644 --- a/public/language/de/admin/development/info.json +++ b/public/language/de/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Verbindungsanzahl", "guests": "Gäste", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/el/admin/development/info.json b/public/language/el/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/el/admin/development/info.json +++ b/public/language/el/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/en-US/admin/development/info.json b/public/language/en-US/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/en-US/admin/development/info.json +++ b/public/language/en-US/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/development/info.json b/public/language/en-x-pirate/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/en-x-pirate/admin/development/info.json +++ b/public/language/en-x-pirate/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/es/admin/development/info.json b/public/language/es/admin/development/info.json index 92821fcf36..809d0c85bc 100644 --- a/public/language/es/admin/development/info.json +++ b/public/language/es/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Número de conexiones", "guests": "Invitados", - "info": "Información", - "heap-dump": "Heap Dump" + "info": "Información" } \ No newline at end of file diff --git a/public/language/et/admin/development/info.json b/public/language/et/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/et/admin/development/info.json +++ b/public/language/et/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/fa-IR/admin/development/info.json b/public/language/fa-IR/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/fa-IR/admin/development/info.json +++ b/public/language/fa-IR/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/fi/admin/development/info.json b/public/language/fi/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/fi/admin/development/info.json +++ b/public/language/fi/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/fr/admin/development/info.json b/public/language/fr/admin/development/info.json index 3a17f92ad9..afcbe555aa 100644 --- a/public/language/fr/admin/development/info.json +++ b/public/language/fr/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "nombre de connexions", "guests": "Invités", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/gl/admin/development/info.json b/public/language/gl/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/gl/admin/development/info.json +++ b/public/language/gl/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/he/admin/development/info.json b/public/language/he/admin/development/info.json index 0532e8febf..d20aa255f5 100644 --- a/public/language/he/admin/development/info.json +++ b/public/language/he/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "כמות חיבורים", "guests": "אורחים", - "info": "מידע", - "heap-dump": "Heap Dump" + "info": "מידע" } \ No newline at end of file diff --git a/public/language/hr/admin/development/info.json b/public/language/hr/admin/development/info.json index ebc7612366..3b21db6c9a 100644 --- a/public/language/hr/admin/development/info.json +++ b/public/language/hr/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Gosti", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/hu/admin/development/info.json b/public/language/hu/admin/development/info.json index 130d0efdda..48bf12d34b 100644 --- a/public/language/hu/admin/development/info.json +++ b/public/language/hu/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Vendégek", - "info": "Információ", - "heap-dump": "Heap Dump" + "info": "Információ" } \ No newline at end of file diff --git a/public/language/hy/admin/development/info.json b/public/language/hy/admin/development/info.json index cf5128c6fc..ca2bdf9f66 100644 --- a/public/language/hy/admin/development/info.json +++ b/public/language/hy/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Հյուրեր", - "info": "տեղեկատվություն", - "heap-dump": "Heap Dump" + "info": "տեղեկատվություն" } \ No newline at end of file diff --git a/public/language/id/admin/development/info.json b/public/language/id/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/id/admin/development/info.json +++ b/public/language/id/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/it/admin/development/info.json b/public/language/it/admin/development/info.json index 7e2ef613ef..d5bb340bc5 100644 --- a/public/language/it/admin/development/info.json +++ b/public/language/it/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Conteggio connessioni", "guests": "Ospiti", - "info": "Informazioni", - "heap-dump": "Heap Dump" + "info": "Informazioni" } \ No newline at end of file diff --git a/public/language/ja/admin/development/info.json b/public/language/ja/admin/development/info.json index 1214f8a023..857c419a66 100644 --- a/public/language/ja/admin/development/info.json +++ b/public/language/ja/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "ゲスト数", - "info": "情報", - "heap-dump": "Heap Dump" + "info": "情報" } \ No newline at end of file diff --git a/public/language/ko/admin/development/info.json b/public/language/ko/admin/development/info.json index 7184d8060f..08cb8a739b 100644 --- a/public/language/ko/admin/development/info.json +++ b/public/language/ko/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "비회원", - "info": "정보", - "heap-dump": "Heap Dump" + "info": "정보" } \ No newline at end of file diff --git a/public/language/lt/admin/development/info.json b/public/language/lt/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/lt/admin/development/info.json +++ b/public/language/lt/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/lv/admin/development/info.json b/public/language/lv/admin/development/info.json index d1798f4254..87cb89d8cf 100644 --- a/public/language/lv/admin/development/info.json +++ b/public/language/lv/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Viesi", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/ms/admin/development/info.json b/public/language/ms/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/ms/admin/development/info.json +++ b/public/language/ms/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/nb/admin/development/info.json b/public/language/nb/admin/development/info.json index fc3af734be..deb1975761 100644 --- a/public/language/nb/admin/development/info.json +++ b/public/language/nb/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Antall tilkoblinger", "guests": "Gjester", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/nl/admin/development/info.json b/public/language/nl/admin/development/info.json index 19fa4d7cfd..b5ee1a8838 100644 --- a/public/language/nl/admin/development/info.json +++ b/public/language/nl/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Gasten", - "info": "Informatie", - "heap-dump": "Heap Dump" + "info": "Informatie" } \ No newline at end of file diff --git a/public/language/nn-NO/admin/development/info.json b/public/language/nn-NO/admin/development/info.json index 30c27cddb3..2d28c9fcfb 100644 --- a/public/language/nn-NO/admin/development/info.json +++ b/public/language/nn-NO/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Tilkoplingar", "guests": "Gjestar", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/pl/admin/development/info.json b/public/language/pl/admin/development/info.json index 3a90dc57cb..a56206ad41 100644 --- a/public/language/pl/admin/development/info.json +++ b/public/language/pl/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Liczba połączeń", "guests": "Goście", - "info": "Informacja", - "heap-dump": "Heap Dump" + "info": "Informacja" } \ No newline at end of file diff --git a/public/language/pt-BR/admin/development/info.json b/public/language/pt-BR/admin/development/info.json index bc6c6aeecd..e0fabb3ddb 100644 --- a/public/language/pt-BR/admin/development/info.json +++ b/public/language/pt-BR/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Visitantes", - "info": "Informação", - "heap-dump": "Heap Dump" + "info": "Informação" } \ No newline at end of file diff --git a/public/language/pt-PT/admin/development/info.json b/public/language/pt-PT/admin/development/info.json index 5f7c5e7d6c..9dc6b6665e 100644 --- a/public/language/pt-PT/admin/development/info.json +++ b/public/language/pt-PT/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Convidados", - "info": "Informação", - "heap-dump": "Heap Dump" + "info": "Informação" } \ No newline at end of file diff --git a/public/language/ro/admin/development/info.json b/public/language/ro/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/ro/admin/development/info.json +++ b/public/language/ro/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/ru/admin/development/info.json b/public/language/ru/admin/development/info.json index 748d45e9ed..a7f1481f88 100644 --- a/public/language/ru/admin/development/info.json +++ b/public/language/ru/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Гостей", - "info": "Сырые данные", - "heap-dump": "Heap Dump" + "info": "Сырые данные" } \ No newline at end of file diff --git a/public/language/rw/admin/development/info.json b/public/language/rw/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/rw/admin/development/info.json +++ b/public/language/rw/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/sc/admin/development/info.json b/public/language/sc/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/sc/admin/development/info.json +++ b/public/language/sc/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/sk/admin/development/info.json b/public/language/sk/admin/development/info.json index eb4ce931a7..c6dbfd349e 100644 --- a/public/language/sk/admin/development/info.json +++ b/public/language/sk/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Hostia", - "info": "Informácie", - "heap-dump": "Heap Dump" + "info": "Informácie" } \ No newline at end of file diff --git a/public/language/sl/admin/development/info.json b/public/language/sl/admin/development/info.json index 36dbf4ba32..f53089170f 100644 --- a/public/language/sl/admin/development/info.json +++ b/public/language/sl/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Gostje", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/sq-AL/admin/development/info.json b/public/language/sq-AL/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/sq-AL/admin/development/info.json +++ b/public/language/sq-AL/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/sr/admin/development/info.json b/public/language/sr/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/sr/admin/development/info.json +++ b/public/language/sr/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/sv/admin/development/info.json b/public/language/sv/admin/development/info.json index 40e8fded15..9834719daf 100644 --- a/public/language/sv/admin/development/info.json +++ b/public/language/sv/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Guests", - "info": "Info", - "heap-dump": "Heap Dump" + "info": "Info" } \ No newline at end of file diff --git a/public/language/th/admin/development/info.json b/public/language/th/admin/development/info.json index f161f88b83..b78747dc04 100644 --- a/public/language/th/admin/development/info.json +++ b/public/language/th/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "จำนวนการเชื่อมต่อ", "guests": "ผู้เยี่ยมเยียน", - "info": "ข้อมูล", - "heap-dump": "Heap Dump" + "info": "ข้อมูล" } \ No newline at end of file diff --git a/public/language/tr/admin/development/info.json b/public/language/tr/admin/development/info.json index cc0eaf9242..d1bccbc674 100644 --- a/public/language/tr/admin/development/info.json +++ b/public/language/tr/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Ziyaretçiler", - "info": "Bilgi", - "heap-dump": "Heap Dump" + "info": "Bilgi" } \ No newline at end of file diff --git a/public/language/uk/admin/development/info.json b/public/language/uk/admin/development/info.json index 0db8f04563..42f90c8282 100644 --- a/public/language/uk/admin/development/info.json +++ b/public/language/uk/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Connection Count", "guests": "Гостей", - "info": "Інфо", - "heap-dump": "Heap Dump" + "info": "Інфо" } \ No newline at end of file diff --git a/public/language/vi/admin/development/info.json b/public/language/vi/admin/development/info.json index 088d0fb147..f8e2480b94 100644 --- a/public/language/vi/admin/development/info.json +++ b/public/language/vi/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "Số Lượng Kết Nối", "guests": "Khách", - "info": "Thông tin", - "heap-dump": "Heap Dump" + "info": "Thông tin" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/development/info.json b/public/language/zh-CN/admin/development/info.json index 2a7845e5ca..ac389d848e 100644 --- a/public/language/zh-CN/admin/development/info.json +++ b/public/language/zh-CN/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "连接数", "guests": "游客", - "info": "信息", - "heap-dump": "Heap Dump" + "info": "信息" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/development/info.json b/public/language/zh-TW/admin/development/info.json index d57fd95abd..df3bc6cd12 100644 --- a/public/language/zh-TW/admin/development/info.json +++ b/public/language/zh-TW/admin/development/info.json @@ -22,6 +22,5 @@ "connection-count": "連線次數", "guests": "訪客", - "info": "資訊", - "heap-dump": "Heap Dump" + "info": "資訊" } \ No newline at end of file From 559a2d233de905ec56fe95eb9d51a2d260617dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 15:09:55 -0400 Subject: [PATCH 137/828] feat: add ap pageviews analytics --- public/language/en-GB/admin/dashboard.json | 1 + public/src/admin/dashboard.js | 21 ++++++++++++++++++--- src/analytics.js | 11 +++++++++++ src/controllers/admin/dashboard.js | 2 +- src/middleware/activitypub.js | 6 ++++++ src/routes/activitypub.js | 1 + src/socket.io/admin/analytics.js | 1 + 7 files changed, 39 insertions(+), 4 deletions(-) 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/src/admin/dashboard.js b/public/src/admin/dashboard.js index a2b624c5a8..5fa55a7e0d 100644 --- a/public/src/admin/dashboard.js +++ b/public/src/admin/dashboard.js @@ -165,6 +165,7 @@ function setupGraphs(callback) { 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.page-views-ap', []), t.translateKey('admin/dashboard:graphs.unique-visitors', []), t.translateKey('admin/dashboard:graphs.registered-users', []), t.translateKey('admin/dashboard:graphs.guest-users', []), @@ -231,6 +232,18 @@ function setupGraphs(callback) { 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: translations[5], + fill: 'origin', + tension: tension, + backgroundColor: 'rgba(151,187,205,0.2)', borderColor: 'rgba(151,187,205,1)', pointBackgroundColor: 'rgba(151,187,205,1)', pointHoverBackgroundColor: 'rgba(151,187,205,1)', @@ -247,7 +260,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', @@ -269,7 +283,7 @@ function setupGraphs(callback) { type: 'linear', title: { display: true, - text: translations[4], + text: translations[5], }, beginAtZero: true, }, @@ -446,7 +460,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/src/analytics.js b/src/analytics.js index b70aec7e5c..a0fadeab68 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); @@ -117,6 +118,10 @@ Analytics.pageView = async function (payload) { } }; +Analytics.apPageView = function () { + local.apPageViews += 1; +}; + Analytics.writeData = async function () { const today = new Date(); const month = new Date(); @@ -162,6 +167,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/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index aa173eca07..20f31c2b67 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -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/middleware/activitypub.js b/src/middleware/activitypub.js index b38caa552a..4390335c0b 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -3,11 +3,17 @@ const db = require('../database'); const meta = require('../meta'); const activitypub = require('../activitypub'); +const analytics = require('../analytics'); const middleware = module.exports; middleware.enabled = async (req, res, next) => next(!meta.config.activitypubEnabled ? 'route' : undefined); +middleware.pageview = async (req, res, next) => { + analytics.apPageView(); + 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. diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js index 760287e102..815fd738ef 100644 --- a/src/routes/activitypub.js +++ b/src/routes/activitypub.js @@ -14,6 +14,7 @@ module.exports = function (app, middleware, controllers) { const middlewares = [ middleware.activitypub.enabled, + middleware.activitypub.pageview, middleware.activitypub.assertS2S, middleware.activitypub.verify, middleware.activitypub.configureResponse, diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js index 8af8881873..03cffb6d20 100644 --- a/src/socket.io/admin/analytics.js +++ b/src/socket.io/admin/analytics.js @@ -30,6 +30,7 @@ Analytics.get = async function (socket, data) { 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)); From 5d16fdc93f7469d000720c8accd2ebdbd4e82864 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 11 Jul 2025 19:10:21 +0000 Subject: [PATCH 138/828] chore(i18n): fallback strings for new resources: nodebb.admin-dashboard --- public/language/ar/admin/dashboard.json | 1 + public/language/az/admin/dashboard.json | 1 + public/language/bg/admin/dashboard.json | 1 + public/language/bn/admin/dashboard.json | 1 + public/language/cs/admin/dashboard.json | 1 + public/language/da/admin/dashboard.json | 1 + public/language/de/admin/dashboard.json | 1 + public/language/el/admin/dashboard.json | 1 + public/language/en-US/admin/dashboard.json | 1 + public/language/en-x-pirate/admin/dashboard.json | 1 + public/language/es/admin/dashboard.json | 1 + public/language/et/admin/dashboard.json | 1 + public/language/fa-IR/admin/dashboard.json | 1 + public/language/fi/admin/dashboard.json | 1 + public/language/fr/admin/dashboard.json | 1 + public/language/gl/admin/dashboard.json | 1 + public/language/he/admin/dashboard.json | 1 + public/language/hr/admin/dashboard.json | 1 + public/language/hu/admin/dashboard.json | 1 + public/language/hy/admin/dashboard.json | 1 + public/language/id/admin/dashboard.json | 1 + public/language/it/admin/dashboard.json | 1 + public/language/ja/admin/dashboard.json | 1 + public/language/ko/admin/dashboard.json | 1 + public/language/lt/admin/dashboard.json | 1 + public/language/lv/admin/dashboard.json | 1 + public/language/ms/admin/dashboard.json | 1 + public/language/nb/admin/dashboard.json | 1 + public/language/nl/admin/dashboard.json | 1 + public/language/nn-NO/admin/dashboard.json | 1 + public/language/pl/admin/dashboard.json | 1 + public/language/pt-BR/admin/dashboard.json | 1 + public/language/pt-PT/admin/dashboard.json | 1 + public/language/ro/admin/dashboard.json | 1 + public/language/ru/admin/dashboard.json | 1 + public/language/rw/admin/dashboard.json | 1 + public/language/sc/admin/dashboard.json | 1 + public/language/sk/admin/dashboard.json | 1 + public/language/sl/admin/dashboard.json | 1 + public/language/sq-AL/admin/dashboard.json | 1 + public/language/sr/admin/dashboard.json | 1 + public/language/sv/admin/dashboard.json | 1 + public/language/th/admin/dashboard.json | 1 + public/language/tr/admin/dashboard.json | 1 + public/language/uk/admin/dashboard.json | 1 + public/language/vi/admin/dashboard.json | 1 + public/language/zh-CN/admin/dashboard.json | 1 + public/language/zh-TW/admin/dashboard.json | 1 + 48 files changed, 48 insertions(+) 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/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/bg/admin/dashboard.json b/public/language/bg/admin/dashboard.json index d7839ed1ff..6473ec0b24 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 Page Views", "graphs.unique-visitors": "Уникални посетители", "graphs.registered-users": "Регистрирани потребители", "graphs.guest-users": "Гости", 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/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/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/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/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/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-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/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/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/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/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/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/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/he/admin/dashboard.json b/public/language/he/admin/dashboard.json index bdaa869915..17233a0e35 100644 --- a/public/language/he/admin/dashboard.json +++ b/public/language/he/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/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/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/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/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/it/admin/dashboard.json b/public/language/it/admin/dashboard.json index 691b2914e7..46188e52a9 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": "ActivityPub Page Views", "graphs.unique-visitors": "Visitatori Unici", "graphs.registered-users": "Utenti Registrati", "graphs.guest-users": "Utenti ospiti", 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/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/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/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/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/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/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/nn-NO/admin/dashboard.json b/public/language/nn-NO/admin/dashboard.json index c852084c8f..80004f87e3 100644 --- a/public/language/nn-NO/admin/dashboard.json +++ b/public/language/nn-NO/admin/dashboard.json @@ -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/pl/admin/dashboard.json b/public/language/pl/admin/dashboard.json index 5e7684f78a..9e65a94b2f 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": "ActivityPub Page Views", "graphs.unique-visitors": "Unikalni użytkownicy", "graphs.registered-users": "Zarejestrowani użytkownicy", "graphs.guest-users": "Użytkownicy-goście", 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-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/ro/admin/dashboard.json b/public/language/ro/admin/dashboard.json index 6ad973f5f3..0be6d5866c 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": "ActivityPub Page Views", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", 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/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/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/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/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/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/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/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/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/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/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/vi/admin/dashboard.json b/public/language/vi/admin/dashboard.json index 6d2a736380..0117515771 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": "ActivityPub Page Views", "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/zh-CN/admin/dashboard.json b/public/language/zh-CN/admin/dashboard.json index 2214fb1dfe..aae77c7825 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 Page Views", "graphs.unique-visitors": "单一访客", "graphs.registered-users": "已注册用户", "graphs.guest-users": "游客", 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", From 020e0ad12eee643725987457a67cda67fba719a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 15:18:44 -0400 Subject: [PATCH 139/828] test: add openapi spec --- public/openapi/read/admin/analytics.yaml | 4 ++++ 1 file changed, 4 insertions(+) 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 From 01f2effcedc3f8800bcd65e26fce44d1b8174da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 11 Jul 2025 15:38:21 -0400 Subject: [PATCH 140/828] fix: add missing ap pageview middleware --- src/routes/activitypub.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js index 815fd738ef..c09b7b9821 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) From 32e4db8ea8e18b5de6934d9aaf48e50045f4ebff Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sat, 12 Jul 2025 09:19:22 +0000 Subject: [PATCH 141/828] Latest translations and fallbacks --- public/language/fi/admin/settings/activitypub.json | 10 +++++----- public/language/fi/world.json | 12 ++++++------ .../language/fr/admin/manage/user-custom-fields.json | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/public/language/fi/admin/settings/activitypub.json b/public/language/fi/admin/settings/activitypub.json index 94f9ad7822..9f4cefb5a5 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -1,18 +1,18 @@ { - "intro-lead": "What is Federation?", + "intro-lead": "Mikä on federaatio?", "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", + "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)", diff --git a/public/language/fi/world.json b/public/language/fi/world.json index 7fdb1569f2..8af989e006 100644 --- a/public/language/fi/world.json +++ b/public/language/fi/world.json @@ -1,12 +1,12 @@ { "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "popular": "Suositut aiheet", + "recent": "Kaikki aiheet", + "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!", 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", From 352f4a0c35252778399fe85d590154c710908df4 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 13 Jul 2025 09:19:24 +0000 Subject: [PATCH 142/828] Latest translations and fallbacks --- public/language/bg/admin/dashboard.json | 2 +- public/language/fi/admin/settings/activitypub.json | 2 +- public/language/vi/admin/dashboard.json | 2 +- public/language/zh-CN/admin/dashboard.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/bg/admin/dashboard.json b/public/language/bg/admin/dashboard.json index 6473ec0b24..f749044a69 100644 --- a/public/language/bg/admin/dashboard.json +++ b/public/language/bg/admin/dashboard.json @@ -75,7 +75,7 @@ "graphs.page-views-registered": "Преглеждания на страниците от регистрирани потребители", "graphs.page-views-guest": "Преглеждания на страниците от гости", "graphs.page-views-bot": "Преглеждания на страниците от ботове", - "graphs.page-views-ap": "ActivityPub Page Views", + "graphs.page-views-ap": "Преглеждания на страницата от ActivityPub", "graphs.unique-visitors": "Уникални посетители", "graphs.registered-users": "Регистрирани потребители", "graphs.guest-users": "Гости", diff --git a/public/language/fi/admin/settings/activitypub.json b/public/language/fi/admin/settings/activitypub.json index 9f4cefb5a5..c14826527c 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -1,6 +1,6 @@ { "intro-lead": "Mikä on federaatio?", - "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 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", diff --git a/public/language/vi/admin/dashboard.json b/public/language/vi/admin/dashboard.json index 0117515771..d12d7771e2 100644 --- a/public/language/vi/admin/dashboard.json +++ b/public/language/vi/admin/dashboard.json @@ -75,7 +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": "ActivityPub Page Views", + "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/zh-CN/admin/dashboard.json b/public/language/zh-CN/admin/dashboard.json index aae77c7825..df503c4369 100644 --- a/public/language/zh-CN/admin/dashboard.json +++ b/public/language/zh-CN/admin/dashboard.json @@ -75,7 +75,7 @@ "graphs.page-views-registered": "注册用户页面浏览量", "graphs.page-views-guest": "游客页面浏览量", "graphs.page-views-bot": "爬虫页面浏览量", - "graphs.page-views-ap": "ActivityPub Page Views", + "graphs.page-views-ap": "ActivityPub 页面浏览量", "graphs.unique-visitors": "单一访客", "graphs.registered-users": "已注册用户", "graphs.guest-users": "游客", From e5de79ff7db451b9c9517b7336b7cb94046d5fae Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 14 Jul 2025 09:19:32 +0000 Subject: [PATCH 143/828] Latest translations and fallbacks --- public/language/pl/admin/dashboard.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/pl/admin/dashboard.json b/public/language/pl/admin/dashboard.json index 9e65a94b2f..48bc39c7bd 100644 --- a/public/language/pl/admin/dashboard.json +++ b/public/language/pl/admin/dashboard.json @@ -75,7 +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": "ActivityPub Page Views", + "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", From e838bb268f24e71a37b3ecec8064be39e73e1137 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:24:42 -0400 Subject: [PATCH 144/828] fix(deps): update dependency cron to v4.3.2 (#13546) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b3c3f78258..2f6a01663f 100644 --- a/install/package.json +++ b/install/package.json @@ -61,7 +61,7 @@ "connect-pg-simple": "10.0.0", "connect-redis": "9.0.0", "cookie-parser": "1.4.7", - "cron": "4.3.1", + "cron": "4.3.2", "cropperjs": "1.6.2", "csrf-sync": "4.2.1", "daemon": "1.1.0", From d8c26bec45d033b48e6ef3094d271729631dcce9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:24:51 -0400 Subject: [PATCH 145/828] fix(deps): update dependency webpack to v5.100.1 (#13544) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2f6a01663f..940d153ac9 100644 --- a/install/package.json +++ b/install/package.json @@ -147,7 +147,7 @@ "tough-cookie": "5.1.2", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.100.0", + "webpack": "5.100.1", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From 97a5d54387a174b028a74b40be67d6fae5fe682a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:25:17 -0400 Subject: [PATCH 146/828] chore(deps): update dependency @eslint/js to v9.31.0 (#13545) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 940d153ac9..f309b7828a 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.30.1", + "@eslint/js": "9.31.0", "@stylistic/eslint-plugin": "5.1.0", "eslint-config-nodebb": "1.1.9", "eslint-plugin-import": "2.32.0", From 1ad97ac19425a096b7720586b4753e61c90cdc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 15 Jul 2025 13:02:46 -0400 Subject: [PATCH 147/828] refactor: closes #13547, process user uploads via batch reduce processed user count to 100 per batch --- .../4.3.0/normalize_thumbs_uploads.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/upgrades/4.3.0/normalize_thumbs_uploads.js b/src/upgrades/4.3.0/normalize_thumbs_uploads.js index ef12d54f81..3a33daee9f 100644 --- a/src/upgrades/4.3.0/normalize_thumbs_uploads.js +++ b/src/upgrades/4.3.0/normalize_thumbs_uploads.js @@ -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)); - - bulkRemove.push([`uid:${uid}:uploads`, userUpload.value]); - promises.push(db.delete(`upload:${md5(userUpload.value)}`)); - } + 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)}`)); + } + }); + 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, }); }, }; From a08551a5e1685aaff20f3ac449ad852952e07872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 16 Jul 2025 17:42:23 -0400 Subject: [PATCH 148/828] refactor: add names to caches, add max to request cache --- src/activitypub/index.js | 3 +++ src/cache/lru.js | 3 +++ src/cache/ttl.js | 5 +++++ src/middleware/index.js | 1 + src/notifications.js | 1 + src/request.js | 2 ++ 6 files changed, 15 insertions(+) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 78cb3f26f6..ef69d0bdf4 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -20,14 +20,17 @@ const pubsub = require('../pubsub'); const analytics = require('../analytics'); 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 }); diff --git a/src/cache/lru.js b/src/cache/lru.js index 094d3a3e93..2d46e5a5fe 100644 --- a/src/cache/lru.js +++ b/src/cache/lru.js @@ -31,6 +31,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; diff --git a/src/cache/ttl.js b/src/cache/ttl.js index 8647d0b9ac..fdc134be81 100644 --- a/src/cache/ttl.js +++ b/src/cache/ttl.js @@ -3,10 +3,15 @@ module.exports = function (opts) { const TTLCache = require('@isaacs/ttlcache'); const os = require('os'); + const winston = require('winston'); + const chalk = require('chalk'); const pubsub = require('../pubsub'); 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; diff --git a/src/middleware/index.js b/src/middleware/index.js index 417d8309bc..67d8e2faa0 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -23,6 +23,7 @@ const controllers = { }; const delayCache = cacheCreate({ + name: 'delay-middleware', ttl: 1000 * 60, max: 200, }); diff --git a/src/notifications.js b/src/notifications.js index df7cf51fb4..4e6e7873f3 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -22,6 +22,7 @@ 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, diff --git a/src/request.js b/src/request.js index ce5e95d5bb..713c3dd6ac 100644 --- a/src/request.js +++ b/src/request.js @@ -12,6 +12,8 @@ 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(); From 0fdde132082ff037da63f202c3af2459f70a8e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 16 Jul 2025 18:10:21 -0400 Subject: [PATCH 149/828] refactor: another missing cache name --- src/activitypub/actors.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index fdaed03c2c..98fdeb74d4 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -14,6 +14,7 @@ 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 }); From 272008bb51084e688c6dc03e8df1c7c46a9f9ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 16 Jul 2025 20:23:57 -0400 Subject: [PATCH 150/828] refactor: add missing cache name --- src/activitypub/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index f7708b9bd9..ad45b67910 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -21,6 +21,7 @@ const activitypub = require('.'); const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/; const webfingerCache = ttl({ + name: 'ap-webfinger-cache', max: 5000, ttl: 1000 * 60 * 60 * 24, // 24 hours }); From 1d7c32a52f2927d59d04db9c902a0124cea856b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 17 Jul 2025 12:34:52 -0400 Subject: [PATCH 151/828] refactor: show both days and hours --- src/controllers/admin/info.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 6f63faf8a9..3c0457cd82 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -117,14 +117,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() { From e4a0160e085a067251339ff08229c262d90c5fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 17 Jul 2025 21:34:14 -0400 Subject: [PATCH 152/828] refactor: copy session/headers when building req --- src/api/helpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index 168e5539b6..7df860a569 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -35,7 +35,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 +44,7 @@ exports.buildReqObject = (req, payload) => { path: referer.slice(referer.indexOf(host) + host.length), baseUrl: req.baseUrl, originalUrl: req.originalUrl, - headers: headers, + headers: { ...headers }, }; }; From 0b398bba4f0169e8eeaf4d14d44fec9d602626fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:07:44 -0400 Subject: [PATCH 153/828] fix(deps): update dependency webpack to v5.100.2 (#13549) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f309b7828a..61af2cfddd 100644 --- a/install/package.json +++ b/install/package.json @@ -147,7 +147,7 @@ "tough-cookie": "5.1.2", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.100.1", + "webpack": "5.100.2", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From 57564190f3c2fd85a057ba68a893bf896336ba29 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:07:59 -0400 Subject: [PATCH 154/828] fix(deps): update dependency ace-builds to v1.43.2 (#13548) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 61af2cfddd..206f1d5342 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.43.1", + "ace-builds": "1.43.2", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", From 12b9f4c743b4247e6b2b8361251a6bec9980a2ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:22:31 -0400 Subject: [PATCH 155/828] fix(deps): update dependency compression to v1.8.1 (#13553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 206f1d5342..b87e0efe34 100644 --- a/install/package.json +++ b/install/package.json @@ -55,7 +55,7 @@ "clipboard": "2.0.11", "commander": "14.0.0", "compare-versions": "6.1.1", - "compression": "1.8.0", + "compression": "1.8.1", "connect-flash": "0.1.1", "connect-mongo": "5.1.0", "connect-pg-simple": "10.0.0", From 3f520c33eff5d2753bcfc9afaa5fbdad09055147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 18 Jul 2025 21:35:08 -0400 Subject: [PATCH 156/828] fix: add missing cache name --- src/middleware/uploads.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middleware/uploads.js b/src/middleware/uploads.js index 9b434b3286..f7b016193f 100644 --- a/src/middleware/uploads.js +++ b/src/middleware/uploads.js @@ -20,6 +20,7 @@ exports.ratelimit = helpers.try(async (req, res, next) => { } if (!cache) { cache = cacheCreate({ + name: 'upload-rate-limit-cache', ttl: meta.config.uploadRateLimitCooldown * 1000, }); } From 35ca0e3b475e46adf33b58254b9ea2c0c5abab4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:03:18 -0400 Subject: [PATCH 157/828] fix(deps): update dependency multer to v2.0.2 (#13556) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b87e0efe34..91a62de796 100644 --- a/install/package.json +++ b/install/package.json @@ -94,7 +94,7 @@ "mongodb": "6.17.0", "morgan": "1.10.0", "mousetrap": "1.6.5", - "multer": "2.0.1", + "multer": "2.0.2", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.3.0", From 0e457f15858f341b05656bec2fa737968b923628 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:03:45 -0400 Subject: [PATCH 158/828] fix(deps): update dependency morgan to v1.10.1 (#13555) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 91a62de796..62ddeabe39 100644 --- a/install/package.json +++ b/install/package.json @@ -92,7 +92,7 @@ "mime": "3.0.0", "mkdirp": "3.0.1", "mongodb": "6.17.0", - "morgan": "1.10.0", + "morgan": "1.10.1", "mousetrap": "1.6.5", "multer": "2.0.2", "nconf": "0.13.0", From 0eb0a67ae570ba5fe92059ad099aabb31a4c7a34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:04:10 -0400 Subject: [PATCH 159/828] fix(deps): update dependency express-session to v1.18.2 (#13554) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 62ddeabe39..c348ae71e3 100644 --- a/install/package.json +++ b/install/package.json @@ -68,7 +68,7 @@ "diff": "8.0.2", "esbuild": "0.25.6", "express": "4.21.2", - "express-session": "1.18.1", + "express-session": "1.18.2", "express-useragent": "1.0.15", "fetch-cookie": "3.1.0", "file-loader": "6.2.0", From 1697e36f3abbcd36d31aa64a16b1dfd93cc4b016 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:34:52 -0400 Subject: [PATCH 160/828] fix(deps): update dependency esbuild to v0.25.7 (#13557) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c348ae71e3..fd81f70658 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.6", + "esbuild": "0.25.7", "express": "4.21.2", "express-session": "1.18.2", "express-useragent": "1.0.15", From 25c24298fb277bd98891060ce09a05415b313de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 19 Jul 2025 17:20:59 -0400 Subject: [PATCH 161/828] fix: closes #13558, override/extend json opts from config.json --- src/webserver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webserver.js b/src/webserver.js index c27a4e5d4a..3cf390c329 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -239,12 +239,13 @@ function configureBodyParser(app) { } app.use(bodyParser.urlencoded(urlencodedOpts)); - const jsonOpts = nconf.get('bodyParser:json') || { + const jsonOpts = { type: [ 'application/json', 'application/ld+json', 'application/activity+json', ], + ...nconf.get('bodyParser:json'), }; app.use(bodyParser.json(jsonOpts)); } From eac3d0a043e66638cea34c78f10bb97fee32f66c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 20 Jul 2025 11:57:34 -0400 Subject: [PATCH 162/828] fix: redis connect host/port --- src/database/redis/connection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index 2a38cf1a79..fb2aceb33b 100644 --- a/src/database/redis/connection.js +++ b/src/database/redis/connection.js @@ -40,11 +40,11 @@ connection.connect = async function (options) { // Else, connect over tcp/ip cxn = createClient({ ...options.options, - host: redis_socket_or_host, - port: options.port, password: options.password, database: options.database, socket: { + host: redis_socket_or_host, + port: options.port, reconnectStrategy: 3000, }, }); From 54fae3b12b778f11b09f8d3150a6a9cff6f4ee44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 20 Jul 2025 13:38:31 -0400 Subject: [PATCH 163/828] set max on upload rate limit --- src/middleware/uploads.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middleware/uploads.js b/src/middleware/uploads.js index f7b016193f..fdbfc3dbda 100644 --- a/src/middleware/uploads.js +++ b/src/middleware/uploads.js @@ -21,6 +21,7 @@ exports.ratelimit = helpers.try(async (req, res, next) => { if (!cache) { cache = cacheCreate({ name: 'upload-rate-limit-cache', + max: 100, ttl: meta.config.uploadRateLimitCooldown * 1000, }); } From 6a732e36166fd9ace0cd2c7692f23defea4a5fdc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:00:49 -0400 Subject: [PATCH 164/828] fix(deps): update dependency esbuild to v0.25.8 (#13559) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fd81f70658..b1f4a37e9d 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.7", + "esbuild": "0.25.8", "express": "4.21.2", "express-session": "1.18.2", "express-useragent": "1.0.15", From c43c3533507854740006e2cc4589a16b5d6b9c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 21 Jul 2025 21:22:40 -0400 Subject: [PATCH 165/828] fix: change the client side reloginTimer to match setting when setting is changed restart timer closes #13561 --- public/src/admin/admin.js | 36 ++--------------------- public/src/admin/modules/relogin-timer.js | 36 +++++++++++++++++++++++ public/src/admin/settings.js | 7 +++-- 3 files changed, 44 insertions(+), 35 deletions(-) create mode 100644 public/src/admin/modules/relogin-timer.js diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index e0d4e49b7f..7e68c65b8c 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'); 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 247f9646b2..f189718426 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 () { @@ -217,6 +217,9 @@ define('admin/settings', [ for (const [field, value] of Object.entries(data)) { app.config[field] = value; + if (field === 'adminReloginDuration') { + reloginTimer.start(parseInt(value, 10)); + } } callback(); From 8ba230a205edd3a2149f9bec612b451b65620306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 22 Jul 2025 10:39:27 -0400 Subject: [PATCH 166/828] refactor: change default teaser to last-post --- install/data/defaults.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index b17bbbb135..b6ae97a3c7 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -76,7 +76,7 @@ "profile:keepAllUserImages": 0, "gdpr_enabled": 1, "allowProfileImageUploads": 1, - "teaserPost": "last-reply", + "teaserPost": "last-post", "showPostPreviewsOnHover": 1, "allowPrivateGroups": 1, "unreadCutoff": 2, From 8eedb38a99d35d3009e46937d9813943c20b502a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 22 Jul 2025 10:51:54 -0400 Subject: [PATCH 167/828] test: test fixes for default teaser change --- public/openapi/components/schemas/TopicObject.yaml | 7 +++++++ public/openapi/read/unread.yaml | 7 +++++++ test/topics.js | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml index b26623072e..30ba9d3a77 100644 --- a/public/openapi/components/schemas/TopicObject.yaml +++ b/public/openapi/components/schemas/TopicObject.yaml @@ -106,6 +106,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 +121,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 diff --git a/public/openapi/read/unread.yaml b/public/openapi/read/unread.yaml index e916231d65..ea69f666dd 100644 --- a/public/openapi/read/unread.yaml +++ b/public/openapi/read/unread.yaml @@ -138,6 +138,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 +153,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/test/topics.js b/test/topics.js index dcea6ccd09..bc92bbff09 100644 --- a/test/topics.js +++ b/test/topics.js @@ -2000,7 +2000,8 @@ describe('Topic\'s', () => { it('should get teasers with 2 params', (done) => { topics.getTeasers([topic1.topicData, topic2.topicData], 1, (err, teasers) => { assert.ifError(err); - assert.deepEqual([undefined, undefined], teasers); + assert(teasers[0]); + assert(teasers[1]); done(); }); }); From 1776bd1d7e732774a0a557b07d3d6a37483077a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 22 Jul 2025 10:58:17 -0400 Subject: [PATCH 168/828] test: fix meta test --- test/meta.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/meta.js b/test/meta.js index 77667ddbf2..93b5b443bb 100644 --- a/test/meta.js +++ b/test/meta.js @@ -257,15 +257,10 @@ describe('meta', () => { }); }); - it('should use default value if value is null', (done) => { - meta.configs.set('teaserPost', null, (err) => { - assert.ifError(err); - meta.configs.get('teaserPost', (err, value) => { - assert.ifError(err); - assert.strictEqual(value, 'last-reply'); - done(); - }); - }); + it('should use default value if value is null', async () => { + await meta.configs.set('teaserPost', null); + const value = await meta.configs.get('teaserPost'); + assert.strictEqual(value, 'last-post'); }); it('should fail if field is invalid', (done) => { From f6ed7ec21c763395704b66564b2a08cd41f830c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 22 Jul 2025 16:28:37 -0400 Subject: [PATCH 169/828] fix: don't translate text on admin logs page --- src/controllers/admin/logs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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)), }); }; From de71cc63104660a522ebad079cbe60d26c1515d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 22 Jul 2025 16:35:55 -0400 Subject: [PATCH 170/828] refactor: log uid that failed --- src/activitypub/actors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 98fdeb74d4..d48acda82b 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -655,7 +655,7 @@ Actors.prune = async () => { await user.deleteAccount(uid); deletionCount += 1; } catch (err) { - winston.error(err.stack); + winston.error(`Failed to delete user with uid ${uid}: ${err.stack}`); } } else { notDeletedDueToLocalContent += 1; From 8e9d38430c676cc66fe96ade6152a4ef25f53e56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 09:49:52 -0400 Subject: [PATCH 171/828] fix(deps): update dependency mongodb to v6.18.0 (#13563) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b1f4a37e9d..6cc1ff3cad 100644 --- a/install/package.json +++ b/install/package.json @@ -91,7 +91,7 @@ "lru-cache": "11.1.0", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.17.0", + "mongodb": "6.18.0", "morgan": "1.10.1", "mousetrap": "1.6.5", "multer": "2.0.2", From 1262aee843249946d9da351a9db0ad9fc355af2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 12:19:56 -0400 Subject: [PATCH 172/828] fix(deps): update dependency redis to v5.6.1 (#13564) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6cc1ff3cad..8a6e77f313 100644 --- a/install/package.json +++ b/install/package.json @@ -122,7 +122,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.6.0", + "redis": "5.6.1", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 65364bfa0f62fa589e4dc1d8ded1bf966b8fd510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 24 Jul 2025 23:54:38 -0400 Subject: [PATCH 173/828] 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 --- src/activitypub/mocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index f9995b3b72..5268dd1c01 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -191,7 +191,7 @@ Mocks.profile = async (actors) => { const iconBackgrounds = await user.getIconBackgrounds(); let bgColor = Array.prototype.reduce.call(preferredUsername, (cur, next) => cur + next.charCodeAt(), 0); bgColor = iconBackgrounds[bgColor % iconBackgrounds.length]; - + summary = summary || ''; // Replace emoji in summary if (tag && Array.isArray(tag)) { tag From 637373e31a610187e1d6f563968f2238c61b2e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 25 Jul 2025 13:37:20 -0400 Subject: [PATCH 174/828] chore: up eslint --- install/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/package.json b/install/package.json index e5ce75d014..3f38c795d4 100644 --- a/install/package.json +++ b/install/package.json @@ -160,9 +160,9 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.31.0", - "@stylistic/eslint-plugin": "5.1.0", - "eslint-config-nodebb": "1.1.9", + "@eslint/js": "9.32.0", + "@stylistic/eslint-plugin": "5.2.2", + "eslint-config-nodebb": "1.1.10", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", From 2d1a5fea11ee7583e1ae16bad3e5f3f956155ea5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Jul 2025 17:44:14 -0400 Subject: [PATCH 175/828] fix(deps): update dependency satori to v0.16.1 (#13560) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3f38c795d4..d76a16c942 100644 --- a/install/package.json +++ b/install/package.json @@ -127,7 +127,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.89.2", - "satori": "0.15.2", + "satori": "0.16.1", "semver": "7.7.2", "serve-favicon": "2.5.1", "sharp": "0.32.6", From 0997fbfa4db471c1a6afe694067acc7b149f07a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 27 Jul 2025 10:35:17 -0400 Subject: [PATCH 176/828] fix: clearTimeout if item is evicted from cache --- src/activitypub/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index ef69d0bdf4..f4dd3cb5d2 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -348,7 +348,16 @@ ActivityPub.get = async (type, id, uri, options) => { } }; -ActivityPub.retryQueue = lru({ name: 'activitypub-retry-queue', max: 4000, ttl: 1000 * 60 * 60 * 24 * 60 }); +ActivityPub.retryQueue = lru({ + name: 'activitypub-retry-queue', + max: 4000, + ttl: 1000 * 60 * 60 * 24 * 60, + dispose: (value) => { + if (value) { + clearTimeout(value); + } + }, +}); // handle clearing retry queue from another member of the cluster pubsub.on(`activitypub-retry-queue:lruCache:del`, (keys) => { From 6fc8dfa9408fc7087d5d059f28caa285108c5b55 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:45:02 -0400 Subject: [PATCH 177/828] fix(deps): update dependency webpack to v5.101.0 (#13567) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d76a16c942..37fe782cfb 100644 --- a/install/package.json +++ b/install/package.json @@ -146,7 +146,7 @@ "tough-cookie": "5.1.2", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.100.2", + "webpack": "5.101.0", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From b3a4a128cdfc361949beeb0a6e9e7f30e486bb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20U=C5=9Fakl=C4=B1?= Date: Wed, 30 Jul 2025 09:32:58 -0400 Subject: [PATCH 178/828] 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 --- src/activitypub/inbox.js | 6 +- src/activitypub/index.js | 150 +++++++++++++++++++++++---------------- 2 files changed, 94 insertions(+), 62 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index ec2f9d7fb0..cd13982b8c 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -617,6 +617,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 f4dd3cb5d2..fac6a92ed8 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -14,9 +14,7 @@ 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 requestCache = ttl({ @@ -69,28 +67,30 @@ ActivityPub.feps = require('./feps'); ActivityPub.startJobs = () => { ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); - new CronJob('0 0 * * *', async () => { + async function tryCronJob(method) { if (!meta.config.activitypubEnabled) { return; } try { - await ActivityPub.notes.prune(); - await db.sortedSetsRemoveRangeByScore(['activities:datetime'], '-inf', Date.now() - 604800000); + 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 () => { - if (!meta.config.activitypubEnabled) { - return; - } - try { - await ActivityPub.actors.prune(); - } catch (err) { - winston.error(err.stack); - } + 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); }; ActivityPub.resolveId = async (uid, id) => { @@ -348,29 +348,11 @@ ActivityPub.get = async (type, id, uri, options) => { } }; -ActivityPub.retryQueue = lru({ - name: 'activitypub-retry-queue', - max: 4000, - ttl: 1000 * 60 * 60 * 24 * 60, - dispose: (value) => { - if (value) { - clearTimeout(value); - } - }, -}); - -// 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); - +async function sendMessage(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, @@ -382,25 +364,15 @@ 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; } } @@ -429,17 +401,75 @@ 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 sendMessage(uri, id, type, payload); + if (!ok) { + const queueId = `${type}:${id}:${new URL(uri).hostname}`; + 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)); }; +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}`))).filter(Boolean); + + const retryQueueAdd = []; + const retryQueuedSet = []; + const queueIdsToRemove = []; + + const oneMinute = 1000 * 60; + await Promise.all(queuedData.map(async (data) => { + const { queueId, uri, id, type, attempts, payload } = data; + const payloadObj = JSON.parse(payload); + + const ok = await sendMessage(uri, id, type, payloadObj); + + if (ok || attempts > 10) { + 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.sortedSetRemove('ap:retry:queue', queueIdsToRemove), + db.deleteAll(queueIdsToRemove.map(id => `ap:retry:queue:${id}`)), + ]); +} + ActivityPub.record = async ({ id, type, actor }) => { const now = Date.now(); const { hostname } = new URL(actor); From bba18e31027c82b64eb75787679a63b3a936aa86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 30 Jul 2025 09:37:36 -0400 Subject: [PATCH 179/828] feat: add expose-gc flag to loader --- loader.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/loader.js b/loader.js index 8b25906452..a7817ac8b9 100644 --- a/loader.js +++ b/loader.js @@ -102,6 +102,9 @@ function forkWorker(index, isPrimary) { if (nconf.get('max-memory')) { args.push(`--max-old-space-size=${nconf.get('max-memory')}`); } + if (nconf.get('expose-gc')) { + args.push('--expose-gc'); + } if (!ports[index]) { return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`); } From 70d3a29c32a1d5386722ac728c1efb611ae7abd1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:54:26 -0400 Subject: [PATCH 180/828] fix(deps): update dependency satori to v0.16.2 (#13569) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 37fe782cfb..b1c2204b24 100644 --- a/install/package.json +++ b/install/package.json @@ -127,7 +127,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.89.2", - "satori": "0.16.1", + "satori": "0.16.2", "semver": "7.7.2", "serve-favicon": "2.5.1", "sharp": "0.32.6", From 6eab44a01d41900e39592d83c346e0ea5332f689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:00:40 -0400 Subject: [PATCH 181/828] refactor: use promise.all --- src/api/posts.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/posts.js b/src/api/posts.js index d8971796b3..b0661cab30 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -489,10 +489,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']); From 90a6512970cad99abf764436eed7c658d26f5f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:02:49 -0400 Subject: [PATCH 182/828] feat: add filter:post.getDiffs --- src/api/posts.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/posts.js b/src/api/posts.js index b0661cab30..3689ef03ed 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -508,8 +508,9 @@ 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, @@ -519,7 +520,8 @@ postsAPI.getDiffs = async (caller, data) => { 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) => { From 97d4994afbac711d8ee27c37b66cadb90bbc2c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:02:49 -0400 Subject: [PATCH 183/828] feat: add filter:post.getDiffs From 472df3aa225897880db9ed8386f93b7fb7762cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:00:40 -0400 Subject: [PATCH 184/828] refactor: use promise.all From 1071ac0cea6b00d669bae2e4afc4c20e982dd684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:14:19 -0400 Subject: [PATCH 185/828] test: fix openapi --- public/openapi/write/posts/pid/diffs.yaml | 2 ++ src/api/posts.js | 1 + 2 files changed, 3 insertions(+) diff --git a/public/openapi/write/posts/pid/diffs.yaml b/public/openapi/write/posts/pid/diffs.yaml index ea76f7ea66..524167bc6f 100644 --- a/public/openapi/write/posts/pid/diffs.yaml +++ b/public/openapi/write/posts/pid/diffs.yaml @@ -37,6 +37,8 @@ get: type: string username: type: string + uid: + type: string editable: type: boolean deletable: diff --git a/src/api/posts.js b/src/api/posts.js index 3689ef03ed..54454e6e52 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -515,6 +515,7 @@ postsAPI.getDiffs = async (caller, data) => { 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, From 7393bdd4447a591ef260f5cf5922efd432aa4add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:17:26 -0400 Subject: [PATCH 186/828] test: fix spec --- public/openapi/write/posts/pid/diffs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/openapi/write/posts/pid/diffs.yaml b/public/openapi/write/posts/pid/diffs.yaml index 524167bc6f..2d60361dc8 100644 --- a/public/openapi/write/posts/pid/diffs.yaml +++ b/public/openapi/write/posts/pid/diffs.yaml @@ -38,7 +38,7 @@ get: username: type: string uid: - type: string + type: number editable: type: boolean deletable: From 95f6688c04067e4b15c3471354ce680c80c8bac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 09:24:04 -0400 Subject: [PATCH 187/828] test: one more fix --- public/openapi/write/posts/pid/diffs.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/openapi/write/posts/pid/diffs.yaml b/public/openapi/write/posts/pid/diffs.yaml index 2d60361dc8..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: From e851a52390d766851c1bcf2012c1994c2286b7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jul 2025 18:44:09 -0400 Subject: [PATCH 188/828] feat: add new brite skin from bootswatch --- public/scss/skins.scss | 7 ++++++- src/meta/css.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/public/scss/skins.scss b/public/scss/skins.scss index 4940bbbe08..05f05b2622 100644 --- a/public/scss/skins.scss +++ b/public/scss/skins.scss @@ -1,6 +1,11 @@ // 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/src/meta/css.js b/src/meta/css.js index 4b7e999383..1c8f9f0329 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', @@ -270,7 +270,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', ]; From 5c69c8bf9c97870ceb5530c1a715faec0905a2c1 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 1 Aug 2025 09:20:24 +0000 Subject: [PATCH 189/828] Latest translations and fallbacks --- public/language/he/admin/dashboard.json | 2 +- public/language/he/error.json | 4 ++-- public/language/he/modules.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/he/admin/dashboard.json b/public/language/he/admin/dashboard.json index 17233a0e35..1304c758a1 100644 --- a/public/language/he/admin/dashboard.json +++ b/public/language/he/admin/dashboard.json @@ -75,7 +75,7 @@ "graphs.page-views-registered": "צפיות בדפים-רשומים", "graphs.page-views-guest": "צפיות בדפים-אורחים", "graphs.page-views-bot": "צפיות בדפים-בוטים", - "graphs.page-views-ap": "ActivityPub Page Views", + "graphs.page-views-ap": "ActivityPub תצוגת דפים", "graphs.unique-visitors": "מבקרים ייחודיים", "graphs.registered-users": "משתמשים רשומים", "graphs.guest-users": "משתמשים אורחים", diff --git a/public/language/he/error.json b/public/language/he/error.json index a8e7a433d5..a492795106 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -3,7 +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.", + "reserved-ip-address": "אין לבקש בקשות רשת לטווחי IP שמורים.", "not-logged-in": "נראה שאינכם מחוברים למערכת.", "account-locked": "חשבונכם נחסם באופן זמני", "search-requires-login": "חיפוש מצריך חשבון - אנא הירשמו או התחברו.", @@ -237,7 +237,7 @@ "socket-reconnect-failed": "לא ניתן להגיע לשרת בשלב זה. לחצו כאן כדי לנסות שוב, או נסו שוב במועד מאוחר יותר", "invalid-plugin-id": "מזהה תוסף לא תקין", "plugin-not-whitelisted": "לא ניתן להתקין את התוסף – ניתן להתקין דרך הניהול רק תוספים שנמצאים ברשימה הלבנה של מנהל החבילות של NodeBB.", - "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", + "cannot-toggle-system-plugin": "לא ניתן להחליף מצב של תוסף מערכת", "plugin-installation-via-acp-disabled": "התקנת תוסף באמצעות ACP מושבתת", "plugins-set-in-configuration": "לא ניתן לשנות את מצב התוסף כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), שנו את התצורה במקום זאת.", "theme-not-set-in-configuration": "כאשר מגדירים תוספים פעילים בתצורה, שינוי ערכות נושא מחייב הוספת ערכת הנושא החדשה לרשימת התוספים הפעילים לפני עדכון שלו ב-ACP", diff --git a/public/language/he/modules.json b/public/language/he/modules.json index 12e592b6e2..8505d4ccba 100644 --- a/public/language/he/modules.json +++ b/public/language/he/modules.json @@ -48,7 +48,7 @@ "chat.add-user": "הוספת משתמש", "chat.notification-settings": "הגדרות התראות", "chat.default-notification-setting": "הגדרת ברירת מחדל להתראות", - "chat.join-leave-messages": "Join/Leave Messages", + "chat.join-leave-messages": "הצטרפות/השארת הודעות", "chat.notification-setting-room-default": "ברירת המחדל של החדר", "chat.notification-setting-none": "ללא התראות", "chat.notification-setting-at-mention-only": "@אזכור בלבד", From d5f6d158f49aab651cf89963aa895ef6846bb184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 1 Aug 2025 11:50:13 -0400 Subject: [PATCH 190/828] refactor: if user.delete fails in actor prune remove from ap sets/keys --- src/activitypub/actors.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index d48acda82b..50ddafaac2 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -609,6 +609,8 @@ Actors.prune = async () => { let deletionCountNonExisting = 0; let notDeletedDueToLocalContent = 0; const preservedIds = []; + const cleanupUids = []; + await batch.processArray(ids, async (ids) => { const exists = await Promise.all([ db.exists(ids.map(id => `userRemote:${id}`)), @@ -656,6 +658,9 @@ Actors.prune = async () => { 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; @@ -663,6 +668,14 @@ Actors.prune = async () => { } })); + 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); From a8bf4ea069347b22bbca554a392c31fee2c4002d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 1 Aug 2025 11:59:59 -0400 Subject: [PATCH 191/828] fix: ap queue id to use payload.type payload.id --- src/activitypub/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index fac6a92ed8..1da83850d9 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -409,7 +409,7 @@ ActivityPub.send = async (type, id, targets, payload) => { await Promise.all(inboxBatch.map(async (uri) => { const ok = await sendMessage(uri, id, type, payload); if (!ok) { - const queueId = `${type}:${id}:${new URL(uri).hostname}`; + const queueId = `${payload.type}:${payload.id}:${new URL(uri).hostname}`; const nextTryOn = Date.now() + oneMinute; retryQueueAdd.push(['ap:retry:queue', nextTryOn, queueId]); retryQueuedSet.push([`ap:retry:queue:${queueId}`, { From 8305a7425ad5cff79374c2d32247354a734f5a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 1 Aug 2025 12:26:38 -0400 Subject: [PATCH 192/828] refactor: remove old arg --- loader.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/loader.js b/loader.js index 446f8b3d9e..427c952ff4 100644 --- a/loader.js +++ b/loader.js @@ -106,9 +106,7 @@ function forkWorker(index, isPrimary) { if (nconf.get('expose-gc')) { execArgv.push('--expose-gc'); } - if (nconf.get('expose-gc')) { - args.push('--expose-gc'); - } + if (!ports[index]) { return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`); } From bc40d79cf8954cd724888eb5562b0ebc5b2b1e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 1 Aug 2025 18:46:27 -0400 Subject: [PATCH 193/828] refactor: dont del if cache disabled --- src/cache/lru.js | 3 +++ src/cache/ttl.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/cache/lru.js b/src/cache/lru.js index 2d46e5a5fe..a1dbfbd705 100644 --- a/src/cache/lru.js +++ b/src/cache/lru.js @@ -95,6 +95,9 @@ module.exports = function (opts) { }; cache.del = function (keys) { + if (!cache.enabled) { + return; + } if (!Array.isArray(keys)) { keys = [keys]; } diff --git a/src/cache/ttl.js b/src/cache/ttl.js index fdc134be81..c8ed90af57 100644 --- a/src/cache/ttl.js +++ b/src/cache/ttl.js @@ -70,6 +70,9 @@ module.exports = function (opts) { }; cache.del = function (keys) { + if (!cache.enabled) { + return; + } if (!Array.isArray(keys)) { keys = [keys]; } From 0b4efa14a99f9d986522c74e1691cdf4766107fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:37:04 -0400 Subject: [PATCH 194/828] fix(deps): update dependency cron to v4.3.3 (#13573) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 03f6e3f3f0..a433aacdc0 100644 --- a/install/package.json +++ b/install/package.json @@ -60,7 +60,7 @@ "connect-pg-simple": "10.0.0", "connect-redis": "9.0.0", "cookie-parser": "1.4.7", - "cron": "4.3.2", + "cron": "4.3.3", "cropperjs": "1.6.2", "csrf-sync": "4.2.1", "daemon": "1.1.0", From 27d60a19f9025231bb5d98f6ed54b17b4da81967 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:37:15 -0400 Subject: [PATCH 195/828] fix(deps): update dependency redis to v5.7.0 (#13570) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a433aacdc0..947ffa166f 100644 --- a/install/package.json +++ b/install/package.json @@ -121,7 +121,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.6.1", + "redis": "5.7.0", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 8ce5498f2351bf513f295d25441e20e56d9dd051 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 2 Aug 2025 09:48:59 -0400 Subject: [PATCH 196/828] fix: add rel canonical to remote user profiles --- src/controllers/accounts/profile.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index b22e6eb73c..de0e744eb5 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -52,6 +52,10 @@ 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.render('account/profile', userData); @@ -174,5 +178,12 @@ function addTags(res, userData) { type: 'application/activity+json', href: `${nconf.get('url')}/uid/${userData.uid}`, }); + + if (!utils.isNumber(userData.uid)) { + res.locals.linkTags.push({ + rel: 'canonical', + href: userData.url || userData.uid, + }); + } } } From c8ad086779eb4ff56cb2063f9ce0e99532c23571 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 2 Aug 2025 09:52:13 -0400 Subject: [PATCH 197/828] fix: duplicate canonical link header --- src/controllers/accounts/profile.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index de0e744eb5..f235a3c69a 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -167,10 +167,17 @@ 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, + }); + } if (meta.config.activitypubEnabled) { res.locals.linkTags.push({ @@ -178,12 +185,5 @@ function addTags(res, userData) { type: 'application/activity+json', href: `${nconf.get('url')}/uid/${userData.uid}`, }); - - if (!utils.isNumber(userData.uid)) { - res.locals.linkTags.push({ - rel: 'canonical', - href: userData.url || userData.uid, - }); - } } } From fe1601608d2540e2994375035fa042bb9b385dba Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 3 Aug 2025 02:33:54 -0400 Subject: [PATCH 198/828] fix: set noindex tag on remote profiles as well --- src/controllers/accounts/profile.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index f235a3c69a..fab317fe17 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -55,6 +55,7 @@ profileController.get = async function (req, res, next) { if (!utils.isNumber(userData.uid)) { res.set('Link', `<${userData.url || userData.uid}>; rel="canonical"`); + res.set('x-robots-tag', 'noindex'); } } @@ -177,6 +178,10 @@ function addTags(res, userData) { rel: 'canonical', href: userData.url || userData.uid, }); + res.locals.metaTags.push({ + name: 'robots', + content: 'noindex', + }); } if (meta.config.activitypubEnabled) { From 2a6e4b0a8dd9c83f432d80b2929115d190602797 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 3 Aug 2025 09:19:18 +0000 Subject: [PATCH 199/828] Latest translations and fallbacks --- public/language/it/admin/dashboard.json | 2 +- public/language/vi/error.json | 4 ++-- public/language/vi/pages.json | 10 +++++----- public/language/vi/search.json | 6 +++--- public/language/vi/topic.json | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/public/language/it/admin/dashboard.json b/public/language/it/admin/dashboard.json index 46188e52a9..ceaff8c06e 100644 --- a/public/language/it/admin/dashboard.json +++ b/public/language/it/admin/dashboard.json @@ -75,7 +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": "ActivityPub Page Views", + "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/vi/error.json b/public/language/vi/error.json index 09cb43c110..ec481020a1 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -202,7 +202,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", @@ -225,7 +225,7 @@ "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!", "cannot-block-self": "Bạn không thể tự khóa bạn!", diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json index 44579ce5f7..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", diff --git a/public/language/vi/search.json b/public/language/vi/search.json index 63ba203e7d..43b468535f 100644 --- a/public/language/vi/search.json +++ b/public/language/vi/search.json @@ -38,7 +38,7 @@ "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/topic.json b/public/language/vi/topic.json index a8960d63db..ddb40f03a3 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -154,7 +154,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ủ đề", @@ -195,7 +195,7 @@ "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ũ. 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 chủ đề mới", + "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", From c6889f0864c1c7e51c4fbc152311f5e9ef115f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 3 Aug 2025 06:43:31 -0400 Subject: [PATCH 200/828] fix: readd retry items --- src/activitypub/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 1da83850d9..8b997e81d7 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -464,7 +464,10 @@ async function retryFailedMessages() { }]); } })); + await Promise.all([ + db.sortedSetAddBulk(retryQueueAdd), + db.setObjectBulk(retryQueuedSet), db.sortedSetRemove('ap:retry:queue', queueIdsToRemove), db.deleteAll(queueIdsToRemove.map(id => `ap:retry:queue:${id}`)), ]); From 340618d3e0945a7a4487c8adf79941923e20f40a Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 4 Aug 2025 09:19:55 +0000 Subject: [PATCH 201/828] Latest translations and fallbacks --- .../language/nb/admin/manage/privileges.json | 4 ++-- .../nb/admin/settings/activitypub.json | 2 +- public/language/nb/admin/settings/post.json | 2 +- .../language/nb/admin/settings/reputation.json | 16 ++++++++-------- public/language/nb/error.json | 10 +++++----- public/language/nb/flags.json | 2 +- public/language/nb/global.json | 10 +++++----- public/language/nb/notifications.json | 18 +++++++++--------- public/language/nb/pages.json | 10 +++++----- public/language/nb/search.json | 12 ++++++------ public/language/nb/topic.json | 6 +++--- public/language/nb/user.json | 10 +++++----- .../nn-NO/admin/manage/privileges.json | 2 +- .../nn-NO/admin/settings/activitypub.json | 2 +- public/language/nn-NO/admin/settings/post.json | 2 +- .../nn-NO/admin/settings/reputation.json | 14 +++++++------- public/language/nn-NO/error.json | 10 +++++----- public/language/nn-NO/flags.json | 2 +- public/language/nn-NO/global.json | 10 +++++----- public/language/nn-NO/notifications.json | 18 +++++++++--------- public/language/nn-NO/pages.json | 10 +++++----- public/language/nn-NO/search.json | 12 ++++++------ public/language/nn-NO/topic.json | 6 +++--- public/language/nn-NO/user.json | 12 ++++++------ 24 files changed, 101 insertions(+), 101 deletions(-) diff --git a/public/language/nb/admin/manage/privileges.json b/public/language/nb/admin/manage/privileges.json index 28702994b2..2d655c05eb 100644 --- a/public/language/nb/admin/manage/privileges.json +++ b/public/language/nb/admin/manage/privileges.json @@ -35,8 +35,8 @@ "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/settings/activitypub.json b/public/language/nb/admin/settings/activitypub.json index 70161ed834..7ee414b599 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", diff --git a/public/language/nb/admin/settings/post.json b/public/language/nb/admin/settings/post.json index 548446786d..11738ae699 100644 --- a/public/language/nb/admin/settings/post.json +++ b/public/language/nb/admin/settings/post.json @@ -6,7 +6,7 @@ "sorting.newest-to-oldest": "Nyeste til eldste", "sorting.recently-replied": "Sist besvart", "sorting.recently-created": "Nylig opprettet", - "sorting.most-votes": "Flest stemmer", + "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..c3604622a9 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)", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index e7fbac1aaa..f85994d9cb 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -173,11 +173,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.", @@ -202,9 +202,9 @@ "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)", "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.", diff --git a/public/language/nb/flags.json b/public/language/nb/flags.json index 305a699267..7c871e2806 100644 --- a/public/language/nb/flags.json +++ b/public/language/nb/flags.json @@ -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 fe49ecbeaf..02a897451c 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -73,11 +73,11 @@ "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", diff --git a/public/language/nb/notifications.json b/public/language/nb/notifications.json index 6c56f4b626..76b3aaf0c9 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -11,7 +11,7 @@ "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", @@ -19,7 +19,7 @@ "group-chat": "Gruppechat", "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", @@ -32,10 +32,10 @@ "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 har anbefalt innlegget ditt i %2.", + "upvoted-your-post-in-dual": "%1 og %2 har anbefalt innlegget ditt i %3.", + "upvoted-your-post-in-triple": "%1, %2 og %3 har anbefalt innlegget ditt i %4.", + "upvoted-your-post-in-multiple": "%1, %2 og %3 andre har anbefalt innlegget ditt i %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", @@ -80,9 +80,9 @@ "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-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", diff --git a/public/language/nb/pages.json b/public/language/nb/pages.json index 90436898a9..2cc9a26c2d 100644 --- a/public/language/nb/pages.json +++ b/public/language/nb/pages.json @@ -6,10 +6,10 @@ "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", + "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", "ip-blacklist": "IP-svarteliste", @@ -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/search.json b/public/language/nb/search.json index e0798274ba..146febd8da 100644 --- a/public/language/nb/search.json +++ b/public/language/nb/search.json @@ -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/topic.json b/public/language/nb/topic.json index 54fc6d6007..0a6634871c 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -190,7 +190,7 @@ "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": "Opprett nytt innlegg i stedet?", @@ -218,8 +218,8 @@ "post-quick-reply": "Raskt 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", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 10f16695ae..d64bf07fba 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -119,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.", @@ -139,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", diff --git a/public/language/nn-NO/admin/manage/privileges.json b/public/language/nn-NO/admin/manage/privileges.json index 42dd969247..5c9b423ca1 100644 --- a/public/language/nn-NO/admin/manage/privileges.json +++ b/public/language/nn-NO/admin/manage/privileges.json @@ -35,7 +35,7 @@ "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/settings/activitypub.json b/public/language/nn-NO/admin/settings/activitypub.json index 96d0526ecc..cd19292284 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", diff --git a/public/language/nn-NO/admin/settings/post.json b/public/language/nn-NO/admin/settings/post.json index 10d4b92671..ba184cf442 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", 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/error.json b/public/language/nn-NO/error.json index 1268223a30..b41828b756 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -173,11 +173,11 @@ "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", @@ -202,9 +202,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.", 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 d6801a9670..699e995f73 100644 --- a/public/language/nn-NO/global.json +++ b/public/language/nn-NO/global.json @@ -73,11 +73,11 @@ "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", diff --git a/public/language/nn-NO/notifications.json b/public/language/nn-NO/notifications.json index 5677ae316f..8ea688912e 100644 --- a/public/language/nn-NO/notifications.json +++ b/public/language/nn-NO/notifications.json @@ -11,7 +11,7 @@ "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", @@ -19,7 +19,7 @@ "group-chat": "Gruppechat", "public-chat": "Offentleg chat", "follows": "Følgjarar", - "upvote": "Tilrår", + "upvote": "Anbefaler", "awards": "Utmerkingar", "new-flags": "Nye rapporteringar", "my-flags": "Rapporteringar tilordna meg", @@ -32,10 +32,10 @@ "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 har anbefalt innlegget ditt i %2.", + "upvoted-your-post-in-dual": "%1 og %2 har anbefalt innlegget ditt i %3.", + "upvoted-your-post-in-triple": "%1, %2 og %3 har anbefalt innlegget ditt i %4.", + "upvoted-your-post-in-multiple": "%1, %2 og %3 andre har anbefalt innlegget ditt i %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", @@ -80,9 +80,9 @@ "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-upvote": "Når nokon anbefaler innlegget ditt", + "notificationType-new-topic": "Når nokon du følgjer opprettar eit innlegg", + "notificationType-new-topic-with-tag": "Når eit innlegg blir oppretta med eit emneord du følgjer", "notificationType-new-topic-in-category": "Når eit innlegg vert oppretta i ein kategori du følgjer", "notificationType-new-reply": "Når eit nytt svar vert posta i eit innlegg du følgjer", "notificationType-post-edit": "Når eit svar blir redigert i eit innlegg du følgjer", diff --git a/public/language/nn-NO/pages.json b/public/language/nn-NO/pages.json index 4d1cdb955d..bbac702b1c 100644 --- a/public/language/nn-NO/pages.json +++ b/public/language/nn-NO/pages.json @@ -6,10 +6,10 @@ "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", + "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", @@ -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/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/topic.json b/public/language/nn-NO/topic.json index 4e6c94845e..3e588d10a2 100644 --- a/public/language/nn-NO/topic.json +++ b/public/language/nn-NO/topic.json @@ -80,7 +80,7 @@ "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", @@ -190,7 +190,7 @@ "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 innlegg i staden?", @@ -218,7 +218,7 @@ "post-quick-reply": "Raskt 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", diff --git a/public/language/nn-NO/user.json b/public/language/nn-NO/user.json index 702ba8b92a..a32fdf935f 100644 --- a/public/language/nn-NO/user.json +++ b/public/language/nn-NO/user.json @@ -119,14 +119,14 @@ "has-no-follower": "Denne brukaren har ingen følgjarar :(", "follows-no-one": "Denne brukaren følgjer 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-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", @@ -139,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", From e1423636a5404f265e7a6d41c6d0afed74b0b8f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 5 Aug 2025 10:46:10 -0400 Subject: [PATCH 202/828] feat: closes #13578, increase uniquevisitors on ap pageviews like normal pageviews --- src/analytics.js | 21 +++++++++++++-------- src/middleware/activitypub.js | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/analytics.js b/src/analytics.js index a0fadeab68..773f7088af 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -102,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(); @@ -113,14 +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.apPageView = function () { - local.apPageViews += 1; -}; +} Analytics.writeData = async function () { const today = new Date(); diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js index 4390335c0b..f1e78a1cd6 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -10,7 +10,7 @@ const middleware = module.exports; middleware.enabled = async (req, res, next) => next(!meta.config.activitypubEnabled ? 'route' : undefined); middleware.pageview = async (req, res, next) => { - analytics.apPageView(); + await analytics.apPageView({ ip: req.ip }); next(); }; From 9d39ed512fa96a518f81d09f54bafa6f3e86b2ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 5 Aug 2025 17:51:53 -0400 Subject: [PATCH 203/828] feat: only mark notifications read that match current filter closes #13574 --- public/src/client/notifications.js | 2 +- public/src/modules/notifications.js | 17 +++++++++++++---- src/notifications.js | 16 +++++++++++++--- src/socket.io/notifications.js | 4 ++-- 4 files changed, 29 insertions(+), 10 deletions(-) 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/modules/notifications.js b/public/src/modules/notifications.js index 891ca17fe1..712b99ce98 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -105,7 +105,7 @@ define('notifications', [ }); if (!unreadNotifs[notifData.nid]) { - unreadNotifs[notifData.nid] = true; + unreadNotifs[notifData.nid] = notifData; } }; @@ -165,12 +165,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/src/notifications.js b/src/notifications.js index 4e6e7873f3..bcf27c7d66 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -383,10 +383,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/socket.io/notifications.js b/src/socket.io/notifications.js index 2b0df88114..c861f6c725 100644 --- a/src/socket.io/notifications.js +++ b/src/socket.io/notifications.js @@ -34,8 +34,8 @@ 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) { + await notifications.markAllRead(socket.uid, data.filter); user.notifications.pushCount(socket.uid); }; From f8a0a7e194b0acd3242c00ec65a598b0de7d6ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 5 Aug 2025 18:00:44 -0400 Subject: [PATCH 204/828] test: fix notification tests --- src/socket.io/notifications.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js index c861f6c725..263193260a 100644 --- a/src/socket.io/notifications.js +++ b/src/socket.io/notifications.js @@ -35,7 +35,8 @@ SocketNotifs.markUnread = async function (socket, nid) { }; SocketNotifs.markAllRead = async function (socket, data) { - await notifications.markAllRead(socket.uid, data.filter); + const filter = data && data.filter ? data.filter : ''; + await notifications.markAllRead(socket.uid, filter); user.notifications.pushCount(socket.uid); }; From 25bc9ba00b859c3b43ac55adfd0a7dc99f45d087 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:01:42 -0400 Subject: [PATCH 205/828] chore(deps): update redis docker tag to v8.2.0 (#13577) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e3bdaf2ebd..fb60386988 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.0.3' + image: 'redis:8.2.0' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 0cfee0de3a..7ae3698c87 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.0.3-alpine + image: redis:8.2.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 dd415cc5fb..8788ee6230 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:8.0.3-alpine + image: redis:8.2.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 06273610b7..f154ba11fc 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:8.0.3-alpine + image: redis:8.2.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 From 3c3e44860613aac0412d489faafc400b100bc12f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 13:12:05 -0400 Subject: [PATCH 206/828] fix(deps): update dependency redis to v5.8.0 (#13580) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 947ffa166f..0a92594858 100644 --- a/install/package.json +++ b/install/package.json @@ -121,7 +121,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.7.0", + "redis": "5.8.0", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 5ce556d41f76649df62a97d3f24d2fd8bea16391 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 13:12:19 -0400 Subject: [PATCH 207/828] fix(deps): update dependency fs-extra to v11.3.1 (#13579) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0a92594858..8d348f3fd3 100644 --- a/install/package.json +++ b/install/package.json @@ -71,7 +71,7 @@ "express-useragent": "1.0.15", "fetch-cookie": "3.1.0", "file-loader": "6.2.0", - "fs-extra": "11.3.0", + "fs-extra": "11.3.1", "graceful-fs": "4.2.11", "helmet": "7.2.0", "html-to-text": "9.0.5", From 34ecdf2043d5d3bdade79af292cf7fb4e8f91bbb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 13:12:31 -0400 Subject: [PATCH 208/828] chore(deps): update dependency lint-staged to v16.1.4 (#13575) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8d348f3fd3..72fc65b7b7 100644 --- a/install/package.json +++ b/install/package.json @@ -168,7 +168,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "26.1.0", - "lint-staged": "16.1.2", + "lint-staged": "16.1.4", "mocha": "11.7.1", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 3895a0590c27e54e001cf32785db703d84df921e Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 6 Aug 2025 17:48:34 +0000 Subject: [PATCH 209/828] chore: update changelog for v4.4.6 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d512874927..163d3d882c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +#### 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 From b4ff79061f8018e6d17a476567c256c88e5fa1ac Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 6 Aug 2025 13:50:08 -0400 Subject: [PATCH 210/828] fix: image handling when image url received is not a path with an extension --- src/activitypub/mocks.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index e5a8e8e363..e1e9d82b69 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -84,7 +84,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; @@ -101,7 +101,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; } From c305cc9069035f606b47139dc77d617c37b0fd70 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 7 Aug 2025 09:20:34 +0000 Subject: [PATCH 211/828] Latest translations and fallbacks --- public/language/nn-NO/modules.json | 4 ++-- public/language/pt-BR/admin/advanced/database.json | 2 +- public/language/pt-BR/global.json | 4 ++-- public/language/pt-BR/notifications.json | 2 +- public/language/pt-BR/pages.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/language/nn-NO/modules.json b/public/language/nn-NO/modules.json index e2e97396c5..9bf943c4a4 100644 --- a/public/language/nn-NO/modules.json +++ b/public/language/nn-NO/modules.json @@ -9,14 +9,14 @@ "chat.chat-with-usernames": "Chat med %1", "chat.chat-with-usernames-and-x-others": "Chat med %1 og %2 andre", "chat.send": "Publiser", - "chat.no-active": "Du har ingen aktive chattar", + "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", 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/global.json b/public/language/pt-BR/global.json index 798c4ef50d..d3b69cd790 100644 --- a/public/language/pt-BR/global.json +++ b/public/language/pt-BR/global.json @@ -79,7 +79,7 @@ "upvoters": "Votos positivos", "upvoted": "Votou positivamente", "downvoters": "Votos negativos", - "downvoted": "Votou negativamente", + "downvoted": "Rebaixou", "views": "Visualizações", "posters": "Posters", "watching": "Watching", @@ -136,7 +136,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/notifications.json b/public/language/pt-BR/notifications.json index 0d94954404..6cc2461f7d 100644 --- a/public/language/pt-BR/notifications.json +++ b/public/language/pt-BR/notifications.json @@ -65,7 +65,7 @@ "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", 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", From 88b40e1e9d79777cd153c2e6c587a95a01b06a39 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 8 Aug 2025 09:19:47 +0000 Subject: [PATCH 212/828] Latest translations and fallbacks --- public/language/nb/admin/menu.json | 4 +-- public/language/nb/admin/settings/post.json | 2 +- .../nb/admin/settings/reputation.json | 6 ++-- public/language/nb/category.json | 6 ++-- public/language/nb/error.json | 16 +++++----- public/language/nb/flags.json | 6 ++-- public/language/nb/global.json | 4 +-- public/language/nb/modules.json | 20 ++++++------ public/language/nb/notifications.json | 8 ++--- public/language/nb/pages.json | 6 ++-- public/language/nb/recent.json | 4 +-- public/language/nb/user.json | 8 ++--- public/language/nb/users.json | 4 +-- public/language/nn-NO/admin/dashboard.json | 2 +- .../nn-NO/admin/manage/categories.json | 10 +++--- public/language/nn-NO/admin/manage/users.json | 4 +-- .../language/nn-NO/admin/settings/post.json | 2 +- .../language/nn-NO/admin/settings/user.json | 4 +-- public/language/nn-NO/category.json | 20 ++++++------ public/language/nn-NO/email.json | 2 +- public/language/nn-NO/error.json | 2 +- public/language/nn-NO/global.json | 18 +++++------ public/language/nn-NO/ip-blacklist.json | 2 +- public/language/nn-NO/modules.json | 4 +-- public/language/nn-NO/notifications.json | 32 +++++++++---------- public/language/nn-NO/pages.json | 8 ++--- public/language/nn-NO/recent.json | 4 +-- public/language/nn-NO/tags.json | 4 +-- public/language/nn-NO/topic.json | 6 ++-- public/language/nn-NO/unread.json | 2 +- public/language/nn-NO/user.json | 24 +++++++------- public/language/nn-NO/users.json | 4 +-- public/language/nn-NO/world.json | 10 +++--- 33 files changed, 129 insertions(+), 129 deletions(-) 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/post.json b/public/language/nb/admin/settings/post.json index 11738ae699..61f21ea532 100644 --- a/public/language/nb/admin/settings/post.json +++ b/public/language/nb/admin/settings/post.json @@ -5,7 +5,7 @@ "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.recently-created": "Sist opprettet", "sorting.most-votes": "Flest anbefalinger", "sorting.most-posts": "Flest innlegg", "sorting.most-views": "Flest visninger", diff --git a/public/language/nb/admin/settings/reputation.json b/public/language/nb/admin/settings/reputation.json index c3604622a9..ad1df9d37a 100644 --- a/public/language/nb/admin/settings/reputation.json +++ b/public/language/nb/admin/settings/reputation.json @@ -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/category.json b/public/language/nb/category.json index 5da2e6af1f..3fff71221d 100644 --- a/public/language/nb/category.json +++ b/public/language/nb/category.json @@ -18,9 +18,9 @@ "not-watching": "Følger ikke", "ignoring": "Ignorerer", "watching.description": "Varsle meg om nye innlegg.
Vis innlegg 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", + "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 f85994d9cb..a497b4f446 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -64,7 +64,7 @@ "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", @@ -194,13 +194,13 @@ "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 anbefale ditt eget innlegg", "too-many-upvotes-today": "Du kan bare anbefale %1 ganger pr. dag", diff --git a/public/language/nb/flags.json b/public/language/nb/flags.json index 7c871e2806..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", diff --git a/public/language/nb/global.json b/public/language/nb/global.json index 02a897451c..6e48052df8 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -101,8 +101,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/modules.json b/public/language/nb/modules.json index 4a3d1439fa..99e3edc5d9 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": "Publiser", - "chat.no-active": "Du har ingen aktive chatter.", + "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.", @@ -54,15 +54,15 @@ "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", diff --git a/public/language/nb/notifications.json b/public/language/nb/notifications.json index 76b3aaf0c9..a27035905c 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -15,13 +15,13 @@ "tags": "Emneord", "categories": "Kategorier", "replies": "Svar", - "chat": "Chatter", - "group-chat": "Gruppechat", + "chat": "Chat", + "group-chat": "Gruppemelding", "public-chat": "Offentlig chat", "follows": "Følger", "upvote": "Anbefaler", "awards": "Tildelninger", - "new-flags": "Nye flagg", + "new-flags": "Nye rapporteringer", "my-flags": "Flagg som er tildelt meg", "bans": "Forbud", "new-message-from": "Ny melding fra %1", @@ -95,7 +95,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 2cc9a26c2d..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", + "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\"", 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/user.json b/public/language/nb/user.json index d64bf07fba..42f7b51d13 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -61,7 +61,7 @@ "new-chat-with": "Start ny chat med %1", "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", @@ -175,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/nn-NO/admin/dashboard.json b/public/language/nn-NO/admin/dashboard.json index 80004f87e3..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", diff --git a/public/language/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index 6d947aacb2..b665e1fd28 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -86,17 +86,17 @@ "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", diff --git a/public/language/nn-NO/admin/manage/users.json b/public/language/nn-NO/admin/manage/users.json index 7013a7c0df..67f0f73f9b 100644 --- a/public/language/nn-NO/admin/manage/users.json +++ b/public/language/nn-NO/admin/manage/users.json @@ -135,8 +135,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/post.json b/public/language/nn-NO/admin/settings/post.json index ba184cf442..3534e580a9 100644 --- a/public/language/nn-NO/admin/settings/post.json +++ b/public/language/nn-NO/admin/settings/post.json @@ -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/user.json b/public/language/nn-NO/admin/settings/user.json index 76c93d24c6..77b367a63b 100644 --- a/public/language/nn-NO/admin/settings/user.json +++ b/public/language/nn-NO/admin/settings/user.json @@ -63,7 +63,7 @@ "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", @@ -81,7 +81,7 @@ "default-notification-settings": "Standard varslingsinnstillingar", "categoryWatchState": "Kategori-overvåking", "categoryWatchState.tracking": "Sporing", - "categoryWatchState.notwatching": "Følgjer ikkje", + "categoryWatchState.notwatching": "Følger ikkje", "categoryWatchState.ignoring": "Ignorerer", "restrictions-new": "Nye restriksjonar", "restrictions.rep-threshold": "Omdømmegrense", diff --git a/public/language/nn-NO/category.json b/public/language/nn-NO/category.json index 2ef1b7bff5..6ef0275e7f 100644 --- a/public/language/nn-NO/category.json +++ b/public/language/nn-NO/category.json @@ -7,24 +7,24 @@ "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ølgjer denne kategorien enno. Følg den for å få oppdateringer.", + "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 innlegg.
Vis innlegg 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 b41828b756..61cb577098 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -154,7 +154,7 @@ "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.", diff --git a/public/language/nn-NO/global.json b/public/language/nn-NO/global.json index 699e995f73..73fa0477eb 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,8 +63,8 @@ "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", @@ -82,7 +82,7 @@ "downvoted": "Stemte ned", "views": "Visningar", "posters": "Innleggsskrivarar", - "watching": "Følgjer", + "watching": "Følger", "reputation": "Omdømme", "lastpost": "Siste innlegg", "firstpost": "Fyrste innlegg", @@ -101,9 +101,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/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 9bf943c4a4..2d61b117af 100644 --- a/public/language/nn-NO/modules.json +++ b/public/language/nn-NO/modules.json @@ -4,7 +4,7 @@ "chat.placeholder": "Skriv chatmelding her, dra og slepp bilete", "chat.placeholder.mobile": "Skriv chatmelding", "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", @@ -20,7 +20,7 @@ "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", diff --git a/public/language/nn-NO/notifications.json b/public/language/nn-NO/notifications.json index 8ea688912e..bb091eacc6 100644 --- a/public/language/nn-NO/notifications.json +++ b/public/language/nn-NO/notifications.json @@ -16,9 +16,9 @@ "categories": "Kategoriar", "replies": "Svar", "chat": "Meldingar", - "group-chat": "Gruppechat", + "group-chat": "Gruppemelding", "public-chat": "Offentleg chat", - "follows": "Følgjarar", + "follows": "Følgarar", "upvote": "Anbefaler", "awards": "Utmerkingar", "new-flags": "Nye rapporteringar", @@ -57,10 +57,10 @@ "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 skrive eit nytt innlegg 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-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", @@ -81,12 +81,12 @@ "email-only": "Berre e-post", "notification-and-email": "Varsel og e-post", "notificationType-upvote": "Når nokon anbefaler innlegget ditt", - "notificationType-new-topic": "Når nokon du følgjer opprettar eit innlegg", - "notificationType-new-topic-with-tag": "Når eit innlegg blir oppretta med eit emneord du følgjer", - "notificationType-new-topic-in-category": "Når eit innlegg vert oppretta i ein kategori du følgjer", - "notificationType-new-reply": "Når eit nytt svar vert posta i eit innlegg du følgjer", - "notificationType-post-edit": "Når eit svar blir redigert i eit innlegg du følgjer", - "notificationType-follow": "Når nokon startar å følgje deg", + "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 mottek ei chatmelding", "notificationType-new-group-chat": "Når du mottek ei gruppemelding", "notificationType-new-public-chat": "Når du mottek ei offentleg gruppemelding", @@ -98,8 +98,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 bbac702b1c..596398d6cc 100644 --- a/public/language/nn-NO/pages.json +++ b/public/language/nn-NO/pages.json @@ -5,7 +5,7 @@ "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", + "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", @@ -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", 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/tags.json b/public/language/nn-NO/tags.json index 19f4a2b929..fb28e5bf2d 100644 --- a/public/language/nn-NO/tags.json +++ b/public/language/nn-NO/tags.json @@ -8,8 +8,8 @@ "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": "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.", diff --git a/public/language/nn-NO/topic.json b/public/language/nn-NO/topic.json index 3e588d10a2..d32abd1b7e 100644 --- a/public/language/nn-NO/topic.json +++ b/public/language/nn-NO/topic.json @@ -88,10 +88,10 @@ "watch": "Følg", "unwatch": "Ikkje følg", "watch.title": "Varsle meg om nye svar på dette innlegget", - "unwatch.title": "Slutt å følgje 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 innlegg som ulest.", "not-watching.description": "Ikkje varsle meg om nye svar.
Vis innlegg i ulest om kategorien ikkje er ignorert.", diff --git a/public/language/nn-NO/unread.json b/public/language/nn-NO/unread.json index 71e51f34dd..2a90cf157f 100644 --- a/public/language/nn-NO/unread.json +++ b/public/language/nn-NO/unread.json @@ -10,7 +10,7 @@ "topics-marked-as-read.success": "Emne merka som lest!", "all-topics": "Alle emne", "new-topics": "Nye innlegg", - "watched-topics": "Emne du følgjer", + "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 a32fdf935f..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", @@ -61,10 +61,10 @@ "new-chat-with": "Start ny chat med %1", "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,7 +104,7 @@ "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", @@ -116,12 +116,12 @@ "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 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 anbefalt nokon innlegg enno.", diff --git a/public/language/nn-NO/users.json b/public/language/nn-NO/users.json index 6d080dba84..c734d32d2c 100644 --- a/public/language/nn-NO/users.json +++ b/public/language/nn-NO/users.json @@ -1,6 +1,6 @@ { "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", @@ -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 978f5841bf..16b01238e2 100644 --- a/public/language/nn-NO/world.json +++ b/public/language/nn-NO/world.json @@ -6,14 +6,14 @@ "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.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!", "show-categories": "Show categories", From e7b479954ac0044f576cb51a0eaaa018bf965397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 8 Aug 2025 13:21:43 -0400 Subject: [PATCH 213/828] chore: up widget essentials --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 97e4509313..0308a6526a 100644 --- a/install/package.json +++ b/install/package.json @@ -110,7 +110,7 @@ "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.43", "nodebb-theme-persona": "14.1.12", - "nodebb-widget-essentials": "7.0.38", + "nodebb-widget-essentials": "7.0.39", "nodemailer": "7.0.3", "nprogress": "0.2.0", "passport": "0.7.0", From c8694333733b63bffdf5d44fa203bea2a2548901 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:51:09 -0400 Subject: [PATCH 214/828] chore(deps): update dependency sass-embedded to v1.90.0 (#13581) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6108fce1a7..eddc05ca6b 100644 --- a/install/package.json +++ b/install/package.json @@ -176,7 +176,7 @@ "smtp-server": "3.14.0" }, "optionalDependencies": { - "sass-embedded": "1.89.2" + "sass-embedded": "1.90.0" }, "resolutions": { "*/jquery": "3.7.1" From abf7dd74d0138d3c76f3b82a346e39208186da12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:51:20 -0400 Subject: [PATCH 215/828] fix(deps): update dependency sass to v1.90.0 (#13582) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index eddc05ca6b..c078d85f1f 100644 --- a/install/package.json +++ b/install/package.json @@ -126,7 +126,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.89.2", + "sass": "1.90.0", "satori": "0.16.2", "semver": "7.7.2", "serve-favicon": "2.5.1", From e68deaaca1f88a2ed9b3034948523d89793f8a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 8 Aug 2025 13:54:12 -0400 Subject: [PATCH 216/828] chore: up eslibt --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 6108fce1a7..298112b805 100644 --- a/install/package.json +++ b/install/package.json @@ -161,8 +161,8 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.32.0", - "@stylistic/eslint-plugin": "5.2.2", - "eslint-config-nodebb": "1.1.10", + "@stylistic/eslint-plugin": "^5.x", + "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", From f8733e06a783580ca88ee735eb317e0dafac427e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 8 Aug 2025 16:10:11 -0400 Subject: [PATCH 217/828] refactor: show code/stack when dep check fails --- src/cli/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/index.js b/src/cli/index.js index a413ec6ef1..e868b702c4 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -42,7 +42,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(); From 18a6c98c9d3d7cc294d676ed3780c31d1d415f66 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 11 Aug 2025 09:20:05 +0000 Subject: [PATCH 218/828] Latest translations and fallbacks --- public/language/fi/category.json | 4 ++-- public/language/fi/modules.json | 8 ++++---- public/language/fi/recent.json | 2 +- public/language/fi/unread.json | 2 +- public/language/fi/user.json | 18 +++++++++--------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/public/language/fi/category.json b/public/language/fi/category.json index 1df5d1b218..2d2f4f99de 100644 --- a/public/language/fi/category.json +++ b/public/language/fi/category.json @@ -1,8 +1,8 @@ { "category": "Kategoria", "subcategories": "Alikategoria", - "uncategorized": "Uncategorized", - "uncategorized.description": "Topics that do not strictly fit in with any existing categories", + "uncategorized": "Kategoroimattomat", + "uncategorized.description": "Aiheet jotka eivät suoraan sovi minkään kategorian alle.", "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", diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index dcf1fcea47..f118e6568a 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,7 +42,7 @@ "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", @@ -70,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 ", 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/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 735c574adb..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!", From e90b524b66f855f5e8d553b1adafb1c68f28dd30 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 12 Aug 2025 09:19:59 +0000 Subject: [PATCH 219/828] Latest translations and fallbacks --- public/language/nb/notifications.json | 2 +- public/language/nb/user.json | 6 +++--- public/language/nn-NO/admin/settings/sounds.json | 2 +- public/language/nn-NO/error.json | 16 ++++++++-------- public/language/nn-NO/modules.json | 12 ++++++------ public/language/nn-NO/notifications.json | 6 +++--- public/language/nn-NO/users.json | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/public/language/nb/notifications.json b/public/language/nb/notifications.json index a27035905c..db81e4b272 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -87,7 +87,7 @@ "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", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 42f7b51d13..0f2cc20fa5 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -104,10 +104,10 @@ "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 chat-meldinger fra følgende brukere", - "chat-deny-list": "Ikke tillat chat-meldinger fra følgende brukere", + "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 innlegg) i samsvar med valgte tidspunkt", 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/error.json b/public/language/nn-NO/error.json index 61cb577098..c25a7f8cb2 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -37,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.", @@ -160,14 +160,14 @@ "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.", diff --git a/public/language/nn-NO/modules.json b/public/language/nn-NO/modules.json index 2d61b117af..36314e1771 100644 --- a/public/language/nn-NO/modules.json +++ b/public/language/nn-NO/modules.json @@ -1,8 +1,8 @@ { "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 siste innlegg", "chat.usernames-and-x-others": "%1 og %2 andre", @@ -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.", @@ -55,14 +55,14 @@ "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", diff --git a/public/language/nn-NO/notifications.json b/public/language/nn-NO/notifications.json index bb091eacc6..85e838c866 100644 --- a/public/language/nn-NO/notifications.json +++ b/public/language/nn-NO/notifications.json @@ -87,9 +87,9 @@ "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 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-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", diff --git a/public/language/nn-NO/users.json b/public/language/nn-NO/users.json index c734d32d2c..1d2aea1465 100644 --- a/public/language/nn-NO/users.json +++ b/public/language/nn-NO/users.json @@ -7,7 +7,7 @@ "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", From c10656ec52753d335e584b1208703b34ae908860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 12 Aug 2025 17:06:57 -0400 Subject: [PATCH 220/828] feat: add wordpress --- public/language/en-GB/social.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From 82037dee0073fa4a3462bc8476798380f1277d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 12 Aug 2025 17:06:57 -0400 Subject: [PATCH 221/828] feat: add wordpress --- public/language/en-GB/social.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From eeabc99092267b5216e634073b8f78862805f70a Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 12 Aug 2025 21:07:22 +0000 Subject: [PATCH 222/828] chore(i18n): fallback strings for new resources: nodebb.social --- public/language/ar/social.json | 4 +++- public/language/az/social.json | 4 +++- public/language/bg/social.json | 4 +++- public/language/bn/social.json | 4 +++- public/language/cs/social.json | 4 +++- public/language/da/social.json | 4 +++- public/language/de/social.json | 4 +++- public/language/el/social.json | 4 +++- public/language/en-US/social.json | 4 +++- public/language/en-x-pirate/social.json | 4 +++- public/language/es/social.json | 4 +++- public/language/et/social.json | 4 +++- public/language/fa-IR/social.json | 4 +++- public/language/fi/social.json | 4 +++- public/language/fr/social.json | 4 +++- public/language/gl/social.json | 4 +++- public/language/he/social.json | 4 +++- public/language/hr/social.json | 4 +++- public/language/hu/social.json | 4 +++- public/language/hy/social.json | 4 +++- public/language/id/social.json | 4 +++- public/language/it/social.json | 4 +++- public/language/ja/social.json | 4 +++- public/language/ko/social.json | 4 +++- public/language/lt/social.json | 4 +++- public/language/lv/social.json | 4 +++- public/language/ms/social.json | 4 +++- public/language/nb/social.json | 4 +++- public/language/nl/social.json | 4 +++- public/language/nn-NO/social.json | 4 +++- public/language/pl/social.json | 4 +++- public/language/pt-BR/social.json | 4 +++- public/language/pt-PT/social.json | 4 +++- public/language/ro/social.json | 4 +++- public/language/ru/social.json | 4 +++- public/language/rw/social.json | 4 +++- public/language/sc/social.json | 4 +++- public/language/sk/social.json | 4 +++- public/language/sl/social.json | 4 +++- public/language/sq-AL/social.json | 4 +++- public/language/sr/social.json | 4 +++- public/language/sv/social.json | 4 +++- public/language/th/social.json | 4 +++- public/language/tr/social.json | 4 +++- public/language/uk/social.json | 4 +++- public/language/vi/social.json | 4 +++- public/language/zh-CN/social.json | 4 +++- public/language/zh-TW/social.json | 4 +++- 48 files changed, 144 insertions(+), 48 deletions(-) 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/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/bg/social.json b/public/language/bg/social.json index 931e80c8c8..5de0d2c5d3 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": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file 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/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/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/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/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/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-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/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/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/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/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/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/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/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/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/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/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/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/it/social.json b/public/language/it/social.json index 4fb4c6bd3e..23f4777e8f 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": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file 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/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/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/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/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/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/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/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/pl/social.json b/public/language/pl/social.json index 25d33196d0..5c621da4e3 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": "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/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-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/ro/social.json b/public/language/ro/social.json index 2ba690a187..5b8dd99a46 100644 --- a/public/language/ro/social.json +++ b/public/language/ro/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/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/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/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/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/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/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/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/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/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/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/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/vi/social.json b/public/language/vi/social.json index 2a1194b3fd..e0ea9432b0 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": "Sign in with WordPress", + "sign-up-with-wordpress": "Sign up with WordPress" } \ No newline at end of file diff --git a/public/language/zh-CN/social.json b/public/language/zh-CN/social.json index ff9388001d..134f5e2715 100644 --- a/public/language/zh-CN/social.json +++ b/public/language/zh-CN/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/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 From 49de4f375e0646e7641c0daf041f93fa29aa3d19 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 13 Aug 2025 09:19:46 +0000 Subject: [PATCH 223/828] Latest translations and fallbacks --- public/language/bg/social.json | 4 ++-- public/language/cs/modules.json | 20 ++++++++++---------- public/language/cs/register.json | 4 ++-- public/language/cs/unread.json | 2 +- public/language/zh-CN/social.json | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/public/language/bg/social.json b/public/language/bg/social.json index 5de0d2c5d3..df4532b68e 100644 --- a/public/language/bg/social.json +++ b/public/language/bg/social.json @@ -9,6 +9,6 @@ "continue-with-facebook": "Продължаване с Facebook", "sign-in-with-linkedin": "Вписване с LinkedIn", "sign-up-with-linkedin": "Регистриране с LinkedIn", - "sign-in-with-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "sign-in-with-wordpress": "Вписване с WordPress", + "sign-up-with-wordpress": "Регистриране с WordPress" } \ No newline at end of file diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index 7ba9a7a2a3..868ec52467 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -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", @@ -89,13 +89,13 @@ "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", @@ -121,7 +121,7 @@ "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/register.json b/public/language/cs/register.json index 356bcc7f2b..f275d147fa 100644 --- a/public/language/cs/register.json +++ b/public/language/cs/register.json @@ -22,8 +22,8 @@ "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-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/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/zh-CN/social.json b/public/language/zh-CN/social.json index 134f5e2715..2800dfa892 100644 --- a/public/language/zh-CN/social.json +++ b/public/language/zh-CN/social.json @@ -7,8 +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-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "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 From 8c6992f525ce0d9e6f3edd00723da86b137484c6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Aug 2025 09:34:58 -0400 Subject: [PATCH 224/828] feat: add Urdu localisation, thank you! --- .tx/config | 75 +++++ public/language/ur/admin/admin.json | 18 ++ public/language/ur/admin/advanced/cache.json | 10 + .../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 | 122 ++++++++ public/language/ur/admin/manage/digest.json | 22 ++ public/language/ur/admin/manage/groups.json | 49 ++++ .../language/ur/admin/manage/privileges.json | 66 +++++ .../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 | 152 ++++++++++ public/language/ur/admin/menu.json | 93 ++++++ .../ur/admin/settings/activitypub.json | 26 ++ .../language/ur/admin/settings/advanced.json | 47 ++++ public/language/ur/admin/settings/api.json | 29 ++ public/language/ur/admin/settings/chat.json | 17 ++ .../language/ur/admin/settings/cookies.json | 13 + public/language/ur/admin/settings/email.json | 54 ++++ .../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 | 7 + .../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 | 46 +++ public/language/ur/admin/settings/user.json | 98 +++++++ .../ur/admin/settings/web-crawler.json | 10 + public/language/ur/aria.json | 9 + public/language/ur/category.json | 30 ++ public/language/ur/email.json | 61 ++++ public/language/ur/error.json | 266 ++++++++++++++++++ public/language/ur/flags.json | 101 +++++++ public/language/ur/global.json | 154 ++++++++++ public/language/ur/groups.json | 66 +++++ 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 | 105 +++++++ 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 | 23 ++ public/language/ur/themes/persona.json | 10 + public/language/ur/top.json | 4 + public/language/ur/topic.json | 228 +++++++++++++++ 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 | 21 ++ 77 files changed, 3561 insertions(+) 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/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 diff --git a/.tx/config b/.tx/config index 681e9a6709..d92404ea4f 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,6 +1261,7 @@ 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 @@ -1293,6 +1316,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 +1371,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 +1426,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 +1481,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 +1536,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 +1591,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 +1646,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 +1701,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 +1756,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 +1811,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 +1866,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 +1921,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 +1976,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 +2031,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 +2086,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 +2141,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 +2196,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 +2251,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 +2306,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 +2361,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 +2416,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 +2471,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 +2526,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 +2581,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 +2636,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 +2691,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 +2746,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 +2801,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 +2909,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 +2964,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 +3019,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 +3074,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 +3129,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 +3184,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 +3239,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 +3294,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 +3349,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 +3404,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 +3459,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 +3514,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 +3569,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 +3624,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 +3679,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 +3734,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 +3789,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 +3844,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 +3899,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 +3954,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 +4009,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 +4064,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 +4119,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 +4174,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/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..c8889cc414 --- /dev/null +++ b/public/language/ur/admin/advanced/cache.json @@ -0,0 +1,10 @@ +{ + "cache": "کیش", + "post-cache": "پوسٹ کیش", + "group-cache": "گروپ کیش", + "local-cache": "لوکل کیش", + "object-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..89ece3e795 --- /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": "پروسیس میموری", + "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..03d4608fbd --- /dev/null +++ b/public/language/ur/admin/manage/categories.json @@ -0,0 +1,122 @@ +{ + "manage-categories": "زمرہ جات کا انتظام", + "add-category": "زمرہ شامل کریں", + "jump-to": "پر جائیں…", + "settings": "زمرہ کی ترتیبات", + "edit-category": "زمرہ ترمیم کریں", + "privileges": "اختیارات", + "back-to-categories": "زمرہ جات پر واپس", + "name": "زمرہ کا نام", + "handle": "زمرہ کا شناخت کنندہ", + "handle.help": "زمرہ کا شناخت کنندہ اس زمرہ کو دیگر نیٹ ورکس میں پیش کرنے کے لیے استعمال ہوتا ہے، جیسے کہ صارف نام۔ اس شناخت کنندہ کا موجودہ صارف نام یا صارف گروپ سے مماثل نہیں ہونا چاہیے۔", + "description": "زمرہ کی تفصیل", + "federatedDescription": "فیڈریٹڈ تفصیل", + "federatedDescription.help": "یہ متن زمرہ کی تفصیل میں شامل کیا جائے گا جب دیگر ویب سائٹس اور ایپلی کیشنز اس کے بارے میں معلومات طلب کریں گی۔", + "federatedDescription.default": "یہ فورم میں ایک زمرہ ہے جس میں موضوعاتی بحثیں شامل ہیں۔ آپ اس فورم کا ذکر کرکے ایک نئی بحث شروع کر سکتے ہیں۔", + "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.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/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..cfd5d95854 --- /dev/null +++ b/public/language/ur/admin/manage/privileges.json @@ -0,0 +1,66 @@ +{ + "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": "موضوعات میں جواب دیں", + "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..ff733a3fc7 --- /dev/null +++ b/public/language/ur/admin/manage/users.json @@ -0,0 +1,152 @@ +{ + "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": "مرضی کے صارف فیلڈز", + "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.hours": "گھنٹے", + "temp-ban.days": "دن", + "temp-ban.explanation": "پابندی کا دورانیہ درج کریں۔ 0 کی قیمت پابندی کو مستقل بنائے گی۔", + + "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..2cf9782052 --- /dev/null +++ b/public/language/ur/admin/settings/activitypub.json @@ -0,0 +1,26 @@ +{ + "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) اگر پروب کو مقررہ وقت کے اندر جواب نہ ملے، تو صارف کو براہ راست لنک کے ایڈریس پر بھیجا جائے گا۔ اگر ویب سائٹس سست جواب دیتی ہیں اور آپ انہیں زیادہ وقت دینا چاہتے ہیں تو بڑا نمبر سیٹ کریں۔", + + "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..332beeca5b --- /dev/null +++ b/public/language/ur/admin/settings/chat.json @@ -0,0 +1,17 @@ +{ + "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": "گفتگو کے پیغامات کے درمیان وقت (ملی سیکنڈز)", + "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/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..9acc6b43e2 --- /dev/null +++ b/public/language/ur/admin/settings/email.json @@ -0,0 +1,54 @@ +{ + "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 سرٹیفکیٹس استعمال کرنے کی اجازت ملے گی۔", + + "template": "ای میل ٹیمپلیٹ میں ترمیم کریں", + "template.select": "ای میل ٹیمپلیٹ منتخب کریں", + "template.revert": "اصل پر واپس جائیں", + "testing": "ای میل ٹیسٹنگ", + "testing.select": "ای میل ٹیمپلیٹ منتخب کریں", + "testing.send": "ٹیسٹ ای میل بھیجیں", + "testing.send-help": "ٹیسٹ ای میل موجودہ لاگ ان صارف کے ای میل پر بھیجا جائے گا۔", + "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..948e0c4993 --- /dev/null +++ b/public/language/ur/admin/settings/notifications.json @@ -0,0 +1,7 @@ +{ + "notifications": "اطلاعات", + "welcome-notification": "خوش آمدید اطلاع", + "welcome-notification-link": "خوش آمدید اطلاع کے لیے لنک", + "welcome-notification-uid": "خوش آمدید اطلاع کے لیے صارف آئی ڈی", + "post-queue-notification-uid": "پوسٹ کیو کے لیے صارف آئی ڈی" +} \ 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..f44da0e354 --- /dev/null +++ b/public/language/ur/admin/settings/uploads.json @@ -0,0 +1,46 @@ +{ + "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": "جن تصاویر کی اونچائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔", + "allow-topic-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..24fe7ddd89 --- /dev/null +++ b/public/language/ur/admin/settings/web-crawler.json @@ -0,0 +1,10 @@ +{ + "crawlability-settings": "کرال ایبلٹی کی ترتیبات", + "robots-txt": "حسب ضرورت 'Robots.txt' فائل طے شدہ فائل استعمال کرنے کے لیے خالی چھوڑ دیں", + "sitemap-feed-settings": "ویب سائٹ کے نقشے اور فیڈز کی ترتیبات", + "disable-rss-feeds": "RSS فیڈز کو غیر فعال کریں", + "disable-sitemap-xml": "ویب سائٹ کا نقشہ ('Sitemap.xml') غیر فعال کریں", + "sitemap-topics": "ویب سائٹ کے نقشے میں دکھائے جانے والے موضوعات کی تعداد", + "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..04f277985d --- /dev/null +++ b/public/language/ur/aria.json @@ -0,0 +1,9 @@ +{ + "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 کے لیے گروپ صفحہ کا لنک" +} \ 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..3c60ef0b98 --- /dev/null +++ b/public/language/ur/category.json @@ -0,0 +1,30 @@ +{ + "category": "زمرہ", + "subcategories": "ذیلی زمرہ جات", + "uncategorized": "بغیر زمرہ", + "uncategorized.description": "وہ موضوعات جو کسی مخصوص زمرے میں فٹ نہیں ہوتے", + "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..21e92491e6 --- /dev/null +++ b/public/language/ur/error.json @@ -0,0 +1,266 @@ +{ + "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": "یہ موضوع پہلے سے بحال ہو چکا ہے", + "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": "موضوع کو اسی زمرے میں منتقل نہیں کیا جا سکتا!", + "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.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..fe2671f98d --- /dev/null +++ b/public/language/ur/global.json @@ -0,0 +1,154 @@ +{ + "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": "پوسٹس", + "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..2b05f7e9d5 --- /dev/null +++ b/public/language/ur/groups.json @@ -0,0 +1,66 @@ +{ + "all-groups": "تمام گروپس", + "groups": "گروپس", + "members": "اراکین", + "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..c3a7a21c7c --- /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 نے کہا:", + "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..b7ccdf3753 --- /dev/null +++ b/public/language/ur/notifications.json @@ -0,0 +1,105 @@ +{ + "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": "مجھے تفویض کردہ رپورٹس", + "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 نے آپ کی پوسٹ پر %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-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-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-notify": "قطار میں موجود پوسٹ کو ایک اطلاع موصول ہوئی ہے:
'%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..a76466fe61 --- /dev/null +++ b/public/language/ur/themes/harmony.json @@ -0,0 +1,23 @@ +{ + "theme-name": "ہارمنی تھیم", + "skins": "سکنز", + "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..bcc6b33aae --- /dev/null +++ b/public/language/ur/topic.json @@ -0,0 +1,228 @@ +{ + "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 پر", + "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.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-fork": "تقسیم کریں", + "bookmark": "بک مارک", + "bookmarks": "بک مارکس", + "bookmarks.has-no-bookmarks": "آپ نے ابھی تک کسی پوسٹ کے لیے بک مارکس محفوظ نہیں کیے۔", + "copy-permalink": "مستقل لنک کاپی کریں", + "go-to-original": "اصل پوسٹ دیکھیں", + "loading-more-posts": "مزید پوسٹس لوڈ ہو رہی ہیں", + "move-topic": "موضوع منتقل کریں", + "move-topics": "موضوعات منتقل کریں", + "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": "نیچے ان صارفین کو نامزد کریں جو اس پوسٹ کو ترمیم کر سکتے ہیں۔", + "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.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)" +} \ 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..98ab943724 --- /dev/null +++ b/public/language/ur/world.json @@ -0,0 +1,21 @@ +{ + "name": "جهان", + "popular": "مقبول موضوعات", + "recent": "تمام موضوعات", + "help": "مدد", + + "help.title": "یہ صفحہ کیا ہے؟", + "help.intro": "فیڈی ورس میں آپ کے اپنے گوشے میں خوش آمدید۔", + "help.fediverse": "„فیڈی ورس“ ایک ایسی نیٹ ورک ہے جس میں باہم مربوط ایپلیکیشنز اور ویب سائٹس ایک دوسرے سے بات چیت کرتی ہیں اور جن کے صارفین ایک دوسرے کو دیکھ سکتے ہیں۔ یہ فورم فیڈریٹڈ ہے اور اس سوشل نیٹ ورک (جسے „فیڈی ورس“ کہا جاتا ہے) کے ساتھ تعامل کر سکتا ہے۔ یہ صفحہ فیڈی ورس میں آپ کا اپنا گوشہ ہے۔ اس میں آپ صرف ان موضوعات کو دیکھیں گے جو ان صارفین نے بنائے یا شیئر کیے ہیں جنہیں آپ فالو کرتے ہیں۔", + "help.build": "شروع میں یہاں زیادہ موضوعات نہیں ہو سکتے۔ یہ معمول کی بات ہے۔ جب آپ دوسرے صارفین کو فالو کرنا شروع کریں گے تو آپ یہاں زیادہ مواد دیکھنا شروع کریں گے۔", + "help.federating": "اسی طرح، اگر اس فورم سے باہر کے صارفین آپ کو فالو کرنا شروع کر دیں، تو آپ کی پوسٹس ان کے ایپلیکیشنز اور ویب سائٹس پر ظاہر ہونا شروع ہو جائیں گی۔", + "help.next-generation": "یہ سوشل نیٹ ورک کی نئی نسل ہے۔ آج ہی سے حصہ ڈالنا شروع کریں!", + + "onboard.title": "فیڈی ورس کی طرف آپ کی کھڑکی…", + "onboard.what": "یہ آپ کی ذاتی نوعیت کی کیٹیگری ہے جو صرف اس فورم سے باہر کے مواد پر مشتمل ہے۔ یہاں وہ چیزیں ظاہر ہوتی ہیں جو آپ کے فالو کیے ہوئے لوگوں نے بنائیں یا شیئر کیں۔", + "onboard.why": "اس فورم سے باہر بہت کچھ ہو رہا ہے، اور ہر چیز آپ کے مفادات سے مطابقت نہیں رکھتی۔ اس لیے مخصوص لوگوں کو فالو کرنا یہ ظاہر کرنے کا بہترین طریقہ ہے کہ آپ ان سے مزید دیکھنا چاہتے ہیں۔", + "onboard.how": "اس دوران، آپ اس فورم کے قابل رسائی مواد کو دیکھنے کے لیے اوپر کے بٹن استعمال کر سکتے ہیں۔ اس طرح آپ نئے مواد کی دریافت شروع کر سکتے ہیں!", + + "show-categories": "زمرہ جات دکھائیں", + "hide-categories": "زمرہ جات چھپائیں" +} \ No newline at end of file From 673896390fa15289a83602c2daef5d9cc9452bbd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Aug 2025 10:00:39 -0400 Subject: [PATCH 225/828] fix: cache lookup error when doing loopback calls --- src/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/request.js b/src/request.js index 713c3dd6ac..3ffde3916e 100644 --- a/src/request.js +++ b/src/request.js @@ -48,7 +48,7 @@ async function init() { */ function lookup(hostname, options, callback) { let { ok, lookup } = checkCache.get(hostname); - lookup = [...lookup]; + lookup = lookup && [...lookup]; if (!ok) { throw new Error('lookup-failed'); } From cc6fd49c4d2ddc6970ea23011dece5ba91517ec0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Aug 2025 10:01:05 -0400 Subject: [PATCH 226/828] fix: protocol-relative URLs being accidentally munged, #13592 --- src/posts/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts/parse.js b/src/posts/parse.js index 8d0c902e46..f2017c2106 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -88,7 +88,7 @@ module.exports = function (Posts) { while (current !== null) { if (current[1]) { try { - parsed = url.parse(current[1]); + parsed = new URL(current[1]); if (!parsed.protocol) { if (current[1].startsWith('/')) { // Internal link From c67aa43f14fae778fd634a9b2544a3893bec5976 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:02:52 -0400 Subject: [PATCH 227/828] fix(deps): update dependency webpack to v5.101.1 (#13588) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7997f48900..05b2f27cf5 100644 --- a/install/package.json +++ b/install/package.json @@ -146,7 +146,7 @@ "tough-cookie": "5.1.2", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.101.0", + "webpack": "5.101.1", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From 06c38247402948b758c98697129d04899d2bc092 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Aug 2025 14:41:44 -0400 Subject: [PATCH 228/828] fix: regression caused by cc6fd49c4d2ddc6970ea23011dece5ba91517ec0 --- src/posts/parse.js | 13 +++---------- test/posts.js | 8 ++++---- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/posts/parse.js b/src/posts/parse.js index f2017c2106..46efafb0d6 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -88,16 +88,9 @@ module.exports = function (Posts) { while (current !== null) { if (current[1]) { try { - parsed = new URL(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/test/posts.js b/test/posts.js index 2fc22904fc..4fce2f2ff5 100644 --- a/test/posts.js +++ b/test/posts.js @@ -763,18 +763,18 @@ describe('Post\'s', () => { it('should turn relative links in post body to absolute urls', (done) => { const nconf = require('nconf'); - const content = 'test youtube'; + const content = 'test youtube'; const parsedContent = posts.relativeToAbsolute(content, posts.urlRegex); - assert.equal(parsedContent, `test youtube`); + assert.equal(parsedContent, `test youtube`); done(); }); it('should turn relative links in post body to absolute urls', (done) => { const nconf = require('nconf'); - const content = 'test youtube some test '; + const content = 'test youtube some test '; let parsedContent = posts.relativeToAbsolute(content, posts.urlRegex); parsedContent = posts.relativeToAbsolute(parsedContent, posts.imgRegex); - assert.equal(parsedContent, `test youtube some test `); + assert.equal(parsedContent, `test youtube some test `); done(); }); }); From 0481549734b50dc9e93ffe9609127cdb0d4d5aaf Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Aug 2025 15:26:32 -0400 Subject: [PATCH 229/828] test: use protocol of test runner --- test/posts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/posts.js b/test/posts.js index 4fce2f2ff5..2ba85734d4 100644 --- a/test/posts.js +++ b/test/posts.js @@ -765,7 +765,7 @@ describe('Post\'s', () => { const nconf = require('nconf'); const content = 'test youtube'; const parsedContent = posts.relativeToAbsolute(content, posts.urlRegex); - assert.equal(parsedContent, `test youtube`); + assert.equal(parsedContent, `test youtube`); done(); }); @@ -774,7 +774,7 @@ describe('Post\'s', () => { const content = 'test youtube some test '; let parsedContent = posts.relativeToAbsolute(content, posts.urlRegex); parsedContent = posts.relativeToAbsolute(parsedContent, posts.imgRegex); - assert.equal(parsedContent, `test youtube some test `); + assert.equal(parsedContent, `test youtube some test `); done(); }); }); From 0f72b8cd6f53de5160e267a5502c799024e5173d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:34:49 -0400 Subject: [PATCH 230/828] fix(deps): update dependency redis to v5.8.1 (#13594) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 05b2f27cf5..3bf9adb8a8 100644 --- a/install/package.json +++ b/install/package.json @@ -121,7 +121,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.8.0", + "redis": "5.8.1", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 9ef4cfa2e2374b47bb7e33e09750af0837751635 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:35:05 -0400 Subject: [PATCH 231/828] fix(deps): update dependency esbuild to v0.25.9 (#13593) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3bf9adb8a8..d31c7b1a0e 100644 --- a/install/package.json +++ b/install/package.json @@ -65,7 +65,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.8", + "esbuild": "0.25.9", "express": "4.21.2", "express-session": "1.18.2", "express-useragent": "1.0.15", From 311bbefa427db5f56e7f76a8bbe6215c912b758d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:35:28 -0400 Subject: [PATCH 232/828] chore(deps): update actions/checkout action to v5 (#13590) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- .github/workflows/test.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 742f443f9c..9156fdbba0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fb60386988..1fa339ed62 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -81,7 +81,7 @@ jobs: - 27017:27017 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: cp install/package.json package.json From 076cc9e868119597a7c532e06e8507d9203c961a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 13 Aug 2025 17:36:55 -0400 Subject: [PATCH 233/828] lint: remove unused url --- src/posts/parse.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/posts/parse.js b/src/posts/parse.js index 46efafb0d6..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'); From ecab347b2d042cf0c52c63434e3d5fe43e3f520d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Aug 2025 18:37:20 -0400 Subject: [PATCH 234/828] fix: add missing file to ur language folder --- public/language/ur/_DO_NOT_EDIT_FILES_HERE.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 public/language/ur/_DO_NOT_EDIT_FILES_HERE.md 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 From e079f8b291fe97b5df1d5fdb33f3f80e04ece1bd Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 14 Aug 2025 09:20:43 +0000 Subject: [PATCH 235/828] Latest translations and fallbacks --- public/language/cs/admin/menu.json | 4 ++-- public/language/cs/admin/settings/advanced.json | 2 +- public/language/cs/admin/settings/post.json | 4 ++-- public/language/cs/global.json | 4 ++-- public/language/cs/modules.json | 6 +++--- public/language/cs/notifications.json | 16 ++++++++-------- public/language/cs/pages.json | 4 ++-- public/language/cs/register.json | 2 +- public/language/cs/search.json | 4 ++-- public/language/cs/tags.json | 4 ++-- public/language/cs/themes/harmony.json | 2 +- public/language/cs/themes/persona.json | 2 +- public/language/cs/topic.json | 10 +++++----- public/language/cs/user.json | 12 ++++++------ public/language/cs/users.json | 2 +- public/language/cs/world.json | 2 +- public/language/it/social.json | 4 ++-- public/language/pl/social.json | 4 ++-- 18 files changed, 44 insertions(+), 44 deletions(-) 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/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/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/global.json b/public/language/cs/global.json index 8e2dd50230..e02fc73a56 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -37,7 +37,7 @@ "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é", @@ -147,7 +147,7 @@ "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" diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index 868ec52467..57219f8b88 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -80,7 +80,7 @@ "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.discard": "Jste si jisti, že chcete zrušit tento příspěvek?", @@ -100,12 +100,12 @@ "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", diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json index 86769f152b..ef870baa7b 100644 --- a/public/language/cs/notifications.json +++ b/public/language/cs/notifications.json @@ -12,15 +12,15 @@ "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ě", "bans": "Blokace", @@ -82,14 +82,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", @@ -97,7 +97,7 @@ "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", + "notificationType-new-reward": "Když získáte novou odměnu", "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.", 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 f275d147fa..f0ccb8e441 100644 --- a/public/language/cs/register.json +++ b/public/language/cs/register.json @@ -21,7 +21,7 @@ "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": "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.", 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/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..0a93e71d13 100644 --- a/public/language/cs/themes/harmony.json +++ b/public/language/cs/themes/harmony.json @@ -5,7 +5,7 @@ "expand": "Expand", "sidebar-toggle": "Sidebar Toggle", "login-register-to-search": "Login or register to search.", - "settings.title": "Theme settings", + "settings.title": "Nastavení motivu", "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.", 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..0d788c1eec 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -136,7 +136,7 @@ "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", + "copy-permalink": "Zkopírovat odkaz", "go-to-original": "View Original Post", "loading-more-posts": "Načítání více příspěvků", "move-topic": "Přesunout téma", @@ -165,7 +165,7 @@ "manage-editors-instruction": "Manage the users who can edit this post below.", "composer.title-placeholder": "Zadejte název tématu…", "composer.handle-placeholder": "Enter your name/handle here", - "composer.hide": "Hide", + "composer.hide": "Skrýt", "composer.discard": "Zrušit", "composer.submit": "Odeslat", "composer.additional-options": "Additional Options", @@ -188,11 +188,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", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index dc591695da..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,7 +100,7 @@ "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", @@ -134,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í", @@ -162,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 7fdb1569f2..9073a90af5 100644 --- a/public/language/cs/world.json +++ b/public/language/cs/world.json @@ -2,7 +2,7 @@ "name": "World", "popular": "Popular topics", "recent": "All topics", - "help": "Help", + "help": "Nápověda", "help.title": "What is this page?", "help.intro": "Welcome to your corner of the fediverse.", diff --git a/public/language/it/social.json b/public/language/it/social.json index 23f4777e8f..6868cad941 100644 --- a/public/language/it/social.json +++ b/public/language/it/social.json @@ -9,6 +9,6 @@ "continue-with-facebook": "Continua con Facebook", "sign-in-with-linkedin": "Accedi con LinkedIn", "sign-up-with-linkedin": "Registrati con LinkedIn", - "sign-in-with-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "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/pl/social.json b/public/language/pl/social.json index 5c621da4e3..07559bcee7 100644 --- a/public/language/pl/social.json +++ b/public/language/pl/social.json @@ -9,6 +9,6 @@ "continue-with-facebook": "Kontynuuj z Facebook", "sign-in-with-linkedin": "Zaloguj się przez LinkedIn", "sign-up-with-linkedin": "Zarejestruj się przez LinkedIn", - "sign-in-with-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "sign-in-with-wordpress": "Zaloguj z WordPress", + "sign-up-with-wordpress": "Zarejestruj z WordPress" } \ No newline at end of file From bfdf47b69eae3a6c36998c9e36c0e1eb07ee9baf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 18:49:27 -0400 Subject: [PATCH 236/828] chore(deps): update dependency @eslint/js to v9.33.0 (#13589) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d31c7b1a0e..2431ab7ed2 100644 --- a/install/package.json +++ b/install/package.json @@ -160,7 +160,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.32.0", + "@eslint/js": "9.33.0", "@stylistic/eslint-plugin": "^5.x", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 3a1ebae79627dbb214c04af293ed8246eb1915c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 14 Aug 2025 19:05:25 -0400 Subject: [PATCH 237/828] dont spam logs --- src/controllers/activitypub/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index b3bc85aab6..121c8536aa 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -144,7 +144,7 @@ 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.`); + activitypub.helpers.log(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`); return res.sendStatus(200); } From ceb65d138f67f904b77b8e51117225a70f3f5512 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:01:45 -0400 Subject: [PATCH 238/828] fix(deps): update dependency tough-cookie to v6 (#13600) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2431ab7ed2..dce84b193a 100644 --- a/install/package.json +++ b/install/package.json @@ -143,7 +143,7 @@ "timeago": "1.6.7", "tinycon": "0.6.8", "toobusy-js": "0.5.1", - "tough-cookie": "5.1.2", + "tough-cookie": "6.0.0", "undici": "^7.10.0", "validator": "13.15.15", "webpack": "5.101.1", From 62d15a0e05a7be87f123ae9643ebc90261d3cd78 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:01:56 -0400 Subject: [PATCH 239/828] chore(deps): update postgres docker tag to v17.6 (#13599) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker-compose-pgsql.yml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 7ae3698c87..e2f0777802 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.5-alpine + image: postgres:17.6-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb diff --git a/docker-compose.yml b/docker-compose.yml index f154ba11fc..96c0edd504 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: - redis postgres: - image: postgres:17.5-alpine + image: postgres:17.6-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb From f5b0444b1c03c716cfe4c81d89d122dd53af545a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:02:07 -0400 Subject: [PATCH 240/828] fix(deps): update dependency nodebb-widget-essentials to v7.0.40 (#13597) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index dce84b193a..1dbc8a4356 100644 --- a/install/package.json +++ b/install/package.json @@ -109,7 +109,7 @@ "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.46", "nodebb-theme-persona": "14.1.12", - "nodebb-widget-essentials": "7.0.39", + "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.5", "nprogress": "0.2.0", "passport": "0.7.0", From 90bddccbc5d5e884a98b83752c38c0480827b2b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:02:33 -0400 Subject: [PATCH 241/828] fix(deps): update dependency webpack to v5.101.2 (#13598) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1dbc8a4356..952cc81b3c 100644 --- a/install/package.json +++ b/install/package.json @@ -146,7 +146,7 @@ "tough-cookie": "6.0.0", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.101.1", + "webpack": "5.101.2", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From f4f7953ae3af04367e52010746782514500f8d51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 Aug 2025 18:43:50 -0400 Subject: [PATCH 242/828] chore(deps): update dependency lint-staged to v16.1.5 (#13585) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 952cc81b3c..f4a5cad632 100644 --- a/install/package.json +++ b/install/package.json @@ -168,7 +168,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "26.1.0", - "lint-staged": "16.1.4", + "lint-staged": "16.1.5", "mocha": "11.7.1", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 1515580940b343b8897e9497f6a15590262594e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 17 Aug 2025 11:17:47 -0400 Subject: [PATCH 243/828] test: add logs for test that's timing out --- test/activitypub/notes.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index 2b0ee2b87d..b018fb9788 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -467,20 +467,24 @@ describe('Notes', () => { }); it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { - this.timeout(60000); + this.timeout(30000); + const start = Date.now(); const { id: remoteCid } = helpers.mocks.group({ id: `https://example.com/${utils.generateUUID()}`, }); + console.log('1', Date.now() - start); const { note, id } = helpers.mocks.note({ audience: [remoteCid], }); + console.log('2', Date.now() - start); const { activity } = helpers.mocks.create(note); - + console.log('3', Date.now() - start); await activitypub.inbox.create({ body: activity }); - + console.log('4', Date.now() - start); assert(await posts.exists(id)); - + console.log('5', Date.now() - start); const cid = await posts.getCidByPid(id); + console.log('6', Date.now() - start); assert.strictEqual(cid, -1); }); }); From 75639c86bd53e6bdf3294e1928a6a8bd00ceaeef Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 10 Aug 2025 22:32:37 -0400 Subject: [PATCH 244/828] feat: re-jigger 'add category' button to allow addition of remote category to main index --- .../en-GB/admin/manage/categories.json | 4 +++ public/src/admin/manage/categories.js | 25 +++++++++++++++++++ src/views/admin/manage/categories.tpl | 11 +++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index f51152f22d..378421f30c 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -1,6 +1,8 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", @@ -103,6 +105,8 @@ "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.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/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 7342d0c1de..c25e44670c 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 () { @@ -151,6 +152,30 @@ 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(); + modal.modal('hide'); + return false; + } + + modal.find('form').on('submit', submit); + }); + }; + Categories.create = function (payload) { api.post('/categories', payload, function (err, data) { if (err) { diff --git a/src/views/admin/manage/categories.tpl b/src/views/admin/manage/categories.tpl index bed5850325..243936577e 100644 --- a/src/views/admin/manage/categories.tpl +++ b/src/views/admin/manage/categories.tpl @@ -8,7 +8,16 @@ - +
+ + +
From cb0b609289ad072d153481afb711c86d8a85b709 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 12 Aug 2025 15:38:49 -0400 Subject: [PATCH 245/828] refactor: category listing logic to allow remote categories to be added, disabled, and re-arranged in main forum index --- .../en-GB/admin/manage/categories.json | 2 + public/src/admin/manage/categories.js | 20 +++++-- src/api/activitypub.js | 10 ++++ src/categories/index.js | 8 +-- src/categories/update.js | 14 +++-- src/controllers/admin/categories.js | 57 +++++++++++++++++-- src/routes/admin.js | 2 + src/views/admin/partials/categories/add.tpl | 12 ++++ .../partials/categories/category-rows.tpl | 9 ++- 9 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 src/views/admin/partials/categories/add.tpl diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 378421f30c..d66dd814a1 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -3,11 +3,13 @@ "add-category": "Add category", "add-local-category": "Add Local category", "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index c25e44670c..c50edfc3df 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -69,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 { @@ -81,6 +81,8 @@ define('admin/manage/categories', [ }); }); + $('.categories').on('click', 'a[data-action="remove"]', Categories.removeCategory); + $('#toggle-collapse-all').on('click', function () { const $this = $(this); const isCollapsed = parseInt($this.attr('data-collapsed'), 10) === 1; @@ -167,8 +169,11 @@ define('admin/manage/categories', [ }); function submit() { - // const formData = modal.find('form').serializeObject(); - modal.modal('hide'); + const formData = modal.find('form').serializeObject(); + api.post('/api/admin/manage/categories', formData).then(() => { + ajaxify.refresh(); + modal.modal('hide'); + }).catch(alerts.error); return false; } @@ -176,6 +181,11 @@ define('admin/manage/categories', [ }); }; + Categories.removeCategory = function () { + const cid = this.getAttribute('data-cid'); + api.del(`/api/admin/manage/categories/${encodeURIComponent(cid)}`).then(ajaxify.refresh); + }; + Categories.create = function (payload) { api.post('/categories', payload, function (err, data) { if (err) { @@ -212,7 +222,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}"]`); @@ -264,7 +274,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/src/api/activitypub.js b/src/api/activitypub.js index 1f074f6776..7e4ae7da18 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -187,6 +187,11 @@ activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj }) activitypubApi.update = {}; activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { + // Local users only + if (!utils.isNumber(uid)) { + return; + } + const [object, targets] = await Promise.all([ activitypub.mocks.actors.user(uid), db.getSortedSetMembers(`followersRemote:${caller.uid}`), @@ -203,6 +208,11 @@ activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { }); activitypubApi.update.category = enabledCheck(async (caller, { cid }) => { + // Local categories only + if (!utils.isNumber(cid)) { + return; + } + const [object, targets] = await Promise.all([ activitypub.mocks.actors.category(cid), activitypub.notes.getCategoryFollowers(cid), diff --git a/src/categories/index.js b/src/categories/index.js index de7ff6d769..15cabac24e 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -101,7 +101,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(); }; @@ -274,7 +274,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) { @@ -289,12 +289,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/update.js b/src/categories/update.js index 2f2effd96d..bf32317ae2 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([ diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 7d9eb61a18..95d8376882 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) { @@ -67,9 +69,16 @@ categoriesController.getAll = async function (req, res) { 'order', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'subCategoriesPerPage', 'description', ]; - 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 })); + + // Append remote categories + 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 +185,41 @@ 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.removeRemote = async function (req, res) { + if (utils.isNumber(req.params.cid)) { + return helpers.formatApiResponse(400, res); + } + + await db.sortedSetRemove('cid:0:children', req.params.cid); + cache.del('cid:0:children'); + res.sendStatus(200); +}; diff --git a/src/routes/admin.js b/src/routes/admin.js index b7e751695c..967746b304 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -81,6 +81,8 @@ 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.delete(`/api/${name}/manage/categories/:cid`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.removeRemote)); const multer = require('multer'); const storage = multer.diskStorage({}); diff --git a/src/views/admin/partials/categories/add.tpl b/src/views/admin/partials/categories/add.tpl new file mode 100644 index 0000000000..05131c050f --- /dev/null +++ b/src/views/admin/partials/categories/add.tpl @@ -0,0 +1,12 @@ +
+
+ + +
+ +
+ + +

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

+
+ \ No newline at end of file diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl index 57b3676093..4cef2ae416 100644 --- a/src/views/admin/partials/categories/category-rows.tpl +++ b/src/views/admin/partials/categories/category-rows.tpl @@ -24,9 +24,11 @@ From bdcf28a3d9063e229d7b0aa39cf9c440832b7f1e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 17 Aug 2025 22:07:30 -0400 Subject: [PATCH 246/828] feat: ability to add/remove auto-categorization rules for incoming federated content --- .../en-GB/admin/settings/activitypub.json | 10 ++++++ src/activitypub/index.js | 1 + src/controllers/admin/settings.js | 10 +++--- src/controllers/write/admin.js | 21 ++++++++++++ src/routes/write/admin.js | 3 ++ src/views/admin/settings/activitypub.tpl | 34 +++++++++++++++++++ 6 files changed, 74 insertions(+), 5 deletions(-) diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/src/activitypub/index.js b/src/activitypub/index.js index 8b997e81d7..bc4b66bf53 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -64,6 +64,7 @@ ActivityPub.contexts = require('./contexts'); ActivityPub.actors = require('./actors'); ActivityPub.instances = require('./instances'); ActivityPub.feps = require('./feps'); +ActivityPub.rules = require('./rules'); ActivityPub.startJobs = () => { ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js index 69d74ddbae..762a2d896f 100644 --- a/src/controllers/admin/settings.js +++ b/src/controllers/admin/settings.js @@ -159,11 +159,15 @@ settingsController.api = async (req, res) => { }; settingsController.activitypub = async (req, res) => { - const instanceCount = await activitypub.instances.getCount(); + const [instanceCount, rules] = await Promise.all([ + activitypub.instances.getCount(), + activitypub.rules.list(), + ]); res.render('admin/settings/activitypub', { title: `[[admin/menu:settings/activitypub]]`, instanceCount, + rules, }); }; @@ -186,7 +190,3 @@ settingsController.advanced = async (req, res) => { groupsExemptFromMaintenanceMode: groupData, }); }; - - - - diff --git a/src/controllers/write/admin.js b/src/controllers/write/admin.js index c4c8e29c8c..132462faaf 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,22 @@ 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) { + 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()); +}; diff --git a/src/routes/write/admin.js b/src/routes/write/admin.js index 4a70e48022..d5b0fcad9c 100644 --- a/src/routes/write/admin.js +++ b/src/routes/write/admin.js @@ -25,5 +25,8 @@ 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); + return router; }; diff --git a/src/views/admin/settings/activitypub.tpl b/src/views/admin/settings/activitypub.tpl index 749fb57629..7a4f1e8430 100644 --- a/src/views/admin/settings/activitypub.tpl +++ b/src/views/admin/settings/activitypub.tpl @@ -44,6 +44,40 @@ +
+
[[admin/settings/activitypub:rules]]
+
+
+

[[admin/settings/activitypub:rules-intro]]

+
+ + + + + + + + {{{ each rules }}} + + + + + + + {{{ end }}} + + + + + + +
[[admin/settings/activitypub:rules.type]][[admin/settings/activitypub:rules.value]][[admin/settings/activitypub:rules.cid]]
{./type}{./value}{./cid}
+ +
+
+
+
+
[[admin/settings/activitypub:pruning]]
From 40bda8fca4ae6543aa024046bfc72331e037a2f7 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 18 Aug 2025 20:09:26 +0000 Subject: [PATCH 247/828] chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories, nodebb.admin-settings-activitypub --- public/language/ar/admin/manage/categories.json | 6 ++++++ public/language/ar/admin/settings/activitypub.json | 10 ++++++++++ public/language/az/admin/manage/categories.json | 6 ++++++ public/language/az/admin/settings/activitypub.json | 10 ++++++++++ public/language/bg/admin/manage/categories.json | 6 ++++++ public/language/bg/admin/settings/activitypub.json | 10 ++++++++++ public/language/bn/admin/manage/categories.json | 6 ++++++ public/language/bn/admin/settings/activitypub.json | 10 ++++++++++ public/language/cs/admin/manage/categories.json | 6 ++++++ public/language/cs/admin/settings/activitypub.json | 10 ++++++++++ public/language/da/admin/manage/categories.json | 6 ++++++ public/language/da/admin/settings/activitypub.json | 10 ++++++++++ public/language/de/admin/manage/categories.json | 6 ++++++ public/language/de/admin/settings/activitypub.json | 10 ++++++++++ public/language/el/admin/manage/categories.json | 6 ++++++ public/language/el/admin/settings/activitypub.json | 10 ++++++++++ public/language/en-US/admin/manage/categories.json | 6 ++++++ public/language/en-US/admin/settings/activitypub.json | 10 ++++++++++ .../language/en-x-pirate/admin/manage/categories.json | 6 ++++++ .../en-x-pirate/admin/settings/activitypub.json | 10 ++++++++++ public/language/es/admin/manage/categories.json | 6 ++++++ public/language/es/admin/settings/activitypub.json | 10 ++++++++++ public/language/et/admin/manage/categories.json | 6 ++++++ public/language/et/admin/settings/activitypub.json | 10 ++++++++++ public/language/fa-IR/admin/manage/categories.json | 6 ++++++ public/language/fa-IR/admin/settings/activitypub.json | 10 ++++++++++ public/language/fi/admin/manage/categories.json | 6 ++++++ public/language/fi/admin/settings/activitypub.json | 10 ++++++++++ public/language/fr/admin/manage/categories.json | 6 ++++++ public/language/fr/admin/settings/activitypub.json | 10 ++++++++++ public/language/gl/admin/manage/categories.json | 6 ++++++ public/language/gl/admin/settings/activitypub.json | 10 ++++++++++ public/language/he/admin/manage/categories.json | 6 ++++++ public/language/he/admin/settings/activitypub.json | 10 ++++++++++ public/language/hr/admin/manage/categories.json | 6 ++++++ public/language/hr/admin/settings/activitypub.json | 10 ++++++++++ public/language/hu/admin/manage/categories.json | 6 ++++++ public/language/hu/admin/settings/activitypub.json | 10 ++++++++++ public/language/hy/admin/manage/categories.json | 6 ++++++ public/language/hy/admin/settings/activitypub.json | 10 ++++++++++ public/language/id/admin/manage/categories.json | 6 ++++++ public/language/id/admin/settings/activitypub.json | 10 ++++++++++ public/language/it/admin/manage/categories.json | 6 ++++++ public/language/it/admin/settings/activitypub.json | 10 ++++++++++ public/language/ja/admin/manage/categories.json | 6 ++++++ public/language/ja/admin/settings/activitypub.json | 10 ++++++++++ public/language/ko/admin/manage/categories.json | 6 ++++++ public/language/ko/admin/settings/activitypub.json | 10 ++++++++++ public/language/lt/admin/manage/categories.json | 6 ++++++ public/language/lt/admin/settings/activitypub.json | 10 ++++++++++ public/language/lv/admin/manage/categories.json | 6 ++++++ public/language/lv/admin/settings/activitypub.json | 10 ++++++++++ public/language/ms/admin/manage/categories.json | 6 ++++++ public/language/ms/admin/settings/activitypub.json | 10 ++++++++++ public/language/nb/admin/manage/categories.json | 6 ++++++ public/language/nb/admin/settings/activitypub.json | 10 ++++++++++ public/language/nl/admin/manage/categories.json | 6 ++++++ public/language/nl/admin/settings/activitypub.json | 10 ++++++++++ public/language/nn-NO/admin/manage/categories.json | 6 ++++++ public/language/nn-NO/admin/settings/activitypub.json | 10 ++++++++++ public/language/pl/admin/manage/categories.json | 6 ++++++ public/language/pl/admin/settings/activitypub.json | 10 ++++++++++ public/language/pt-BR/admin/manage/categories.json | 6 ++++++ public/language/pt-BR/admin/settings/activitypub.json | 10 ++++++++++ public/language/pt-PT/admin/manage/categories.json | 6 ++++++ public/language/pt-PT/admin/settings/activitypub.json | 10 ++++++++++ public/language/ro/admin/manage/categories.json | 6 ++++++ public/language/ro/admin/settings/activitypub.json | 10 ++++++++++ public/language/ru/admin/manage/categories.json | 6 ++++++ public/language/ru/admin/settings/activitypub.json | 10 ++++++++++ public/language/rw/admin/manage/categories.json | 6 ++++++ public/language/rw/admin/settings/activitypub.json | 10 ++++++++++ public/language/sc/admin/manage/categories.json | 6 ++++++ public/language/sc/admin/settings/activitypub.json | 10 ++++++++++ public/language/sk/admin/manage/categories.json | 6 ++++++ public/language/sk/admin/settings/activitypub.json | 10 ++++++++++ public/language/sl/admin/manage/categories.json | 6 ++++++ public/language/sl/admin/settings/activitypub.json | 10 ++++++++++ public/language/sq-AL/admin/manage/categories.json | 6 ++++++ public/language/sq-AL/admin/settings/activitypub.json | 10 ++++++++++ public/language/sr/admin/manage/categories.json | 6 ++++++ public/language/sr/admin/settings/activitypub.json | 10 ++++++++++ public/language/sv/admin/manage/categories.json | 6 ++++++ public/language/sv/admin/settings/activitypub.json | 10 ++++++++++ public/language/th/admin/manage/categories.json | 6 ++++++ public/language/th/admin/settings/activitypub.json | 10 ++++++++++ public/language/tr/admin/manage/categories.json | 6 ++++++ public/language/tr/admin/settings/activitypub.json | 10 ++++++++++ public/language/uk/admin/manage/categories.json | 6 ++++++ public/language/uk/admin/settings/activitypub.json | 10 ++++++++++ public/language/ur/admin/manage/categories.json | 6 ++++++ public/language/ur/admin/settings/activitypub.json | 10 ++++++++++ public/language/vi/admin/manage/categories.json | 6 ++++++ public/language/vi/admin/settings/activitypub.json | 10 ++++++++++ public/language/zh-CN/admin/manage/categories.json | 6 ++++++ public/language/zh-CN/admin/settings/activitypub.json | 10 ++++++++++ public/language/zh-TW/admin/manage/categories.json | 6 ++++++ public/language/zh-TW/admin/settings/activitypub.json | 10 ++++++++++ 98 files changed, 784 insertions(+) diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json index 626e15e5bc..f2469f7414 100644 --- a/public/language/ar/admin/manage/categories.json +++ b/public/language/ar/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/ar/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/ar/admin/settings/activitypub.json +++ b/public/language/ar/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json index 460c4cd852..6540f6c4e9 100644 --- a/public/language/az/admin/manage/categories.json +++ b/public/language/az/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Kateqoriyaları idarə et", "add-category": "Kateqoriya əlavə et", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/az/admin/settings/activitypub.json index f7bb02e10b..51ca76de78 100644 --- a/public/language/az/admin/settings/activitypub.json +++ b/public/language/az/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index 31531b4c16..98a44cd73a 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Управление на категориите", "add-category": "Добавяне на категория", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "jump-to": "Прехвърляне към…", "settings": "Настройки на категорията", "edit-category": "Редактиране на категорията", "privileges": "Правомощия", "back-to-categories": "Назад към категориите", + "id": "Category ID", "name": "Име на категорията", "handle": "Идентификатор на категорията", "handle.help": "Идентификаторът на категорията се ползва за представяне на тази категория в други мрежи, подобно на потребителското име. Този идентификатор не трябва да съвпада със съществуващо потребителско име или потребителска група.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "Категорията е изтрита!", "alert.copy-success": "Настройките са копирани!", diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 59c76176bd..d127f769be 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "server-filtering": "Филтриране", "count": "Този NodeBB в момента знае за наличието на %1 сървър(а)", "server.filter-help": "Посочете сървърите, с които не искате Вашият NodeBB да осъществява връзка. Или можете вместо това да посочите конкретни сървъри, с които разрешавате връзката. И двете възможности са налични, но може да изберете само една от тях.", diff --git a/public/language/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/bn/admin/manage/categories.json +++ b/public/language/bn/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/bn/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/bn/admin/settings/activitypub.json +++ b/public/language/bn/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/cs/admin/manage/categories.json index 6095eb1293..8a8942a9f1 100644 --- a/public/language/cs/admin/manage/categories.json +++ b/public/language/cs/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/cs/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/cs/admin/settings/activitypub.json +++ b/public/language/cs/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/da/admin/manage/categories.json b/public/language/da/admin/manage/categories.json index bd3e7b8f22..54ce8cd595 100644 --- a/public/language/da/admin/manage/categories.json +++ b/public/language/da/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/da/admin/settings/activitypub.json index 360d4ebfcf..315433f6ff 100644 --- a/public/language/da/admin/settings/activitypub.json +++ b/public/language/da/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json index 1cc1282011..327f17ae07 100644 --- a/public/language/de/admin/manage/categories.json +++ b/public/language/de/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Kategorien verwalten", "add-category": "Kategorie hinzufügen", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/de/admin/settings/activitypub.json index 7475cf5d4b..d99d511964 100644 --- a/public/language/de/admin/settings/activitypub.json +++ b/public/language/de/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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.", diff --git a/public/language/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/el/admin/manage/categories.json +++ b/public/language/el/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/el/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/el/admin/settings/activitypub.json +++ b/public/language/el/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/en-US/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/en-US/admin/manage/categories.json +++ b/public/language/en-US/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/en-US/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/en-US/admin/settings/activitypub.json +++ b/public/language/en-US/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/en-x-pirate/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/en-x-pirate/admin/manage/categories.json +++ b/public/language/en-x-pirate/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/en-x-pirate/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/en-x-pirate/admin/settings/activitypub.json +++ b/public/language/en-x-pirate/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/es/admin/manage/categories.json index 23d64eb58a..74e14ce149 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/es/admin/settings/activitypub.json index 6e6585b295..18eccf1079 100644 --- a/public/language/es/admin/settings/activitypub.json +++ b/public/language/es/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/et/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/et/admin/manage/categories.json +++ b/public/language/et/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/et/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/et/admin/settings/activitypub.json +++ b/public/language/et/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/fa-IR/admin/manage/categories.json index 53f79fbd20..f80ae0e28e 100644 --- a/public/language/fa-IR/admin/manage/categories.json +++ b/public/language/fa-IR/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/fa-IR/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/fa-IR/admin/settings/activitypub.json +++ b/public/language/fa-IR/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/fi/admin/manage/categories.json index d458c9c5e9..0b50fb20ad 100644 --- a/public/language/fi/admin/manage/categories.json +++ b/public/language/fi/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Hallitse kategorioita", "add-category": "Lisää kategoria", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/fi/admin/settings/activitypub.json index c14826527c..92b3f6c69f 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/fr/admin/manage/categories.json index fd10c37800..35b588d572 100644 --- a/public/language/fr/admin/manage/categories.json +++ b/public/language/fr/admin/manage/categories.json @@ -1,11 +1,15 @@ { "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", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/fr/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/fr/admin/settings/activitypub.json +++ b/public/language/fr/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/gl/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/gl/admin/manage/categories.json +++ b/public/language/gl/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/gl/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/gl/admin/settings/activitypub.json +++ b/public/language/gl/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index c6837046d1..e250ec3867 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "ניהול קטגוריות", "add-category": "הוספת קטגוריה", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "jump-to": "קפיצה אל...", "settings": "הגדרות קטגוריות", "edit-category": "עריכת קטגוריה", "privileges": "הרשאות", "back-to-categories": "חזרה לרשימת הקטגוריות", + "id": "Category ID", "name": "שם קטגוריה", "handle": "מקשר קטגוריה", "handle.help": "המקשר לקטגוריה שלך משמשת כייצוג של קטגוריה זו ברשתות אחרות, בדומה לשם משתמש. נקודת אחיזה בקטגוריה אינה יכולה להתאים לשם משתמש או קבוצת משתמשים קיימים.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "הקטגוריה נמחקה!", "alert.copy-success": "ההגדרות הועתקו!", diff --git a/public/language/he/admin/settings/activitypub.json b/public/language/he/admin/settings/activitypub.json index 912aab7975..242a8b2fee 100644 --- a/public/language/he/admin/settings/activitypub.json +++ b/public/language/he/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "server-filtering": "סינון", "count": "NodeBB זה מודע כרגע ל-%1 שרתים", "server.filter-help": "ציין שרתים שברצונך למנוע מהתאחדות עם ה-NodeBB שלך. לחלופין, אתה יכול לבחור באופן סלקטיבי פדרציה מאושרים עם שרתים ספציפיים, במקום זאת. שתי האפשרויות נתמכות, אם כי הן סותרות זו את זו.", diff --git a/public/language/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json index 7375875737..f7169fb5d7 100644 --- a/public/language/hr/admin/manage/categories.json +++ b/public/language/hr/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/hr/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/hr/admin/settings/activitypub.json +++ b/public/language/hr/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/hu/admin/manage/categories.json index 78abec813a..865c015202 100644 --- a/public/language/hu/admin/manage/categories.json +++ b/public/language/hu/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/hu/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/hu/admin/settings/activitypub.json +++ b/public/language/hu/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/hy/admin/manage/categories.json index 7e07ab83be..16ea33717d 100644 --- a/public/language/hy/admin/manage/categories.json +++ b/public/language/hy/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Կառավարեք կատեգորիաները", "add-category": "Ավելացնել Կատեգորիա", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.confirm-purge": "Վստա՞հ եք, որ ուզում եք մաքրել այս «%1» կատեգորիան: Զգուշացում: Այս կատեգորիայի բոլոր թեմաներն ու գրառումները կջնջվեն: Կատեգորիայի մաքրումը կհեռացնի բոլոր թեմաներն ու գրառումները և կջնջի կատեգորիան տվյալների բազայից: Եթե ցանկանում եք ժամանակավորապես հեռացնել կատեգորիան, փոխարենը կցանկանաք «անջատել» կատեգորիան:", "alert.purge-success": "Կատեգորիան մաքրվել է:", "alert.copy-success": "Կարգավորումները պատճենվեցին:", diff --git a/public/language/hy/admin/settings/activitypub.json b/public/language/hy/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/hy/admin/settings/activitypub.json +++ b/public/language/hy/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/id/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/id/admin/manage/categories.json +++ b/public/language/id/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/id/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/id/admin/settings/activitypub.json +++ b/public/language/id/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index 6ff6e53395..09a091aae8 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Gestisci categorie", "add-category": "Aggiungi categoria", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "jump-to": "Vai a...", "settings": "Impostazioni Categoria", "edit-category": "Modifica categoria", "privileges": "Privilegi", "back-to-categories": "Torna alle categorie", + "id": "Category ID", "name": "Nome Categoria", "handle": "Pseudonimo categoria", "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.", @@ -103,6 +107,8 @@ "alert.create-success": "Categoria creata con successo!", "alert.none-active": "Hai una categoria non attiva.", "alert.create": "Crea una 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.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/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index 23c19f82e1..06dd5212aa 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.", + "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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.", diff --git a/public/language/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json index 0044cf2f6d..44252e8c1a 100644 --- a/public/language/ja/admin/manage/categories.json +++ b/public/language/ja/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "カテゴリが切り離されました!", "alert.copy-success": "設定をコピーしました。", diff --git a/public/language/ja/admin/settings/activitypub.json b/public/language/ja/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/ja/admin/settings/activitypub.json +++ b/public/language/ja/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/ko/admin/manage/categories.json index 2afb2d7a89..2625775110 100644 --- a/public/language/ko/admin/manage/categories.json +++ b/public/language/ko/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "카테고리 관리", "add-category": "카테고리 추가", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "카테고리를 정리했습니다!", "alert.copy-success": "설정을 복사했습니다!", diff --git a/public/language/ko/admin/settings/activitypub.json b/public/language/ko/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/ko/admin/settings/activitypub.json +++ b/public/language/ko/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/lt/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/lt/admin/manage/categories.json +++ b/public/language/lt/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/lt/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/lt/admin/settings/activitypub.json +++ b/public/language/lt/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/lv/admin/manage/categories.json index 1358f338ac..fba8b697ad 100644 --- a/public/language/lv/admin/manage/categories.json +++ b/public/language/lv/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/lv/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/lv/admin/settings/activitypub.json +++ b/public/language/lv/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/ms/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/ms/admin/manage/categories.json +++ b/public/language/ms/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/ms/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/ms/admin/settings/activitypub.json +++ b/public/language/ms/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json index 472daa0fd0..0cf4511dd6 100644 --- a/public/language/nb/admin/manage/categories.json +++ b/public/language/nb/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Administrer kategorier", "add-category": "Legg til kategori", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/nb/admin/settings/activitypub.json index 7ee414b599..c334bb8f82 100644 --- a/public/language/nb/admin/settings/activitypub.json +++ b/public/language/nb/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index 6988838842..31db61a5dd 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/nl/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/nl/admin/settings/activitypub.json +++ b/public/language/nl/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index b665e1fd28..900971a999 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Administrer kategoriar", "add-category": "Legg til kategori", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/nn-NO/admin/settings/activitypub.json index cd19292284..97288214be 100644 --- a/public/language/nn-NO/admin/settings/activitypub.json +++ b/public/language/nn-NO/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index e7979f084c..6d87f47995 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Zarządzaj kategoriami", "add-category": "Dodaj kategorię", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "jump-to": "Skocz do...", "settings": "Ustawienia kategorii", "edit-category": "Edytuj kategorię", "privileges": "Uprawnienia", "back-to-categories": "Wróć do kategorii", + "id": "Category ID", "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.", @@ -103,6 +107,8 @@ "alert.create-success": "Kategoria pomyślnie dodana!", "alert.none-active": "Nie masz aktywnych kategorii.", "alert.create": "Utwórz 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.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/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index 31aa6e7282..1221119e2a 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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": "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json index 28318bdae9..c07d5537a5 100644 --- a/public/language/pt-BR/admin/manage/categories.json +++ b/public/language/pt-BR/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/pt-BR/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/pt-BR/admin/settings/activitypub.json +++ b/public/language/pt-BR/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json index 352db735c2..75d054e475 100644 --- a/public/language/pt-PT/admin/manage/categories.json +++ b/public/language/pt-PT/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/pt-PT/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/pt-PT/admin/settings/activitypub.json +++ b/public/language/pt-PT/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/ro/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json index 456149cc90..36843c00a8 100644 --- a/public/language/ru/admin/manage/categories.json +++ b/public/language/ru/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "Категория очищена!", "alert.copy-success": "Настройки скопированы!", diff --git a/public/language/ru/admin/settings/activitypub.json b/public/language/ru/admin/settings/activitypub.json index 229272d32e..fbe52e32c0 100644 --- a/public/language/ru/admin/settings/activitypub.json +++ b/public/language/ru/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "server-filtering": "Фильтрация", "count": "В настоящее время NodeBB знает о %1 сервере(ах)", "server.filter-help": "Укажите серверы, для которых вы хотели бы запретить объединение с вашим NodeBB. В качестве альтернативы вы можете выборочно разрешить объединение с определенными серверами. Поддерживаются оба варианта, хотя они и являются взаимоисключающими.", diff --git a/public/language/rw/admin/manage/categories.json b/public/language/rw/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/rw/admin/manage/categories.json +++ b/public/language/rw/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/rw/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/rw/admin/settings/activitypub.json +++ b/public/language/rw/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/sc/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/sc/admin/manage/categories.json +++ b/public/language/sc/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/sc/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/sc/admin/settings/activitypub.json +++ b/public/language/sc/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/sk/admin/manage/categories.json index f6768fe299..5cd7b32ebe 100644 --- a/public/language/sk/admin/manage/categories.json +++ b/public/language/sk/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/sk/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/sk/admin/settings/activitypub.json +++ b/public/language/sk/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/sl/admin/manage/categories.json index acd1f9a506..129cce0663 100644 --- a/public/language/sl/admin/manage/categories.json +++ b/public/language/sl/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/sl/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/sl/admin/settings/activitypub.json +++ b/public/language/sl/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/sq-AL/admin/manage/categories.json +++ b/public/language/sq-AL/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/sq-AL/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/sq-AL/admin/settings/activitypub.json +++ b/public/language/sq-AL/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/sr/admin/manage/categories.json index f51152f22d..d66dd814a1 100644 --- a/public/language/sr/admin/manage/categories.json +++ b/public/language/sr/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/sr/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/sr/admin/settings/activitypub.json +++ b/public/language/sr/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/sv/admin/manage/categories.json index 3e10ad60d8..e43fb441af 100644 --- a/public/language/sv/admin/manage/categories.json +++ b/public/language/sv/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/sv/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/sv/admin/settings/activitypub.json +++ b/public/language/sv/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/th/admin/manage/categories.json index f64e658a6b..fd61be437c 100644 --- a/public/language/th/admin/manage/categories.json +++ b/public/language/th/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "จัดการหมวดหมู่", "add-category": "เพิ่มหมวดหมู่", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/th/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/th/admin/settings/activitypub.json +++ b/public/language/th/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/tr/admin/manage/categories.json index 1cad49d52c..af50cc0700 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.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/settings/activitypub.json b/public/language/tr/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/tr/admin/settings/activitypub.json +++ b/public/language/tr/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/manage/categories.json b/public/language/uk/admin/manage/categories.json index dfae6ebe3d..0d2dc60d0a 100644 --- a/public/language/uk/admin/manage/categories.json +++ b/public/language/uk/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "Категорію стерто!", "alert.copy-success": "Налаштування скопійовано!", diff --git a/public/language/uk/admin/settings/activitypub.json b/public/language/uk/admin/settings/activitypub.json index 94f9ad7822..9a11dbe972 100644 --- a/public/language/uk/admin/settings/activitypub.json +++ b/public/language/uk/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json index 03d4608fbd..d050877aa2 100644 --- a/public/language/ur/admin/manage/categories.json +++ b/public/language/ur/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "زمرہ جات کا انتظام", "add-category": "زمرہ شامل کریں", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "jump-to": "پر جائیں…", "settings": "زمرہ کی ترتیبات", "edit-category": "زمرہ ترمیم کریں", "privileges": "اختیارات", "back-to-categories": "زمرہ جات پر واپس", + "id": "Category ID", "name": "زمرہ کا نام", "handle": "زمرہ کا شناخت کنندہ", "handle.help": "زمرہ کا شناخت کنندہ اس زمرہ کو دیگر نیٹ ورکس میں پیش کرنے کے لیے استعمال ہوتا ہے، جیسے کہ صارف نام۔ اس شناخت کنندہ کا موجودہ صارف نام یا صارف گروپ سے مماثل نہیں ہونا چاہیے۔", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

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

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

", "alert.purge-success": "زمرہ حذف ہو گیا!", "alert.copy-success": "ترتیبات نقل ہو گئیں!", diff --git a/public/language/ur/admin/settings/activitypub.json b/public/language/ur/admin/settings/activitypub.json index 2cf9782052..47f5deb78f 100644 --- a/public/language/ur/admin/settings/activitypub.json +++ b/public/language/ur/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "server-filtering": "فلٹرنگ", "count": "یہ نوڈ بی بی فی الحال %1 سرور(ز) کے بارے میں جانتا ہے", "server.filter-help": "ان سرورز کی نشاندہی کریں جن کے ساتھ آپ نہیں چاہتے کہ آپ کا نوڈ بی بی رابطہ قائم کرے۔ یا آپ اس کے بجائے مخصوص سرورز کی نشاندہی کر سکتے ہیں جن کے ساتھ رابطہ کی اجازت ہے۔ دونوں اختیارات دستیاب ہیں، لیکن آپ صرف ایک کا انتخاب کر سکتے ہیں۔", diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index d89112626c..fac5970559 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Quản lý Danh mục", "add-category": "Thêm Danh Mục", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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", + "id": "Category ID", "name": "Tên Chuyên 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ó.", @@ -103,6 +107,8 @@ "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": "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.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/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index a2502f039a..c9e23e8b06 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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": "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index bc9da615d5..39ea767d4e 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "管理版块", "add-category": "添加版块", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "jump-to": "跳转…", "settings": "版块设置", "edit-category": "编辑版块", "privileges": "权限", "back-to-categories": "回到版块", + "id": "Category ID", "name": "版块名", "handle": "版块句柄", "handle.help": "您的版块句柄在其他网络中用作该版块的代表,类似于用户名。版块句柄不得与现有的用户名或用户组相匹配。", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

警告! 版块将被清除!

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

", "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 748ffbbbbe..984119a7dc 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "server-filtering": "过滤", "count": "该 NodeBB 目前可检测到 %1 台服务器", "server.filter-help": "指定您希望禁止与 NodeBB 联邦化的服务器。或者,您也可以选择性地 允许 与特定服务器联邦化。两者只能选其一。", diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json index 7a857a1d8c..4da07dc01c 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -1,11 +1,15 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", "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.", @@ -103,6 +107,8 @@ "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.confirm-purge": "

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

警告! 版面將被清除!

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

", "alert.purge-success": "版面已刪除!", "alert.copy-success": "設定已複製!", diff --git a/public/language/zh-TW/admin/settings/activitypub.json b/public/language/zh-TW/admin/settings/activitypub.json index 87f654e125..44aebd6ee2 100644 --- a/public/language/zh-TW/admin/settings/activitypub.json +++ b/public/language/zh-TW/admin/settings/activitypub.json @@ -18,6 +18,16 @@ "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.", + "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", + "rules.add": "Add New Rule", + "rules.type": "Type", + "rules.value": "Value", + "rules.cid": "Category", + "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.", From 10d84d0329e997775dbe1c423a39ce8cdb71555f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 19 Aug 2025 09:20:26 +0000 Subject: [PATCH 248/828] Latest translations and fallbacks --- .../language/bg/admin/manage/categories.json | 12 ++-- .../bg/admin/settings/activitypub.json | 18 +++--- .../de/admin/settings/activitypub.json | 50 ++++++++--------- public/language/de/error.json | 56 +++++++++---------- public/language/vi/social.json | 4 +- 5 files changed, 70 insertions(+), 70 deletions(-) diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index 98a44cd73a..51a23cb96b 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -1,15 +1,15 @@ { "manage-categories": "Управление на категориите", "add-category": "Добавяне на категория", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", + "add-local-category": "Добавяне на локална категория", + "add-remote-category": "Добавяне на отдалечена категория", + "remove": "Премахване", "jump-to": "Прехвърляне към…", "settings": "Настройки на категорията", "edit-category": "Редактиране на категорията", "privileges": "Правомощия", "back-to-categories": "Назад към категориите", - "id": "Category ID", + "id": "Идентификатор на категорията", "name": "Име на категорията", "handle": "Идентификатор на категорията", "handle.help": "Идентификаторът на категорията се ползва за представяне на тази категория в други мрежи, подобно на потребителското име. Този идентификатор не трябва да съвпада със съществуващо потребителско име или потребителска група.", @@ -107,8 +107,8 @@ "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.add": "Добавяне на категория", + "alert.add-help": "Отдалечена категория може да бъде добавена в списъка с категории, като посочите нейния идентификатор.

Забележка – отдалечената категория може да не отразява всички публикувани теми, освен ако поне един локален потребител не я следи/наблюдава.", "alert.confirm-purge": "

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

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

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

", "alert.purge-success": "Категорията е изтрита!", "alert.copy-success": "Настройките са копирани!", diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index d127f769be..756867ad16 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -18,15 +18,15 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", - "rules.add": "Add New Rule", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "rules": "Категоризиране", + "rules-intro": "Съдържанието открито чрез ActivityPub може да бъде категоризирано автоматично следвайки определени правила (например дума отбелязана с диез)", + "rules.modal.title": "Как работи това", + "rules.modal.instructions": "Цялото входящо съдържание се проверява спрямо правилата и ако има съвпадения – те се преместват в избраната категория.

Забележка Съдържанието, което вече е категоризирано (например в отдалечена категория) няма да преминава тези проверки.", + "rules.modal.values-multiple": "Ако искате да се проверяват няколко стойности, разделете ги със запетая (пример: едно,две,три)", + "rules.add": "Добавяне на ново правило", + "rules.type": "Тип", + "rules.value": "Стойност", + "rules.cid": "Категория", "server-filtering": "Филтриране", "count": "Този NodeBB в момента знае за наличието на %1 сървър(а)", diff --git a/public/language/de/admin/settings/activitypub.json b/public/language/de/admin/settings/activitypub.json index d99d511964..2a56054558 100644 --- a/public/language/de/admin/settings/activitypub.json +++ b/public/language/de/admin/settings/activitypub.json @@ -1,36 +1,36 @@ { "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": "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", - "rules.add": "Add New Rule", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "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.modal.values-multiple": "Um mehrere Werte abzugleichen, Einträge mit einem Komma trennen (z. B. eins,zwei,drei).", + "rules.add": "Neue Regel hinzufügen", + "rules.type": "Typ", + "rules.value": "Wert", + "rules.cid": "Kategorie", "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/error.json b/public/language/de/error.json index dfb5a8da31..b292fdf3f1 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -3,7 +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": "Network requests to reserved IP ranges are not allowed.", + "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.", @@ -68,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", @@ -155,9 +155,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-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-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", @@ -172,7 +172,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.", @@ -186,20 +186,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", @@ -235,14 +235,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", - "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", - "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", @@ -256,11 +256,11 @@ "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/vi/social.json b/public/language/vi/social.json index e0ea9432b0..ff5bafd340 100644 --- a/public/language/vi/social.json +++ b/public/language/vi/social.json @@ -9,6 +9,6 @@ "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-in-with-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "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 From 057e3b790bed4bd69b453a9ff7fcb1d805c8ff14 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 19 Aug 2025 19:54:57 -0400 Subject: [PATCH 249/828] fix: add missing files --- public/src/admin/settings/activitypub.js | 79 +++++++++++++++++++ src/activitypub/rules.js | 33 ++++++++ .../admin/partials/activitypub/rules.tpl | 26 ++++++ 3 files changed, 138 insertions(+) create mode 100644 public/src/admin/settings/activitypub.js create mode 100644 src/activitypub/rules.js create mode 100644 src/views/admin/partials/activitypub/rules.tpl diff --git a/public/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js new file mode 100644 index 0000000000..ad112b2398 --- /dev/null +++ b/public/src/admin/settings/activitypub.js @@ -0,0 +1,79 @@ +'use strict'; + +define('admin/settings/activitypub', [ + 'benchpress', + 'bootbox', + 'categorySelector', + 'api', +], function (Benchpress, bootbox, categorySelector, api) { + const Module = {}; + + Module.init = function () { + const rulesEl = document.getElementById('rules'); + if (rulesEl) { + rulesEl.addEventListener('click', (e) => { + const subselector = e.target.closest('[data-action]'); + if (subselector) { + const action = subselector.getAttribute('data-action'); + switch (action) { + case 'rules.add': { + Module.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; + } + }); + } + } + } + }); + } + }; + + Module.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; + } + }); + }; + const modal = bootbox.dialog({ + title: '[[admin/settings/activitypub:rules.add]]', + message: html, + buttons: { + save: { + label: '[[global:save]]', + className: 'btn-primary', + callback: submit, + }, + }, + }); + + // 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', + }); + }); + }; + + return Module; +}); diff --git a/src/activitypub/rules.js b/src/activitypub/rules.js new file mode 100644 index 0000000000..3a9a560552 --- /dev/null +++ b/src/activitypub/rules.js @@ -0,0 +1,33 @@ +'use strict'; + +const db = require('../database'); +const utils = require('../utils'); + +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(); + + 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}`), + ]); +}; \ No newline at end of file diff --git a/src/views/admin/partials/activitypub/rules.tpl b/src/views/admin/partials/activitypub/rules.tpl new file mode 100644 index 0000000000..e321a86744 --- /dev/null +++ b/src/views/admin/partials/activitypub/rules.tpl @@ -0,0 +1,26 @@ +

[[admin/settings/activitypub:rules.modal.title]]

+

[[admin/settings/activitypub:rules.modal.instructions]]

+ +
+ +
+
+ + +
+
+ + +

[[admin/settings/activitypub:rules.modal.values-multiple]]

+
+
+ +
+ +
+ +
+
\ No newline at end of file From 981d3c29f8decef737a007a36cfd81c08f98dfac Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 20 Aug 2025 09:21:29 +0000 Subject: [PATCH 250/828] Latest translations and fallbacks --- .../language/it/admin/manage/categories.json | 10 +++++----- .../it/admin/settings/activitypub.json | 14 +++++++------- .../language/pl/admin/manage/categories.json | 12 ++++++------ .../pl/admin/settings/activitypub.json | 18 +++++++++--------- .../zh-CN/admin/manage/categories.json | 12 ++++++------ .../zh-CN/admin/settings/activitypub.json | 18 +++++++++--------- public/language/zh-CN/post-queue.json | 2 +- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index 09a091aae8..307b2f822f 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -1,15 +1,15 @@ { "manage-categories": "Gestisci categorie", "add-category": "Aggiungi categoria", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", + "add-local-category": "Aggiungi categoria locale", + "add-remote-category": "Aggiungi categoria remota", + "remove": "Rimuovi", "jump-to": "Vai a...", "settings": "Impostazioni Categoria", "edit-category": "Modifica categoria", "privileges": "Privilegi", "back-to-categories": "Torna alle categorie", - "id": "Category ID", + "id": "ID Categoria", "name": "Nome Categoria", "handle": "Pseudonimo categoria", "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.", @@ -107,7 +107,7 @@ "alert.create-success": "Categoria creata con successo!", "alert.none-active": "Hai una categoria non attiva.", "alert.create": "Crea una Categoria", - "alert.add": "Add a Category", + "alert.add": "Aggiungi una categoria", "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.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!", diff --git a/public/language/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index 06dd5212aa..dff4b4b5d0 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -18,15 +18,15 @@ "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.", - "rules": "Categorization", + "rules": "Categorizzazione", "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.title": "Come funziona", "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", - "rules.add": "Add New Rule", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "rules.modal.values-multiple": "Per abbinare più valori, separa le voci con una virgola (es. uno,due,tre)", + "rules.add": "Aggiungi nuova regola", + "rules.type": "Tipo", + "rules.value": "Valore", + "rules.cid": "Categoria", "server-filtering": "Filtraggio", "count": "Questo NodeBB è attualmente a conoscenza di %1 server", diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index 6d87f47995..80835e8152 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -1,15 +1,15 @@ { "manage-categories": "Zarządzaj kategoriami", "add-category": "Dodaj kategorię", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", + "add-local-category": "Dodaj lokalną kategorię", + "add-remote-category": "Dodaj zdalną kategorię", + "remove": "Usuń", "jump-to": "Skocz do...", "settings": "Ustawienia kategorii", "edit-category": "Edytuj kategorię", "privileges": "Uprawnienia", "back-to-categories": "Wróć do kategorii", - "id": "Category ID", + "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.", @@ -107,8 +107,8 @@ "alert.create-success": "Kategoria pomyślnie dodana!", "alert.none-active": "Nie masz aktywnych kategorii.", "alert.create": "Utwórz 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.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.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/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index 1221119e2a..8d9f135667 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -18,15 +18,15 @@ "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": "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", - "rules.add": "Add New Rule", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "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.modal.values-multiple": "Aby dopasować wiele wartości oddziel je przecinkami (np. jeden,dwa,trzy)", + "rules.add": "Dodaj nową regułę", + "rules.type": "Typ", + "rules.value": "Wartość", + "rules.cid": "Kategoria", "server-filtering": "Filtrowanie", "count": "NodeBB obecnie wykrywa 1% serwerów", diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 39ea767d4e..52302c243e 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -1,15 +1,15 @@ { "manage-categories": "管理版块", "add-category": "添加版块", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", + "add-local-category": "添加本地版块", + "add-remote-category": "添加远程版块", + "remove": "移除", "jump-to": "跳转…", "settings": "版块设置", "edit-category": "编辑版块", "privileges": "权限", "back-to-categories": "回到版块", - "id": "Category ID", + "id": "版块 ID", "name": "版块名", "handle": "版块句柄", "handle.help": "您的版块句柄在其他网络中用作该版块的代表,类似于用户名。版块句柄不得与现有的用户名或用户组相匹配。", @@ -107,8 +107,8 @@ "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.add": "添加一个版块", + "alert.add-help": "可以通过指定其句柄将远程版块添加到版块列表中。

注: — 远程版块可能无法反映所有已发布的主题,除非至少有一名本地用户关注或订阅该版块。", "alert.confirm-purge": "

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

警告! 版块将被清除!

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

", "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 984119a7dc..42a1a34f94 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -18,15 +18,15 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", - "rules.add": "Add New Rule", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "rules": "分类", + "rules-intro": "通过 ActivityPub 发现的内容可根据特定规则(如标签)进行自动分类。", + "rules.modal.title": "它的工作原理", + "rules.modal.instructions": "所有传入的内容都会根据这些分类规则进行检查,符合规则的内容会自动移动到指定的版块目录中。

注: 已分类的内容(即位于远程版块中的内容)将不会受这些规则的影响。", + "rules.modal.values-multiple": "要匹配多个值,请用逗号分隔各条目 (如:一,二,三)", + "rules.add": "添加规则", + "rules.type": "类型", + "rules.value": "值", + "rules.cid": "版块", "server-filtering": "过滤", "count": "该 NodeBB 目前可检测到 %1 台服务器", 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的选定帖子吗?", From 44c0413c753f309a19f9c88c65102b0acadb6f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 20 Aug 2025 11:48:19 -0400 Subject: [PATCH 251/828] chore: use fontsource-utils/scss to get rid of deprecation warning closes #13520 --- install/package.json | 1 + public/scss/admin/fonts.scss | 28 ++++++++++++++++------------ src/meta/minifier.js | 1 + 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/install/package.json b/install/package.json index f4a5cad632..7719a9bccd 100644 --- a/install/package.json +++ b/install/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@adactive/bootstrap-tagsinput": "0.8.2", + "@fontsource-utils/scss": "0.2.1", "@fontsource/inter": "5.2.6", "@fontsource/poppins": "5.2.6", "@fortawesome/fontawesome-free": "6.7.2", 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/src/meta/minifier.js b/src/meta/minifier.js index 6a617cc45d..5583a8da5a 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -162,6 +162,7 @@ actions.buildCSS = async function buildCSS(data) { try { const opts = { loadPaths: data.paths, + importers: [new sass.NodePackageImporter()] }; if (data.minify) { opts.silenceDeprecations = [ From 996740bdf9a97fe6b852c8eaea991fa90cf8ef0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:49:29 -0400 Subject: [PATCH 252/828] fix(deps): update dependency webpack to v5.101.3 (#13602) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7719a9bccd..a151eb8a5c 100644 --- a/install/package.json +++ b/install/package.json @@ -147,7 +147,7 @@ "tough-cookie": "6.0.0", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.101.2", + "webpack": "5.101.3", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.3", From 138c6753749cf0fb321c2d259035c7963def980d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:14:16 -0400 Subject: [PATCH 253/828] fix(deps): update dependency redis to v5.8.2 (#13606) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a151eb8a5c..5cb8bdc885 100644 --- a/install/package.json +++ b/install/package.json @@ -122,7 +122,7 @@ "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.8.1", + "redis": "5.8.2", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 02228c04efd2918d5ead2f18aa124c9670011f38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:14:31 -0400 Subject: [PATCH 254/828] chore(deps): update redis docker tag to v8.2.1 (#13603) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1fa339ed62..fe9710ac5a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.2.0' + image: 'redis:8.2.1' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index e2f0777802..3e32023939 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.2.0-alpine + image: redis:8.2.1-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 8788ee6230..98bceb6721 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:8.2.0-alpine + image: redis:8.2.1-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 96c0edd504..ec096eb060 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:8.2.0-alpine + image: redis:8.2.1-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 From f6e1a2e55cd6f9d079c1de0ffc61c32c5842e9c8 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 21 Aug 2025 09:19:42 +0000 Subject: [PATCH 255/828] Latest translations and fallbacks --- public/language/it/admin/settings/activitypub.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index dff4b4b5d0..b5d040d985 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -19,7 +19,7 @@ "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.", "rules": "Categorizzazione", - "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "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": "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.modal.values-multiple": "Per abbinare più valori, separa le voci con una virgola (es. uno,due,tre)", From 8af76f3cae535b82828b37dbb048faa70732ac3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 10:33:35 -0400 Subject: [PATCH 256/828] fix(deps): update dependency nodebb-theme-peace to v2.2.47 (#13608) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5cb8bdc885..bff47afe3a 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-rewards-essentials": "1.0.2", "nodebb-theme-harmony": "2.1.16", "nodebb-theme-lavender": "7.1.19", - "nodebb-theme-peace": "2.2.46", + "nodebb-theme-peace": "2.2.47", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.5", From 2f4cf26c599bbfe184036b738224a122d220ee88 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 10:33:45 -0400 Subject: [PATCH 257/828] fix(deps): update dependency nodebb-theme-harmony to v2.1.17 (#13607) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index bff47afe3a..2b4cfd92c3 100644 --- a/install/package.json +++ b/install/package.json @@ -106,7 +106,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.16", + "nodebb-theme-harmony": "2.1.17", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.47", "nodebb-theme-persona": "14.1.12", From 8bef68001554a6c57ad277640556257779ca3e47 Mon Sep 17 00:00:00 2001 From: Marco Beyer Date: Thu, 21 Aug 2025 16:44:28 +0200 Subject: [PATCH 258/828] (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 --- src/controllers/category.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/controllers/category.js b/src/controllers/category.js index fe3809b825..52030a9465 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -222,12 +222,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, }); } From 181aa9c2edf0b1494cf2a6d85c839abbedd11e09 Mon Sep 17 00:00:00 2001 From: Marco Beyer Date: Thu, 21 Aug 2025 16:45:06 +0200 Subject: [PATCH 259/828] (fix) fixed typos in activitypub urls (#13610) --- src/controllers/category.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/category.js b/src/controllers/category.js index 52030a9465..d03a14c9e1 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -172,7 +172,7 @@ 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); @@ -259,7 +259,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}`, }); } } From 5dfd2413356a2cdaa95bdf94571e40cd7e45b395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 21 Aug 2025 10:49:13 -0400 Subject: [PATCH 260/828] lint: fix lint issue --- src/controllers/category.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/category.js b/src/controllers/category.js index d03a14c9e1..bf72a8cff0 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -222,7 +222,7 @@ function addTags(categoryData, res, currentPage) { ]; if (categoryData.backgroundImage) { - let {backgroundImage} = categoryData; + let { backgroundImage } = categoryData; backgroundImage = utils.decodeHTMLEntities(backgroundImage); if (!backgroundImage.startsWith('http')) { backgroundImage = url + backgroundImage.replace(new RegExp(`^${nconf.get('relative_path')}`), ''); From 9bdf24f08be55906f3251d64738ef873fbba8e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 21 Aug 2025 21:25:14 -0400 Subject: [PATCH 261/828] fix: catch exceptions in assertPayload, closes #13611 --- src/middleware/activitypub.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js index f1e78a1cd6..4504f83d44 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -4,6 +4,7 @@ const db = require('../database'); const meta = require('../meta'); const activitypub = require('../activitypub'); const analytics = require('../analytics'); +const helpers = require('./helpers'); const middleware = module.exports; @@ -59,7 +60,7 @@ 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...'); @@ -131,7 +132,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; From 6d60f9457a2b6fb614e76846bc18db8a1a10c143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 21 Aug 2025 21:29:53 -0400 Subject: [PATCH 262/828] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2b4cfd92c3..7883155862 100644 --- a/install/package.json +++ b/install/package.json @@ -106,7 +106,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.17", + "nodebb-theme-harmony": "2.1.18", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.47", "nodebb-theme-persona": "14.1.12", From fdd0152ee4da48f6d9cd65ae63a562b543f48385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 21 Aug 2025 21:32:10 -0400 Subject: [PATCH 263/828] chore: up peace --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7883155862..c07c7b644c 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-rewards-essentials": "1.0.2", "nodebb-theme-harmony": "2.1.18", "nodebb-theme-lavender": "7.1.19", - "nodebb-theme-peace": "2.2.47", + "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.5", From 929ae61646156845550ba57fb4883f424c7bb79c Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 22 Aug 2025 09:20:14 +0000 Subject: [PATCH 264/828] Latest translations and fallbacks --- public/language/nb/topic.json | 2 +- public/language/nn-NO/topic.json | 2 +- .../language/vi/admin/manage/categories.json | 14 +++++++------- .../vi/admin/settings/activitypub.json | 18 +++++++++--------- public/language/vi/post-queue.json | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 0a6634871c..81f986bc40 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -215,7 +215,7 @@ "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": "Anbefal innlegg", diff --git a/public/language/nn-NO/topic.json b/public/language/nn-NO/topic.json index d32abd1b7e..14f21172dd 100644 --- a/public/language/nn-NO/topic.json +++ b/public/language/nn-NO/topic.json @@ -215,7 +215,7 @@ "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": "Anbefal innlegg", diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index fac5970559..45f3d12d89 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -1,16 +1,16 @@ { "manage-categories": "Quản lý Danh mục", "add-category": "Thêm Danh Mục", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", + "add-local-category": "Thêm danh mục Cục Bộ", + "add-remote-category": "Thêm danh mục Từ Xa", + "remove": "Xóa", "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", - "id": "Category ID", - "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", @@ -107,8 +107,8 @@ "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": "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.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.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/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index c9e23e8b06..92210da96d 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -18,15 +18,15 @@ "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": "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", - "rules.add": "Add New Rule", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "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.modal.values-multiple": "Để phù hợp với nhiều giá trị, tách các mục riêng biệt với dấu phẩy (vd: một,hai,ba)", + "rules.add": "Thêm Quy Tắc Mới", + "rules.type": "Loại", + "rules.value": "Giá trị", + "rules.cid": "Danh mục", "server-filtering": "Lọc", "count": "NodeBB này hiện đã biết về %1 máy chủ", 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", From c16f9d649532ceb5187cd3c54b2e97258990edab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 22 Aug 2025 08:50:09 -0400 Subject: [PATCH 265/828] fix: mark-all read notifications button --- public/src/modules/notifications.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 712b99ce98..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); From 09898b94ecca86d961bbaf51761db0d8faf0ceca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 22 Aug 2025 11:06:47 -0400 Subject: [PATCH 266/828] 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 --- src/categories/index.js | 3 +++ src/database/mongo/hash.js | 8 ++++++-- src/database/postgres/hash.js | 2 +- src/database/redis/hash.js | 2 +- test/database/hash.js | 10 ++++++++++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/categories/index.js b/src/categories/index.js index 15cabac24e..047de142c8 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -85,6 +85,9 @@ Categories.getCategoryById = async function (data) { }; Categories.getCidByHandle = async function (handle) { + if (!handle) { + return null; + } let cid = await db.sortedSetScore('categoryhandle:cid', handle); if (!cid) { // remote cids 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/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/redis/hash.js b/src/database/redis/hash.js index c03bff055b..f046e62180 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -96,7 +96,7 @@ module.exports = function (module) { }; module.getObjectField = async function (key, field) { - if (!key) { + if (!key || !field) { return null; } const cachedData = {}; diff --git a/test/database/hash.js b/test/database/hash.js index 55b75c0df6..f1bace299e 100644 --- a/test/database/hash.js +++ b/test/database/hash.js @@ -285,6 +285,16 @@ describe('Hash methods', () => { }); }); + it('should return null if field is falsy', async () => { + const values = await Promise.all([ + db.getObjectField('hashTestObject', ''), + db.getObjectField('hashTestObject', null), + db.getObjectField('hashTestObject', false), + db.getObjectField('hashTestObject', undefined), + ]); + assert.deepStrictEqual(values, [null, null, null, null]); + }); + it('should return null and not error', async () => { const data = await db.getObjectField('hashTestObject', ['field1', 'field2']); assert.strictEqual(data, null); From ae7fa6958d35df44cb180ef32bad5957882ec59e Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 24 Aug 2025 09:19:45 +0000 Subject: [PATCH 267/828] Latest translations and fallbacks --- public/language/it/admin/manage/categories.json | 2 +- public/language/it/admin/settings/activitypub.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index 307b2f822f..b9ad1c8be3 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -108,7 +108,7 @@ "alert.none-active": "Hai una categoria non attiva.", "alert.create": "Crea una Categoria", "alert.add": "Aggiungi una categoria", - "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.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.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/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index b5d040d985..13a6269df3 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -21,7 +21,7 @@ "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": "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.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.modal.values-multiple": "Per abbinare più valori, separa le voci con una virgola (es. uno,due,tre)", "rules.add": "Aggiungi nuova regola", "rules.type": "Tipo", From 70d7e3292920fd07a5f9e02ea20d4d746e51bd72 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 10:11:09 -0400 Subject: [PATCH 268/828] fix: remove special-case logic that added a requested object to a topic if its defined context didn't actually contain it --- src/activitypub/contexts.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/activitypub/contexts.js b/src/activitypub/contexts.js index 0748e42ece..9d6c948a5e 100644 --- a/src/activitypub/contexts.js +++ b/src/activitypub/contexts.js @@ -113,19 +113,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; }; From 312df523933492d65e7262d76803e0fd373bbb7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 25 Aug 2025 11:18:04 -0400 Subject: [PATCH 269/828] fix: only process unique slugs --- src/user/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/user/index.js b/src/user/index.js index 5e4c08e39f..df0fa13616 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -128,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))); From 165af50dc8297cf133def10e1edc6ab9a551a0d3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 11:47:01 -0400 Subject: [PATCH 270/828] feat: apply auto-categorization logic --- src/activitypub/notes.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 64c6dcd53f..7e1a50e7da 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -144,6 +144,11 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { options.cid = remoteCid || recipientCids.shift(); } + // Auto-categorization (takes place only if all other categorization efforts fail) + if (!options.cid) { + options.cid = await assignCategory(mainPost); + } + // mainPid ok to leave as-is title = title || activitypub.helpers.generateTitle(utils.decodeHTMLEntities(content || sourceContent)); @@ -384,6 +389,29 @@ async function assertRelation(post) { return followers > 0 || uids.length; } +async function assignCategory(post) { + let cid = undefined; + const rules = await activitypub.rules.list(); + const tags = await Notes._normalizeTags(post._activitypub.tag || []); + + cid = rules.reduce((cid, { type, value, cid: target }) => { + if (!cid) { + switch (type) { + case 'hashtag': { + if (tags.includes(value)) { + return target; + } + break; + } + } + } + + return cid; + }, cid); + + return cid; +} + Notes.updateLocalRecipients = async (id, { to, cc }) => { const recipients = new Set([...(to || []), ...(cc || [])]); const uids = new Set(); From 3cdf28bd2cff610255e94075548068fe89e839db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 25 Aug 2025 11:48:34 -0400 Subject: [PATCH 271/828] test: latest sharp --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0308a6526a..0ec9b10ef3 100644 --- a/install/package.json +++ b/install/package.json @@ -131,7 +131,7 @@ "satori": "0.13.1", "semver": "7.7.2", "serve-favicon": "2.5.0", - "sharp": "0.32.6", + "sharp": "0.34.3", "sitemap": "8.0.0", "socket.io": "4.8.1", "socket.io-client": "4.8.1", From c0248ca52b93c0854c2ba0fdc22d34c933c3ed90 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 12:05:50 -0400 Subject: [PATCH 272/828] docs: openapi schema fixes for auto-categorization commits --- .../components/schemas/admin/rulesObject.yaml | 21 +++++++++++ .../openapi/read/admin/manage/categories.yaml | 2 ++ .../read/admin/settings/activitypub.yaml | 21 +++++++++++ .../write/admin/activitypub/rules.yaml | 36 +++++++++++++++++++ .../write/admin/activitypub/rules/rid.yaml | 25 +++++++++++++ src/controllers/admin/categories.js | 1 - 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 public/openapi/components/schemas/admin/rulesObject.yaml create mode 100644 public/openapi/write/admin/activitypub/rules.yaml create mode 100644 public/openapi/write/admin/activitypub/rules/rid.yaml diff --git a/public/openapi/components/schemas/admin/rulesObject.yaml b/public/openapi/components/schemas/admin/rulesObject.yaml new file mode 100644 index 0000000000..6b8db905ff --- /dev/null +++ b/public/openapi/components/schemas/admin/rulesObject.yaml @@ -0,0 +1,21 @@ +RulesObject: + type: array + items: + 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 \ No newline at end of file diff --git a/public/openapi/read/admin/manage/categories.yaml b/public/openapi/read/admin/manage/categories.yaml index b4e6102ac1..bbf8efb8fe 100644 --- a/public/openapi/read/admin/manage/categories.yaml +++ b/public/openapi/read/admin/manage/categories.yaml @@ -48,6 +48,8 @@ get: type: string order: type: number + isLocal: + type: boolean subCategoriesPerPage: type: number children: diff --git a/public/openapi/read/admin/settings/activitypub.yaml b/public/openapi/read/admin/settings/activitypub.yaml index b0871999b6..892c56030f 100644 --- a/public/openapi/read/admin/settings/activitypub.yaml +++ b/public/openapi/read/admin/settings/activitypub.yaml @@ -16,4 +16,25 @@ get: instanceCount: type: number description: The number of ActivityPub-enabled instances that this forum knows about. + rules: + type: array + items: + 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 - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/write/admin/activitypub/rules.yaml b/public/openapi/write/admin/activitypub/rules.yaml new file mode 100644 index 0000000000..46473530a8 --- /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/rulesObject.yaml#/RulesObject 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..72f71149da --- /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/rulesObject.yaml#/RulesObject diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 95d8376882..e6bb7aaa41 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -72,7 +72,6 @@ categoriesController.getAll = async function (req, res) { let categoriesData = await categories.getCategoriesFields(cids, fields); ({ categories: categoriesData } = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields })); - // Append remote categories categoriesData = categoriesData.map((category) => { category.isLocal = utils.isNumber(category.cid); return category; From 1ea10eff1c3b678c21a52a21bed183b8d65a8da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 25 Aug 2025 12:08:55 -0400 Subject: [PATCH 273/828] test: sharp invalid png --- test/uploads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/uploads.js b/test/uploads.js index 4fc0e41a64..1e5b3d19e3 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -205,7 +205,7 @@ describe('Upload Controllers', () => { const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/brokenimage.png'), {}, jar, csrf_token); assert.strictEqual(response.statusCode, 500); assert(body && body.status && body.status.message); - assert.strictEqual(body.status.message, 'Input file contains unsupported image format'); + assert.strictEqual(body.status.message, 'pngload_buffer: end of stream'); }); it('should fail if file is not an image', (done) => { From d4bf5f0c2f3b682d4e62d911b5607768c949c802 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 13:23:25 -0400 Subject: [PATCH 274/828] lint: fix comma dangle --- src/meta/minifier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/minifier.js b/src/meta/minifier.js index 5583a8da5a..0ab268dc4f 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -162,7 +162,7 @@ actions.buildCSS = async function buildCSS(data) { try { const opts = { loadPaths: data.paths, - importers: [new sass.NodePackageImporter()] + importers: [new sass.NodePackageImporter()], }; if (data.minify) { opts.silenceDeprecations = [ From e8401472c0f92eb54858228e94b56a2628a42cf0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 16:48:33 -0400 Subject: [PATCH 275/828] fix: add missing routes to write.yaml --- public/openapi/write.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 5f58bdfbf1..250aa12f20 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -270,6 +270,10 @@ 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' /files/: $ref: 'write/files.yaml' /files/folder: From a771b17fac34760e0c9d923281b707e942167f42 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 23:51:33 -0400 Subject: [PATCH 276/828] fix: relative paths in openapi schema --- public/openapi/write/admin/activitypub/rules.yaml | 4 ++-- public/openapi/write/admin/activitypub/rules/rid.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/openapi/write/admin/activitypub/rules.yaml b/public/openapi/write/admin/activitypub/rules.yaml index 46473530a8..b4ce4816ef 100644 --- a/public/openapi/write/admin/activitypub/rules.yaml +++ b/public/openapi/write/admin/activitypub/rules.yaml @@ -31,6 +31,6 @@ post: type: object properties: status: - $ref: ../../components/schemas/Status.yaml#/Status + $ref: ../../../components/schemas/Status.yaml#/Status response: - $ref: ../../components/schemas/admin/rulesObject.yaml#/RulesObject + $ref: ../../../components/schemas/admin/rulesObject.yaml#/RulesObject diff --git a/public/openapi/write/admin/activitypub/rules/rid.yaml b/public/openapi/write/admin/activitypub/rules/rid.yaml index 72f71149da..189f6863a4 100644 --- a/public/openapi/write/admin/activitypub/rules/rid.yaml +++ b/public/openapi/write/admin/activitypub/rules/rid.yaml @@ -20,6 +20,6 @@ delete: type: object properties: status: - $ref: ../../../components/schemas/Status.yaml#/Status + $ref: ../../../../components/schemas/Status.yaml#/Status response: - $ref: ../../components/schemas/admin/rulesObject.yaml#/RulesObject + $ref: ../../../../components/schemas/admin/rulesObject.yaml#/RulesObject From dfc558cdeb0ecb62cc1ffafaeb64dded51b97b2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:01:39 -0400 Subject: [PATCH 277/828] chore(deps): update dependency @eslint/js to v9.34.0 (#13612) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index eac3e9d50d..91efda9a1d 100644 --- a/install/package.json +++ b/install/package.json @@ -161,7 +161,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.33.0", + "@eslint/js": "9.34.0", "@stylistic/eslint-plugin": "^5.x", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 29a7402fc9b5593bd56172ed80d341f29610f731 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:21:33 -0400 Subject: [PATCH 278/828] fix(deps): update dependency bootstrap to v5.3.8 (#13618) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 91efda9a1d..d838972f5a 100644 --- a/install/package.json +++ b/install/package.json @@ -47,7 +47,7 @@ "benchpressjs": "2.5.5", "body-parser": "2.2.0", "bootbox": "6.0.4", - "bootstrap": "5.3.7", + "bootstrap": "5.3.8", "bootswatch": "5.3.7", "chalk": "4.1.2", "chart.js": "4.5.0", From e504ee348c7d54b8b450f870d16399b0cca24343 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:32:11 -0400 Subject: [PATCH 279/828] chore(deps): update dependency sass-embedded to v1.91.0 (#13614) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d838972f5a..f2d55ac700 100644 --- a/install/package.json +++ b/install/package.json @@ -177,7 +177,7 @@ "smtp-server": "3.14.0" }, "optionalDependencies": { - "sass-embedded": "1.90.0" + "sass-embedded": "1.91.0" }, "resolutions": { "*/jquery": "3.7.1" From 08ea56bd126b92ba32e218771908aa45fcc7ded2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:32:19 -0400 Subject: [PATCH 280/828] fix(deps): update dependency sass to v1.91.0 (#13615) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f2d55ac700..31d9be6e03 100644 --- a/install/package.json +++ b/install/package.json @@ -127,7 +127,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.90.0", + "sass": "1.91.0", "satori": "0.16.2", "semver": "7.7.2", "serve-favicon": "2.5.1", From f5ad786240e4f23e9a0d5bda13a287ec0fa07e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 Aug 2025 11:52:02 -0400 Subject: [PATCH 281/828] fix: jquery selector on post edit --- public/src/client/topic/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index 4a8a36a768..fa4710d0e3 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -162,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)); }); } From 69a6c1502fd62f283951800ec848bad0f10ea67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 Aug 2025 12:04:58 -0400 Subject: [PATCH 282/828] test: catch error in failing test --- test/activitypub/notes.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index b018fb9788..a00116fbb5 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -479,7 +479,13 @@ describe('Notes', () => { console.log('2', Date.now() - start); const { activity } = helpers.mocks.create(note); console.log('3', Date.now() - start); - await activitypub.inbox.create({ body: activity }); + try { + await activitypub.inbox.create({ body: activity }); + } catch (err) { + console.log('error in test', err.stack); + assert(false); + } + console.log('4', Date.now() - start); assert(await posts.exists(id)); console.log('5', Date.now() - start); From e79dfeb7c3cc0f86237808ff2c0b8c8937e978aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 Aug 2025 13:56:47 -0400 Subject: [PATCH 283/828] fix: rare crash if queued item is no longer in db but id is in post:queue --- src/posts/queue.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/posts/queue.js b/src/posts/queue.js index 8c1bbf90d0..a6aca50cca 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)) ); } From 567f453b79041c6bf8086c92052656bc8645b13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 Aug 2025 14:09:03 -0400 Subject: [PATCH 284/828] chore: enable dbsearch on new installs --- src/install.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/install.js b/src/install.js index f0903d3a16..064d5d77ff 100644 --- a/src/install.js +++ b/src/install.js @@ -526,6 +526,7 @@ async function enableDefaultPlugins() { let defaultEnabled = [ 'nodebb-plugin-composer-default', + 'nodebb-plugin-dbsearch', 'nodebb-plugin-markdown', 'nodebb-plugin-mentions', 'nodebb-plugin-web-push', From 5ee1fd02bb2dca745f03ec27e982223b498662b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 Aug 2025 19:23:39 -0400 Subject: [PATCH 285/828] refactor: add missing awaits fix error message, lock not using second param --- src/activitypub/notes.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 7e1a50e7da..da683c51a7 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -56,15 +56,16 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } const id = !activitypub.helpers.isUri(input) ? input.id : input; - const lockStatus = await lock(id, '[[error:activitypub.already-asserting]]'); + const lockStatus = await lock(id); 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); + await unlock(id); const { tid } = context; return { tid, count: 0 }; } else if (context.context) { @@ -85,7 +86,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // Can't resolve — give up. if (!chain.length) { - unlock(id); + await unlock(id); return null; } @@ -108,7 +109,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (tid && members.every(Boolean)) { // All cached, return early. activitypub.helpers.log('[notes/assert] No new notes to process.'); - unlock(id); + await unlock(id); return { tid, count: 0 }; } @@ -137,6 +138,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { }).shift(); } catch (e) { // noop + winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); } if (remoteCid || recipientCids.length) { @@ -169,6 +171,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { 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) { @@ -176,7 +179,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`); } - unlock(id); + await unlock(id); return null; } From 027d6f307c8ef7b5d6a6b6ef5c309b44e515d25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 00:06:32 -0400 Subject: [PATCH 286/828] fix: closes #13620 --- src/activitypub/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index bc4b66bf53..f413b9f644 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -16,6 +16,7 @@ const utils = require('../utils'); const ttl = require('../cache/ttl'); const batch = require('../batch'); const analytics = require('../analytics'); +const crypto = require('crypto'); const requestCache = ttl({ name: 'ap-request-cache', @@ -410,7 +411,7 @@ ActivityPub.send = async (type, id, targets, payload) => { await Promise.all(inboxBatch.map(async (uri) => { const ok = await sendMessage(uri, id, type, payload); if (!ok) { - const queueId = `${payload.type}:${payload.id}:${new URL(uri).hostname}`; + 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}`, { From bf279d71b02911b33d267810efc631c49bf3e2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 11:02:12 -0400 Subject: [PATCH 287/828] fix: closes #13501 add missing await --- test/activitypub/actors.js | 2 +- test/mocks/databasemock.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index 8271fdb335..8c3c02e905 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -828,7 +828,7 @@ describe('Pruning', () => { assert.strictEqual(result.counts.missing, 0); meta.config.activitypubUserPruneDays = 0; - user.deleteAccount(uid); + await user.deleteAccount(uid); }); it('should purge the user if they have no content (posts, likes, etc.)', async () => { diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 41b01b2c72..179288bbcf 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -14,7 +14,9 @@ const util = require('util'); process.env.NODE_ENV = process.env.TEST_ENV || 'production'; global.env = process.env.NODE_ENV || 'production'; - +if (!process.env.hasOwnProperty('CI')) { + process.env.CI = 'true'; +} const winston = require('winston'); const packageInfo = require('../../package.json'); From 79c6e72ce639e177779d3e4040a2cd6c34c27ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 11:29:43 -0400 Subject: [PATCH 288/828] test: more logs for failing test --- src/activitypub/inbox.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index cd13982b8c..b4c51f7588 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -34,25 +34,27 @@ function reject(type, object, target, senderType = 'uid', id = 0) { inbox.create = async (req) => { const { object, actor } = req.body; - + const start = Date.now(); // Alternative logic for non-public objects const isPublic = [...(object.to || []), ...(object.cc || [])].includes(activitypub._constants.publicAddress); if (!isPublic) { return await activitypub.notes.assertPrivate(object); } - + console.log(' 4a', Date.now() - start); // Category sync, remove when cross-posting available const { cids } = await activitypub.actors.getLocalFollowers(actor); let cid = null; if (cids.size > 0) { cid = Array.from(cids)[0]; } - + console.log(' 4b', Date.now() - start); const asserted = await activitypub.notes.assert(0, object, { cid }); + console.log(' 4c', Date.now() - start); if (asserted) { await activitypub.feps.announce(object.id, req.body); // api.activitypub.add(req, { pid: object.id }); } + console.log(' 4d', Date.now() - start); }; inbox.add = async (req) => { From 029da6c52e5e3d9a84dcecc7ff9dd898f9583cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 12:10:30 -0400 Subject: [PATCH 289/828] test: debug timeout --- src/activitypub/notes.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index da683c51a7..7ecc8a6891 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -54,16 +54,17 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (!input) { return null; } - + const start = Date.now(); const id = !activitypub.helpers.isUri(input) ? input.id : input; const lockStatus = await lock(id); if (!lockStatus) { // unable to achieve lock, stop processing. winston.warn('[activitypub/notes.assert] Unable to acquire lock, skipping processing of', id); return null; } - + console.log(' 4b1', Date.now() - start); let chain; let context = await activitypub.contexts.get(uid, id); + console.log(' 4b2', Date.now() - start); if (context.tid) { await unlock(id); const { tid } = context; @@ -77,13 +78,13 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } else { context = undefined; } - + console.log(' 4b3', Date.now() - start); if (!chain || !chain.length) { // Fall back to inReplyTo traversal on context retrieval failure chain = Array.from(await Notes.getParentChain(uid, input)); chain.reverse(); } - + console.log(' 4b4', Date.now() - start); // Can't resolve — give up. if (!chain.length) { await unlock(id); @@ -98,12 +99,12 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { const hasTid = !!tid; const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; - + console.log(' 4b5', Date.now() - start); if (options.cid && cid === -1) { // Move topic if currently uncategorized await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); } - + console.log(' 4b6', Date.now() - start); 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)) { @@ -112,14 +113,16 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { await unlock(id); return { tid, count: 0 }; } - + console.log(' 4b7', Date.now() - start); if (hasTid) { mainPid = await topics.getTopicField(tid, 'mainPid'); } else { // Check recipients/audience for category (local or remote) + console.log(' 4b8', Date.now() - start); const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); + console.log(' 4b9', Date.now() - start); await activitypub.actors.assert(Array.from(set)); - + console.log(' 4b10', Date.now() - start); // Local const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id))); const recipientCids = resolved @@ -270,7 +273,7 @@ Notes.assertPrivate = async (object) => { if (!object || !object.id || !activitypub.helpers.isUri(object.id)) { return null; } - + const localUids = []; const recipients = new Set([...(object.to || []), ...(object.cc || [])]); await Promise.all(Array.from(recipients).map(async (value) => { From 681ce8bf2f5e81e637494f550a2e8bff63273d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 12:23:10 -0400 Subject: [PATCH 290/828] test: add more logs --- src/activitypub/actors.js | 11 +++++++---- src/activitypub/notes.js | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 50ddafaac2..58cf6e8f6b 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -118,11 +118,13 @@ Actors.assert = async (ids, options = {}) => { activitypub.helpers.log(`[activitypub/actors] Asserting ${ids.length} actor(s)`); // NOTE: MAKE SURE EVERY DB ADDITION HAS A CORRESPONDING REMOVAL IN ACTORS.REMOVE! + const start = Date.now(); const urlMap = new Map(); const followersUrlMap = new Map(); const pubKeysMap = new Map(); const categories = new Set(); + console.log(' 4b9a', Date.now() - start); let actors = await Promise.all(ids.map(async (id) => { try { activitypub.helpers.log(`[activitypub/actors] Processing ${id}`); @@ -198,6 +200,7 @@ Actors.assert = async (ids, options = {}) => { return null; } })); + console.log(' 4b9b', Date.now() - start); actors = actors.filter(Boolean); // remove unresolvable actors if (!actors.length && !categories.size) { return []; @@ -206,7 +209,7 @@ Actors.assert = async (ids, options = {}) => { // Build userData object for storage const profiles = (await activitypub.mocks.profile(actors)).filter(Boolean); const now = Date.now(); - + console.log(' 4b9c', Date.now() - start); const bulkSet = profiles.reduce((memo, profile) => { const key = `userRemote:${profile.uid}`; memo.push([key, profile], [`${key}:keys`, pubKeysMap.get(profile.uid)]); @@ -245,7 +248,7 @@ Actors.assert = async (ids, options = {}) => { return memo; }, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} }); - + console.log(' 4b9d', Date.now() - start); // Removals await Promise.all([ db.sortedSetRemoveBulk(queries.searchRemove), @@ -259,7 +262,7 @@ Actors.assert = async (ids, options = {}) => { db.sortedSetAddBulk(queries.searchAdd), db.setObject('handle:uid', queries.handleAdd), ]); - + console.log(' 4b9e', Date.now() - start); // Handle any actors that should be asserted as a group instead if (categories.size) { const assertion = await Actors.assertGroup(Array.from(categories), options); @@ -271,7 +274,7 @@ Actors.assert = async (ids, options = {}) => { // otherwise, assertGroup returned true and output can be safely ignored. } - + console.log(' 4b9f', Date.now() - start); return actors; }; diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 7ecc8a6891..8472c6515c 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -120,7 +120,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // Check recipients/audience for category (local or remote) console.log(' 4b8', Date.now() - start); const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); - console.log(' 4b9', Date.now() - start); + console.log(' 4b9', Date.now() - start, Array.from(set)); await activitypub.actors.assert(Array.from(set)); console.log(' 4b10', Date.now() - start); // Local From f703a94b3145def82c7602a396c2bc6f2da6a6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 12:34:24 -0400 Subject: [PATCH 291/828] test: add more logs --- src/activitypub/actors.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 58cf6e8f6b..f9f00f5e5e 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -124,19 +124,22 @@ Actors.assert = async (ids, options = {}) => { const followersUrlMap = new Map(); const pubKeysMap = new Map(); const categories = new Set(); - console.log(' 4b9a', Date.now() - start); + console.log(' 4b9a', Date.now() - start, ids); let actors = await Promise.all(ids.map(async (id) => { try { + console.log(' 4b9a1', Date.now() - start, id); 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' }); - + console.log(' 4b9a2', Date.now() - start, id); // webfinger backreference check const { hostname: domain } = new URL(id); const { actorUri: canonicalId } = await activitypub.helpers.query(`${actor.preferredUsername}@${domain}`); + console.log(' 4b9a3', Date.now() - start, id); if (id !== canonicalId) { return null; } + let typeOk = false; if (Array.isArray(actor.type)) { typeOk = actor.type.some(type => activitypub._constants.acceptableActorTypes.has(type)); @@ -149,7 +152,7 @@ Actors.assert = async (ids, options = {}) => { categories.add(actor.id); } } - + console.log(' 4b9a4', Date.now() - start, id); if ( !typeOk || !activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop)) @@ -169,7 +172,7 @@ Actors.assert = async (ids, options = {}) => { // no action required activitypub.helpers.log(`[activitypub/actor.assert] Unable to retrieve follower counts for ${actor.id}`); } - + console.log(' 4b9a5', Date.now() - start, id); // Save url for backreference const url = Array.isArray(actor.url) ? actor.url.shift() : actor.url; if (url && url !== actor.id) { @@ -186,6 +189,7 @@ Actors.assert = async (ids, options = {}) => { return actor; } catch (e) { + console.log('any errors?', e.message); if (e.code === 'ap_get_410') { const exists = await user.exists(id); if (exists) { From 8e160fe05eff2acd5c8f4ee4d167b9dc5073290b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 12:49:42 -0400 Subject: [PATCH 292/828] test: more logs --- src/activitypub/actors.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index f9f00f5e5e..66e2a52f8e 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -191,12 +191,19 @@ Actors.assert = async (ids, options = {}) => { } catch (e) { console.log('any errors?', e.message); if (e.code === 'ap_get_410') { + console.log('ap_get_410 1', id); + const exists = await user.exists(id); + console.log('ap_get_410 2 exists', exists); if (exists) { try { + console.log('ap_get_410 3', id); await user.deleteAccount(id); + console.log('ap_get_410 4', id); } catch (e) { + console.log('ap_get_410 5', id, e.message); await activitypub.actors.remove(id); + console.log('ap_get_410 6', id); } } } From 8f7411c3aaeff614a17d56197efc191c21ee1738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 13:08:19 -0400 Subject: [PATCH 293/828] test: add timeout to ap.helpers.query --- src/activitypub/helpers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index ad45b67910..cb6772a408 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -113,8 +113,10 @@ Helpers.query = async (id) => { headers: { accept: 'application/jrd+json', }, + timeout: 5000, })); } catch (e) { + console.log('webfinger error', e.message); return false; } From 5dab17450fb3835363f703a0dea7dfd104209c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 13:27:36 -0400 Subject: [PATCH 294/828] Revert "test: more logs for failing test" This reverts commit 79c6e72ce639e177779d3e4040a2cd6c34c27ce8. --- src/activitypub/inbox.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index b4c51f7588..cd13982b8c 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -34,27 +34,25 @@ function reject(type, object, target, senderType = 'uid', id = 0) { inbox.create = async (req) => { const { object, actor } = req.body; - const start = Date.now(); + // Alternative logic for non-public objects const isPublic = [...(object.to || []), ...(object.cc || [])].includes(activitypub._constants.publicAddress); if (!isPublic) { return await activitypub.notes.assertPrivate(object); } - console.log(' 4a', Date.now() - start); + // Category sync, remove when cross-posting available const { cids } = await activitypub.actors.getLocalFollowers(actor); let cid = null; if (cids.size > 0) { cid = Array.from(cids)[0]; } - console.log(' 4b', Date.now() - start); + const asserted = await activitypub.notes.assert(0, object, { cid }); - console.log(' 4c', Date.now() - start); if (asserted) { await activitypub.feps.announce(object.id, req.body); // api.activitypub.add(req, { pid: object.id }); } - console.log(' 4d', Date.now() - start); }; inbox.add = async (req) => { From 4ad7b592815894bc493d4fe04e672fa212f57a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 13:28:26 -0400 Subject: [PATCH 295/828] Update notes.js --- src/activitypub/notes.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 8472c6515c..da683c51a7 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -54,17 +54,16 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (!input) { return null; } - const start = Date.now(); + const id = !activitypub.helpers.isUri(input) ? input.id : input; const lockStatus = await lock(id); if (!lockStatus) { // unable to achieve lock, stop processing. winston.warn('[activitypub/notes.assert] Unable to acquire lock, skipping processing of', id); return null; } - console.log(' 4b1', Date.now() - start); + let chain; let context = await activitypub.contexts.get(uid, id); - console.log(' 4b2', Date.now() - start); if (context.tid) { await unlock(id); const { tid } = context; @@ -78,13 +77,13 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } else { context = undefined; } - console.log(' 4b3', Date.now() - start); + if (!chain || !chain.length) { // Fall back to inReplyTo traversal on context retrieval failure chain = Array.from(await Notes.getParentChain(uid, input)); chain.reverse(); } - console.log(' 4b4', Date.now() - start); + // Can't resolve — give up. if (!chain.length) { await unlock(id); @@ -99,12 +98,12 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { const hasTid = !!tid; const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; - console.log(' 4b5', Date.now() - start); + if (options.cid && cid === -1) { // Move topic if currently uncategorized await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); } - console.log(' 4b6', Date.now() - start); + 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)) { @@ -113,16 +112,14 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { await unlock(id); return { tid, count: 0 }; } - console.log(' 4b7', Date.now() - start); + if (hasTid) { mainPid = await topics.getTopicField(tid, 'mainPid'); } else { // Check recipients/audience for category (local or remote) - console.log(' 4b8', Date.now() - start); const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); - console.log(' 4b9', Date.now() - start, Array.from(set)); await activitypub.actors.assert(Array.from(set)); - console.log(' 4b10', Date.now() - start); + // Local const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id))); const recipientCids = resolved @@ -273,7 +270,7 @@ Notes.assertPrivate = async (object) => { if (!object || !object.id || !activitypub.helpers.isUri(object.id)) { return null; } - + const localUids = []; const recipients = new Set([...(object.to || []), ...(object.cc || [])]); await Promise.all(Array.from(recipients).map(async (value) => { From be53dbcbb8321c110d2243652bcbe6f4252158df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 13:30:29 -0400 Subject: [PATCH 296/828] remove logs --- src/activitypub/actors.js | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 66e2a52f8e..753310d918 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -118,23 +118,18 @@ Actors.assert = async (ids, options = {}) => { activitypub.helpers.log(`[activitypub/actors] Asserting ${ids.length} actor(s)`); // NOTE: MAKE SURE EVERY DB ADDITION HAS A CORRESPONDING REMOVAL IN ACTORS.REMOVE! - const start = Date.now(); const urlMap = new Map(); const followersUrlMap = new Map(); const pubKeysMap = new Map(); const categories = new Set(); - console.log(' 4b9a', Date.now() - start, ids); let actors = await Promise.all(ids.map(async (id) => { try { - console.log(' 4b9a1', Date.now() - start, id); 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' }); - console.log(' 4b9a2', Date.now() - start, id); // webfinger backreference check const { hostname: domain } = new URL(id); const { actorUri: canonicalId } = await activitypub.helpers.query(`${actor.preferredUsername}@${domain}`); - console.log(' 4b9a3', Date.now() - start, id); if (id !== canonicalId) { return null; } @@ -152,7 +147,7 @@ Actors.assert = async (ids, options = {}) => { categories.add(actor.id); } } - console.log(' 4b9a4', Date.now() - start, id); + if ( !typeOk || !activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop)) @@ -172,7 +167,6 @@ Actors.assert = async (ids, options = {}) => { // no action required activitypub.helpers.log(`[activitypub/actor.assert] Unable to retrieve follower counts for ${actor.id}`); } - console.log(' 4b9a5', Date.now() - start, id); // Save url for backreference const url = Array.isArray(actor.url) ? actor.url.shift() : actor.url; if (url && url !== actor.id) { @@ -189,21 +183,13 @@ Actors.assert = async (ids, options = {}) => { return actor; } catch (e) { - console.log('any errors?', e.message); if (e.code === 'ap_get_410') { - console.log('ap_get_410 1', id); - const exists = await user.exists(id); - console.log('ap_get_410 2 exists', exists); if (exists) { try { - console.log('ap_get_410 3', id); await user.deleteAccount(id); - console.log('ap_get_410 4', id); } catch (e) { - console.log('ap_get_410 5', id, e.message); await activitypub.actors.remove(id); - console.log('ap_get_410 6', id); } } } @@ -211,7 +197,6 @@ Actors.assert = async (ids, options = {}) => { return null; } })); - console.log(' 4b9b', Date.now() - start); actors = actors.filter(Boolean); // remove unresolvable actors if (!actors.length && !categories.size) { return []; @@ -220,7 +205,7 @@ Actors.assert = async (ids, options = {}) => { // Build userData object for storage const profiles = (await activitypub.mocks.profile(actors)).filter(Boolean); const now = Date.now(); - console.log(' 4b9c', Date.now() - start); + const bulkSet = profiles.reduce((memo, profile) => { const key = `userRemote:${profile.uid}`; memo.push([key, profile], [`${key}:keys`, pubKeysMap.get(profile.uid)]); @@ -259,7 +244,7 @@ Actors.assert = async (ids, options = {}) => { return memo; }, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} }); - console.log(' 4b9d', Date.now() - start); + // Removals await Promise.all([ db.sortedSetRemoveBulk(queries.searchRemove), @@ -273,7 +258,7 @@ Actors.assert = async (ids, options = {}) => { db.sortedSetAddBulk(queries.searchAdd), db.setObject('handle:uid', queries.handleAdd), ]); - console.log(' 4b9e', Date.now() - start); + // Handle any actors that should be asserted as a group instead if (categories.size) { const assertion = await Actors.assertGroup(Array.from(categories), options); @@ -285,7 +270,7 @@ Actors.assert = async (ids, options = {}) => { // otherwise, assertGroup returned true and output can be safely ignored. } - console.log(' 4b9f', Date.now() - start); + return actors; }; From 6d856545ec4dd1e0cb44893697457a6b1390351d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:42:18 -0400 Subject: [PATCH 297/828] fix(deps): update dependency mongodb to v6.19.0 (#13619) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 31d9be6e03..905bb9d095 100644 --- a/install/package.json +++ b/install/package.json @@ -91,7 +91,7 @@ "lru-cache": "11.1.0", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.18.0", + "mongodb": "6.19.0", "morgan": "1.10.1", "mousetrap": "1.6.5", "multer": "2.0.2", From 1e0fb20db4fb2cdbfef958d8c4c568e4886bc4c9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2025 16:50:18 -0400 Subject: [PATCH 298/828] feat: adding and removing relays from AP settings page in ACP --- .../en-GB/admin/settings/activitypub.json | 10 ++ public/src/admin/settings/activitypub.js | 62 +++++++++++- src/activitypub/helpers.js | 2 +- src/activitypub/index.js | 1 + src/activitypub/relays.js | 95 +++++++++++++++++++ src/controllers/admin/settings.js | 4 +- src/controllers/write/admin.js | 16 +++- src/routes/write/admin.js | 2 + .../admin/partials/activitypub/relays.tpl | 11 +++ src/views/admin/settings/activitypub.tpl | 33 +++++++ 10 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 src/activitypub/relays.js create mode 100644 src/views/admin/partials/activitypub/relays.tpl diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 9a11dbe972..6b84d772be 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -28,6 +28,16 @@ "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-pending": "Pending", + "relays.state-active": "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/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js index ad112b2398..b405467f77 100644 --- a/public/src/admin/settings/activitypub.js +++ b/public/src/admin/settings/activitypub.js @@ -5,7 +5,8 @@ define('admin/settings/activitypub', [ 'bootbox', 'categorySelector', 'api', -], function (Benchpress, bootbox, categorySelector, api) { + 'alerts', +], function (Benchpress, bootbox, categorySelector, api, alerts) { const Module = {}; Module.init = function () { @@ -29,7 +30,34 @@ define('admin/settings/activitypub', [ if (tbodyEl) { tbodyEl.innerHTML = html; } - }); + }).catch(alerts.error); + } + } + } + }); + } + + 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': { + Module.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 Benchpress.render('admin/settings/activitypub', { relays: data }, 'relays'); + const tbodyEl = document.querySelector('#relays tbody'); + if (tbodyEl) { + tbodyEl.innerHTML = html; + } + }).catch(alerts.error); } } } @@ -49,7 +77,7 @@ define('admin/settings/activitypub', [ if (tbodyEl) { tbodyEl.innerHTML = html; } - }); + }).catch(alerts.error); }; const modal = bootbox.dialog({ title: '[[admin/settings/activitypub:rules.add]]', @@ -75,5 +103,33 @@ define('admin/settings/activitypub', [ }); }; + Module.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 Benchpress.render('admin/settings/activitypub', { relays: data }, 'relays'); + const tbodyEl = document.querySelector('#relays tbody'); + if (tbodyEl) { + tbodyEl.innerHTML = 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, + }, + }, + }); + }); + }; + return Module; }); diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index cb6772a408..a8c5532980 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -38,7 +38,7 @@ Helpers._test = (method, args) => { }, 2500); }; // process.nextTick(() => { -// Helpers._test(activitypub.notes.assert, [1, `https://`]); +// Helpers._test(activitypub.relays.add, ['https://relay.publicsquare.global/actor']); // }); let _lastLog; Helpers.log = (message) => { diff --git a/src/activitypub/index.js b/src/activitypub/index.js index f413b9f644..be1dbf8511 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -66,6 +66,7 @@ ActivityPub.actors = require('./actors'); ActivityPub.instances = require('./instances'); ActivityPub.feps = require('./feps'); ActivityPub.rules = require('./rules'); +ActivityPub.relays = require('./relays'); ActivityPub.startJobs = () => { ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js new file mode 100644 index 0000000000..b222f0b5b7 --- /dev/null +++ b/src/activitypub/relays.js @@ -0,0 +1,95 @@ +'use strict'; + +const nconf = require('nconf'); + +const db = require('../database'); + +const activitypub = module.parent.exports; +const Relays = module.exports; + +Relays.list = async () => { + let relays = await db.getSortedSetMembersWithScores('relays:state'); + relays = relays.reduce((memo, { value, score }) => { + let state = 'Pending'; + switch(score) { + case 1: { + state = 'Establishing'; + break; + } + + case 2: { + state = 'Active'; + break; + } + } + + memo.push({ + url: value, + state, + }); + + 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 (activity) => { + console.log(activity); +}; \ No newline at end of file diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js index 762a2d896f..184c6c0ed6 100644 --- a/src/controllers/admin/settings.js +++ b/src/controllers/admin/settings.js @@ -159,15 +159,17 @@ settingsController.api = async (req, res) => { }; settingsController.activitypub = async (req, res) => { - const [instanceCount, rules] = await Promise.all([ + 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, }); }; diff --git a/src/controllers/write/admin.js b/src/controllers/write/admin.js index 132462faaf..8fc7151dc0 100644 --- a/src/controllers/write/admin.js +++ b/src/controllers/write/admin.js @@ -91,7 +91,7 @@ Admin.activitypub.addRule = async (req, res) => { const { type, value, cid } = req.body; const exists = await categories.exists(cid); if (!value || !exists) { - helpers.formatApiResponse(400, res); + return helpers.formatApiResponse(400, res); } await activitypub.rules.add(type, value, cid); @@ -103,3 +103,17 @@ Admin.activitypub.deleteRule = async (req, res) => { await activitypub.rules.delete(rid); 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/routes/write/admin.js b/src/routes/write/admin.js index d5b0fcad9c..050e1ecf7a 100644 --- a/src/routes/write/admin.js +++ b/src/routes/write/admin.js @@ -27,6 +27,8 @@ module.exports = function () { 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, '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/views/admin/partials/activitypub/relays.tpl b/src/views/admin/partials/activitypub/relays.tpl new file mode 100644 index 0000000000..8f53b167f0 --- /dev/null +++ b/src/views/admin/partials/activitypub/relays.tpl @@ -0,0 +1,11 @@ +

[[admin/settings/activitypub:relays.warning]]

+

[[admin/settings/activitypub:relays.litepub]]

+ +
+ +
+
+ + +
+
\ No newline at end of file diff --git a/src/views/admin/settings/activitypub.tpl b/src/views/admin/settings/activitypub.tpl index 7a4f1e8430..5fa72463a7 100644 --- a/src/views/admin/settings/activitypub.tpl +++ b/src/views/admin/settings/activitypub.tpl @@ -78,6 +78,39 @@
+
+
[[admin/settings/activitypub:relays]]
+
+

[[admin/settings/activitypub:relays.intro]]

+

[[admin/settings/activitypub:relays.warning]]

+
+ + + + + + + + {{{ each relays }}} + + + + + + {{{ end }}} + + + + + + +
[[admin/settings/activitypub:relays.relay]][[admin/settings/activitypub:relays.state]]
{./url}{./state}
+ +
+
+
+
+
[[admin/settings/activitypub:pruning]]
From 4967492f4f65ae719de9c581d04928fd2a53c958 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2025 11:53:27 -0400 Subject: [PATCH 299/828] fix: handle webfinger responses with subject missing scheme --- src/activitypub/helpers.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index a8c5532980..cc4892acb6 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -131,9 +131,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) { From f4d1df7c66b1084da6599568a8071404e991fc58 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2025 12:30:22 -0400 Subject: [PATCH 300/828] feat: relay handshake logic, handle Follow/Accept, send back Accept. --- src/activitypub/helpers.js | 3 +++ src/activitypub/inbox.js | 9 +++++++-- src/activitypub/relays.js | 30 ++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index cc4892acb6..ce1d7e6e2d 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -198,6 +198,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 }; diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index cd13982b8c..faab5c0674 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -367,9 +367,12 @@ inbox.announce = async (req) => { 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]]'); } @@ -454,7 +457,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]]'); } diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js index b222f0b5b7..aa6000f154 100644 --- a/src/activitypub/relays.js +++ b/src/activitypub/relays.js @@ -90,6 +90,32 @@ Relays.remove = async (url) => { ]); }; -Relays.handshake = async (activity) => { - console.log(activity); +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 From b1dbb19c10c08f6a6e54e1d57004ac34e4c64515 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2025 13:53:51 -0400 Subject: [PATCH 301/828] fix: inbox.announce to not reject activities from relays --- src/activitypub/inbox.js | 5 ++++- src/activitypub/relays.js | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index faab5c0674..754720f208 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -288,6 +288,9 @@ inbox.announce = async (req) => { cid = actor; } + // Received via relay? + const fromRelay = await activitypub.relays.is(actor); + switch(true) { case object.type === 'Like': { const id = object.object.id || object.object; @@ -333,7 +336,7 @@ inbox.announce = async (req) => { socketHelpers.sendNotificationToPostOwner(pid, actor, 'announce', 'notifications:activitypub.announce'); } else { // Remote object // Follower check - if (!cid) { + if (!fromRelay && !cid) { const { followers } = await activitypub.actors.getLocalFollowCounts(actor); if (!followers) { winston.verbose(`[activitypub/inbox.announce] Rejecting ${object.id} via ${actor} due to no followers`); diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js index aa6000f154..1aff86c130 100644 --- a/src/activitypub/relays.js +++ b/src/activitypub/relays.js @@ -7,6 +7,10 @@ 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 }) => { From 28b63891d43f3f979570ac1a7fda1b551ae37506 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2025 14:11:51 -0400 Subject: [PATCH 302/828] fix: minor fixes for yukimochi/Activity-Relay compatibility --- public/src/admin/settings/activitypub.js | 8 ++++++++ src/activitypub/index.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/public/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js index b405467f77..6844f8aa56 100644 --- a/public/src/admin/settings/activitypub.js +++ b/public/src/admin/settings/activitypub.js @@ -91,6 +91,10 @@ define('admin/settings/activitypub', [ }, }); + modal.on('shown.bs.modal', function () { + modal.find('input').focus(); + }); + // category switcher categorySelector.init(modal.find('[component="category-selector"]'), { onSelect: function (selectedCategory) { @@ -128,6 +132,10 @@ define('admin/settings/activitypub', [ }, }, }); + + modal.on('shown.bs.modal', function () { + modal.find('input').focus(); + }); }); }; diff --git a/src/activitypub/index.js b/src/activitypub/index.js index be1dbf8511..eeb51c2887 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -47,7 +47,7 @@ ActivityPub._constants = Object.freeze({ ], acceptableActorTypes: new Set(['Application', 'Organization', 'Person', 'Service']), acceptableGroupTypes: new Set(['Group']), - requiredActorProps: ['inbox', 'outbox'], + requiredActorProps: ['inbox'], acceptedProtocols: ['https', ...(process.env.CI === 'true' ? ['http'] : [])], acceptable: { customFields: new Set(['PropertyValue', 'Link', 'Note']), From 6576468e2e98ca34b60b077f679d01aea7e2cb47 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 12:20:36 -0400 Subject: [PATCH 303/828] fix: internationalize relay states --- public/language/en-GB/admin/settings/activitypub.json | 5 +++-- src/activitypub/relays.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 6b84d772be..1b00672e0f 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -35,8 +35,9 @@ "relays.add": "Add New Relay", "relays.relay": "Relay", "relays.state": "State", - "relays.state-pending": "Pending", - "relays.state-active": "Active", + "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)", diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js index 1aff86c130..782d2d4b93 100644 --- a/src/activitypub/relays.js +++ b/src/activitypub/relays.js @@ -14,15 +14,15 @@ Relays.is = async (actor) => { Relays.list = async () => { let relays = await db.getSortedSetMembersWithScores('relays:state'); relays = relays.reduce((memo, { value, score }) => { - let state = 'Pending'; + let state = '[[admin/settings/activitypub:relays.state-0]]'; switch(score) { case 1: { - state = 'Establishing'; + state = '[[admin/settings/activitypub:relays.state-1]]'; break; } case 2: { - state = 'Active'; + state = '[[admin/settings/activitypub:relays.state-2]]'; break; } } From aa26dfb372a285cf391fb302fa1ae826dff2daef Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 12:33:27 -0400 Subject: [PATCH 304/828] feat: send local posts out to established relays --- src/activitypub/feps.js | 21 +++++++++++++++------ src/activitypub/relays.js | 9 +++++---- src/views/admin/settings/activitypub.tpl | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index 7b7816516d..712126c506 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -4,6 +4,7 @@ const nconf = require('nconf'); const posts = require('../posts'); const utils = require('../utils'); +const { default: PG } = require('pg'); const activitypub = module.parent.exports; const Feps = module.exports; @@ -18,21 +19,29 @@ Feps.announce = async function announce(id, activity) { return; } + let relays = await activitypub.relays.list(); + relays = relays.reduce((memo, { state, url }) => { + if (state === 2) { + memo.push(url); + } + return memo; + }, []); const followers = await activitypub.notes.getCategoryFollowers(cid); - if (!followers.length) { + const targets = relays.concat(followers); + if (!targets.length) { return; } const { actor } = activity; if (actor && !actor.startsWith(nconf.get('url'))) { - followers.unshift(actor); + targets.unshift(actor); } const now = Date.now(); 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, { + 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', cid, targets, { id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now}`, type: 'Announce', actor: `${nconf.get('url')}/category/${cid}`, @@ -43,8 +52,8 @@ Feps.announce = async function announce(id, activity) { } } - activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing ${activity.type} (${activity.id}) to followers of cid ${cid}`); - await activitypub.send('cid', cid, followers, { + 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', cid, targets, { id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now + 1}`, type: 'Announce', actor: `${nconf.get('url')}/category/${cid}`, diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js index 782d2d4b93..afaeaaa05f 100644 --- a/src/activitypub/relays.js +++ b/src/activitypub/relays.js @@ -14,22 +14,23 @@ Relays.is = async (actor) => { Relays.list = async () => { let relays = await db.getSortedSetMembersWithScores('relays:state'); relays = relays.reduce((memo, { value, score }) => { - let state = '[[admin/settings/activitypub:relays.state-0]]'; + let label = '[[admin/settings/activitypub:relays.state-0]]'; switch(score) { case 1: { - state = '[[admin/settings/activitypub:relays.state-1]]'; + label = '[[admin/settings/activitypub:relays.state-1]]'; break; } case 2: { - state = '[[admin/settings/activitypub:relays.state-2]]'; + label = '[[admin/settings/activitypub:relays.state-2]]'; break; } } memo.push({ url: value, - state, + state: score, + label, }); return memo; diff --git a/src/views/admin/settings/activitypub.tpl b/src/views/admin/settings/activitypub.tpl index 5fa72463a7..9a7e3493bf 100644 --- a/src/views/admin/settings/activitypub.tpl +++ b/src/views/admin/settings/activitypub.tpl @@ -94,7 +94,7 @@ {{{ each relays }}} {./url} - {./state} + {./label} {{{ end }}} From 40973ca7d1d25eb6d0daa0c0d4f0586ad01d665f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 13:52:45 -0400 Subject: [PATCH 305/828] fix: parseAndTranslate bug --- public/src/admin/settings/activitypub.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js index 6844f8aa56..8c79d95b4b 100644 --- a/public/src/admin/settings/activitypub.js +++ b/public/src/admin/settings/activitypub.js @@ -52,10 +52,10 @@ define('admin/settings/activitypub', [ case 'relays.remove': { const url = subselector.closest('tr').getAttribute('data-url'); api.del(`/admin/activitypub/relays/${encodeURIComponent(url)}`, {}).then(async (data) => { - const html = await Benchpress.render('admin/settings/activitypub', { relays: data }, 'relays'); + const html = await app.parseAndTranslate('admin/settings/activitypub', 'relays', { relays: data }); const tbodyEl = document.querySelector('#relays tbody'); if (tbodyEl) { - tbodyEl.innerHTML = html; + $(tbodyEl).html(html); } }).catch(alerts.error); } @@ -114,10 +114,10 @@ define('admin/settings/activitypub', [ const payload = Object.fromEntries(new FormData(formEl)); api.post('/admin/activitypub/relays', payload).then(async (data) => { - const html = await Benchpress.render('admin/settings/activitypub', { relays: data }, 'relays'); + const html = await app.parseAndTranslate('admin/settings/activitypub', 'relays', { relays: data }); const tbodyEl = document.querySelector('#relays tbody'); if (tbodyEl) { - tbodyEl.innerHTML = html; + $(tbodyEl).html(html); } }).catch(alerts.error); }; From a9a12a9f08b6a4773fb0980d6757916a1c8ad5ec Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 14:16:24 -0400 Subject: [PATCH 306/828] docs: update openapi schema for relays and rules --- .../components/schemas/admin/relays.yaml | 18 ++++++++++++ .../components/schemas/admin/rules.yaml | 23 +++++++++++++++ .../components/schemas/admin/rulesObject.yaml | 21 -------------- .../read/admin/settings/activitypub.yaml | 23 ++------------- .../write/admin/activitypub/relays.yaml | 28 +++++++++++++++++++ .../write/admin/activitypub/relays/url.yaml | 25 +++++++++++++++++ .../write/admin/activitypub/rules.yaml | 2 +- .../write/admin/activitypub/rules/rid.yaml | 2 +- 8 files changed, 99 insertions(+), 43 deletions(-) create mode 100644 public/openapi/components/schemas/admin/relays.yaml create mode 100644 public/openapi/components/schemas/admin/rules.yaml delete mode 100644 public/openapi/components/schemas/admin/rulesObject.yaml create mode 100644 public/openapi/write/admin/activitypub/relays.yaml create mode 100644 public/openapi/write/admin/activitypub/relays/url.yaml 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/components/schemas/admin/rulesObject.yaml b/public/openapi/components/schemas/admin/rulesObject.yaml deleted file mode 100644 index 6b8db905ff..0000000000 --- a/public/openapi/components/schemas/admin/rulesObject.yaml +++ /dev/null @@ -1,21 +0,0 @@ -RulesObject: - type: array - items: - 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 \ 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 892c56030f..5ce26a5820 100644 --- a/public/openapi/read/admin/settings/activitypub.yaml +++ b/public/openapi/read/admin/settings/activitypub.yaml @@ -17,24 +17,7 @@ get: type: number description: The number of ActivityPub-enabled instances that this forum knows about. rules: - type: array - items: - 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 + $ref: ../../../components/schemas/admin/rules.yaml#/RulesArray + relays: + $ref: ../../../components/schemas/admin/relays.yaml#/RelayArray - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/write/admin/activitypub/relays.yaml b/public/openapi/write/admin/activitypub/relays.yaml new file mode 100644 index 0000000000..6c8ac89f5a --- /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..c108e65680 --- /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 + example: https://example.org/actor + 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#/RelayArray diff --git a/public/openapi/write/admin/activitypub/rules.yaml b/public/openapi/write/admin/activitypub/rules.yaml index b4ce4816ef..8a74425101 100644 --- a/public/openapi/write/admin/activitypub/rules.yaml +++ b/public/openapi/write/admin/activitypub/rules.yaml @@ -33,4 +33,4 @@ post: status: $ref: ../../../components/schemas/Status.yaml#/Status response: - $ref: ../../../components/schemas/admin/rulesObject.yaml#/RulesObject + $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 index 189f6863a4..08243c16a2 100644 --- a/public/openapi/write/admin/activitypub/rules/rid.yaml +++ b/public/openapi/write/admin/activitypub/rules/rid.yaml @@ -22,4 +22,4 @@ delete: status: $ref: ../../../../components/schemas/Status.yaml#/Status response: - $ref: ../../../../components/schemas/admin/rulesObject.yaml#/RulesObject + $ref: ../../../../components/schemas/admin/rules.yaml#/RulesArray From cb00fb3bccf91210db8e868ad4689ba200684117 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 27 Aug 2025 18:17:35 +0000 Subject: [PATCH 307/828] chore(i18n): fallback strings for new resources: nodebb.admin-settings-activitypub --- public/language/ar/admin/settings/activitypub.json | 11 +++++++++++ public/language/az/admin/settings/activitypub.json | 11 +++++++++++ public/language/bg/admin/settings/activitypub.json | 11 +++++++++++ public/language/bn/admin/settings/activitypub.json | 11 +++++++++++ public/language/cs/admin/settings/activitypub.json | 11 +++++++++++ public/language/da/admin/settings/activitypub.json | 11 +++++++++++ public/language/de/admin/settings/activitypub.json | 11 +++++++++++ public/language/el/admin/settings/activitypub.json | 11 +++++++++++ public/language/en-US/admin/settings/activitypub.json | 11 +++++++++++ .../en-x-pirate/admin/settings/activitypub.json | 11 +++++++++++ public/language/es/admin/settings/activitypub.json | 11 +++++++++++ public/language/et/admin/settings/activitypub.json | 11 +++++++++++ public/language/fa-IR/admin/settings/activitypub.json | 11 +++++++++++ public/language/fi/admin/settings/activitypub.json | 11 +++++++++++ public/language/fr/admin/settings/activitypub.json | 11 +++++++++++ public/language/gl/admin/settings/activitypub.json | 11 +++++++++++ public/language/he/admin/settings/activitypub.json | 11 +++++++++++ public/language/hr/admin/settings/activitypub.json | 11 +++++++++++ public/language/hu/admin/settings/activitypub.json | 11 +++++++++++ public/language/hy/admin/settings/activitypub.json | 11 +++++++++++ public/language/id/admin/settings/activitypub.json | 11 +++++++++++ public/language/it/admin/settings/activitypub.json | 11 +++++++++++ public/language/ja/admin/settings/activitypub.json | 11 +++++++++++ public/language/ko/admin/settings/activitypub.json | 11 +++++++++++ public/language/lt/admin/settings/activitypub.json | 11 +++++++++++ public/language/lv/admin/settings/activitypub.json | 11 +++++++++++ public/language/ms/admin/settings/activitypub.json | 11 +++++++++++ public/language/nb/admin/settings/activitypub.json | 11 +++++++++++ public/language/nl/admin/settings/activitypub.json | 11 +++++++++++ public/language/nn-NO/admin/settings/activitypub.json | 11 +++++++++++ public/language/pl/admin/settings/activitypub.json | 11 +++++++++++ public/language/pt-BR/admin/settings/activitypub.json | 11 +++++++++++ public/language/pt-PT/admin/settings/activitypub.json | 11 +++++++++++ public/language/ro/admin/settings/activitypub.json | 11 +++++++++++ public/language/ru/admin/settings/activitypub.json | 11 +++++++++++ public/language/rw/admin/settings/activitypub.json | 11 +++++++++++ public/language/sc/admin/settings/activitypub.json | 11 +++++++++++ public/language/sk/admin/settings/activitypub.json | 11 +++++++++++ public/language/sl/admin/settings/activitypub.json | 11 +++++++++++ public/language/sq-AL/admin/settings/activitypub.json | 11 +++++++++++ public/language/sr/admin/settings/activitypub.json | 11 +++++++++++ public/language/sv/admin/settings/activitypub.json | 11 +++++++++++ public/language/th/admin/settings/activitypub.json | 11 +++++++++++ public/language/tr/admin/settings/activitypub.json | 11 +++++++++++ public/language/uk/admin/settings/activitypub.json | 11 +++++++++++ public/language/ur/admin/settings/activitypub.json | 11 +++++++++++ public/language/vi/admin/settings/activitypub.json | 11 +++++++++++ public/language/zh-CN/admin/settings/activitypub.json | 11 +++++++++++ public/language/zh-TW/admin/settings/activitypub.json | 11 +++++++++++ 49 files changed, 539 insertions(+) diff --git a/public/language/ar/admin/settings/activitypub.json b/public/language/ar/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/ar/admin/settings/activitypub.json +++ b/public/language/ar/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/az/admin/settings/activitypub.json b/public/language/az/admin/settings/activitypub.json index 51ca76de78..db67fe8e2b 100644 --- a/public/language/az/admin/settings/activitypub.json +++ b/public/language/az/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 756867ad16..54c6a2ce25 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "rules.value": "Стойност", "rules.cid": "Категория", + "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/bn/admin/settings/activitypub.json b/public/language/bn/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/bn/admin/settings/activitypub.json +++ b/public/language/bn/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/cs/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/cs/admin/settings/activitypub.json +++ b/public/language/cs/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/da/admin/settings/activitypub.json b/public/language/da/admin/settings/activitypub.json index 315433f6ff..c868684859 100644 --- a/public/language/da/admin/settings/activitypub.json +++ b/public/language/da/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/de/admin/settings/activitypub.json b/public/language/de/admin/settings/activitypub.json index 2a56054558..f08220e9df 100644 --- a/public/language/de/admin/settings/activitypub.json +++ b/public/language/de/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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": "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.", diff --git a/public/language/el/admin/settings/activitypub.json b/public/language/el/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/el/admin/settings/activitypub.json +++ b/public/language/el/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/en-US/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/en-US/admin/settings/activitypub.json +++ b/public/language/en-US/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/en-x-pirate/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/en-x-pirate/admin/settings/activitypub.json +++ b/public/language/en-x-pirate/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/es/admin/settings/activitypub.json index 18eccf1079..da972be67d 100644 --- a/public/language/es/admin/settings/activitypub.json +++ b/public/language/es/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/et/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/et/admin/settings/activitypub.json +++ b/public/language/et/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/fa-IR/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/fa-IR/admin/settings/activitypub.json +++ b/public/language/fa-IR/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/fi/admin/settings/activitypub.json index 92b3f6c69f..8fc3168448 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/fr/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/fr/admin/settings/activitypub.json +++ b/public/language/fr/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/gl/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/gl/admin/settings/activitypub.json +++ b/public/language/gl/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/he/admin/settings/activitypub.json b/public/language/he/admin/settings/activitypub.json index 242a8b2fee..7d7f2360cf 100644 --- a/public/language/he/admin/settings/activitypub.json +++ b/public/language/he/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/hr/admin/settings/activitypub.json b/public/language/hr/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/hr/admin/settings/activitypub.json +++ b/public/language/hr/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/hu/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/hu/admin/settings/activitypub.json +++ b/public/language/hu/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/hy/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/hy/admin/settings/activitypub.json +++ b/public/language/hy/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/id/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/id/admin/settings/activitypub.json +++ b/public/language/id/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index 13a6269df3..ed794f82b7 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "rules.value": "Valore", "rules.cid": "Categoria", + "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": "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.", diff --git a/public/language/ja/admin/settings/activitypub.json b/public/language/ja/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/ja/admin/settings/activitypub.json +++ b/public/language/ja/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/ko/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/ko/admin/settings/activitypub.json +++ b/public/language/ko/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/lt/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/lt/admin/settings/activitypub.json +++ b/public/language/lt/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/lv/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/lv/admin/settings/activitypub.json +++ b/public/language/lv/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/ms/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/ms/admin/settings/activitypub.json +++ b/public/language/ms/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/nb/admin/settings/activitypub.json b/public/language/nb/admin/settings/activitypub.json index c334bb8f82..2d280b94e5 100644 --- a/public/language/nb/admin/settings/activitypub.json +++ b/public/language/nb/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/nl/admin/settings/activitypub.json b/public/language/nl/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/nl/admin/settings/activitypub.json +++ b/public/language/nl/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/nn-NO/admin/settings/activitypub.json b/public/language/nn-NO/admin/settings/activitypub.json index 97288214be..f75280fc73 100644 --- a/public/language/nn-NO/admin/settings/activitypub.json +++ b/public/language/nn-NO/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/pl/admin/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index 8d9f135667..c2e7bc7853 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "rules.value": "Wartość", "rules.cid": "Kategoria", + "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": "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/pt-BR/admin/settings/activitypub.json b/public/language/pt-BR/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/pt-BR/admin/settings/activitypub.json +++ b/public/language/pt-BR/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/pt-PT/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/pt-PT/admin/settings/activitypub.json +++ b/public/language/pt-PT/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/ro/admin/settings/activitypub.json b/public/language/ro/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/ru/admin/settings/activitypub.json b/public/language/ru/admin/settings/activitypub.json index fbe52e32c0..55eb27a108 100644 --- a/public/language/ru/admin/settings/activitypub.json +++ b/public/language/ru/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/rw/admin/settings/activitypub.json b/public/language/rw/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/rw/admin/settings/activitypub.json +++ b/public/language/rw/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/sc/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/sc/admin/settings/activitypub.json +++ b/public/language/sc/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/sk/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/sk/admin/settings/activitypub.json +++ b/public/language/sk/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/sl/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/sl/admin/settings/activitypub.json +++ b/public/language/sl/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/sq-AL/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/sq-AL/admin/settings/activitypub.json +++ b/public/language/sq-AL/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/sr/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/sr/admin/settings/activitypub.json +++ b/public/language/sr/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/sv/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/sv/admin/settings/activitypub.json +++ b/public/language/sv/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/th/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/th/admin/settings/activitypub.json +++ b/public/language/th/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/tr/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/tr/admin/settings/activitypub.json +++ b/public/language/tr/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/activitypub.json b/public/language/uk/admin/settings/activitypub.json index 9a11dbe972..1b00672e0f 100644 --- a/public/language/uk/admin/settings/activitypub.json +++ b/public/language/uk/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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/ur/admin/settings/activitypub.json b/public/language/ur/admin/settings/activitypub.json index 47f5deb78f..537f7354eb 100644 --- a/public/language/ur/admin/settings/activitypub.json +++ b/public/language/ur/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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": "ان سرورز کی نشاندہی کریں جن کے ساتھ آپ نہیں چاہتے کہ آپ کا نوڈ بی بی رابطہ قائم کرے۔ یا آپ اس کے بجائے مخصوص سرورز کی نشاندہی کر سکتے ہیں جن کے ساتھ رابطہ کی اجازت ہے۔ دونوں اختیارات دستیاب ہیں، لیکن آپ صرف ایک کا انتخاب کر سکتے ہیں۔", diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index 92210da96d..c9e7308bb1 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "rules.value": "Giá trị", "rules.cid": "Danh mục", + "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": "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/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 42a1a34f94..169b4e8913 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "rules.value": "值", "rules.cid": "版块", + "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/zh-TW/admin/settings/activitypub.json b/public/language/zh-TW/admin/settings/activitypub.json index 44aebd6ee2..6fdf6c0602 100644 --- a/public/language/zh-TW/admin/settings/activitypub.json +++ b/public/language/zh-TW/admin/settings/activitypub.json @@ -28,6 +28,17 @@ "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.", From 560cc2ebf928825733be56af3defade2ba1c32bd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 14:21:41 -0400 Subject: [PATCH 308/828] docs: openapi typo --- public/openapi/read/admin/settings/activitypub.yaml | 2 +- public/openapi/write/admin/activitypub/relays/url.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/openapi/read/admin/settings/activitypub.yaml b/public/openapi/read/admin/settings/activitypub.yaml index 5ce26a5820..48a0415fef 100644 --- a/public/openapi/read/admin/settings/activitypub.yaml +++ b/public/openapi/read/admin/settings/activitypub.yaml @@ -19,5 +19,5 @@ get: rules: $ref: ../../../components/schemas/admin/rules.yaml#/RulesArray relays: - $ref: ../../../components/schemas/admin/relays.yaml#/RelayArray + $ref: ../../../components/schemas/admin/relays.yaml#/RelaysArray - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/openapi/write/admin/activitypub/relays/url.yaml b/public/openapi/write/admin/activitypub/relays/url.yaml index c108e65680..81e247274a 100644 --- a/public/openapi/write/admin/activitypub/relays/url.yaml +++ b/public/openapi/write/admin/activitypub/relays/url.yaml @@ -22,4 +22,4 @@ delete: status: $ref: ../../../../components/schemas/Status.yaml#/Status response: - $ref: ../../../../components/schemas/admin/relays.yaml#/RelayArray + $ref: ../../../../components/schemas/admin/relays.yaml#/RelaysArray From 771b8dcb2db7ad5340d1149fae2d3dcc739d3ae1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 15:08:51 -0400 Subject: [PATCH 309/828] fix: random hotkeys adding dependencies to my project smh --- src/activitypub/feps.js | 1 - src/activitypub/helpers.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index 712126c506..b8bf765bef 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -4,7 +4,6 @@ const nconf = require('nconf'); const posts = require('../posts'); const utils = require('../utils'); -const { default: PG } = require('pg'); const activitypub = module.parent.exports; const Feps = module.exports; diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index ce1d7e6e2d..ccef568be1 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -38,7 +38,7 @@ Helpers._test = (method, args) => { }, 2500); }; // process.nextTick(() => { -// Helpers._test(activitypub.relays.add, ['https://relay.publicsquare.global/actor']); +// Helpers._test(activitypub.notes.assert, [1, `https://`]); // }); let _lastLog; Helpers.log = (message) => { From 0f44034ec32ef74d04bae68afad681625f67e51a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2025 15:21:38 -0400 Subject: [PATCH 310/828] docs: add missing routes to openapi schema --- public/openapi/write.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 250aa12f20..7771469725 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -274,6 +274,10 @@ paths: $ref: 'write/admin/activitypub/rules.yaml' /admin/activitypub/rules/{rid}: $ref: 'write/admin/activitypub/rules/rid.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: From 8a326a6e7479e11e2bcc4bf462f55f008bd60719 Mon Sep 17 00:00:00 2001 From: ledlamp Date: Wed, 27 Aug 2025 15:42:30 -0700 Subject: [PATCH 311/828] 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 --- src/webserver.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/webserver.js b/src/webserver.js index c27a4e5d4a..13310bef92 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -277,9 +277,12 @@ async function listen() { } } port = parseInt(port, 10); - if ((port !== 80 && port !== 443) || nconf.get('trust_proxy') === true) { - winston.info('🤝 Enabling \'trust proxy\''); - app.enable('trust proxy'); + + let trust_proxy = nconf.get('trust_proxy'); + if (trust_proxy == null && ![80, 443].includes(port)) trust_proxy = true; + if (trust_proxy) { + winston.info(`🤝 Setting 'trust proxy' to ${JSON.stringify(trust_proxy)}`); + app.set('trust proxy', trust_proxy); } if ((port === 80 || port === 443) && process.env.NODE_ENV !== 'development') { From f83d2536ce905fb00dd82dd652776bea82c91926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 27 Aug 2025 18:46:37 -0400 Subject: [PATCH 312/828] refactor: braces --- src/webserver.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webserver.js b/src/webserver.js index 72b9d59e40..ab8dc5bc0d 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -280,7 +280,9 @@ async function listen() { port = parseInt(port, 10); let trust_proxy = nconf.get('trust_proxy'); - if (trust_proxy == null && ![80, 443].includes(port)) trust_proxy = true; + if (trust_proxy == null && ![80, 443].includes(port)) { + trust_proxy = true; + } if (trust_proxy) { winston.info(`🤝 Setting 'trust proxy' to ${JSON.stringify(trust_proxy)}`); app.set('trust proxy', trust_proxy); From 788301a56a7ab40821de59b207cb563118f61a95 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 28 Aug 2025 00:03:07 -0400 Subject: [PATCH 313/828] fix: missed a tab character --- public/openapi/write/admin/activitypub/relays.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/openapi/write/admin/activitypub/relays.yaml b/public/openapi/write/admin/activitypub/relays.yaml index 6c8ac89f5a..0f58c7580d 100644 --- a/public/openapi/write/admin/activitypub/relays.yaml +++ b/public/openapi/write/admin/activitypub/relays.yaml @@ -11,9 +11,9 @@ post: type: object properties: url: - type: string - description: The relay actor endpoint - example: https://example.org/actor + type: string + description: The relay actor endpoint + example: https://example.org/actor responses: '200': description: rule successfully created From c67983cc501727b02e442bcdab3bab55951437e0 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 28 Aug 2025 09:20:35 +0000 Subject: [PATCH 314/828] Latest translations and fallbacks --- .../bg/admin/settings/activitypub.json | 20 +++++++++---------- .../zh-CN/admin/settings/activitypub.json | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 54c6a2ce25..83b33df719 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -28,16 +28,16 @@ "rules.value": "Стойност", "rules.cid": "Категория", - "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", + "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 сървър(а)", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 169b4e8913..39de463e70 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -28,16 +28,16 @@ "rules.value": "值", "rules.cid": "版块", - "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", + "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 台服务器", From cbdc90a43283d7071fa2c8bfea610bd73e128edf Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 28 Aug 2025 09:55:13 -0400 Subject: [PATCH 315/828] fix: re-ordering dependencies because raisins --- test/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api.js b/test/api.js index fbb36b24b8..80d88f0194 100644 --- a/test/api.js +++ b/test/api.js @@ -11,8 +11,8 @@ const util = require('util'); const wait = util.promisify(setTimeout); -const request = require('../src/request'); const db = require('./mocks/databasemock'); +const request = require('../src/request'); const helpers = require('./helpers'); const meta = require('../src/meta'); const user = require('../src/user'); From 5f7085f34d01062cc3eed78fd98b22a3d1d9ef1c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 28 Aug 2025 11:52:22 -0400 Subject: [PATCH 316/828] fix: urlencoded param in openapi spec example --- public/openapi/write/admin/activitypub/relays/url.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/openapi/write/admin/activitypub/relays/url.yaml b/public/openapi/write/admin/activitypub/relays/url.yaml index 81e247274a..4f4b182ffb 100644 --- a/public/openapi/write/admin/activitypub/relays/url.yaml +++ b/public/openapi/write/admin/activitypub/relays/url.yaml @@ -9,8 +9,8 @@ delete: schema: type: string required: true - description: The relay actor endpoint - example: https://example.org/actor + description: The relay actor endpoint, URL encoded. + example: https%3A%2F%2Fexample.org%2Factor responses: '200': description: rule successfully deleted From b73ee309e0a39d16bdf3e13ca80486d4a2b7e55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 28 Aug 2025 12:39:44 -0400 Subject: [PATCH 317/828] refactor: remove invalid queued items catch invalid json in payload --- src/activitypub/index.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index eeb51c2887..df455ac89f 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -441,20 +441,34 @@ ActivityPub.send = async (type, id, targets, payload) => { 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}`))).filter(Boolean); + 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) => { - const { queueId, uri, id, type, attempts, payload } = data; - const payloadObj = JSON.parse(payload); + 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 sendMessage(uri, id, type, payloadObj); - - if (ok || attempts > 10) { + if (ok) { queueIdsToRemove.push(queueId); } else { const nextAttempt = (parseInt(attempts, 10) || 0) + 1; From a0be4a28da5c97f315b1a1b89d3140c21f7f5867 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 28 Aug 2025 12:45:46 -0400 Subject: [PATCH 318/828] fix: remove webfinger error log --- src/activitypub/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index ccef568be1..e6eb2e1c08 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -116,7 +116,6 @@ Helpers.query = async (id) => { timeout: 5000, })); } catch (e) { - console.log('webfinger error', e.message); return false; } From 826863223583653a2b58969d2b21ce58ce93eed4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 28 Aug 2025 14:12:04 -0400 Subject: [PATCH 319/828] feat: add sbd dependency to improve title generation (and for summary generation, later) --- install/package.json | 1 + src/activitypub/notes.js | 6 ++- test/activitypub.js | 98 ++++++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/install/package.json b/install/package.json index 905bb9d095..479ade0698 100644 --- a/install/package.json +++ b/install/package.json @@ -129,6 +129,7 @@ "sanitize-html": "2.17.0", "sass": "1.91.0", "satori": "0.16.2", + "sbd": "^1.0.19", "semver": "7.7.2", "serve-favicon": "2.5.1", "sharp": "0.34.3", diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index da683c51a7..a416766bc0 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -2,6 +2,7 @@ const winston = require('winston'); const nconf = require('nconf'); +const tokenizer = require('sbd'); const db = require('../database'); const batch = require('../batch'); @@ -152,7 +153,10 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } // mainPid ok to leave as-is - title = title || activitypub.helpers.generateTitle(utils.decodeHTMLEntities(content || sourceContent)); + if (!title) { + const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); + title = sentences.shift(); + } // Remove any custom emoji from title if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { diff --git a/test/activitypub.js b/test/activitypub.js index 4a619e1c8f..89de67fdcb 100644 --- a/test/activitypub.js +++ b/test/activitypub.js @@ -167,55 +167,55 @@ describe('ActivityPub integration', () => { }); }); - describe('.generateTitle', () => { - describe('test strings', () => { - const cases = new Map([ - // first paragraph element - ['

test title

abc', 'test title'], - - // other tags like h1 or span - ['

Lorem ipsum dolor sit amet

consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.

', 'Lorem ipsum dolor sit amet'], - ['Lorem ipsum dolor sit amet

consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.

', 'Lorem ipsum dolor sit amet'], - - // first line's text otherwise - ['Lorem ipsum dolor sit amet\n\nconsectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.', 'Lorem ipsum dolor sit amet'], - - // first sentence of matched line/element - ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'], - - // other sentence ending symbols - ['Lorem ipsum dolor sit amet, consectetur adipiscing elit? Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit?'], - - // Content after line breaks can be discarded - ['

Intro text
example.org/

more text

', 'Intro text'], - - // HTML without outer wrapping element - ['Lorem ipsum dolor sit amet', 'Lorem ipsum dolor sit amet'], - - // Two sentences with punctuation - ['Lorem ipsum. Dolor sit amet.', 'Lorem ipsum.'], - - // Additional tests? - // ['', ''], - ]); - - cases.forEach((value, key) => { - it('should convert as expected', () => { - const title = activitypub.helpers.generateTitle(key); - assert.strictEqual(title, value); - }); - }); - }); - - it('should trim down the title if it is too long per settings', () => { - const value = meta.config.maximumTitleLength; - meta.config.maximumTitleLength = 10; - const source = '@@@@@@@@@@@@@@@@@@@@'; - const title = activitypub.helpers.generateTitle(source); - assert.strictEqual(title, '@@@@@@@...'); - meta.config.maximumTitleLength = value; - }); - }); + // describe('.generateTitle', () => { + // describe('test strings', () => { + // const cases = new Map([ + // // first paragraph element + // ['

test title

abc', 'test title'], + + // // other tags like h1 or span + // ['

Lorem ipsum dolor sit amet

consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.

', 'Lorem ipsum dolor sit amet'], + // ['Lorem ipsum dolor sit amet

consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.

', 'Lorem ipsum dolor sit amet'], + + // // first line's text otherwise + // ['Lorem ipsum dolor sit amet\n\nconsectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.', 'Lorem ipsum dolor sit amet'], + + // // first sentence of matched line/element + // ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'], + + // // other sentence ending symbols + // ['Lorem ipsum dolor sit amet, consectetur adipiscing elit? Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit?'], + + // // Content after line breaks can be discarded + // ['

Intro text
example.org/

more text

', 'Intro text'], + + // // HTML without outer wrapping element + // ['Lorem ipsum dolor sit amet', 'Lorem ipsum dolor sit amet'], + + // // Two sentences with punctuation + // ['Lorem ipsum. Dolor sit amet.', 'Lorem ipsum.'], + + // // Additional tests? + // // ['', ''], + // ]); + + // cases.forEach((value, key) => { + // it('should convert as expected', () => { + // const title = activitypub.helpers.generateTitle(key); + // assert.strictEqual(title, value); + // }); + // }); + // }); + + // it('should trim down the title if it is too long per settings', () => { + // const value = meta.config.maximumTitleLength; + // meta.config.maximumTitleLength = 10; + // const source = '@@@@@@@@@@@@@@@@@@@@'; + // const title = activitypub.helpers.generateTitle(source); + // assert.strictEqual(title, '@@@@@@@...'); + // meta.config.maximumTitleLength = value; + // }); + // }); describe('.remoteAnchorToLocalProfile', () => { const uuid1 = utils.generateUUID(); From 35641f377ca20882d43123241bef839641c68df9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 28 Aug 2025 14:27:41 -0400 Subject: [PATCH 320/828] 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. --- src/activitypub/mocks.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 2f65471dee..b3c641e0c3 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'); @@ -756,7 +757,17 @@ Mocks.notes.public = async (post) => { attachment: normalizeAttachment(noteAttachment), }; - summary = post.content; + const sentences = tokenizer.sentences(post.content, { sanitize: true }); + // Append sentences to summary until it contains just under 500 characters of content + const limit = 500; + summary = sentences.reduce((memo, sentence) => { + const remaining = limit - memo.length; + if (sentence.length < remaining) { + memo += ` ${sentence}`; + } + + return memo; + }, ''); } let context = await posts.getPostField(post.pid, 'context'); From a0e78ff853c37b89862d29b5dddd1dd23e8b0176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 29 Aug 2025 12:50:06 -0400 Subject: [PATCH 321/828] fix: closes #13625, fix utils.params so it works with relative_paths --- public/src/client/login.js | 5 ++++- public/src/client/register.js | 5 ++++- public/src/utils.common.js | 6 ++++-- test/utils.js | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/public/src/client/login.js b/public/src/client/login.js index f7508b8ec2..e2495fb07f 100644 --- a/public/src/client/login.js +++ b/public/src/client/login.js @@ -56,7 +56,10 @@ define('forum/login', ['hooks', 'translator', 'jquery-form'], function (hooks, t success: function (data) { hooks.fire('action:app.loggedIn', data); const pathname = utils.urlToLocation(data.next).pathname; - const params = utils.params({ url: data.next }); + const params = utils.params({ + url: data.next, + relative_path: config.relative_path, + }); params.loggedin = true; delete params.register; // clear register message incase it exists const qs = $.param(params); diff --git a/public/src/client/register.js b/public/src/client/register.js index f989901e7b..ca86be35f0 100644 --- a/public/src/client/register.js +++ b/public/src/client/register.js @@ -83,7 +83,10 @@ define('forum/register', [ if (data.next) { const pathname = utils.urlToLocation(data.next).pathname; - const params = utils.params({ url: data.next }); + const params = utils.params({ + url: data.next, + relative_path: config.relative_path, + }); params.registered = true; const qs = $.param(params); diff --git a/public/src/utils.common.js b/public/src/utils.common.js index 0014b3ae85..47520b2425 100644 --- a/public/src/utils.common.js +++ b/public/src/utils.common.js @@ -571,9 +571,11 @@ const utils = { 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'), ''); + const cleanurl = options.url.replace(new RegExp(`/^${(options.relative_path || '')}/`, 'g'), ''); url = new URL(document.location); - url.pathname = options.url; + const queryIndex = cleanurl.indexOf('?'); + url.search = queryIndex !== -1 ? cleanurl.slice(queryIndex) : ''; + url.pathname = cleanurl; } else { url = new URL(options.url || document.location); } diff --git a/test/utils.js b/test/utils.js index 3e6338934c..e9ccbd4108 100644 --- a/test/utils.js +++ b/test/utils.js @@ -320,6 +320,39 @@ describe('Utility Methods', () => { done(); }); + it('should get url params for relative url', (done) => { + const params = utils.params({ + url: '/page?foo=1&bar=test&herp=2', + relative_path: '', + }); + assert.strictEqual(params.foo, 1); + assert.strictEqual(params.bar, 'test'); + assert.strictEqual(params.herp, 2); + done(); + }); + + it('should get url params for relative url', (done) => { + const params = utils.params({ + url: '/page?foo=1&bar=test&herp=2', + relative_path: '/forum', + }); + assert.strictEqual(params.foo, 1); + assert.strictEqual(params.bar, 'test'); + assert.strictEqual(params.herp, 2); + done(); + }); + + it('should get url params for relative url', (done) => { + const params = utils.params({ + url: '/forum/page?foo=1&bar=test&herp=2', + relative_path: '/forum', + }); + assert.strictEqual(params.foo, 1); + assert.strictEqual(params.bar, 'test'); + assert.strictEqual(params.herp, 2); + done(); + }); + it('should get url params as arrays', (done) => { const params = utils.params({ url: 'http://nodebb.org?foo=1&bar=test&herp[]=2&herp[]=3' }); assert.strictEqual(params.foo, 1); From 648c454303096a8b1d58abc7dbde572bda3784f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 29 Aug 2025 13:07:46 -0400 Subject: [PATCH 322/828] refactor: leaner utils.params for relative path --- public/src/utils.common.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/public/src/utils.common.js b/public/src/utils.common.js index 47520b2425..4ecf17e4f2 100644 --- a/public/src/utils.common.js +++ b/public/src/utils.common.js @@ -570,12 +570,7 @@ const utils = { params: function (options = {}) { let url; if (options.url && !options.url.startsWith('http')) { - // relative path passed in - const cleanurl = options.url.replace(new RegExp(`/^${(options.relative_path || '')}/`, 'g'), ''); - url = new URL(document.location); - const queryIndex = cleanurl.indexOf('?'); - url.search = queryIndex !== -1 ? cleanurl.slice(queryIndex) : ''; - url.pathname = cleanurl; + url = new URL(options.url, 'http://dummybase'); } else { url = new URL(options.url || document.location); } From 4ef605b1aa222bce8b4f1b828f8642af3d15b196 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 29 Aug 2025 13:33:14 -0400 Subject: [PATCH 323/828] fix: #13622, WordPress blog URLs not asserting properly --- src/activitypub/index.js | 70 +++++++++++++++++++++++++--------------- src/activitypub/notes.js | 4 ++- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index df455ac89f..4c1f3eee69 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -563,6 +563,43 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { }; }; +ActivityPub.checkHeader = async (url, timeout) => { + timeout = timeout || meta.config.activitypubProbeTimeout || 2000; + const { response } = await request.head(url, { + timeout, + }); + const { headers } = response; + 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('='); + memo[cur[0]] = cur[1].slice(1, -1); + return memo; + }, {}); + + if (parts.rel === 'alternate' && parts.type === 'application/activity+json') { + apLink = url; + } + }); + + return apLink; + } + + 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 @@ -629,37 +666,18 @@ ActivityPub.probe = async ({ uid, 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 { probeRateLimit.set(uid, true); - return await checkHeader(meta.config.activitypubProbeTimeout || 2000); + 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/notes.js b/src/activitypub/notes.js index a416766bc0..ca58459b8d 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -56,13 +56,15 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { return null; } - const id = !activitypub.helpers.isUri(input) ? input.id : input; + let id = !activitypub.helpers.isUri(input) ? input.id : input; const lockStatus = await lock(id); if (!lockStatus) { // unable to achieve lock, stop processing. winston.warn('[activitypub/notes.assert] Unable to acquire lock, skipping processing of', id); return null; } + id = await activitypub.checkHeader(id); + let chain; let context = await activitypub.contexts.get(uid, id); if (context.tid) { From f67265daa78b80095fe197637e2bd61400286795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 29 Aug 2025 15:23:19 -0400 Subject: [PATCH 324/828] refactor: revert, don't need to pass relative_path --- public/src/client/login.js | 5 +---- public/src/client/register.js | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/public/src/client/login.js b/public/src/client/login.js index e2495fb07f..f7508b8ec2 100644 --- a/public/src/client/login.js +++ b/public/src/client/login.js @@ -56,10 +56,7 @@ define('forum/login', ['hooks', 'translator', 'jquery-form'], function (hooks, t success: function (data) { hooks.fire('action:app.loggedIn', data); const pathname = utils.urlToLocation(data.next).pathname; - const params = utils.params({ - url: data.next, - relative_path: config.relative_path, - }); + const params = utils.params({ url: data.next }); params.loggedin = true; delete params.register; // clear register message incase it exists const qs = $.param(params); diff --git a/public/src/client/register.js b/public/src/client/register.js index ca86be35f0..f989901e7b 100644 --- a/public/src/client/register.js +++ b/public/src/client/register.js @@ -83,10 +83,7 @@ define('forum/register', [ if (data.next) { const pathname = utils.urlToLocation(data.next).pathname; - const params = utils.params({ - url: data.next, - relative_path: config.relative_path, - }); + const params = utils.params({ url: data.next }); params.registered = true; const qs = $.param(params); From 9d4a9b83ccabb28da178bb041900d90a24951ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 29 Aug 2025 21:02:14 -0400 Subject: [PATCH 325/828] 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 --- src/posts/edit.js | 4 +++- test/topics.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/posts/edit.js b/src/posts/edit.js index b18bf99078..0b8b6ff009 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -49,12 +49,14 @@ 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.sourceContent || data.content) !== oldContent) || topic.renamed || topic.tagsupdated; diff --git a/test/topics.js b/test/topics.js index bc92bbff09..eaae7d76c7 100644 --- a/test/topics.js +++ b/test/topics.js @@ -2506,6 +2506,35 @@ describe('Topic\'s', () => { const score = await db.sortedSetScore('topics:scheduled', topicData.tid); assert(!score); }); + + it('should properly update timestamp in cid::pids after editing and posting immediately', async () => { + const scheduleTimestamp = Date.now() + (86400000 * 365); + const result = await topics.post({ + cid: categoryObj.cid, + title: 'testing cid::pids', + content: 'some content here', + uid: adminUid, + timestamp: scheduleTimestamp, + }); + const { mainPid } = result.topicData; + + assert.strictEqual( + await db.isSortedSetMember(`cid:${categoryObj.cid}:pids`, mainPid), + false, + ); + + // edit main post and publish + await posts.edit({ + uid: adminUid, + pid: mainPid, + content: 'some content here - edited', + timestamp: Date.now(), + }); + + // the score in cid::pids should be less than Date.now() + const score = await db.sortedSetScore(`cid:${categoryObj.cid}:pids`, mainPid); + assert(score < Date.now(), 'Post in cid::pids has wrong score, it should not be in the future'); + }); }); }); From 931b7345e4565720caba707612f54b53df0cf50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bli=C5=BAniuk?= Date: Sat, 30 Aug 2025 03:07:29 +0200 Subject: [PATCH 326/828] 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 --- .github/workflows/docker.yml | 112 +++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 742f443f9c..a1a7cd9871 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -16,15 +16,29 @@ 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 + - os: ubuntu-24.04-arm + platforms: linux/arm/v7 + required: false + continue-on-error: ${{ !matrix.required }} + runs-on: ${{ matrix.os }} steps: - + - name: Prepare + run: | + platform=${{ matrix.platforms }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -34,39 +48,87 @@ 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 }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}}.x - type=raw,value=latest,enable={{is_default_branch}} - type=ref,event=branch,enable=${{ github.event.repository.default_branch != github.ref }} - type=raw,value=${{ env.CURRENT_DATE_NST }} - flavor: | - latest=true - + images: ${{ env.IMAGE }} + - 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 + id: build 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 }} + 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@v4 + 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@v4 + 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}} + type=semver,pattern={{major}}.x + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch,enable=${{ github.event.repository.default_branch != github.ref }} + type=raw,value=${{ env.CURRENT_DATE_NST }} + flavor: | + latest=true + - 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 }} \ No newline at end of file From b517e27d60ccf9a41e42045b314aa51439a6ff16 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sat, 30 Aug 2025 09:19:56 +0000 Subject: [PATCH 327/828] Latest translations and fallbacks --- .../pl/admin/settings/activitypub.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/language/pl/admin/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index c2e7bc7853..5ea1cccc2b 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -28,16 +28,16 @@ "rules.value": "Wartość", "rules.cid": "Kategoria", - "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", + "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", From 19aa8a716868602b771a41e20299f188f789c254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 30 Aug 2025 13:24:33 -0400 Subject: [PATCH 328/828] fix: display proper id if lock fails --- src/activitypub/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index ca58459b8d..52f5d373e3 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -59,7 +59,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { let id = !activitypub.helpers.isUri(input) ? input.id : input; const lockStatus = await lock(id); if (!lockStatus) { // unable to achieve lock, stop processing. - winston.warn('[activitypub/notes.assert] Unable to acquire lock, skipping processing of', id); + winston.warn(`[activitypub/notes.assert] Unable to acquire lock, skipping processing of ${id}`); return null; } From 70bbed93cedc6335ebb8070207388403875bda62 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2025 11:12:43 -0400 Subject: [PATCH 329/828] test: delete commented-out test --- test/activitypub.js | 50 --------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/test/activitypub.js b/test/activitypub.js index 89de67fdcb..bdc7247905 100644 --- a/test/activitypub.js +++ b/test/activitypub.js @@ -167,56 +167,6 @@ describe('ActivityPub integration', () => { }); }); - // describe('.generateTitle', () => { - // describe('test strings', () => { - // const cases = new Map([ - // // first paragraph element - // ['

test title

abc', 'test title'], - - // // other tags like h1 or span - // ['

Lorem ipsum dolor sit amet

consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.

', 'Lorem ipsum dolor sit amet'], - // ['Lorem ipsum dolor sit amet

consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.

', 'Lorem ipsum dolor sit amet'], - - // // first line's text otherwise - // ['Lorem ipsum dolor sit amet\n\nconsectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.', 'Lorem ipsum dolor sit amet'], - - // // first sentence of matched line/element - // ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'], - - // // other sentence ending symbols - // ['Lorem ipsum dolor sit amet, consectetur adipiscing elit? Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit?'], - - // // Content after line breaks can be discarded - // ['

Intro text
example.org/

more text

', 'Intro text'], - - // // HTML without outer wrapping element - // ['Lorem ipsum dolor sit amet', 'Lorem ipsum dolor sit amet'], - - // // Two sentences with punctuation - // ['Lorem ipsum. Dolor sit amet.', 'Lorem ipsum.'], - - // // Additional tests? - // // ['', ''], - // ]); - - // cases.forEach((value, key) => { - // it('should convert as expected', () => { - // const title = activitypub.helpers.generateTitle(key); - // assert.strictEqual(title, value); - // }); - // }); - // }); - - // it('should trim down the title if it is too long per settings', () => { - // const value = meta.config.maximumTitleLength; - // meta.config.maximumTitleLength = 10; - // const source = '@@@@@@@@@@@@@@@@@@@@'; - // const title = activitypub.helpers.generateTitle(source); - // assert.strictEqual(title, '@@@@@@@...'); - // meta.config.maximumTitleLength = value; - // }); - // }); - describe('.remoteAnchorToLocalProfile', () => { const uuid1 = utils.generateUUID(); const id1 = `https://example.org/uuid/${uuid1}`; From 07b9cd16bdd44a5069fdbb496c7a886f4e6dd471 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:20:27 -0400 Subject: [PATCH 330/828] fix(deps): update dependency nodemailer to v7.0.6 (#13630) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 479ade0698..070ece9435 100644 --- a/install/package.json +++ b/install/package.json @@ -111,7 +111,7 @@ "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.12", "nodebb-widget-essentials": "7.0.40", - "nodemailer": "7.0.5", + "nodemailer": "7.0.6", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", From 4ade6007855f159ac14c73d3ff851aa3d3a188be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:25:35 -0400 Subject: [PATCH 331/828] chore(deps): pin dependency @stylistic/eslint-plugin to 5.3.1 (#13634) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 070ece9435..2884d602a3 100644 --- a/install/package.json +++ b/install/package.json @@ -163,7 +163,7 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.34.0", - "@stylistic/eslint-plugin": "^5.x", + "@stylistic/eslint-plugin": "5.3.1", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", From 7adabd600d89349233ffc629313781725d311116 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:25:56 -0400 Subject: [PATCH 332/828] fix(deps): update dependency ace-builds to v1.43.3 (#13633) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2884d602a3..2365e5ed0e 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.43.2", + "ace-builds": "1.43.3", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", From 2dc39f1e3e7e0f24ab03a819a213ac6f53677cbc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:27:55 -0400 Subject: [PATCH 333/828] fix(deps): update dependency satori to v0.18.2 (#13628) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2365e5ed0e..2ad0782fac 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.91.0", - "satori": "0.16.2", + "satori": "0.18.2", "sbd": "^1.0.19", "semver": "7.7.2", "serve-favicon": "2.5.1", From 8c4d68a728898db8c513d553444cd45215d1a3b3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 3 Sep 2025 17:42:15 +0000 Subject: [PATCH 334/828] chore: incrementing version number - v4.5.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2ad0782fac..0fc29e5722 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.4.6", + "version": "4.5.0", "homepage": "https://www.nodebb.org", "repository": { "type": "git", From 86d03b1e463c275e8fc94e0ff5aadbf774a7dbbd Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 3 Sep 2025 17:42:16 +0000 Subject: [PATCH 335/828] chore: update changelog for v4.5.0 --- CHANGELOG.md | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 163d3d882c..96dd2a28f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,342 @@ +#### 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 From 5d6535719a2fc2594bcf62d4890c433b5703a206 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2025 14:02:58 -0400 Subject: [PATCH 336/828] chore: update default settings undoTimeout reduced to 0 post queue default enabled with minimum reputation to bypass set to 1 --- install/data/defaults.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index b6ae97a3c7..d90730d396 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,7 +36,7 @@ "maximumTagsPerTopic": 5, "minimumTagLength": 3, "maximumTagLength": 15, - "undoTimeout": 10000, + "undoTimeout": 0, "allowTopicsThumbnail": 1, "registrationType": "normal", "registrationApprovalType": "normal", From 7adfe39ea1ab349593e59ee6cccfdc7914805a65 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2025 14:46:01 -0400 Subject: [PATCH 337/828] fix: remove faulty code that tried to announce a remote object but couldn't as the ID was not a number --- src/api/topics.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/topics.js b/src/api/topics.js index 38e3cefbf8..65f7b485ee 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -322,8 +322,6 @@ topicsAPI.move = async (caller, { tid, cid }) => { 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); } await events.log({ From 0f9015f050a42843770c9351d63fb2e158e4e4d9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2025 14:45:31 -0400 Subject: [PATCH 338/828] fix: deprecated call to api.topics.move --- public/src/client/topic/move.js | 31 +++++++++++++++++++++---------- src/socket.io/topics/move.js | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/public/src/client/topic/move.js b/public/src/client/topic/move.js index ba4f055e68..df23091357 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; @@ -88,15 +88,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/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]]'); From c07e81d2ab9c3eea95739936707cf4dd2eb3a601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 3 Sep 2025 20:57:55 -0400 Subject: [PATCH 339/828] chore: up dbsearch --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0fc29e5722..14e65fabd7 100644 --- a/install/package.json +++ b/install/package.json @@ -98,7 +98,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", "nodebb-plugin-composer-default": "10.3.0", - "nodebb-plugin-dbsearch": "6.3.1", + "nodebb-plugin-dbsearch": "6.3.2", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.2.1", From 86d9016f02cea7f0ae50a73fb3285133e1130fe6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2025 10:29:17 -0400 Subject: [PATCH 340/828] fix: regression that caused Piefed (or potentially others) content to be dropped on receipt --- src/activitypub/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 4c1f3eee69..11f16322e2 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -585,6 +585,9 @@ ActivityPub.checkHeader = async (url, timeout) => { .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; }, {}); From 0c48e0e909ada2c14d9b959970a4048acf6dd2cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 4 Sep 2025 10:48:53 -0400 Subject: [PATCH 341/828] feat: use _variables.scss overrides from acp in custom skins and bootswatch skins as well --- src/meta/css.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/meta/css.js b/src/meta/css.js index 1c8f9f0329..304cc2554d 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -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; } From e6996846acd220bbb2974e0dbe765b684719caea Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2025 11:20:17 -0400 Subject: [PATCH 342/828] fix: use existing id if checkHeader returns false --- src/activitypub/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 52f5d373e3..ce9371fd26 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -63,7 +63,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { return null; } - id = await activitypub.checkHeader(id); + id = (await activitypub.checkHeader(id)) || id; let chain; let context = await activitypub.contexts.get(uid, id); From 9221d34f01e8db62a88ae56b380c6c68016ad4c3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2025 11:45:33 -0400 Subject: [PATCH 343/828] fix: remove test for 1b12 announce on topic move (as this no longer occurs) --- test/activitypub/feps.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/test/activitypub/feps.js b/test/activitypub/feps.js index 705df788e1..76b7d35cf6 100644 --- a/test/activitypub/feps.js +++ b/test/activitypub/feps.js @@ -221,30 +221,6 @@ describe('FEPs', () => { }); describe('extended actions not explicitly specified in 1b12', () => { - it('should be called when a topic is moved from uncategorized to another category', async () => { - const { topicData, postData } = await topics.post({ - uid, - cid: -1, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - - assert(topicData); - - await api.topics.move({ uid: adminUid }, { - tid: topicData.tid, - cid, - }); - - assert.strictEqual(activitypub._sent.size, 2); - - const key = Array.from(activitypub._sent.keys())[0]; - const activity = activitypub._sent.get(key); - - assert(activity && activity.object && typeof activity.object === 'object'); - assert.strictEqual(activity.object.id, `${nconf.get('url')}/post/${postData.pid}`); - }); - it('should be called for a newly forked topic', async () => { const { topicData } = await topics.post({ uid, From 8d7e35378fef8d93c92c37a0e974256a40253ea5 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2025 11:47:40 -0400 Subject: [PATCH 344/828] fix: remove unused dependency --- src/api/topics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/topics.js b/src/api/topics.js index 65f7b485ee..d44fffae04 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -8,7 +8,6 @@ const meta = require('../meta'); const privileges = require('../privileges'); const events = require('../events'); const batch = require('../batch'); -const activitypub = require('../activitypub'); const activitypubApi = require('./activitypub'); const apiHelpers = require('./helpers'); From 7a9e09a696589d64712e5f16ef4ee7d3c363752e Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 4 Sep 2025 16:02:47 +0000 Subject: [PATCH 345/828] chore: incrementing version number - v4.5.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 14e65fabd7..6a5457e093 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.5.0", + "version": "4.5.1", "homepage": "https://www.nodebb.org", "repository": { "type": "git", From a9fffd7ca07416184a2f6ec512b7cca95502e8b2 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 4 Sep 2025 16:02:47 +0000 Subject: [PATCH 346/828] chore: update changelog for v4.5.1 --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96dd2a28f1..beaab4c40c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +#### 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 From 2ea624fc8e945a3bd44c4cb10848f21b2c733f63 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2025 16:55:04 -0400 Subject: [PATCH 347/828] fix: use newline_boundaries param for tokenizer during title and summary generation, attempt to serve HTML in summary generation --- src/activitypub/mocks.js | 14 +++++++++++--- src/activitypub/notes.js | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index b3c641e0c3..380823fa31 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -757,17 +757,25 @@ Mocks.notes.public = async (post) => { attachment: normalizeAttachment(noteAttachment), }; - const sentences = tokenizer.sentences(post.content, { sanitize: true }); + 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 remaining = limit - memo.length; - if (sentence.length < remaining) { + 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'); diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index ce9371fd26..5739d25ecf 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -156,7 +156,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); + const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true, newline_boundaries: true }); title = sentences.shift(); } From 2de200b31129632b146e84e09268f68afb47786f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 5 Sep 2025 09:20:19 +0000 Subject: [PATCH 348/828] Latest translations and fallbacks --- public/language/vi/admin/settings/activitypub.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index c9e7308bb1..df53588641 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -34,10 +34,10 @@ "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", + "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ủ", From 15f9fbaa5cec0708ad248cfc1ca3f6f5e44ec210 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 5 Sep 2025 13:11:52 -0400 Subject: [PATCH 349/828] feat: add minor pre-processing step to better handle header elements in incoming html --- install/package.json | 1 + src/activitypub/notes.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 14e65fabd7..b2c185bacf 100644 --- a/install/package.json +++ b/install/package.json @@ -120,6 +120,7 @@ "pg-cursor": "2.15.3", "postcss": "8.5.6", "postcss-clean": "1.2.0", + "pretty": "^2.0.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", "redis": "5.8.2", diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 5739d25ecf..1671f8b1c6 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -3,6 +3,7 @@ const winston = require('winston'); const nconf = require('nconf'); const tokenizer = require('sbd'); +const pretty = require('pretty'); const db = require('../database'); const batch = require('../batch'); @@ -156,7 +157,8 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true, newline_boundaries: true }); + const prettified = pretty(content || sourceContent); + const sentences = tokenizer.sentences(prettified, { sanitize: true, newline_boundaries: true }); title = sentences.shift(); } From 9bfce68b5eea7bc91edb24939761bbe82d434c69 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 5 Sep 2025 14:39:23 -0400 Subject: [PATCH 350/828] test: disable post queue when testing posting logic --- test/topics.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/topics.js b/test/topics.js index eaae7d76c7..023b737bf3 100644 --- a/test/topics.js +++ b/test/topics.js @@ -54,6 +54,14 @@ describe('Topic\'s', () => { }); describe('.post', () => { + before(() => { + meta.config.postQueue = 0; + }); + + after(() => { + meta.config.postQueue = 1; + }); + it('should fail to create topic with invalid data', async () => { try { await apiTopics.create({ uid: 0 }, null); @@ -2337,6 +2345,12 @@ describe('Topic\'s', () => { content: 'The content of scheduled test topic', timestamp: new Date(Date.now() + 86400000).getTime(), }; + + meta.config.postQueue = 0; + }); + + after(() => { + meta.config.postQueue = 1; }); it('should create a scheduled topic as pinned, deleted, included in "topics:scheduled" zset and with a timestamp in future', async () => { From 290a9395c095f2589804f954fc0f45bebb74b0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 6 Sep 2025 13:47:46 -0400 Subject: [PATCH 351/828] fix: pass object to.auth --- src/database/redis/connection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index fb2aceb33b..8792bbdf3f 100644 --- a/src/database/redis/connection.js +++ b/src/database/redis/connection.js @@ -71,7 +71,7 @@ connection.connect = async function (options) { }); if (options.password) { - cxn.auth(options.password); + cxn.auth({ password: options.password }); } }); }; From 5528c6eb1949bc72f6e450dfeb127059cadbf72c Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 8 Sep 2025 09:20:47 +0000 Subject: [PATCH 352/828] Latest translations and fallbacks --- public/language/vi/admin/settings/activitypub.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index df53588641..d6da5bd675 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -28,12 +28,12 @@ "rules.value": "Giá trị", "rules.cid": "Danh mục", - "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": "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", From b3ffa00789f17d7a33aade67ac2f0ee6b8d29a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 8 Sep 2025 09:29:32 -0400 Subject: [PATCH 353/828] fix: closes #13641, log test email sending errors server side --- src/socket.io/admin/email.js | 96 +++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/src/socket.io/admin/email.js b/src/socket.io/admin/email.js index ed5bce7a60..b2a160b1f3 100644 --- a/src/socket.io/admin/email.js +++ b/src/socket.io/admin/email.js @@ -1,5 +1,7 @@ 'use strict'; +const winston = require('winston'); + const meta = require('../../meta'); const userDigest = require('../../user/digest'); const userEmail = require('../../user/email'); @@ -14,55 +16,59 @@ 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; } }; From 527f27af2948af908006af7fe3683a2aee207fc5 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 8 Sep 2025 12:00:32 -0400 Subject: [PATCH 354/828] fix: make auto-categorization logic case-insensitive --- src/activitypub/notes.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index ce9371fd26..3b15ab0137 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -399,15 +399,18 @@ async function assertRelation(post) { } async function assignCategory(post) { + activitypub.helpers.log('[activitypub] Checking auto-categorization rules.'); let cid = undefined; const rules = await activitypub.rules.list(); - const tags = await Notes._normalizeTags(post._activitypub.tag || []); + 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)) { + if (tags.includes(value.toLowerCase())) { + activitypub.helpers.log(`[activitypub] - Rule match: #${value}; cid: ${target}`); return target; } break; From 1d6a9fe738bdbe628306bbaa640fb30eaea86e45 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 8 Sep 2025 14:57:51 -0400 Subject: [PATCH 355/828] feat: allow user auto-categorization rule --- .../en-GB/admin/settings/activitypub.json | 3 ++- public/src/admin/settings/activitypub.js | 18 +++++++++++++++++- src/activitypub/notes.js | 7 +++++++ src/activitypub/rules.js | 11 +++++++++++ src/views/admin/partials/activitypub/rules.tpl | 3 ++- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js index 8c79d95b4b..0da00293de 100644 --- a/public/src/admin/settings/activitypub.js +++ b/public/src/admin/settings/activitypub.js @@ -6,7 +6,8 @@ define('admin/settings/activitypub', [ 'categorySelector', 'api', 'alerts', -], function (Benchpress, bootbox, categorySelector, api, alerts) { + 'translator', +], function (Benchpress, bootbox, categorySelector, api, alerts, translator) { const Module = {}; Module.init = function () { @@ -95,6 +96,21 @@ define('admin/settings/activitypub', [ 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) { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 3a00323859..cc5e7fe393 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -417,6 +417,13 @@ async function assignCategory(post) { } break; } + + case 'user': { + if (post.uid === value) { + activitypub.helpers.log(`[activitypub] - Rule match: user ${value}; cid: ${target}`); + return target; + } + } } } diff --git a/src/activitypub/rules.js b/src/activitypub/rules.js index 3a9a560552..9b3a002aab 100644 --- a/src/activitypub/rules.js +++ b/src/activitypub/rules.js @@ -3,6 +3,8 @@ const db = require('../database'); const utils = require('../utils'); +const activitypub = require('.'); + const Rules = module.exports; Rules.list = async () => { @@ -19,6 +21,15 @@ Rules.list = async () => { 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), diff --git a/src/views/admin/partials/activitypub/rules.tpl b/src/views/admin/partials/activitypub/rules.tpl index e321a86744..33273942b5 100644 --- a/src/views/admin/partials/activitypub/rules.tpl +++ b/src/views/admin/partials/activitypub/rules.tpl @@ -8,13 +8,14 @@
-

[[admin/settings/activitypub:rules.modal.values-multiple]]

+

From 8939010195babe649267c317b20666727ac12345 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 8 Sep 2025 19:37:21 +0000 Subject: [PATCH 356/828] chore(i18n): fallback strings for new resources: nodebb.admin-settings-activitypub --- public/language/ar/admin/settings/activitypub.json | 3 ++- public/language/az/admin/settings/activitypub.json | 3 ++- public/language/bg/admin/settings/activitypub.json | 3 ++- public/language/bn/admin/settings/activitypub.json | 3 ++- public/language/cs/admin/settings/activitypub.json | 3 ++- public/language/da/admin/settings/activitypub.json | 3 ++- public/language/de/admin/settings/activitypub.json | 3 ++- public/language/el/admin/settings/activitypub.json | 3 ++- public/language/en-US/admin/settings/activitypub.json | 3 ++- public/language/en-x-pirate/admin/settings/activitypub.json | 3 ++- public/language/es/admin/settings/activitypub.json | 3 ++- public/language/et/admin/settings/activitypub.json | 3 ++- public/language/fa-IR/admin/settings/activitypub.json | 3 ++- public/language/fi/admin/settings/activitypub.json | 3 ++- public/language/fr/admin/settings/activitypub.json | 3 ++- public/language/gl/admin/settings/activitypub.json | 3 ++- public/language/he/admin/settings/activitypub.json | 3 ++- public/language/hr/admin/settings/activitypub.json | 3 ++- public/language/hu/admin/settings/activitypub.json | 3 ++- public/language/hy/admin/settings/activitypub.json | 3 ++- public/language/id/admin/settings/activitypub.json | 3 ++- public/language/it/admin/settings/activitypub.json | 3 ++- public/language/ja/admin/settings/activitypub.json | 3 ++- public/language/ko/admin/settings/activitypub.json | 3 ++- public/language/lt/admin/settings/activitypub.json | 3 ++- public/language/lv/admin/settings/activitypub.json | 3 ++- public/language/ms/admin/settings/activitypub.json | 3 ++- public/language/nb/admin/settings/activitypub.json | 3 ++- public/language/nl/admin/settings/activitypub.json | 3 ++- public/language/nn-NO/admin/settings/activitypub.json | 3 ++- public/language/pl/admin/settings/activitypub.json | 3 ++- public/language/pt-BR/admin/settings/activitypub.json | 3 ++- public/language/pt-PT/admin/settings/activitypub.json | 3 ++- public/language/ro/admin/settings/activitypub.json | 3 ++- public/language/ru/admin/settings/activitypub.json | 3 ++- public/language/rw/admin/settings/activitypub.json | 3 ++- public/language/sc/admin/settings/activitypub.json | 3 ++- public/language/sk/admin/settings/activitypub.json | 3 ++- public/language/sl/admin/settings/activitypub.json | 3 ++- public/language/sq-AL/admin/settings/activitypub.json | 3 ++- public/language/sr/admin/settings/activitypub.json | 3 ++- public/language/sv/admin/settings/activitypub.json | 3 ++- public/language/th/admin/settings/activitypub.json | 3 ++- public/language/tr/admin/settings/activitypub.json | 3 ++- public/language/uk/admin/settings/activitypub.json | 3 ++- public/language/ur/admin/settings/activitypub.json | 3 ++- public/language/vi/admin/settings/activitypub.json | 3 ++- public/language/zh-CN/admin/settings/activitypub.json | 3 ++- public/language/zh-TW/admin/settings/activitypub.json | 3 ++- 49 files changed, 98 insertions(+), 49 deletions(-) diff --git a/public/language/ar/admin/settings/activitypub.json b/public/language/ar/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/ar/admin/settings/activitypub.json +++ b/public/language/ar/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/az/admin/settings/activitypub.json b/public/language/az/admin/settings/activitypub.json index db67fe8e2b..2399a20908 100644 --- a/public/language/az/admin/settings/activitypub.json +++ b/public/language/az/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 83b33df719..4c856f6cd3 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "rules-intro": "Съдържанието открито чрез ActivityPub може да бъде категоризирано автоматично следвайки определени правила (например дума отбелязана с диез)", "rules.modal.title": "Как работи това", "rules.modal.instructions": "Цялото входящо съдържание се проверява спрямо правилата и ако има съвпадения – те се преместват в избраната категория.

Забележка Съдържанието, което вече е категоризирано (например в отдалечена категория) няма да преминава тези проверки.", - "rules.modal.values-multiple": "Ако искате да се проверяват няколко стойности, разделете ги със запетая (пример: едно,две,три)", "rules.add": "Добавяне на ново правило", + "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": "Тип", "rules.value": "Стойност", "rules.cid": "Категория", diff --git a/public/language/bn/admin/settings/activitypub.json b/public/language/bn/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/bn/admin/settings/activitypub.json +++ b/public/language/bn/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/cs/admin/settings/activitypub.json b/public/language/cs/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/cs/admin/settings/activitypub.json +++ b/public/language/cs/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/da/admin/settings/activitypub.json b/public/language/da/admin/settings/activitypub.json index c868684859..b70d230cf5 100644 --- a/public/language/da/admin/settings/activitypub.json +++ b/public/language/da/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/de/admin/settings/activitypub.json b/public/language/de/admin/settings/activitypub.json index f08220e9df..34f2635f78 100644 --- a/public/language/de/admin/settings/activitypub.json +++ b/public/language/de/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "Um mehrere Werte abzugleichen, Einträge mit einem Komma trennen (z. B. eins,zwei,drei).", "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", diff --git a/public/language/el/admin/settings/activitypub.json b/public/language/el/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/el/admin/settings/activitypub.json +++ b/public/language/el/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/en-US/admin/settings/activitypub.json b/public/language/en-US/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/en-US/admin/settings/activitypub.json +++ b/public/language/en-US/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/en-x-pirate/admin/settings/activitypub.json b/public/language/en-x-pirate/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/en-x-pirate/admin/settings/activitypub.json +++ b/public/language/en-x-pirate/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/es/admin/settings/activitypub.json b/public/language/es/admin/settings/activitypub.json index da972be67d..5bc9a80078 100644 --- a/public/language/es/admin/settings/activitypub.json +++ b/public/language/es/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/et/admin/settings/activitypub.json b/public/language/et/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/et/admin/settings/activitypub.json +++ b/public/language/et/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/fa-IR/admin/settings/activitypub.json b/public/language/fa-IR/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/fa-IR/admin/settings/activitypub.json +++ b/public/language/fa-IR/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/fi/admin/settings/activitypub.json b/public/language/fi/admin/settings/activitypub.json index 8fc3168448..a916af734e 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/fr/admin/settings/activitypub.json b/public/language/fr/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/fr/admin/settings/activitypub.json +++ b/public/language/fr/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/gl/admin/settings/activitypub.json b/public/language/gl/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/gl/admin/settings/activitypub.json +++ b/public/language/gl/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/he/admin/settings/activitypub.json b/public/language/he/admin/settings/activitypub.json index 7d7f2360cf..784350dfff 100644 --- a/public/language/he/admin/settings/activitypub.json +++ b/public/language/he/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/hr/admin/settings/activitypub.json b/public/language/hr/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/hr/admin/settings/activitypub.json +++ b/public/language/hr/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/hu/admin/settings/activitypub.json b/public/language/hu/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/hu/admin/settings/activitypub.json +++ b/public/language/hu/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/hy/admin/settings/activitypub.json b/public/language/hy/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/hy/admin/settings/activitypub.json +++ b/public/language/hy/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/id/admin/settings/activitypub.json b/public/language/id/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/id/admin/settings/activitypub.json +++ b/public/language/id/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index ed794f82b7..764085b800 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "Per abbinare più valori, separa le voci con una virgola (es. uno,due,tre)", "rules.add": "Aggiungi nuova regola", + "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": "Tipo", "rules.value": "Valore", "rules.cid": "Categoria", diff --git a/public/language/ja/admin/settings/activitypub.json b/public/language/ja/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/ja/admin/settings/activitypub.json +++ b/public/language/ja/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/ko/admin/settings/activitypub.json b/public/language/ko/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/ko/admin/settings/activitypub.json +++ b/public/language/ko/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/lt/admin/settings/activitypub.json b/public/language/lt/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/lt/admin/settings/activitypub.json +++ b/public/language/lt/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/lv/admin/settings/activitypub.json b/public/language/lv/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/lv/admin/settings/activitypub.json +++ b/public/language/lv/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/ms/admin/settings/activitypub.json b/public/language/ms/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/ms/admin/settings/activitypub.json +++ b/public/language/ms/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/nb/admin/settings/activitypub.json b/public/language/nb/admin/settings/activitypub.json index 2d280b94e5..bab848c368 100644 --- a/public/language/nb/admin/settings/activitypub.json +++ b/public/language/nb/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/nl/admin/settings/activitypub.json b/public/language/nl/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/nl/admin/settings/activitypub.json +++ b/public/language/nl/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/nn-NO/admin/settings/activitypub.json b/public/language/nn-NO/admin/settings/activitypub.json index f75280fc73..79f6f05cd6 100644 --- a/public/language/nn-NO/admin/settings/activitypub.json +++ b/public/language/nn-NO/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/pl/admin/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index 5ea1cccc2b..a9ce74deda 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "Aby dopasować wiele wartości oddziel je przecinkami (np. jeden,dwa,trzy)", "rules.add": "Dodaj nową regułę", + "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": "Wartość", "rules.cid": "Kategoria", diff --git a/public/language/pt-BR/admin/settings/activitypub.json b/public/language/pt-BR/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/pt-BR/admin/settings/activitypub.json +++ b/public/language/pt-BR/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/pt-PT/admin/settings/activitypub.json b/public/language/pt-PT/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/pt-PT/admin/settings/activitypub.json +++ b/public/language/pt-PT/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/ro/admin/settings/activitypub.json b/public/language/ro/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/ru/admin/settings/activitypub.json b/public/language/ru/admin/settings/activitypub.json index 55eb27a108..201d4d7a65 100644 --- a/public/language/ru/admin/settings/activitypub.json +++ b/public/language/ru/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/rw/admin/settings/activitypub.json b/public/language/rw/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/rw/admin/settings/activitypub.json +++ b/public/language/rw/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/sc/admin/settings/activitypub.json b/public/language/sc/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/sc/admin/settings/activitypub.json +++ b/public/language/sc/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/sk/admin/settings/activitypub.json b/public/language/sk/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/sk/admin/settings/activitypub.json +++ b/public/language/sk/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/sl/admin/settings/activitypub.json b/public/language/sl/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/sl/admin/settings/activitypub.json +++ b/public/language/sl/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/sq-AL/admin/settings/activitypub.json b/public/language/sq-AL/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/sq-AL/admin/settings/activitypub.json +++ b/public/language/sq-AL/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/sr/admin/settings/activitypub.json b/public/language/sr/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/sr/admin/settings/activitypub.json +++ b/public/language/sr/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/sv/admin/settings/activitypub.json b/public/language/sv/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/sv/admin/settings/activitypub.json +++ b/public/language/sv/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/th/admin/settings/activitypub.json b/public/language/th/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/th/admin/settings/activitypub.json +++ b/public/language/th/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/tr/admin/settings/activitypub.json b/public/language/tr/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/tr/admin/settings/activitypub.json +++ b/public/language/tr/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/uk/admin/settings/activitypub.json b/public/language/uk/admin/settings/activitypub.json index 1b00672e0f..5ab4fa43e8 100644 --- a/public/language/uk/admin/settings/activitypub.json +++ b/public/language/uk/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/ur/admin/settings/activitypub.json b/public/language/ur/admin/settings/activitypub.json index 537f7354eb..9f2b98f72a 100644 --- a/public/language/ur/admin/settings/activitypub.json +++ b/public/language/ur/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index d6da5bd675..d7290381d3 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "Để phù hợp với nhiều giá trị, tách các mục riêng biệt với dấu phẩy (vd: một,hai,ba)", "rules.add": "Thêm Quy Tắc Mới", + "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": "Loại", "rules.value": "Giá trị", "rules.cid": "Danh mục", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 39de463e70..97703c18f8 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "rules-intro": "通过 ActivityPub 发现的内容可根据特定规则(如标签)进行自动分类。", "rules.modal.title": "它的工作原理", "rules.modal.instructions": "所有传入的内容都会根据这些分类规则进行检查,符合规则的内容会自动移动到指定的版块目录中。

注: 已分类的内容(即位于远程版块中的内容)将不会受这些规则的影响。", - "rules.modal.values-multiple": "要匹配多个值,请用逗号分隔各条目 (如:一,二,三)", "rules.add": "添加规则", + "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": "类型", "rules.value": "值", "rules.cid": "版块", diff --git a/public/language/zh-TW/admin/settings/activitypub.json b/public/language/zh-TW/admin/settings/activitypub.json index 6fdf6c0602..dd07276f94 100644 --- a/public/language/zh-TW/admin/settings/activitypub.json +++ b/public/language/zh-TW/admin/settings/activitypub.json @@ -22,8 +22,9 @@ "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.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)", "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", From 3044f3829109aa791e28ad5b4ba41c581492b6fe Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 9 Sep 2025 09:20:59 +0000 Subject: [PATCH 357/828] Latest translations and fallbacks --- public/language/pl/admin/settings/activitypub.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/pl/admin/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index a9ce74deda..3d6712f199 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -23,8 +23,8 @@ "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": "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.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", From a5ea4b405621909796cfb3aaf42525f7ea23952c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 9 Sep 2025 11:07:28 -0400 Subject: [PATCH 358/828] chore: up eslint --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 810b1f65a8..58baf44fb7 100644 --- a/install/package.json +++ b/install/package.json @@ -163,7 +163,7 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", - "@eslint/js": "9.34.0", + "@eslint/js": "9.35.0", "@stylistic/eslint-plugin": "5.3.1", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 10350ea6f6101d4b28efd29affc87ed55d42386a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 9 Sep 2025 11:20:03 -0400 Subject: [PATCH 359/828] revert: post queue changes to fix tests --- install/data/defaults.json | 4 ++-- test/topics.js | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index d90730d396..574a8bfe01 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -24,8 +24,8 @@ "newbieChatMessageDelay": 120000, "notificationSendDelay": 60, "newbieReputationThreshold": 3, - "postQueue": 1, - "postQueueReputationThreshold": 1, + "postQueue": 0, + "postQueueReputationThreshold": 0, "groupsExemptFromPostQueue": ["administrators", "Global Moderators"], "groupsExemptFromNewUserRestrictions": ["administrators", "Global Moderators"], "groupsExemptFromMaintenanceMode": ["administrators", "Global Moderators"], diff --git a/test/topics.js b/test/topics.js index 023b737bf3..eaae7d76c7 100644 --- a/test/topics.js +++ b/test/topics.js @@ -54,14 +54,6 @@ describe('Topic\'s', () => { }); describe('.post', () => { - before(() => { - meta.config.postQueue = 0; - }); - - after(() => { - meta.config.postQueue = 1; - }); - it('should fail to create topic with invalid data', async () => { try { await apiTopics.create({ uid: 0 }, null); @@ -2345,12 +2337,6 @@ describe('Topic\'s', () => { content: 'The content of scheduled test topic', timestamp: new Date(Date.now() + 86400000).getTime(), }; - - meta.config.postQueue = 0; - }); - - after(() => { - meta.config.postQueue = 1; }); it('should create a scheduled topic as pinned, deleted, included in "topics:scheduled" zset and with a timestamp in future', async () => { From 8d6a0f0298679a3c6e4c399decc9d58cb6684529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 9 Sep 2025 11:28:29 -0400 Subject: [PATCH 360/828] test: ap timeouts --- test/mocks/databasemock.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 179288bbcf..460a93c416 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -193,6 +193,7 @@ async function setupMockDefaults() { meta.config.initialPostDelay = 0; meta.config.newbiePostDelay = 0; meta.config.autoDetectLang = 0; + meta.config.activitypubProbeTimeout = 30000; require('../../src/groups').cache.reset(); require('../../src/posts/cache').getOrCreate().reset(); From 0311b98ed7d926502f27a7d84b00c02b73712dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 10 Sep 2025 09:46:39 -0400 Subject: [PATCH 361/828] feat: add topic templates per category, closes #13649 --- install/package.json | 2 +- public/language/en-GB/admin/manage/categories.json | 2 ++ src/views/admin/manage/category.tpl | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6a5457e093..9e94fa1ed3 100644 --- a/install/package.json +++ b/install/package.json @@ -97,7 +97,7 @@ "multer": "2.0.2", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.5.10", - "nodebb-plugin-composer-default": "10.3.0", + "nodebb-plugin-composer-default": "10.3.1", "nodebb-plugin-dbsearch": "6.3.2", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index 5d3b84c639..b935638ac4 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -46,6 +46,16 @@

+
+ + +

+ [[admin/manage/categories:topic-template.help]] +

+
+
From 8d4e46529f861e4514799ba2db3447ff7f8ddd17 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 10 Sep 2025 13:49:20 +0000 Subject: [PATCH 362/828] chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories --- public/language/ar/admin/manage/categories.json | 2 ++ public/language/az/admin/manage/categories.json | 2 ++ public/language/bg/admin/manage/categories.json | 2 ++ public/language/bn/admin/manage/categories.json | 2 ++ public/language/cs/admin/manage/categories.json | 2 ++ public/language/da/admin/manage/categories.json | 2 ++ public/language/de/admin/manage/categories.json | 2 ++ public/language/el/admin/manage/categories.json | 2 ++ public/language/en-US/admin/manage/categories.json | 2 ++ public/language/en-x-pirate/admin/manage/categories.json | 2 ++ public/language/es/admin/manage/categories.json | 2 ++ public/language/et/admin/manage/categories.json | 2 ++ public/language/fa-IR/admin/manage/categories.json | 2 ++ public/language/fi/admin/manage/categories.json | 2 ++ public/language/fr/admin/manage/categories.json | 2 ++ public/language/gl/admin/manage/categories.json | 2 ++ public/language/he/admin/manage/categories.json | 2 ++ public/language/hr/admin/manage/categories.json | 2 ++ public/language/hu/admin/manage/categories.json | 2 ++ public/language/hy/admin/manage/categories.json | 2 ++ public/language/id/admin/manage/categories.json | 2 ++ public/language/it/admin/manage/categories.json | 2 ++ public/language/ja/admin/manage/categories.json | 2 ++ public/language/ko/admin/manage/categories.json | 2 ++ public/language/lt/admin/manage/categories.json | 2 ++ public/language/lv/admin/manage/categories.json | 2 ++ public/language/ms/admin/manage/categories.json | 2 ++ public/language/nb/admin/manage/categories.json | 2 ++ public/language/nl/admin/manage/categories.json | 2 ++ public/language/nn-NO/admin/manage/categories.json | 2 ++ public/language/pl/admin/manage/categories.json | 2 ++ public/language/pt-BR/admin/manage/categories.json | 2 ++ public/language/pt-PT/admin/manage/categories.json | 2 ++ public/language/ro/admin/manage/categories.json | 2 ++ public/language/ru/admin/manage/categories.json | 2 ++ public/language/rw/admin/manage/categories.json | 2 ++ public/language/sc/admin/manage/categories.json | 2 ++ public/language/sk/admin/manage/categories.json | 2 ++ public/language/sl/admin/manage/categories.json | 2 ++ public/language/sq-AL/admin/manage/categories.json | 2 ++ public/language/sr/admin/manage/categories.json | 2 ++ public/language/sv/admin/manage/categories.json | 2 ++ public/language/th/admin/manage/categories.json | 2 ++ public/language/tr/admin/manage/categories.json | 2 ++ public/language/uk/admin/manage/categories.json | 2 ++ public/language/ur/admin/manage/categories.json | 2 ++ public/language/vi/admin/manage/categories.json | 2 ++ public/language/zh-CN/admin/manage/categories.json | 2 ++ public/language/zh-TW/admin/manage/categories.json | 2 ++ 49 files changed, 98 insertions(+) diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json index f2469f7414..bf68795076 100644 --- a/public/language/ar/admin/manage/categories.json +++ b/public/language/ar/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json index 6540f6c4e9..c07a127295 100644 --- a/public/language/az/admin/manage/categories.json +++ b/public/language/az/admin/manage/categories.json @@ -17,6 +17,8 @@ "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ü", diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index 51a23cb96b..21a2ebd641 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "Размер на фоновото изображение", diff --git a/public/language/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/bn/admin/manage/categories.json +++ b/public/language/bn/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/cs/admin/manage/categories.json b/public/language/cs/admin/manage/categories.json index 8a8942a9f1..5277ddf50a 100644 --- a/public/language/cs/admin/manage/categories.json +++ b/public/language/cs/admin/manage/categories.json @@ -17,6 +17,8 @@ "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í", diff --git a/public/language/da/admin/manage/categories.json b/public/language/da/admin/manage/categories.json index 54ce8cd595..a491c842cc 100644 --- a/public/language/da/admin/manage/categories.json +++ b/public/language/da/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json index 327f17ae07..ef9cf71289 100644 --- a/public/language/de/admin/manage/categories.json +++ b/public/language/de/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/el/admin/manage/categories.json +++ b/public/language/el/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/en-US/admin/manage/categories.json b/public/language/en-US/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/en-US/admin/manage/categories.json +++ b/public/language/en-US/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/en-x-pirate/admin/manage/categories.json b/public/language/en-x-pirate/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/en-x-pirate/admin/manage/categories.json +++ b/public/language/en-x-pirate/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json index 74e14ce149..a2eb86107f 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/et/admin/manage/categories.json b/public/language/et/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/et/admin/manage/categories.json +++ b/public/language/et/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/fa-IR/admin/manage/categories.json b/public/language/fa-IR/admin/manage/categories.json index f80ae0e28e..5771d6a3fe 100644 --- a/public/language/fa-IR/admin/manage/categories.json +++ b/public/language/fa-IR/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/fi/admin/manage/categories.json b/public/language/fi/admin/manage/categories.json index 0b50fb20ad..e567de337b 100644 --- a/public/language/fi/admin/manage/categories.json +++ b/public/language/fi/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/fr/admin/manage/categories.json b/public/language/fr/admin/manage/categories.json index 35b588d572..c346a8a60b 100644 --- a/public/language/fr/admin/manage/categories.json +++ b/public/language/fr/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/gl/admin/manage/categories.json b/public/language/gl/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/gl/admin/manage/categories.json +++ b/public/language/gl/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index e250ec3867..5ce040d841 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "גודל תמונת רקע", diff --git a/public/language/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json index f7169fb5d7..e3dcc1ff04 100644 --- a/public/language/hr/admin/manage/categories.json +++ b/public/language/hr/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/hu/admin/manage/categories.json b/public/language/hu/admin/manage/categories.json index 865c015202..8c9378a9c9 100644 --- a/public/language/hu/admin/manage/categories.json +++ b/public/language/hu/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/hy/admin/manage/categories.json b/public/language/hy/admin/manage/categories.json index 16ea33717d..b47fec23be 100644 --- a/public/language/hy/admin/manage/categories.json +++ b/public/language/hy/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "Ֆոնային նկարի չափը", diff --git a/public/language/id/admin/manage/categories.json b/public/language/id/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/id/admin/manage/categories.json +++ b/public/language/id/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index b9ad1c8be3..e6abf93576 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -17,6 +17,8 @@ "federatedDescription": "Descrizione federazione", "federatedDescription.help": "Questo testo sarà aggiunto alla descrizione della categoria quando interrogato da altri siti web/app.", "federatedDescription.default": "Questa è una categoria del forum che contiene discussioni di attualità. Puoi iniziare nuove discussioni menzionando questa categoria.", + "topic-template": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Colore sfondo", "text-color": "Colore testo", "bg-image-size": "Dimensione dell'immagine di sfondo", diff --git a/public/language/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json index 44252e8c1a..dbd1c2ed86 100644 --- a/public/language/ja/admin/manage/categories.json +++ b/public/language/ja/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "背景画像サイズ", diff --git a/public/language/ko/admin/manage/categories.json b/public/language/ko/admin/manage/categories.json index 2625775110..62c37899a0 100644 --- a/public/language/ko/admin/manage/categories.json +++ b/public/language/ko/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "배경 이미지 크기", diff --git a/public/language/lt/admin/manage/categories.json b/public/language/lt/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/lt/admin/manage/categories.json +++ b/public/language/lt/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/lv/admin/manage/categories.json b/public/language/lv/admin/manage/categories.json index fba8b697ad..4746223d60 100644 --- a/public/language/lv/admin/manage/categories.json +++ b/public/language/lv/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/ms/admin/manage/categories.json b/public/language/ms/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/ms/admin/manage/categories.json +++ b/public/language/ms/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json index 0cf4511dd6..d9333c679f 100644 --- a/public/language/nb/admin/manage/categories.json +++ b/public/language/nb/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index 31db61a5dd..bdf146510e 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index 900971a999..dad82a5ee3 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index 80835e8152..0236d7f6dd 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Kolor tła", "text-color": "Kolor tekstu", "bg-image-size": "Wielkość obrazka tła", diff --git a/public/language/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json index c07d5537a5..3b41afca38 100644 --- a/public/language/pt-BR/admin/manage/categories.json +++ b/public/language/pt-BR/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/pt-PT/admin/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json index 75d054e475..3fb24317f8 100644 --- a/public/language/pt-PT/admin/manage/categories.json +++ b/public/language/pt-PT/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json index 36843c00a8..3ab043db8f 100644 --- a/public/language/ru/admin/manage/categories.json +++ b/public/language/ru/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "Размер фонового изображения", diff --git a/public/language/rw/admin/manage/categories.json b/public/language/rw/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/rw/admin/manage/categories.json +++ b/public/language/rw/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/sc/admin/manage/categories.json b/public/language/sc/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/sc/admin/manage/categories.json +++ b/public/language/sc/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/sk/admin/manage/categories.json b/public/language/sk/admin/manage/categories.json index 5cd7b32ebe..34ad2e086e 100644 --- a/public/language/sk/admin/manage/categories.json +++ b/public/language/sk/admin/manage/categories.json @@ -17,6 +17,8 @@ "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í", diff --git a/public/language/sl/admin/manage/categories.json b/public/language/sl/admin/manage/categories.json index 129cce0663..cde764efa8 100644 --- a/public/language/sl/admin/manage/categories.json +++ b/public/language/sl/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/sq-AL/admin/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/sq-AL/admin/manage/categories.json +++ b/public/language/sq-AL/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/sr/admin/manage/categories.json b/public/language/sr/admin/manage/categories.json index d66dd814a1..7532cd9cd1 100644 --- a/public/language/sr/admin/manage/categories.json +++ b/public/language/sr/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/sv/admin/manage/categories.json b/public/language/sv/admin/manage/categories.json index e43fb441af..7287d92af4 100644 --- a/public/language/sv/admin/manage/categories.json +++ b/public/language/sv/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/th/admin/manage/categories.json b/public/language/th/admin/manage/categories.json index fd61be437c..f28cdc9cb0 100644 --- a/public/language/th/admin/manage/categories.json +++ b/public/language/th/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "ขนาดภาพพื้นหลัง", diff --git a/public/language/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json index af50cc0700..4bd70dc4ab 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -17,6 +17,8 @@ "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", diff --git a/public/language/uk/admin/manage/categories.json b/public/language/uk/admin/manage/categories.json index 0d2dc60d0a..556f750f44 100644 --- a/public/language/uk/admin/manage/categories.json +++ b/public/language/uk/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "Розмір фонового зображення", diff --git a/public/language/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json index d050877aa2..175a81965f 100644 --- a/public/language/ur/admin/manage/categories.json +++ b/public/language/ur/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "پس منظر تصویر کا سائز", diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index 45f3d12d89..d1e14a6e9b 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "Topic Template", + "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Màu Nền", "text-color": "Màu Chữ", "bg-image-size": "Kích Thước Hình Nền", diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 52302c243e..2763d4d467 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "背景图片大小", diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json index 4da07dc01c..25f495b009 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -17,6 +17,8 @@ "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": "背景圖片大小", From feda629f821b284e0f16f8c3c4263116306a43d7 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 10 Sep 2025 14:48:24 -0400 Subject: [PATCH 363/828] chore: remove formatApiResponse logging --- src/controllers/helpers.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index e11692867e..a6ade8c73b 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -448,10 +448,6 @@ helpers.getHomePageRoutes = async function (uid) { }; helpers.formatApiResponse = async (statusCode, res, payload) => { - if (!res.hasOwnProperty('req')) { - console.log('formatApiResponse', statusCode, payload); - } - if (res.req.method === 'HEAD') { return res.sendStatus(statusCode); } From 953c051c2e546364a8a28d4097ebceac1876bcac Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 10 Sep 2025 14:59:13 -0400 Subject: [PATCH 364/828] fix: perform Link header check on note assertion only when skipChecks is falsy --- src/activitypub/notes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 3b15ab0137..50ce01bf3c 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -63,7 +63,9 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { return null; } - id = (await activitypub.checkHeader(id)) || id; + if (!options.skipChecks) { + id = (await activitypub.checkHeader(id)) || id; + } let chain; let context = await activitypub.contexts.get(uid, id); From 6adfbb2482e80504665ae552cb6ea48d16c18f04 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:26:21 -0400 Subject: [PATCH 365/828] fix(deps): update dependency lru-cache to v11.2.1 (#13644) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 9f7ba81589..c099390c7d 100644 --- a/install/package.json +++ b/install/package.json @@ -88,7 +88,7 @@ "jsonwebtoken": "9.0.2", "lodash": "4.17.21", "logrotate-stream": "0.2.9", - "lru-cache": "11.1.0", + "lru-cache": "11.2.1", "mime": "3.0.0", "mkdirp": "3.0.1", "mongodb": "6.19.0", From ac90ef8c9a8844dd7dcd9dc89a9692eee455a203 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:27:20 -0400 Subject: [PATCH 366/828] chore(deps): update dependency mocha to v11.7.2 (#13636) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c099390c7d..66b26b29dc 100644 --- a/install/package.json +++ b/install/package.json @@ -172,7 +172,7 @@ "husky": "8.0.3", "jsdom": "26.1.0", "lint-staged": "16.1.5", - "mocha": "11.7.1", + "mocha": "11.7.2", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From 67fa433f1af91f9dff915b4f9002cb7a65a4502f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 11 Sep 2025 09:21:14 +0000 Subject: [PATCH 367/828] Latest translations and fallbacks --- public/language/bg/admin/manage/categories.json | 4 ++-- public/language/bg/admin/settings/activitypub.json | 4 ++-- public/language/pl/admin/manage/categories.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index 21a2ebd641..cd847e56bf 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -17,8 +17,8 @@ "federatedDescription": "Федерирано описание", "federatedDescription.help": "Този текст ще бъде добавен към описанието на категорията, когато други уеб сайтове и приложения изискват информация за нея.", "federatedDescription.default": "Това е категория във форума, съдържаща тематични дискусии. Може да започнете нова дискусия, като споменете този форум.", - "topic-template": "Topic Template", - "topic-template.help": "Define a template for new topics created in this category.", + "topic-template": "Шаблон за темите", + "topic-template.help": "Създайте шаблон за новите теми в тази категория.", "bg-color": "Цвят на фона", "text-color": "Цвят на текста", "bg-image-size": "Размер на фоновото изображение", diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 4c856f6cd3..dea26e2dfa 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -23,8 +23,8 @@ "rules.modal.title": "Как работи това", "rules.modal.instructions": "Цялото входящо съдържание се проверява спрямо правилата и ако има съвпадения – те се преместват в избраната категория.

Забележка Съдържанието, което вече е категоризирано (например в отдалечена категория) няма да преминава тези проверки.", "rules.add": "Добавяне на ново правило", - "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.help-hashtag": "Ще се търсят съвпадения с теми съдържащи тази дума с диез (не се прави разлика между главни и малки букви). Не въвеждайте знака #", + "rules.help-user": "Ще се търсят теми създадени от този потребител. Въведете псевдоним или пълен идентификатор (например bob@example.org или https://example.org/users/bob.", "rules.type": "Тип", "rules.value": "Стойност", "rules.cid": "Категория", diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index 0236d7f6dd..4e6c2f3e9f 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -17,8 +17,8 @@ "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": "Topic Template", - "topic-template.help": "Define a template for new topics created in this category.", + "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", From 6a1e9e8a110193a8540566bbd0adbaebaa047dce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:23:59 -0400 Subject: [PATCH 368/828] fix(deps): update dependency workerpool to v9.3.4 (#13650) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 66b26b29dc..c0ee11a46f 100644 --- a/install/package.json +++ b/install/package.json @@ -152,7 +152,7 @@ "webpack": "5.101.3", "webpack-merge": "6.0.1", "winston": "3.17.0", - "workerpool": "9.3.3", + "workerpool": "9.3.4", "xml": "1.0.1", "xregexp": "5.1.2", "yargs": "17.7.2", From 4f5e770c5f55689f090a4d1867f69ca2b3a4443c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:24:29 -0400 Subject: [PATCH 369/828] chore(deps): update actions/setup-node action to v5 (#13647) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fe9710ac5a..a94157f446 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -86,7 +86,7 @@ jobs: - run: cp install/package.json package.json - name: Install Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node }} From 95fb084ca49084d257e11b58cef9d30ed84394e1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Sep 2025 10:30:21 -0400 Subject: [PATCH 370/828] fix: wrap majority of note assertion logic in try..catch to handle exceptions so that the lock is always released --- src/activitypub/notes.js | 362 ++++++++++++++++++++------------------- 1 file changed, 184 insertions(+), 178 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 4b9685a54c..466de29877 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -64,213 +64,219 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { return null; } - if (!options.skipChecks) { - id = (await activitypub.checkHeader(id)) || id; - } - - let chain; - let context = await activitypub.contexts.get(uid, id); - if (context.tid) { - await 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; + try { + if (!options.skipChecks) { + id = (await activitypub.checkHeader(id)) || id; } - } 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) { - await 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, title, content, sourceContent, _activitypub } = mainPost; - const hasTid = !!tid; + let chain; + let context = await activitypub.contexts.get(uid, id); + if (context.tid) { + await 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; + } - const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; + if (!chain || !chain.length) { + // Fall back to inReplyTo traversal on context retrieval failure + chain = Array.from(await Notes.getParentChain(uid, input)); + chain.reverse(); + } - if (options.cid && cid === -1) { - // Move topic if currently uncategorized - await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); - } + // Can't resolve — give up. + if (!chain.length) { + await unlock(id); + return null; + } - 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.'); - await unlock(id); - return { tid, count: 0 }; - } + // Reorder chain items by timestamp + chain = chain.sort((a, b) => a.timestamp - b.timestamp); - 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); - return assertedGroups[idx] && cidHostname === hostname; - }).shift(); - } catch (e) { - // noop - winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); - } + const mainPost = chain[0]; + let { pid: mainPid, tid, uid: authorId, timestamp, title, content, sourceContent, _activitypub } = mainPost; + const hasTid = !!tid; - if (remoteCid || recipientCids.length) { - // Overrides passed-in value, respect addressing from main post over booster - options.cid = remoteCid || recipientCids.shift(); - } + const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; - // Auto-categorization (takes place only if all other categorization efforts fail) - if (!options.cid) { - options.cid = await assignCategory(mainPost); + if (options.cid && cid === -1) { + // Move topic if currently uncategorized + await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); } - // mainPid ok to leave as-is - if (!title) { - const prettified = pretty(content || sourceContent); - const sentences = tokenizer.sentences(prettified, { sanitize: true, newline_boundaries: true }); - title = sentences.shift(); + 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.'); + await unlock(id); + return { tid, count: 0 }; } - // 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'), ''); - }); - } - } - mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; - - // 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, 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.`); - } + 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); + return assertedGroups[idx] && cidHostname === hostname; + }).shift(); + } catch (e) { + // noop + winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); + } - await unlock(id); - return null; - } + if (remoteCid || recipientCids.length) { + // Overrides passed-in value, respect addressing from main post over booster + options.cid = remoteCid || recipientCids.shift(); + } - tid = tid || utils.generateUUID(); - mainPost.tid = tid; + // Auto-categorization (takes place only if all other categorization efforts fail) + if (!options.cid) { + options.cid = await assignCategory(mainPost); + } - 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 + // mainPid ok to leave as-is + if (!title) { + const prettified = pretty(content || sourceContent); + const sentences = tokenizer.sentences(prettified, { sanitize: true, newline_boundaries: true }); + title = sentences.shift(); + } - // Ensure toPids in replies are ids - if (urlMap.has(post.toPid)) { - post.toPid = urlMap.get(post.toPid); + // 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'), ''); + }); + } } + mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; + + // 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, 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 post; - }).filter((p, idx) => !members[idx]); - const count = unprocessed.length; - activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); - - if (!hasTid) { - const { to, cc, attachment } = 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, - _activitypub: mainPost._activitypub, - }); - unprocessed.shift(); - } catch (e) { - activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); + await unlock(id); return null; } - // These must come after topic is posted - await Promise.all([ - Notes.updateLocalRecipients(mainPid, { to, cc }), - mainPost._activitypub.image ? topics.thumbs.associate({ - id: tid, - path: mainPost._activitypub.image, - }) : null, - posts.attachments.update(mainPid, attachment), - ]); + tid = tid || utils.generateUUID(); + mainPost.tid = tid; - if (context) { - activitypub.helpers.log(`[activitypub/notes.assert] Associating tid ${tid} with context ${context}`); - await topics.setTopicField(tid, 'context', context); - } - } + 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.`); + + if (!hasTid) { + const { to, cc, attachment } = mainPost._activitypub; + const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); - for (const post of unprocessed) { - const { to, cc, attachment } = post._activitypub; + try { + await topics.post({ + tid, + uid: authorId, + cid: options.cid || cid, + pid: mainPid, + title, + timestamp, + tags, + content: mainPost.content, + sourceContent: mainPost.sourceContent, + _activitypub: mainPost._activitypub, + }); + unprocessed.shift(); + } catch (e) { + activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); + return null; + } - 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, + posts.attachments.update(mainPid, attachment), ]); - } 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); + } } - } - await Promise.all([ - Notes.syncUserInboxes(tid, uid), - unlock(id), - ]); + for (const post of unprocessed) { + const { to, cc, attachment } = post._activitypub; - return { tid, count }; + try { + // eslint-disable-next-line no-await-in-loop + await topics.reply(post); + // eslint-disable-next-line no-await-in-loop + await Promise.all([ + Notes.updateLocalRecipients(post.pid, { to, cc }), + posts.attachments.update(post.pid, attachment), + ]); + } catch (e) { + activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); + } + } + + await Promise.all([ + Notes.syncUserInboxes(tid, uid), + unlock(id), + ]); + + return { tid, count }; + } catch (e) { + winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}), releasing lock.`); + await unlock(id); + return null; + } }; Notes.assertPrivate = async (object) => { From 9184a7a4cc8c68c094d9ddbe86dd0c23f578986e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 11 Sep 2025 17:28:56 -0400 Subject: [PATCH 371/828] fix: add missing unlock in nested try/catch --- src/activitypub/notes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 466de29877..d485fdd256 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -231,6 +231,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { unprocessed.shift(); } catch (e) { activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); + await unlock(id); return null; } From f9688b36b67e1706cafacce69f784a9e8e5aeaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 11 Sep 2025 17:44:34 -0400 Subject: [PATCH 372/828] fix: port the try/catch for notes.assert from develop --- src/activitypub/notes.js | 361 ++++++++++++++++++++------------------- 1 file changed, 184 insertions(+), 177 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 50ce01bf3c..733aff354f 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -63,212 +63,219 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { return null; } - if (!options.skipChecks) { - id = (await activitypub.checkHeader(id)) || id; - } - - let chain; - let context = await activitypub.contexts.get(uid, id); - if (context.tid) { - await 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; + try { + if (!options.skipChecks) { + id = (await activitypub.checkHeader(id)) || id; } - } 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) { - await 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, title, content, sourceContent, _activitypub } = mainPost; - const hasTid = !!tid; + let chain; + let context = await activitypub.contexts.get(uid, id); + if (context.tid) { + await 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; + } - const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; + if (!chain || !chain.length) { + // Fall back to inReplyTo traversal on context retrieval failure + chain = Array.from(await Notes.getParentChain(uid, input)); + chain.reverse(); + } - if (options.cid && cid === -1) { - // Move topic if currently uncategorized - await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); - } + // Can't resolve — give up. + if (!chain.length) { + await unlock(id); + return null; + } - 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.'); - await unlock(id); - return { tid, count: 0 }; - } + // Reorder chain items by timestamp + chain = chain.sort((a, b) => a.timestamp - b.timestamp); - 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); - return assertedGroups[idx] && cidHostname === hostname; - }).shift(); - } catch (e) { - // noop - winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); - } + const mainPost = chain[0]; + let { pid: mainPid, tid, uid: authorId, timestamp, title, content, sourceContent, _activitypub } = mainPost; + const hasTid = !!tid; - if (remoteCid || recipientCids.length) { - // Overrides passed-in value, respect addressing from main post over booster - options.cid = remoteCid || recipientCids.shift(); - } + const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; - // Auto-categorization (takes place only if all other categorization efforts fail) - if (!options.cid) { - options.cid = await assignCategory(mainPost); + if (options.cid && cid === -1) { + // Move topic if currently uncategorized + await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); } - // mainPid ok to leave as-is - if (!title) { - const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); - title = sentences.shift(); + 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.'); + await unlock(id); + return { tid, count: 0 }; } - // 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'), ''); - }); - } - } - mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; - - // 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, 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.`); - } + 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); + return assertedGroups[idx] && cidHostname === hostname; + }).shift(); + } catch (e) { + // noop + winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); + } - await unlock(id); - return null; - } + if (remoteCid || recipientCids.length) { + // Overrides passed-in value, respect addressing from main post over booster + options.cid = remoteCid || recipientCids.shift(); + } - tid = tid || utils.generateUUID(); - mainPost.tid = tid; + // Auto-categorization (takes place only if all other categorization efforts fail) + if (!options.cid) { + options.cid = await assignCategory(mainPost); + } - 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 + // mainPid ok to leave as-is + if (!title) { + const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); + title = sentences.shift(); + } - // Ensure toPids in replies are ids - if (urlMap.has(post.toPid)) { - post.toPid = urlMap.get(post.toPid); + // 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'), ''); + }); + } } + mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; + + // 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, 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 post; - }).filter((p, idx) => !members[idx]); - const count = unprocessed.length; - activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); - - if (!hasTid) { - const { to, cc, attachment } = 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, - _activitypub: mainPost._activitypub, - }); - unprocessed.shift(); - } catch (e) { - activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); + await unlock(id); return null; } - // These must come after topic is posted - await Promise.all([ - Notes.updateLocalRecipients(mainPid, { to, cc }), - mainPost._activitypub.image ? topics.thumbs.associate({ - id: tid, - path: mainPost._activitypub.image, - }) : null, - posts.attachments.update(mainPid, attachment), - ]); + tid = tid || utils.generateUUID(); + mainPost.tid = tid; - if (context) { - activitypub.helpers.log(`[activitypub/notes.assert] Associating tid ${tid} with context ${context}`); - await topics.setTopicField(tid, 'context', context); - } - } + 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.`); + + if (!hasTid) { + const { to, cc, attachment } = mainPost._activitypub; + const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); - for (const post of unprocessed) { - const { to, cc, attachment } = post._activitypub; + try { + await topics.post({ + tid, + uid: authorId, + cid: options.cid || cid, + pid: mainPid, + title, + timestamp, + tags, + content: mainPost.content, + sourceContent: mainPost.sourceContent, + _activitypub: mainPost._activitypub, + }); + unprocessed.shift(); + } catch (e) { + activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); + await unlock(id); + return null; + } - 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, + posts.attachments.update(mainPid, attachment), ]); - } 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); + } } - } - await Promise.all([ - Notes.syncUserInboxes(tid, uid), - unlock(id), - ]); + for (const post of unprocessed) { + const { to, cc, attachment } = post._activitypub; - return { tid, count }; + try { + // eslint-disable-next-line no-await-in-loop + await topics.reply(post); + // eslint-disable-next-line no-await-in-loop + await Promise.all([ + Notes.updateLocalRecipients(post.pid, { to, cc }), + posts.attachments.update(post.pid, attachment), + ]); + } catch (e) { + activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); + } + } + + await Promise.all([ + Notes.syncUserInboxes(tid, uid), + unlock(id), + ]); + + return { tid, count }; + } catch (e) { + winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}), releasing lock.`); + await unlock(id); + return null; + } }; Notes.assertPrivate = async (object) => { From 7147a2e31aeb4711c9a8ff398db50f33810243e6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:46:24 -0400 Subject: [PATCH 373/828] chore(deps): update dependency lint-staged to v16.1.6 (#13635) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c0ee11a46f..e4ca5287f7 100644 --- a/install/package.json +++ b/install/package.json @@ -171,7 +171,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "26.1.0", - "lint-staged": "16.1.5", + "lint-staged": "16.1.6", "mocha": "11.7.2", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 10344c98a8730f89a0e16f5c535944016cf1fefc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:58:34 -0400 Subject: [PATCH 374/828] fix(deps): update dependency sass to v1.92.1 (#13645) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e4ca5287f7..76040c41a6 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.91.0", + "sass": "1.92.1", "satori": "0.18.2", "sbd": "^1.0.19", "semver": "7.7.2", From 15b0b54000d237045e0d4aa2dc4c12d6ce07b7d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:58:44 -0400 Subject: [PATCH 375/828] chore(deps): update dependency sass-embedded to v1.92.1 (#13638) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 76040c41a6..45d058319b 100644 --- a/install/package.json +++ b/install/package.json @@ -179,7 +179,7 @@ "smtp-server": "3.14.0" }, "optionalDependencies": { - "sass-embedded": "1.91.0" + "sass-embedded": "1.92.1" }, "resolutions": { "*/jquery": "3.7.1" From eecf9dda6498c649cd2cceaa57508abf5c8fb1ee Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 12 Sep 2025 09:21:18 +0000 Subject: [PATCH 376/828] Latest translations and fallbacks --- public/language/zh-CN/admin/manage/categories.json | 4 ++-- public/language/zh-CN/admin/settings/activitypub.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 2763d4d467..8e07c202d7 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -17,8 +17,8 @@ "federatedDescription": "“联邦化”说明", "federatedDescription.help": "当其他网站/应用程序查询时,该文本将附加到版块描述中。", "federatedDescription.default": "这是一个包含专题讨论的论坛版块。您可以通过提及该版块开始新的讨论。", - "topic-template": "Topic Template", - "topic-template.help": "Define a template for new topics created in this category.", + "topic-template": "主题模板", + "topic-template.help": "为本版块下新建的主题定义模板。", "bg-color": "背景颜色", "text-color": "图标颜色", "bg-image-size": "背景图片大小", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 97703c18f8..5a0ef40eca 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -23,8 +23,8 @@ "rules.modal.title": "它的工作原理", "rules.modal.instructions": "所有传入的内容都会根据这些分类规则进行检查,符合规则的内容会自动移动到指定的版块目录中。

注: 已分类的内容(即位于远程版块中的内容)将不会受这些规则的影响。", "rules.add": "添加规则", - "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.help-hashtag": "包含此不区分大小写的标签的主题将匹配。请勿输入 # 符号。", + "rules.help-user": "由输入用户创建的主题将匹配。请输入用户名或完整ID(例如:bob@example.orghttps://example.org/users/bob.", "rules.type": "类型", "rules.value": "值", "rules.cid": "版块", From f9ddbebacc76c2601d5dbb8adf97b44cb32395b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 12 Sep 2025 11:33:53 -0400 Subject: [PATCH 377/828] fix: remove .auth call --- src/database/redis/connection.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index 8792bbdf3f..7f0b1912f8 100644 --- a/src/database/redis/connection.js +++ b/src/database/redis/connection.js @@ -69,10 +69,6 @@ connection.connect = async function (options) { }).catch((err) => { winston.error('Error connecting to Redis:', err); }); - - if (options.password) { - cxn.auth({ password: options.password }); - } }); }; From 1e82af66a6f2c57f5ef971d6f1b52b7a30f74b36 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 11:36:55 -0400 Subject: [PATCH 378/828] fix(deps): update dependency bootswatch to v5.3.8 (#13651) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 45d058319b..a10b527155 100644 --- a/install/package.json +++ b/install/package.json @@ -48,7 +48,7 @@ "body-parser": "2.2.0", "bootbox": "6.0.4", "bootstrap": "5.3.8", - "bootswatch": "5.3.7", + "bootswatch": "5.3.8", "chalk": "4.1.2", "chart.js": "4.5.0", "cli-graph": "3.2.2", From 56fad0be0d0da2c1f3a12562d07a8f2adab3bffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 12 Sep 2025 19:19:52 -0400 Subject: [PATCH 379/828] fix: check brand:touchIcon for correct path --- src/middleware/index.js | 14 ++++++++++---- test/controllers.js | 12 +++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index 67d8e2faa0..14cb3138e1 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -145,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/, '')); + 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'); } diff --git a/test/controllers.js b/test/controllers.js index b2174d8cf9..5ef313a6d2 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -6,8 +6,8 @@ const fs = require('fs'); const path = require('path'); const util = require('util'); -const request = require('../src/request'); const db = require('./mocks/databasemock'); +const request = require('../src/request'); const api = require('../src/api'); const categories = require('../src/categories'); const topics = require('../src/topics'); @@ -692,6 +692,16 @@ describe('Controllers', () => { assert(body); }); + it('should 404 if brand:touchIcon is not valid', async () => { + const oldValue = meta.config['brand:touchIcon']; + meta.config['brand:touchIcon'] = '../../not/valid'; + + const { response, body } = await request.get(`${nconf.get('url')}/apple-touch-icon`); + assert.strictEqual(response.statusCode, 404); + assert.strictEqual(body, 'Not found'); + meta.config['brand:touchIcon'] = oldValue; + }) + it('should error if guests do not have search privilege', async () => { const { response, body } = await request.get(`${nconf.get('url')}/api/users?query=bar§ion=sort-posts`); From a37521b0167bf3c10c1b856ed317589f4a08bede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 12 Sep 2025 19:27:07 -0400 Subject: [PATCH 380/828] lint: fix --- test/controllers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers.js b/test/controllers.js index 5ef313a6d2..d79cf6de04 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -700,7 +700,7 @@ describe('Controllers', () => { assert.strictEqual(response.statusCode, 404); assert.strictEqual(body, 'Not found'); meta.config['brand:touchIcon'] = oldValue; - }) + }); it('should error if guests do not have search privilege', async () => { From e2dc592c4f21a48bd5f6c10b58773d0b755e32bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 12 Sep 2025 19:50:19 -0400 Subject: [PATCH 381/828] fix: favicon path --- src/webserver.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webserver.js b/src/webserver.js index ab8dc5bc0d..18f57faa40 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -227,6 +227,9 @@ function setupHelmet(app) { function setupFavicon(app) { let faviconPath = meta.config['brand:favicon'] || 'favicon.ico'; faviconPath = path.join(nconf.get('base_dir'), 'public', faviconPath.replace(/assets\/uploads/, 'uploads')); + if (!faviconPath.startsWith(nconf.get('upload_path'))) { + faviconPath = path.join(nconf.get('base_dir'), 'public', 'favicon.ico'); + } if (file.existsSync(faviconPath)) { app.use(nconf.get('relative_path'), favicon(faviconPath)); } From 8a786c717e5366620d12c7b49d34fb0dffbe67a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 13 Sep 2025 17:40:09 -0400 Subject: [PATCH 382/828] fix: if reputation is disabled hide votes on /recent they were only hidden on category page --- install/package.json | 4 ++-- public/src/modules/topicList.js | 8 +++++--- src/controllers/recent.js | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/install/package.json b/install/package.json index 9e94fa1ed3..55dee022a8 100644 --- a/install/package.json +++ b/install/package.json @@ -106,10 +106,10 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.18", + "nodebb-theme-harmony": "2.1.19", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", - "nodebb-theme-persona": "14.1.12", + "nodebb-theme-persona": "14.1.13", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.6", "nprogress": "0.2.0", diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index 6396bb9e8e..bc5a6e4abd 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,6 +213,7 @@ define('topicList', [ const tplData = { topics: topics, showSelect: showSelect, + 'reputation:disabled': data['reputation:disabled'], template: { name: templateName, }, diff --git a/src/controllers/recent.js b/src/controllers/recent.js index 5699fee1b7..656958463b 100644 --- a/src/controllers/recent.js +++ b/src/controllers/recent.js @@ -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) { From dfe19a98c193bdfdd601f4f7e4be06e67f8431b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 13 Sep 2025 17:51:25 -0400 Subject: [PATCH 383/828] fix: don't show votes on unread if rep system disabled add openapi spec --- public/openapi/read/popular.yaml | 2 ++ public/openapi/read/recent.yaml | 2 ++ public/openapi/read/top.yaml | 2 ++ public/openapi/read/unread.yaml | 2 ++ src/controllers/unread.js | 1 + 5 files changed, 9 insertions(+) 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/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/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/unread.yaml b/public/openapi/read/unread.yaml index ea69f666dd..77e9ec44f6 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: diff --git a/src/controllers/unread.js b/src/controllers/unread.js index 9ff73da6ff..871f3252c0 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); }; From 19f391989001c228e5af04e2321525cf8daa9f0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 20:27:47 -0400 Subject: [PATCH 384/828] fix(deps): update dependency commander to v14.0.1 (#13652) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3c9d48dc7c..c9283edddc 100644 --- a/install/package.json +++ b/install/package.json @@ -53,7 +53,7 @@ "chart.js": "4.5.0", "cli-graph": "3.2.2", "clipboard": "2.0.11", - "commander": "14.0.0", + "commander": "14.0.1", "compare-versions": "6.1.1", "compression": "1.8.1", "connect-flash": "0.1.1", From 405d2172acc49321f6ec0109bda6b28f5340d39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Sep 2025 09:32:05 -0400 Subject: [PATCH 385/828] chore: up persona --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 55dee022a8..cd5d08f5c1 100644 --- a/install/package.json +++ b/install/package.json @@ -109,7 +109,7 @@ "nodebb-theme-harmony": "2.1.19", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", - "nodebb-theme-persona": "14.1.13", + "nodebb-theme-persona": "14.1.14", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.6", "nprogress": "0.2.0", From db89250982c6c67194bc08dd319831cd24fdfb6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:09:40 -0400 Subject: [PATCH 386/828] fix(deps): update dependency @fontsource/inter to v5.2.7 (#13655) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 16279e6eb1..554a5e9c84 100644 --- a/install/package.json +++ b/install/package.json @@ -30,7 +30,7 @@ "dependencies": { "@adactive/bootstrap-tagsinput": "0.8.2", "@fontsource-utils/scss": "0.2.1", - "@fontsource/inter": "5.2.6", + "@fontsource/inter": "5.2.7", "@fontsource/poppins": "5.2.6", "@fortawesome/fontawesome-free": "6.7.2", "@isaacs/ttlcache": "1.4.1", From 225bf85e941c95dd0d724ac5b47467918661f806 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2025 12:47:49 -0400 Subject: [PATCH 387/828] fix: #13657, fix remote category data inconsistency in `sendNotificationToPostOwner` --- src/notifications.js | 20 +++++++++++++++++++- src/socket.io/helpers.js | 11 ++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index bcf27c7d66..e71366417e 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'); @@ -84,7 +85,24 @@ Notifications.getMultiple = async function (nids) { const notifications = await db.getObjects(keys); const userKeys = notifications.map(n => n && n.from); - const usersData = await User.getUsersFields(userKeys, ['username', 'userslug', 'picture']); + 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, + }; + } + + return userData; + }); notifications.forEach((notification, index) => { if (notification) { diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index d80638458e..5def5138d4 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -94,7 +94,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']), + !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 +106,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 notifObj = await notifications.create({ type: command, - bodyShort: `[[${notification}, ${displayname}, ${titleEscaped}]]`, + bodyShort: `[[${notification}, ${userData.displayname || userData.name}, ${titleEscaped}]]`, bodyLong: postObj.content, pid: pid, tid: postData.tid, From 52fec49310db9957677c3ab3a3a98ae8ad08f903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Sep 2025 12:57:29 -0400 Subject: [PATCH 388/828] chore: remove obsolete deprecation --- src/meta/minifier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/minifier.js b/src/meta/minifier.js index 0ab268dc4f..324b827797 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -166,7 +166,7 @@ actions.buildCSS = async function buildCSS(data) { }; if (data.minify) { opts.silenceDeprecations = [ - 'legacy-js-api', 'mixed-decls', 'color-functions', + 'legacy-js-api', 'color-functions', 'global-builtin', 'import', ]; } From f67942caecbefab7f062379f653615f33b074b2f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2025 13:53:27 -0400 Subject: [PATCH 389/828] fix: local pids not always converted to absolute URLs on topic actor controller --- src/controllers/activitypub/actors.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/controllers/activitypub/actors.js b/src/controllers/activitypub/actors.js index 0f7a348990..850585e379 100644 --- a/src/controllers/activitypub/actors.js +++ b/src/controllers/activitypub/actors.js @@ -136,6 +136,7 @@ Actors.topic = async function (req, res, next) { 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`, @@ -151,7 +152,6 @@ Actors.topic = async function (req, res, next) { } 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)); @@ -168,15 +168,18 @@ Actors.topic = async function (req, res, next) { } res.set('ETag', digest); - // Convert pids to urls + // 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; } - collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); } + // Convert pids to urls + collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + const object = { '@context': 'https://www.w3.org/ns/activitystreams', id: `${nconf.get('url')}/topic/${req.params.tid}${collection.orderedItems && page ? `?page=${page}` : ''}`, From 5f4790a48c5b30f6d0558aee2d089523b2c9fdce Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2025 14:01:00 -0400 Subject: [PATCH 390/828] feat: allow activities to be addressed to as:Public or Public to be treated as public content --- src/activitypub/inbox.js | 8 ++++++-- src/activitypub/index.js | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 754720f208..c1caacd698 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -32,11 +32,15 @@ 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); } @@ -76,7 +80,7 @@ inbox.add = async (req) => { 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; diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 11f16322e2..167eb16d15 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -38,6 +38,7 @@ 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"', From b66c30a2a73d06c45a6b6d97607b0d9378d56b87 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2025 14:10:02 -0400 Subject: [PATCH 391/828] fix: handle cases where incoming ap object tag can be a non-array --- src/activitypub/notes.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 733aff354f..ef4abe9add 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -31,7 +31,13 @@ async function unlock(value) { Notes._normalizeTags = async (tag, cid) => { const systemTags = (meta.config.systemTags || '').split(','); const maxTags = await categories.getCategoryField(cid, 'maxTags'); - const tags = (tag || []) + let tags = tag || []; + + if (!Array.isArray(tags)) { // the "|| []" should handle null/undefined values... #famouslastwords + tags = [tags]; + } + + tags = tags .map((tag) => { tag.name = tag.name.startsWith('#') ? tag.name.slice(1) : tag.name; return tag; From 68a8db856a9dcad42fb58e505aaacaa173a1401a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Sep 2025 11:23:31 -0400 Subject: [PATCH 392/828] feat: add a new hook to override generateUrl in navigator.js --- public/src/modules/navigator.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 8c557985c3..0776f04c4b 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('filter:navigator.generateUrl', data); + return data.newUrl; } navigator.getCount = () => count; From 9c18c6fe49be49975fb5ea0eef73045890544415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Sep 2025 11:24:14 -0400 Subject: [PATCH 393/828] feat: add a term param to recent controller so it can be controller without req.query.term --- src/controllers/recent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/recent.js b/src/controllers/recent.js index 656958463b..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 || ''; From f7bbec7ccfee65cd21118d430f710a87ffcabcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Sep 2025 11:48:39 -0400 Subject: [PATCH 394/828] fix: switch to action --- public/src/modules/navigator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 0776f04c4b..0532beb3b4 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -446,7 +446,7 @@ define('navigator', [ newUrl, index, }; - hooks.fire('filter:navigator.generateUrl', data); + hooks.fire('action:navigator.generateUrl', data); return data.newUrl; } From b1e134b44ef09c1ce817df240c51fedf605b514c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Sep 2025 19:08:10 -0400 Subject: [PATCH 395/828] pass string to isUUID --- src/controllers/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index ae68290729..4cacc31cd9 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -26,7 +26,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(); } From 8324be2d796301d004c03591dbe211cc3a066a62 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:18:11 -0400 Subject: [PATCH 396/828] fix(deps): update dependency fs-extra to v11.3.2 (#13658) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 554a5e9c84..4820d5b910 100644 --- a/install/package.json +++ b/install/package.json @@ -72,7 +72,7 @@ "express-useragent": "1.0.15", "fetch-cookie": "3.1.0", "file-loader": "6.2.0", - "fs-extra": "11.3.1", + "fs-extra": "11.3.2", "graceful-fs": "4.2.11", "helmet": "7.2.0", "html-to-text": "9.0.5", From b845aa48be5b2fe1ff4a28eccec5ca40981718fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:26:59 -0400 Subject: [PATCH 397/828] fix(deps): update dependency nodebb-theme-harmony to v2.1.20 (#13659) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4820d5b910..fc4bbdb424 100644 --- a/install/package.json +++ b/install/package.json @@ -106,7 +106,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.19", + "nodebb-theme-harmony": "2.1.20", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.14", From f7c47429879f757e08975b5cd003416db00f5568 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2025 10:44:51 -0400 Subject: [PATCH 398/828] fix: add pre-processing step to title generation logic so sbd doesn't fall over so badly --- src/activitypub/helpers.js | 46 -------------------------------------- src/activitypub/notes.js | 6 ++++- 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index e6eb2e1c08..01cfd86d10 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -339,52 +339,6 @@ Helpers.resolveObjects = async (ids) => { return objects.length === 1 ? objects[0] : objects; }; -const titleishTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'p', 'span']; -const titleRegex = new RegExp(`<(${titleishTags.join('|')})>(.+?)`, 'm'); -Helpers.generateTitle = (html) => { - // Given an html string, generates a more appropriate title if possible - let title; - - // Try the first paragraph-like element - const match = html.match(titleRegex); - if (match && match.index === 0) { - title = match[2]; - } - - // Fall back to newline splitting (i.e. if no paragraph elements) - title = title || html.split('\n').filter(Boolean).shift(); - - // Discard everything after a line break element - title = title.replace(/.*/g, ''); - - // Strip html - title = utils.stripHTMLTags(title); - - // Split sentences and use only first one - const sentences = title - .split(/(\.|\?|!)\s/) - .reduce((memo, cur, idx, sentences) => { - if (idx % 2) { - memo.push(`${sentences[idx - 1]}${cur}`); - } else if (idx === sentences.length - 1) { - memo.push(cur); - } - - return memo; - }, []); - - if (sentences.length > 1) { - title = sentences.shift(); - } - - // Truncate down if too long - if (title.length > meta.config.maximumTitleLength) { - title = `${title.slice(0, meta.config.maximumTitleLength - 3)}...`; - } - - return title; -}; - Helpers.remoteAnchorToLocalProfile = async (content, isMarkdown = false) => { let anchorRegex; if (isMarkdown) { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index ef4abe9add..6ccb2ca209 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -165,7 +165,11 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); + // Naive pre-processing prior to sbd tokenization + let sbdInput = content || sourceContent; + sbdInput = sbdInput.replace('

', '

\n

'); + + const sentences = tokenizer.sentences(sbdInput, { sanitize: true, newline_boundaries: true }); title = sentences.shift(); } From 6cca55e37f0bce389c3094c5aae07ed1bbed3297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Sep 2025 10:50:35 -0400 Subject: [PATCH 399/828] fix: use parameterized query for key lookup --- src/database/postgres/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index c0838b45a0..5b3c7f7e9d 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -85,7 +85,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); From 532653110c9e0400967c42352415be6dccb8e6a4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2025 10:58:07 -0400 Subject: [PATCH 400/828] Revert "fix: add pre-processing step to title generation logic so sbd doesn't fall over so badly" This reverts commit f7c47429879f757e08975b5cd003416db00f5568. --- src/activitypub/helpers.js | 46 ++++++++++++++++++++++++++++++++++++++ src/activitypub/notes.js | 6 +---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 01cfd86d10..e6eb2e1c08 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -339,6 +339,52 @@ Helpers.resolveObjects = async (ids) => { return objects.length === 1 ? objects[0] : objects; }; +const titleishTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'p', 'span']; +const titleRegex = new RegExp(`<(${titleishTags.join('|')})>(.+?)`, 'm'); +Helpers.generateTitle = (html) => { + // Given an html string, generates a more appropriate title if possible + let title; + + // Try the first paragraph-like element + const match = html.match(titleRegex); + if (match && match.index === 0) { + title = match[2]; + } + + // Fall back to newline splitting (i.e. if no paragraph elements) + title = title || html.split('\n').filter(Boolean).shift(); + + // Discard everything after a line break element + title = title.replace(/.*/g, ''); + + // Strip html + title = utils.stripHTMLTags(title); + + // Split sentences and use only first one + const sentences = title + .split(/(\.|\?|!)\s/) + .reduce((memo, cur, idx, sentences) => { + if (idx % 2) { + memo.push(`${sentences[idx - 1]}${cur}`); + } else if (idx === sentences.length - 1) { + memo.push(cur); + } + + return memo; + }, []); + + if (sentences.length > 1) { + title = sentences.shift(); + } + + // Truncate down if too long + if (title.length > meta.config.maximumTitleLength) { + title = `${title.slice(0, meta.config.maximumTitleLength - 3)}...`; + } + + return title; +}; + Helpers.remoteAnchorToLocalProfile = async (content, isMarkdown = false) => { let anchorRegex; if (isMarkdown) { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 6ccb2ca209..ef4abe9add 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -165,11 +165,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - // Naive pre-processing prior to sbd tokenization - let sbdInput = content || sourceContent; - sbdInput = sbdInput.replace('

', '

\n

'); - - const sentences = tokenizer.sentences(sbdInput, { sanitize: true, newline_boundaries: true }); + const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); title = sentences.shift(); } From a6674f67a1cfb92f6236e76447e5e9213b1b5710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Sep 2025 10:58:26 -0400 Subject: [PATCH 401/828] lint: remove unused --- src/activitypub/helpers.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 01cfd86d10..f24d18b730 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -8,7 +8,6 @@ const validator = require('validator'); // const cheerio = require('cheerio'); const crypto = require('crypto'); -const meta = require('../meta'); const posts = require('../posts'); const categories = require('../categories'); const messaging = require('../messaging'); @@ -16,7 +15,6 @@ const request = require('../request'); const db = require('../database'); const ttl = require('../cache/ttl'); const user = require('../user'); -const utils = require('../utils'); const activitypub = require('.'); const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/; From 5beeedd67cc2fc08b6dda77f237ba7892b5329b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Sep 2025 11:09:02 -0400 Subject: [PATCH 402/828] Revert "lint: remove unused" This reverts commit a6674f67a1cfb92f6236e76447e5e9213b1b5710. --- src/activitypub/helpers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index e0f96fe3aa..e6eb2e1c08 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -8,6 +8,7 @@ const validator = require('validator'); // const cheerio = require('cheerio'); const crypto = require('crypto'); +const meta = require('../meta'); const posts = require('../posts'); const categories = require('../categories'); const messaging = require('../messaging'); @@ -15,6 +16,7 @@ const request = require('../request'); const db = require('../database'); const ttl = require('../cache/ttl'); const user = require('../user'); +const utils = require('../utils'); const activitypub = require('.'); const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/; From b2d91dc3199b6287d466d48b0babfa8971a58b44 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:41:33 -0400 Subject: [PATCH 403/828] fix(deps): update dependency satori to v0.18.3 (#13660) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fc4bbdb424..357f9899fd 100644 --- a/install/package.json +++ b/install/package.json @@ -129,7 +129,7 @@ "rtlcss": "4.3.0", "sanitize-html": "2.17.0", "sass": "1.92.1", - "satori": "0.18.2", + "satori": "0.18.3", "sbd": "^1.0.19", "semver": "7.7.2", "serve-favicon": "2.5.1", From 3238248eecde18c1f026ede7f4b7b91267dd4fb0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:41:57 -0400 Subject: [PATCH 404/828] chore(deps): update dependency jsdom to v27 (#13653) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 357f9899fd..4a5848ef18 100644 --- a/install/package.json +++ b/install/package.json @@ -170,7 +170,7 @@ "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "lint-staged": "16.1.6", "mocha": "11.7.2", "mocha-lcov-reporter": "1.3.0", From c8680f300af316365f5e1a88f612fab390389d13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:00:39 -0400 Subject: [PATCH 405/828] fix(deps): update dependency sharp to v0.34.4 (#13662) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4a5848ef18..189557cd07 100644 --- a/install/package.json +++ b/install/package.json @@ -133,7 +133,7 @@ "sbd": "^1.0.19", "semver": "7.7.2", "serve-favicon": "2.5.1", - "sharp": "0.34.3", + "sharp": "0.34.4", "sitemap": "8.0.0", "socket.io": "4.8.1", "socket.io-client": "4.8.1", From 9b48bbd5019173c25f00514062b5a930a3940efc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:33:55 -0400 Subject: [PATCH 406/828] fix(deps): update dependency esbuild to v0.25.10 (#13664) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 189557cd07..d0ab4d0d34 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.9", + "esbuild": "0.25.10", "express": "4.21.2", "express-session": "1.18.2", "express-useragent": "1.0.15", From d1f5060f11a257388690d1441726efd58ca88b5a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Sep 2025 13:33:16 -0400 Subject: [PATCH 407/828] fix(deps): bump 2factor to 7.6.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index cd5d08f5c1..f9e5b8b0ed 100644 --- a/install/package.json +++ b/install/package.json @@ -96,7 +96,7 @@ "mousetrap": "1.6.5", "multer": "2.0.2", "nconf": "0.13.0", - "nodebb-plugin-2factor": "7.5.10", + "nodebb-plugin-2factor": "7.6.0", "nodebb-plugin-composer-default": "10.3.1", "nodebb-plugin-dbsearch": "6.3.2", "nodebb-plugin-emoji": "6.0.3", @@ -201,4 +201,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} From 559155da6389ed648461b1dc68904c22c59d0082 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2025 10:34:57 -0400 Subject: [PATCH 408/828] refactor: notes.assert to add finally block, update assertPayload to update instances:lastSeen via method instead of direct db call --- src/activitypub/notes.js | 30 ++++++++---------------------- src/middleware/activitypub.js | 2 +- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 98f40dde8c..044a2f69f7 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -20,15 +20,6 @@ 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; -} - -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'); @@ -64,7 +55,9 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } let id = !activitypub.helpers.isUri(input) ? input.id : input; - const lockStatus = await lock(id); + + 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; @@ -78,7 +71,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { let chain; let context = await activitypub.contexts.get(uid, id); if (context.tid) { - await unlock(id); const { tid } = context; return { tid, count: 0 }; } else if (context.context) { @@ -99,7 +91,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // Can't resolve — give up. if (!chain.length) { - await unlock(id); return null; } @@ -122,7 +113,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (tid && members.every(Boolean)) { // All cached, return early. activitypub.helpers.log('[notes/assert] No new notes to process.'); - await unlock(id); return { tid, count: 0 }; } @@ -196,7 +186,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`); } - await unlock(id); return null; } @@ -237,7 +226,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { unprocessed.shift(); } catch (e) { activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); - await unlock(id); return null; } @@ -273,16 +261,14 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } } - await Promise.all([ - Notes.syncUserInboxes(tid, uid), - unlock(id), - ]); - + await Notes.syncUserInboxes(tid, uid); return { tid, count }; } catch (e) { - winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}), releasing lock.`); - await unlock(id); + 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); } }; diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js index 4504f83d44..45bfffa4b9 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -98,7 +98,7 @@ middleware.assertPayload = helpers.try(async function (req, res, next) { activitypub.helpers.log(`[middleware/activitypub] Blocked incoming activity from ${hostname}.`); return res.sendStatus(403); } - await db.sortedSetAdd('instances:lastSeen', Date.now(), hostname); + await activitypub.instances.log(hostname); // Origin checking if (typeof object !== 'string' && object.hasOwnProperty('id')) { From be9212b59fc97ecb5519efeadb803a3a77dd1486 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2025 10:56:35 -0400 Subject: [PATCH 409/828] 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 --- src/activitypub/index.js | 27 ++++++++++++++++++++++++++- src/activitypub/instances.js | 2 +- src/middleware/activitypub.js | 4 ++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 167eb16d15..6b51236bfa 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -152,7 +152,23 @@ ActivityPub.resolveInboxes = async (ids) => { 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) => { @@ -305,6 +321,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, 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/middleware/activitypub.js b/src/middleware/activitypub.js index 45bfffa4b9..730b4e78af 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -93,12 +93,12 @@ middleware.assertPayload = helpers.try(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 activitypub.instances.log(hostname); + activitypub.instances.log(hostname); // Origin checking if (typeof object !== 'string' && object.hasOwnProperty('id')) { From d122bf4a985e180e60196bdfaaa8e3bf035d1c12 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2025 12:43:11 -0400 Subject: [PATCH 410/828] 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. --- src/activitypub/mocks.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 380823fa31..5cbd7876a7 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -603,7 +603,6 @@ Mocks.notes.public = async (post) => { let inReplyTo = null; let tag = null; let followersUrl; - const isMainPost = post.pid === post.topic.mainPid; let name = null; ({ titleRaw: name } = await topics.getTopicFields(post.tid, ['title'])); @@ -716,7 +715,9 @@ Mocks.notes.public = async (post) => { }); // Special handling for main posts (as:Article w/ as:Note preview) - const noteAttachment = isMainPost ? [...attachment] : null; + const plaintext = posts.sanitizePlaintext(content); + const isArticle = post.pid === post.topic.mainPid && plaintext.length > 500; + const noteAttachment = isArticle ? [...attachment] : null; const [uploads, thumbs] = await Promise.all([ posts.uploads.listWithSizes(post.pid), topics.getTopicField(post.tid, 'thumbs'), @@ -748,7 +749,7 @@ Mocks.notes.public = async (post) => { attachment = normalizeAttachment(attachment); let preview; let summary = null; - if (isMainPost) { + if (isArticle) { preview = { type: 'Note', attributedTo: `${nconf.get('url')}/uid/${post.user.uid}`, @@ -798,7 +799,7 @@ Mocks.notes.public = async (post) => { let object = { '@context': 'https://www.w3.org/ns/activitystreams', id, - type: isMainPost ? 'Article' : 'Note', + type: isArticle ? 'Article' : 'Note', to: Array.from(to), cc: Array.from(cc), inReplyTo, From f9edb13f6209b075d4a53c130d1bba166ae188fa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2025 14:43:04 -0400 Subject: [PATCH 411/828] fix: missing actor assertion on 1b12 announced upboat --- src/activitypub/inbox.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 754720f208..ea5b032a1a 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -298,6 +298,7 @@ inbox.announce = async (req) => { 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'); From 3f8ff7139fc74518ae09576e84ee8cc58a491fe7 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 21 Sep 2025 09:20:01 +0000 Subject: [PATCH 412/828] Latest translations and fallbacks --- public/language/hr/register.json | 2 +- public/language/hr/user.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) 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/user.json b/public/language/hr/user.json index e5d7875220..f6d5802e04 100644 --- a/public/language/hr/user.json +++ b/public/language/hr/user.json @@ -225,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 From 4d68e3fe145e49f38d1f3dc2b45d8409bb3945f6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Sep 2025 11:56:55 -0400 Subject: [PATCH 413/828] fix: re-jig handling of ap tag values so that only hashtags are considered (not Piefed community tags, etc.) --- src/activitypub/notes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index ef4abe9add..832b8d152d 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -38,11 +38,12 @@ Notes._normalizeTags = async (tag, cid) => { } tags = tags + .filter(({ type }) => type === 'Hashtag') .map((tag) => { tag.name = tag.name.startsWith('#') ? tag.name.slice(1) : tag.name; return tag; }) - .filter(o => o.type === 'Hashtag' && !systemTags.includes(o.name)) + .filter(({ name }) => !systemTags.includes(name)) .map(t => t.name); if (tags.length > maxTags) { From d0c058263f5ffbdd7be821d15b3fd3bcbdb5fa12 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Sep 2025 12:14:14 -0400 Subject: [PATCH 414/828] 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. --- src/activitypub/notes.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 832b8d152d..e1fc16c175 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -117,9 +117,8 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { 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)) { + 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.'); await unlock(id); @@ -212,7 +211,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } return post; - }).filter((p, idx) => !members[idx]); + }).filter((p, idx) => !exists[idx]); const count = unprocessed.length; activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); From 218f5eabe2a29a18089dae05526c7ca8b2bfca69 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Sep 2025 10:58:00 -0400 Subject: [PATCH 415/828] fix: #13668, privilege checking on topic create for remote users; was not properly checking against fediverse pseudo-user --- src/topics/create.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/topics/create.js b/src/topics/create.js index 2f41c822b1..43001a9bd3 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -89,11 +89,12 @@ module.exports = function (Topics) { Topics.post = async function (data) { data = await plugins.hooks.fire('filter:topic.post', data); const { uid } = data; + const remoteUid = !utils.isNumber(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), ]); From 6e84e35fc3ac3c02d5741d3255d5286b8906561c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:40:03 -0400 Subject: [PATCH 416/828] fix(deps): update fontsource monorepo (#13663) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/package.json b/install/package.json index b0ef7ebf50..460dbc4564 100644 --- a/install/package.json +++ b/install/package.json @@ -29,9 +29,9 @@ }, "dependencies": { "@adactive/bootstrap-tagsinput": "0.8.2", - "@fontsource-utils/scss": "0.2.1", - "@fontsource/inter": "5.2.7", - "@fontsource/poppins": "5.2.6", + "@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", "@nodebb/spider-detector": "2.0.3", From 1b5804e1c9f118ebed6e88a19a3589f92840af0a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:16:28 -0400 Subject: [PATCH 417/828] fix(deps): update dependency sass to v1.93.2 (#13674) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 460dbc4564..b28334d3d5 100644 --- a/install/package.json +++ b/install/package.json @@ -128,7 +128,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.92.1", + "sass": "1.93.2", "satori": "0.18.3", "sbd": "^1.0.19", "semver": "7.7.2", From df9d637c139b8c6f35bc12c688786053168daf1b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:16:38 -0400 Subject: [PATCH 418/828] chore(deps): update dependency sass-embedded to v1.93.2 (#13673) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b28334d3d5..f5a4e36c86 100644 --- a/install/package.json +++ b/install/package.json @@ -179,7 +179,7 @@ "smtp-server": "3.14.0" }, "optionalDependencies": { - "sass-embedded": "1.92.1" + "sass-embedded": "1.93.2" }, "resolutions": { "*/jquery": "3.7.1" From 00d80616d9d4c2e83c99babd55b768d3c8516bc3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:17:28 -0400 Subject: [PATCH 419/828] fix(deps): update dependency lru-cache to v11.2.2 (#13669) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f5a4e36c86..13cce76075 100644 --- a/install/package.json +++ b/install/package.json @@ -88,7 +88,7 @@ "jsonwebtoken": "9.0.2", "lodash": "4.17.21", "logrotate-stream": "0.2.9", - "lru-cache": "11.2.1", + "lru-cache": "11.2.2", "mime": "3.0.0", "mkdirp": "3.0.1", "mongodb": "6.19.0", From d0921ea5a231d39d25b43b72027acc8a32399a46 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 24 Sep 2025 09:20:40 +0000 Subject: [PATCH 420/828] Latest translations and fallbacks --- public/language/vi/admin/manage/categories.json | 4 ++-- public/language/vi/admin/settings/activitypub.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index d1e14a6e9b..efc0cee1dd 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -17,8 +17,8 @@ "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": "Topic Template", - "topic-template.help": "Define a template for new topics created in this category.", + "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", diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index d7290381d3..c1a9a54a7e 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -23,8 +23,8 @@ "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": "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.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", From 8c553b1854a65a0df02fb36f97e8118a99b6611d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 24 Sep 2025 10:00:57 -0400 Subject: [PATCH 421/828] fix: regression 218f5ea from via, stricter check on whether the calling user is a remote uid --- src/topics/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/create.js b/src/topics/create.js index 43001a9bd3..8f347c736a 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -89,7 +89,7 @@ module.exports = function (Topics) { Topics.post = async function (data) { data = await plugins.hooks.fire('filter:topic.post', data); const { uid } = data; - const remoteUid = !utils.isNumber(uid); + const remoteUid = activitypub.helpers.isUri(uid); const [categoryExists, canCreate, canTag, isAdmin] = await Promise.all([ parseInt(data.cid, 10) > 0 ? categories.exists(data.cid) : true, From 175dc2090654a70add7a7cca401688da93525c63 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 24 Sep 2025 10:42:16 -0400 Subject: [PATCH 422/828] fix: #13676, bug where nested remote categories could not be removed --- src/controllers/admin/categories.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index e6bb7aaa41..a74b362765 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -218,7 +218,9 @@ categoriesController.removeRemote = async function (req, res) { return helpers.formatApiResponse(400, res); } - await db.sortedSetRemove('cid:0:children', req.params.cid); - cache.del('cid:0:children'); + 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); }; From bd80b77a7a06cacaeb527fe8e6896af7dffb56f9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 24 Sep 2025 11:25:20 -0400 Subject: [PATCH 423/828] feat: ability to nickname remote categories, closes #13677 --- .../en-GB/admin/manage/categories.json | 4 ++ public/src/admin/manage/categories.js | 37 +++++++++++++++++-- src/categories/data.js | 6 ++- src/controllers/admin/categories.js | 15 +++++++- src/routes/admin.js | 1 + .../partials/categories/category-rows.tpl | 2 + 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index c50edfc3df..5478beab39 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -81,7 +81,21 @@ define('admin/manage/categories', [ }); }); - $('.categories').on('click', 'a[data-action="remove"]', Categories.removeCategory); + $('.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); @@ -181,9 +195,24 @@ define('admin/manage/categories', [ }); }; - Categories.removeCategory = function () { - const cid = this.getAttribute('data-cid'); - api.del(`/api/admin/manage/categories/${encodeURIComponent(cid)}`).then(ajaxify.refresh); + 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) { diff --git a/src/categories/data.js b/src/categories/data.js index 97c7e3a0c0..dc4467ffa3 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -117,7 +117,7 @@ function modifyCategory(category, fields) { db.parseIntFields(category, intFields, fields); - const escapeFields = ['name', 'description', 'federatedDescription', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'class', 'link']; + const escapeFields = ['name', 'nickname', 'description', 'federatedDescription', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'class', 'link']; escapeFields.forEach((field) => { if (category.hasOwnProperty(field)) { category[field] = validator.escape(String(category[field] || '')); @@ -139,4 +139,8 @@ function modifyCategory(category, fields) { if (category.description) { category.descriptionParsed = category.descriptionParsed || category.description; } + + if (category.nickname) { + category.name = category.nickname; + } } diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index a74b362765..5e503b1964 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -65,9 +65,9 @@ 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', ]; let categoriesData = await categories.getCategoriesFields(cids, fields); ({ categories: categoriesData } = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields })); @@ -213,6 +213,17 @@ categoriesController.addRemote = async function (req, res) { 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); diff --git a/src/routes/admin.js b/src/routes/admin.js index 967746b304..5421b4d0ef 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -82,6 +82,7 @@ function apiRoutes(router, name, middleware, controllers) { 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 multer = require('multer'); diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl index 4cef2ae416..5ed85f9da9 100644 --- a/src/views/admin/partials/categories/category-rows.tpl +++ b/src/views/admin/partials/categories/category-rows.tpl @@ -43,6 +43,8 @@
  • [[admin/manage/categories:analytics]]
  • [[admin/manage/categories:privileges]]
  • [[admin/manage/categories:federation]]
  • + {{{ else }}} +
  • [[admin/manage/categories:rename]]
  • {{{ end }}}
  • [[admin/manage/categories:set-order]]
  • From 8730073af14a605a73c2848075472da640666591 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 24 Sep 2025 15:25:46 +0000 Subject: [PATCH 424/828] chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories --- public/language/ar/admin/manage/categories.json | 4 ++++ public/language/az/admin/manage/categories.json | 4 ++++ public/language/bg/admin/manage/categories.json | 4 ++++ public/language/bn/admin/manage/categories.json | 4 ++++ public/language/cs/admin/manage/categories.json | 4 ++++ public/language/da/admin/manage/categories.json | 4 ++++ public/language/de/admin/manage/categories.json | 4 ++++ public/language/el/admin/manage/categories.json | 4 ++++ public/language/en-US/admin/manage/categories.json | 4 ++++ public/language/en-x-pirate/admin/manage/categories.json | 4 ++++ public/language/es/admin/manage/categories.json | 4 ++++ public/language/et/admin/manage/categories.json | 4 ++++ public/language/fa-IR/admin/manage/categories.json | 4 ++++ public/language/fi/admin/manage/categories.json | 4 ++++ public/language/fr/admin/manage/categories.json | 4 ++++ public/language/gl/admin/manage/categories.json | 4 ++++ public/language/he/admin/manage/categories.json | 4 ++++ public/language/hr/admin/manage/categories.json | 4 ++++ public/language/hu/admin/manage/categories.json | 4 ++++ public/language/hy/admin/manage/categories.json | 4 ++++ public/language/id/admin/manage/categories.json | 4 ++++ public/language/it/admin/manage/categories.json | 4 ++++ public/language/ja/admin/manage/categories.json | 4 ++++ public/language/ko/admin/manage/categories.json | 4 ++++ public/language/lt/admin/manage/categories.json | 4 ++++ public/language/lv/admin/manage/categories.json | 4 ++++ public/language/ms/admin/manage/categories.json | 4 ++++ public/language/nb/admin/manage/categories.json | 4 ++++ public/language/nl/admin/manage/categories.json | 4 ++++ public/language/nn-NO/admin/manage/categories.json | 4 ++++ public/language/pl/admin/manage/categories.json | 4 ++++ public/language/pt-BR/admin/manage/categories.json | 4 ++++ public/language/pt-PT/admin/manage/categories.json | 4 ++++ public/language/ro/admin/manage/categories.json | 4 ++++ public/language/ru/admin/manage/categories.json | 4 ++++ public/language/rw/admin/manage/categories.json | 4 ++++ public/language/sc/admin/manage/categories.json | 4 ++++ public/language/sk/admin/manage/categories.json | 4 ++++ public/language/sl/admin/manage/categories.json | 4 ++++ public/language/sq-AL/admin/manage/categories.json | 4 ++++ public/language/sr/admin/manage/categories.json | 4 ++++ public/language/sv/admin/manage/categories.json | 4 ++++ public/language/th/admin/manage/categories.json | 4 ++++ public/language/tr/admin/manage/categories.json | 4 ++++ public/language/uk/admin/manage/categories.json | 4 ++++ public/language/ur/admin/manage/categories.json | 4 ++++ public/language/vi/admin/manage/categories.json | 4 ++++ public/language/zh-CN/admin/manage/categories.json | 4 ++++ public/language/zh-TW/admin/manage/categories.json | 4 ++++ 49 files changed, 196 insertions(+) diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json index bf68795076..6dad1fda9f 100644 --- a/public/language/ar/admin/manage/categories.json +++ b/public/language/ar/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json index c07a127295..2badd19450 100644 --- a/public/language/az/admin/manage/categories.json +++ b/public/language/az/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index cd847e56bf..6826a56407 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Добавяне на локална категория", "add-remote-category": "Добавяне на отдалечена категория", "remove": "Премахване", + "rename": "Rename", "jump-to": "Прехвърляне към…", "settings": "Настройки на категорията", "edit-category": "Редактиране на категорията", @@ -111,6 +112,9 @@ "alert.create": "Създаване на категория", "alert.add": "Добавяне на категория", "alert.add-help": "Отдалечена категория може да бъде добавена в списъка с категории, като посочите нейния идентификатор.

    Забележка – отдалечената категория може да не отразява всички публикувани теми, освен ако поне един локален потребител не я следи/наблюдава.", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter the 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/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/bn/admin/manage/categories.json +++ b/public/language/bn/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/cs/admin/manage/categories.json b/public/language/cs/admin/manage/categories.json index 5277ddf50a..e2c23f4c4d 100644 --- a/public/language/cs/admin/manage/categories.json +++ b/public/language/cs/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/da/admin/manage/categories.json b/public/language/da/admin/manage/categories.json index a491c842cc..558a24ed5e 100644 --- a/public/language/da/admin/manage/categories.json +++ b/public/language/da/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json index ef9cf71289..388f49bbb7 100644 --- a/public/language/de/admin/manage/categories.json +++ b/public/language/de/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/el/admin/manage/categories.json +++ b/public/language/el/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/en-US/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/en-US/admin/manage/categories.json +++ b/public/language/en-US/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/en-x-pirate/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/en-x-pirate/admin/manage/categories.json +++ b/public/language/en-x-pirate/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json index a2eb86107f..e8d5519c31 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/et/admin/manage/categories.json b/public/language/et/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/et/admin/manage/categories.json +++ b/public/language/et/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/fa-IR/admin/manage/categories.json index 5771d6a3fe..77970e4785 100644 --- a/public/language/fa-IR/admin/manage/categories.json +++ b/public/language/fa-IR/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/fi/admin/manage/categories.json b/public/language/fi/admin/manage/categories.json index e567de337b..7a7c14acb5 100644 --- a/public/language/fi/admin/manage/categories.json +++ b/public/language/fi/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/fr/admin/manage/categories.json b/public/language/fr/admin/manage/categories.json index c346a8a60b..1a8fc79c00 100644 --- a/public/language/fr/admin/manage/categories.json +++ b/public/language/fr/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/gl/admin/manage/categories.json b/public/language/gl/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/gl/admin/manage/categories.json +++ b/public/language/gl/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index 5ce040d841..cae48f5b58 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Add Local category", "add-remote-category": "Add Remote category", "remove": "Remove", + "rename": "Rename", "jump-to": "קפיצה אל...", "settings": "הגדרות קטגוריות", "edit-category": "עריכת קטגוריה", @@ -111,6 +112,9 @@ "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 the 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/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json index e3dcc1ff04..8055d5c45e 100644 --- a/public/language/hr/admin/manage/categories.json +++ b/public/language/hr/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/hu/admin/manage/categories.json b/public/language/hu/admin/manage/categories.json index 8c9378a9c9..3dab541e3a 100644 --- a/public/language/hu/admin/manage/categories.json +++ b/public/language/hu/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/hy/admin/manage/categories.json b/public/language/hy/admin/manage/categories.json index b47fec23be..c69a8758aa 100644 --- a/public/language/hy/admin/manage/categories.json +++ b/public/language/hy/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Add Local category", "add-remote-category": "Add Remote category", "remove": "Remove", + "rename": "Rename", "jump-to": "Անցնել դեպի․․․", "settings": "Կատեգորիայի կարգավորումներ", "edit-category": "Խմբագրել Կատեգորիան", @@ -111,6 +112,9 @@ "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 the 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/id/admin/manage/categories.json b/public/language/id/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/id/admin/manage/categories.json +++ b/public/language/id/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index e6abf93576..1f23efb0bf 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Aggiungi categoria locale", "add-remote-category": "Aggiungi categoria remota", "remove": "Rimuovi", + "rename": "Rename", "jump-to": "Vai a...", "settings": "Impostazioni Categoria", "edit-category": "Modifica categoria", @@ -111,6 +112,9 @@ "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": "Rename a Remote Category", + "alert.rename-help": "Please enter the 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": "

    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/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json index dbd1c2ed86..4ac0a1a088 100644 --- a/public/language/ja/admin/manage/categories.json +++ b/public/language/ja/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/ko/admin/manage/categories.json index 62c37899a0..3ba1e6b434 100644 --- a/public/language/ko/admin/manage/categories.json +++ b/public/language/ko/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Add Local category", "add-remote-category": "Add Remote category", "remove": "Remove", + "rename": "Rename", "jump-to": "이동...", "settings": "카테고리 설정", "edit-category": "카테고리 수정", @@ -111,6 +112,9 @@ "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 the 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/lt/admin/manage/categories.json b/public/language/lt/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/lt/admin/manage/categories.json +++ b/public/language/lt/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/lv/admin/manage/categories.json b/public/language/lv/admin/manage/categories.json index 4746223d60..6ecfca5e1e 100644 --- a/public/language/lv/admin/manage/categories.json +++ b/public/language/lv/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/ms/admin/manage/categories.json b/public/language/ms/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/ms/admin/manage/categories.json +++ b/public/language/ms/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json index d9333c679f..ce4ba41f88 100644 --- a/public/language/nb/admin/manage/categories.json +++ b/public/language/nb/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index bdf146510e..cfad2842cb 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index dad82a5ee3..e6eeb673a4 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index 4e6c2f3e9f..54efd54518 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Dodaj lokalną kategorię", "add-remote-category": "Dodaj zdalną kategorię", "remove": "Usuń", + "rename": "Rename", "jump-to": "Skocz do...", "settings": "Ustawienia kategorii", "edit-category": "Edytuj kategorię", @@ -111,6 +112,9 @@ "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": "Rename a Remote Category", + "alert.rename-help": "Please enter the 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": "

    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/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json index 3b41afca38..5adceac9f5 100644 --- a/public/language/pt-BR/admin/manage/categories.json +++ b/public/language/pt-BR/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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-PT/admin/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json index 3fb24317f8..38e4e941dd 100644 --- a/public/language/pt-PT/admin/manage/categories.json +++ b/public/language/pt-PT/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json index 3ab043db8f..a6208e7dbe 100644 --- a/public/language/ru/admin/manage/categories.json +++ b/public/language/ru/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/rw/admin/manage/categories.json b/public/language/rw/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/rw/admin/manage/categories.json +++ b/public/language/rw/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/sc/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/sc/admin/manage/categories.json +++ b/public/language/sc/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/sk/admin/manage/categories.json b/public/language/sk/admin/manage/categories.json index 34ad2e086e..2bf14fba46 100644 --- a/public/language/sk/admin/manage/categories.json +++ b/public/language/sk/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/sl/admin/manage/categories.json b/public/language/sl/admin/manage/categories.json index cde764efa8..8f9f4c1a2d 100644 --- a/public/language/sl/admin/manage/categories.json +++ b/public/language/sl/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/sq-AL/admin/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/sq-AL/admin/manage/categories.json +++ b/public/language/sq-AL/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/sr/admin/manage/categories.json index 7532cd9cd1..3d1b8c68dc 100644 --- a/public/language/sr/admin/manage/categories.json +++ b/public/language/sr/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/sv/admin/manage/categories.json index 7287d92af4..c86a889a52 100644 --- a/public/language/sv/admin/manage/categories.json +++ b/public/language/sv/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/categories.json b/public/language/th/admin/manage/categories.json index f28cdc9cb0..57d25baac5 100644 --- a/public/language/th/admin/manage/categories.json +++ b/public/language/th/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Add Local category", "add-remote-category": "Add Remote category", "remove": "Remove", + "rename": "Rename", "jump-to": "ไปที่...", "settings": "การตั้งค่าหมวดหมู่", "edit-category": "แก้ไขหมวดหมู่", @@ -111,6 +112,9 @@ "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 the 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/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json index 4bd70dc4ab..0c6ae24494 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/uk/admin/manage/categories.json b/public/language/uk/admin/manage/categories.json index 556f750f44..a9dcdf8830 100644 --- a/public/language/uk/admin/manage/categories.json +++ b/public/language/uk/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json index 175a81965f..1cbb648963 100644 --- a/public/language/ur/admin/manage/categories.json +++ b/public/language/ur/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "Add Local category", "add-remote-category": "Add Remote category", "remove": "Remove", + "rename": "Rename", "jump-to": "پر جائیں…", "settings": "زمرہ کی ترتیبات", "edit-category": "زمرہ ترمیم کریں", @@ -111,6 +112,9 @@ "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 the 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/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index efc0cee1dd..7e79676cf6 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -4,6 +4,7 @@ "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": "Rename", "jump-to": "Chuyển tới...", "settings": "Cài Đặt Chuyên Mục", "edit-category": "Sửa Danh Mục", @@ -111,6 +112,9 @@ "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": "Rename a Remote Category", + "alert.rename-help": "Please enter the 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": "

    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/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 8e07c202d7..0a1a96fab6 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -4,6 +4,7 @@ "add-local-category": "添加本地版块", "add-remote-category": "添加远程版块", "remove": "移除", + "rename": "Rename", "jump-to": "跳转…", "settings": "版块设置", "edit-category": "编辑版块", @@ -111,6 +112,9 @@ "alert.create": "创建一个版块", "alert.add": "添加一个版块", "alert.add-help": "可以通过指定其句柄将远程版块添加到版块列表中。

    注: — 远程版块可能无法反映所有已发布的主题,除非至少有一名本地用户关注或订阅该版块。", + "alert.rename": "Rename a Remote Category", + "alert.rename-help": "Please enter the 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/categories.json b/public/language/zh-TW/admin/manage/categories.json index 25f495b009..daffec9006 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -4,6 +4,7 @@ "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", @@ -111,6 +112,9 @@ "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 the 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": "設定已複製!", From cf3964be6f7a51355e543b4e92e26b7f02b76f7a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 24 Sep 2025 13:48:57 -0400 Subject: [PATCH 425/828] chore: fix grammatical error in language string --- public/language/en-GB/admin/manage/categories.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", From 6055b345e1e55d33320e7b5690f51b6bcda23bd3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 24 Sep 2025 17:49:25 +0000 Subject: [PATCH 426/828] chore(i18n): fallback strings for new resources: nodebb.admin-manage-categories --- public/language/ar/admin/manage/categories.json | 2 +- public/language/az/admin/manage/categories.json | 2 +- public/language/bg/admin/manage/categories.json | 8 ++++---- public/language/bn/admin/manage/categories.json | 2 +- public/language/cs/admin/manage/categories.json | 2 +- public/language/da/admin/manage/categories.json | 2 +- public/language/de/admin/manage/categories.json | 2 +- public/language/el/admin/manage/categories.json | 2 +- public/language/en-US/admin/manage/categories.json | 2 +- public/language/en-x-pirate/admin/manage/categories.json | 2 +- public/language/es/admin/manage/categories.json | 2 +- public/language/et/admin/manage/categories.json | 2 +- public/language/fa-IR/admin/manage/categories.json | 2 +- public/language/fi/admin/manage/categories.json | 2 +- public/language/fr/admin/manage/categories.json | 2 +- public/language/gl/admin/manage/categories.json | 2 +- public/language/he/admin/manage/categories.json | 2 +- public/language/hr/admin/manage/categories.json | 2 +- public/language/hu/admin/manage/categories.json | 2 +- public/language/hy/admin/manage/categories.json | 2 +- public/language/id/admin/manage/categories.json | 2 +- public/language/it/admin/manage/categories.json | 2 +- public/language/ja/admin/manage/categories.json | 2 +- public/language/ko/admin/manage/categories.json | 2 +- public/language/lt/admin/manage/categories.json | 2 +- public/language/lv/admin/manage/categories.json | 2 +- public/language/ms/admin/manage/categories.json | 2 +- public/language/nb/admin/manage/categories.json | 2 +- public/language/nl/admin/manage/categories.json | 2 +- public/language/nn-NO/admin/manage/categories.json | 2 +- public/language/pl/admin/manage/categories.json | 2 +- public/language/pt-BR/admin/manage/categories.json | 2 +- public/language/pt-PT/admin/manage/categories.json | 2 +- public/language/ro/admin/manage/categories.json | 2 +- public/language/ru/admin/manage/categories.json | 2 +- public/language/rw/admin/manage/categories.json | 2 +- public/language/sc/admin/manage/categories.json | 2 +- public/language/sk/admin/manage/categories.json | 2 +- public/language/sl/admin/manage/categories.json | 2 +- public/language/sq-AL/admin/manage/categories.json | 2 +- public/language/sr/admin/manage/categories.json | 2 +- public/language/sv/admin/manage/categories.json | 2 +- public/language/th/admin/manage/categories.json | 2 +- public/language/tr/admin/manage/categories.json | 2 +- public/language/uk/admin/manage/categories.json | 2 +- public/language/ur/admin/manage/categories.json | 2 +- public/language/vi/admin/manage/categories.json | 2 +- public/language/zh-CN/admin/manage/categories.json | 2 +- public/language/zh-TW/admin/manage/categories.json | 2 +- 49 files changed, 52 insertions(+), 52 deletions(-) diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json index 6dad1fda9f..42282e425d 100644 --- a/public/language/ar/admin/manage/categories.json +++ b/public/language/ar/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json index 2badd19450..1fc1544231 100644 --- a/public/language/az/admin/manage/categories.json +++ b/public/language/az/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index 6826a56407..f378442de1 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -4,7 +4,7 @@ "add-local-category": "Добавяне на локална категория", "add-remote-category": "Добавяне на отдалечена категория", "remove": "Премахване", - "rename": "Rename", + "rename": "Преименуване", "jump-to": "Прехвърляне към…", "settings": "Настройки на категорията", "edit-category": "Редактиране на категорията", @@ -112,9 +112,9 @@ "alert.create": "Създаване на категория", "alert.add": "Добавяне на категория", "alert.add-help": "Отдалечена категория може да бъде добавена в списъка с категории, като посочите нейния идентификатор.

    Забележка – отдалечената категория може да не отразява всички публикувани теми, освен ако поне един локален потребител не я следи/наблюдава.", - "alert.rename": "Rename a Remote Category", - "alert.rename-help": "Please enter the 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.rename": "Преименуване на отдалечена категория", + "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.confirm-remove": "Наистина ли искате да премахнете тази категория? Можете да я добавите отново по всяко време.", "alert.confirm-purge": "

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

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

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

    ", "alert.purge-success": "Категорията е изтрита!", "alert.copy-success": "Настройките са копирани!", diff --git a/public/language/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/bn/admin/manage/categories.json +++ b/public/language/bn/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/cs/admin/manage/categories.json b/public/language/cs/admin/manage/categories.json index e2c23f4c4d..1623ea6e66 100644 --- a/public/language/cs/admin/manage/categories.json +++ b/public/language/cs/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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.", diff --git a/public/language/da/admin/manage/categories.json b/public/language/da/admin/manage/categories.json index 558a24ed5e..8985fc7d64 100644 --- a/public/language/da/admin/manage/categories.json +++ b/public/language/da/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json index 388f49bbb7..968badebce 100644 --- a/public/language/de/admin/manage/categories.json +++ b/public/language/de/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/el/admin/manage/categories.json +++ b/public/language/el/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/en-US/admin/manage/categories.json b/public/language/en-US/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/en-US/admin/manage/categories.json +++ b/public/language/en-US/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/en-x-pirate/admin/manage/categories.json b/public/language/en-x-pirate/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/en-x-pirate/admin/manage/categories.json +++ b/public/language/en-x-pirate/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json index e8d5519c31..5cd865c742 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/et/admin/manage/categories.json b/public/language/et/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/et/admin/manage/categories.json +++ b/public/language/et/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/fa-IR/admin/manage/categories.json b/public/language/fa-IR/admin/manage/categories.json index 77970e4785..2f2a16a5ec 100644 --- a/public/language/fa-IR/admin/manage/categories.json +++ b/public/language/fa-IR/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/fi/admin/manage/categories.json b/public/language/fi/admin/manage/categories.json index 7a7c14acb5..42aea3b6bb 100644 --- a/public/language/fi/admin/manage/categories.json +++ b/public/language/fi/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/fr/admin/manage/categories.json b/public/language/fr/admin/manage/categories.json index 1a8fc79c00..fc33293483 100644 --- a/public/language/fr/admin/manage/categories.json +++ b/public/language/fr/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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 !", diff --git a/public/language/gl/admin/manage/categories.json b/public/language/gl/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/gl/admin/manage/categories.json +++ b/public/language/gl/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index cae48f5b58..f52d852820 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "הקטגוריה נמחקה!", diff --git a/public/language/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json index 8055d5c45e..e35b386134 100644 --- a/public/language/hr/admin/manage/categories.json +++ b/public/language/hr/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/hu/admin/manage/categories.json b/public/language/hu/admin/manage/categories.json index 3dab541e3a..92b0b26507 100644 --- a/public/language/hu/admin/manage/categories.json +++ b/public/language/hu/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/hy/admin/manage/categories.json b/public/language/hy/admin/manage/categories.json index c69a8758aa..8a69bcad22 100644 --- a/public/language/hy/admin/manage/categories.json +++ b/public/language/hy/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "Կատեգորիան մաքրվել է:", diff --git a/public/language/id/admin/manage/categories.json b/public/language/id/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/id/admin/manage/categories.json +++ b/public/language/id/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index 1f23efb0bf..dd4562df02 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -113,7 +113,7 @@ "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": "Rename a Remote Category", - "alert.rename-help": "Please enter the a new name for this category. Leave blank to restore original name.", + "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": "

    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!", diff --git a/public/language/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json index 4ac0a1a088..9e2b43ca2a 100644 --- a/public/language/ja/admin/manage/categories.json +++ b/public/language/ja/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "カテゴリが切り離されました!", diff --git a/public/language/ko/admin/manage/categories.json b/public/language/ko/admin/manage/categories.json index 3ba1e6b434..5d15d42604 100644 --- a/public/language/ko/admin/manage/categories.json +++ b/public/language/ko/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "카테고리를 정리했습니다!", diff --git a/public/language/lt/admin/manage/categories.json b/public/language/lt/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/lt/admin/manage/categories.json +++ b/public/language/lt/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/lv/admin/manage/categories.json b/public/language/lv/admin/manage/categories.json index 6ecfca5e1e..5bd9db9d4f 100644 --- a/public/language/lv/admin/manage/categories.json +++ b/public/language/lv/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/ms/admin/manage/categories.json b/public/language/ms/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/ms/admin/manage/categories.json +++ b/public/language/ms/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json index ce4ba41f88..fb07c084b9 100644 --- a/public/language/nb/admin/manage/categories.json +++ b/public/language/nb/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index cfad2842cb..96f61da1a3 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index e6eeb673a4..7d175abcf3 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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", diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index 54efd54518..34d5839353 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -113,7 +113,7 @@ "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": "Rename a Remote Category", - "alert.rename-help": "Please enter the a new name for this category. Leave blank to restore original name.", + "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": "

    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!", diff --git a/public/language/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json index 5adceac9f5..8f8d3bc66e 100644 --- a/public/language/pt-BR/admin/manage/categories.json +++ b/public/language/pt-BR/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/pt-PT/admin/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json index 38e4e941dd..7ec4c606d0 100644 --- a/public/language/pt-PT/admin/manage/categories.json +++ b/public/language/pt-PT/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json index a6208e7dbe..145aaebd0d 100644 --- a/public/language/ru/admin/manage/categories.json +++ b/public/language/ru/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "Категория очищена!", diff --git a/public/language/rw/admin/manage/categories.json b/public/language/rw/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/rw/admin/manage/categories.json +++ b/public/language/rw/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/sc/admin/manage/categories.json b/public/language/sc/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/sc/admin/manage/categories.json +++ b/public/language/sc/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/sk/admin/manage/categories.json b/public/language/sk/admin/manage/categories.json index 2bf14fba46..ead53a4cce 100644 --- a/public/language/sk/admin/manage/categories.json +++ b/public/language/sk/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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á!", diff --git a/public/language/sl/admin/manage/categories.json b/public/language/sl/admin/manage/categories.json index 8f9f4c1a2d..d32877156b 100644 --- a/public/language/sl/admin/manage/categories.json +++ b/public/language/sl/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/sq-AL/admin/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/sq-AL/admin/manage/categories.json +++ b/public/language/sq-AL/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/sr/admin/manage/categories.json b/public/language/sr/admin/manage/categories.json index 3d1b8c68dc..38037f7206 100644 --- a/public/language/sr/admin/manage/categories.json +++ b/public/language/sr/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/sv/admin/manage/categories.json b/public/language/sv/admin/manage/categories.json index c86a889a52..0064d996e3 100644 --- a/public/language/sv/admin/manage/categories.json +++ b/public/language/sv/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/th/admin/manage/categories.json b/public/language/th/admin/manage/categories.json index 57d25baac5..81e8ac0f4a 100644 --- a/public/language/th/admin/manage/categories.json +++ b/public/language/th/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json index 0c6ae24494..cc0566b075 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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!", diff --git a/public/language/uk/admin/manage/categories.json b/public/language/uk/admin/manage/categories.json index a9dcdf8830..2903206195 100644 --- a/public/language/uk/admin/manage/categories.json +++ b/public/language/uk/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "Категорію стерто!", diff --git a/public/language/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json index 1cbb648963..e183820111 100644 --- a/public/language/ur/admin/manage/categories.json +++ b/public/language/ur/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "زمرہ حذف ہو گیا!", diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index 7e79676cf6..915e334af3 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -113,7 +113,7 @@ "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": "Rename a Remote Category", - "alert.rename-help": "Please enter the a new name for this category. Leave blank to restore original name.", + "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": "

    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!", diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 0a1a96fab6..07c53a18aa 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -113,7 +113,7 @@ "alert.add": "添加一个版块", "alert.add-help": "可以通过指定其句柄将远程版块添加到版块列表中。

    注: — 远程版块可能无法反映所有已发布的主题,除非至少有一名本地用户关注或订阅该版块。", "alert.rename": "Rename a Remote Category", - "alert.rename-help": "Please enter the a new name for this category. Leave blank to restore original name.", + "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": "版块已删除!", diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json index daffec9006..844fc6153a 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -113,7 +113,7 @@ "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 the a new name for this category. Leave blank to restore original name.", + "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": "版面已刪除!", From 2b987d09ce487bb97891c8c38d639a2e6c216537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 25 Sep 2025 02:03:14 -0400 Subject: [PATCH 427/828] perf: update old upgrade scripts to use bulkSet/Add fix a missing await --- src/upgrades/1.10.2/upgrade_bans_to_hashes.js | 34 ++++++---- src/upgrades/1.10.2/username_email_history.js | 43 +++++++------ .../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 +- 12 files changed, 185 insertions(+), 170 deletions(-) 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; + progress.total = await db.sortedSetCard('users:joindate'); + 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(':')); - } - } - } + 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']), + ]); - await Promise.all(uids.map(async (uid) => { - await Promise.all([ - updateHistory(uid, `user:${uid}:usernames`, 'username'), - updateHistory(uid, `user:${uid}:emails`, 'email'), - ]); - progress.incr(); - })); + 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, }); }, }; From 32d0ee480844b350cd5502922bfe1109ad913d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 25 Sep 2025 02:03:14 -0400 Subject: [PATCH 428/828] perf: update old upgrade scripts to use bulkSet/Add fix a missing await --- src/upgrades/1.10.2/upgrade_bans_to_hashes.js | 34 ++++++---- src/upgrades/1.10.2/username_email_history.js | 43 +++++++------ .../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 +- 12 files changed, 185 insertions(+), 170 deletions(-) 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; + progress.total = await db.sortedSetCard('users:joindate'); + 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(':')); - } - } - } + 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']), + ]); - await Promise.all(uids.map(async (uid) => { - await Promise.all([ - updateHistory(uid, `user:${uid}:usernames`, 'username'), - updateHistory(uid, `user:${uid}:emails`, 'email'), - ]); - progress.incr(); - })); + 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, }); }, }; From 15fdaba5f6d0e88610ae73bfcc2ecf2e3b0ad08c Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 25 Sep 2025 09:20:45 +0000 Subject: [PATCH 429/828] Latest translations and fallbacks --- public/language/bg/admin/manage/categories.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index f378442de1..ee60e90e0c 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -113,7 +113,7 @@ "alert.add": "Добавяне на категория", "alert.add-help": "Отдалечена категория може да бъде добавена в списъка с категории, като посочите нейния идентификатор.

    Забележка – отдалечената категория може да не отразява всички публикувани теми, освен ако поне един локален потребител не я следи/наблюдава.", "alert.rename": "Преименуване на отдалечена категория", - "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", + "alert.rename-help": "Въведете новото име за тази категория. Оставете празно, за да върнете оригиналното име.", "alert.confirm-remove": "Наистина ли искате да премахнете тази категория? Можете да я добавите отново по всяко време.", "alert.confirm-purge": "

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

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

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

    ", "alert.purge-success": "Категорията е изтрита!", From 0a2fa45da1768c175f1821ea79e9eebdfb83faab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 25 Sep 2025 11:02:12 -0400 Subject: [PATCH 430/828] perf: update upgrade script to use bulk methods add missing progress.total --- src/upgrades/1.10.0/view_deleted_privilege.js | 1 + .../1.10.2/fix_category_topic_zsets.js | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) 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, }); }, }; From 7abdfd86ac87bcd4f5df7d17ae0cdf157177a991 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Sep 2025 11:56:38 -0400 Subject: [PATCH 431/828] fix: skip header checking during note assertion if test runner is active --- src/activitypub/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index e1fc16c175..b85d686f1e 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -71,7 +71,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } try { - if (!options.skipChecks) { + if (!(options.skipChecks || process.env.hasOwnProperty('CI'))) { id = (await activitypub.checkHeader(id)) || id; } From 7184507be2b769269bd6846ed85e15f021c940b4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Sep 2025 15:12:52 -0400 Subject: [PATCH 432/828] fix: #13667, record to instances:lastSeen instead of domains:lastSeen --- src/activitypub/index.js | 2 +- test/activitypub/analytics.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 6b51236bfa..459c86c8fe 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -522,7 +522,7 @@ 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}`]), ]); }; diff --git a/test/activitypub/analytics.js b/test/activitypub/analytics.js index 85c8792115..9faf50ddb0 100644 --- a/test/activitypub/analytics.js +++ b/test/activitypub/analytics.js @@ -1,7 +1,7 @@ 'use strict'; -const nconf = require('nconf'); const assert = require('assert'); +const nconf = require('nconf'); const db = require('../../src/database'); const controllers = require('../../src/controllers'); @@ -105,7 +105,7 @@ describe('Analytics', () => { it('should increment the last seen time of that domain', async () => { const id = `https://example.org/activity/${utils.generateUUID()}`; - const before = await db.sortedSetScore('domains:lastSeen', 'example.org'); + const before = await db.sortedSetScore('instances:lastSeen', 'example.org'); await controllers.activitypub.postInbox({ body: { id, @@ -118,7 +118,7 @@ describe('Analytics', () => { }, }, { sendStatus: () => {} }); - const after = await db.sortedSetScore('domains:lastSeen', 'example.org'); + const after = await db.sortedSetScore('instances:lastSeen', 'example.org'); assert(before && after); assert(before < after); From 051043b6820feecc77ea9a885aa24608252426dc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Sep 2025 15:15:24 -0400 Subject: [PATCH 433/828] doc: 'nickname' and 'descriptionParsed' use in categories controller --- public/openapi/read/admin/manage/categories.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/openapi/read/admin/manage/categories.yaml b/public/openapi/read/admin/manage/categories.yaml index bbf8efb8fe..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: From 158780870062f47e2db4be26f7a845e1f3a223fa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Sep 2025 15:18:26 -0400 Subject: [PATCH 434/828] test: short OPs create Notes again --- test/activitypub/actors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index 8c3c02e905..7fc90659a6 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -768,8 +768,8 @@ describe('Controllers', () => { assert.strictEqual(response.statusCode, 200); }); - it('should return a Article type object', () => { - assert.strictEqual(body.type, 'Article'); + it('should return a Note type object', () => { + assert.strictEqual(body.type, 'Note'); }); }); From 3bba9029320d88563986417d115d324f443e0d15 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Sep 2025 15:29:10 -0400 Subject: [PATCH 435/828] test: more fixes for note vs. article --- test/activitypub/feps.js | 6 +++--- test/activitypub/notes.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/activitypub/feps.js b/test/activitypub/feps.js index 76b7d35cf6..1c97a282e8 100644 --- a/test/activitypub/feps.js +++ b/test/activitypub/feps.js @@ -63,20 +63,20 @@ describe('FEPs', () => { activitypub._sent.clear(); }); - it('should have federated out both Announce(Create(Article)) and Announce(Article)', () => { + it('should have federated out both Announce(Create(Note)) and Announce(Note)', () => { const activities = Array.from(activitypub._sent); const test1 = activities.some((activity) => { [, activity] = activity; return activity.type === 'Announce' && activity.object && activity.object.type === 'Create' && - activity.object.object && activity.object.object.type === 'Article'; + activity.object.object && activity.object.object.type === 'Note'; }); const test2 = activities.some((activity) => { [, activity] = activity; return activity.type === 'Announce' && - activity.object && activity.object.type === 'Article'; + activity.object && activity.object.type === 'Note'; }); assert(test1 && test2); diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index a00116fbb5..b43f2da49f 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -245,7 +245,7 @@ describe('Notes', () => { const { tid } = await api.topics.create({ uid }, { cid, title: utils.generateUUID(), - content: utils.generateUUID(), + content: 'Guaranteed to be more than 500 characters.\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. In vel convallis felis. Phasellus porta erat a elit dignissim efficitur. Sed at sollicitudin erat, finibus sodales ante. Nunc ullamcorper, urna a pulvinar tempor, nunc risus venenatis nunc, id aliquam purus dui ut ante. Nulla sit amet risus sem. Praesent sit amet justo finibus, laoreet odio nec, varius diam. Nullam congue rhoncus lorem, eu accumsan leo aliquam sit amet. Suspendisse fringilla nec libero a tincidunt. Phasellus sapien justo, lacinia ac enim sit amet, pellentesque fermentum neque. Proin sit amet felis vitae libero aliquam pharetra at id nisi. Donec vitae mauris est. Sed hendrerit nisi et nibh auctor hendrerit. Praesent feugiat tortor a dignissim sagittis. Cras sit amet ante justo. Cras consectetur magna vitae volutpat placerat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae', }); assert(tid); From 9b00ff1e5202383d0e3e266c76f0a1f3a2bcc43f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:42:27 -0400 Subject: [PATCH 436/828] fix(deps): update dependency mongodb to v6.20.0 (#13665) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 13cce76075..0ab55f4641 100644 --- a/install/package.json +++ b/install/package.json @@ -91,7 +91,7 @@ "lru-cache": "11.2.2", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.19.0", + "mongodb": "6.20.0", "morgan": "1.10.1", "mousetrap": "1.6.5", "multer": "2.0.2", From 13ce106b213bf0b614385567f2d5f53aff980887 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:01:50 -0400 Subject: [PATCH 437/828] chore(deps): update dependency lint-staged to v16.2.1 (#13672) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0ab55f4641..62c2f75f8d 100644 --- a/install/package.json +++ b/install/package.json @@ -171,7 +171,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "27.0.0", - "lint-staged": "16.1.6", + "lint-staged": "16.2.1", "mocha": "11.7.2", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 3370c06472c84fab5d36d4d0668c7d04e9b6ab8b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:18:05 -0400 Subject: [PATCH 438/828] chore(deps): update dependency @stylistic/eslint-plugin to v5.4.0 (#13671) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 62c2f75f8d..cc1bce4433 100644 --- a/install/package.json +++ b/install/package.json @@ -164,7 +164,7 @@ "@commitlint/config-angular": "19.8.1", "coveralls": "3.1.1", "@eslint/js": "9.35.0", - "@stylistic/eslint-plugin": "5.3.1", + "@stylistic/eslint-plugin": "5.4.0", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", From 8614d8258d2c2ca6cca9f5809cb21811773903c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 25 Sep 2025 19:20:17 -0400 Subject: [PATCH 439/828] test: show tids on test fail --- test/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/topics.js b/test/topics.js index eaae7d76c7..a29f0a84fc 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1421,7 +1421,7 @@ describe('Topic\'s', () => { const result = await topics.post({ uid: adminUid, title: 'deleted unread', content: 'not unread', cid: categoryObj.cid }); await topics.delete(result.topicData.tid, adminUid); const unreadTids = await topics.getUnreadTids({ cid: 0, uid: uid }); - assert(!unreadTids.includes(result.topicData.tid)); + assert(!unreadTids.includes(result.topicData.tid), { unreadTids, tid: result.topicData.tid }); }); }); From 6dab3f2e6365ee10632862fab04e7b15dd469ae5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:33:52 -0400 Subject: [PATCH 440/828] chore(deps): update commitlint monorepo to v20 (#13678) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index cc1bce4433..5bfc78b537 100644 --- a/install/package.json +++ b/install/package.json @@ -160,8 +160,8 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "10.1.0", - "@commitlint/cli": "19.8.1", - "@commitlint/config-angular": "19.8.1", + "@commitlint/cli": "20.0.0", + "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", "@eslint/js": "9.35.0", "@stylistic/eslint-plugin": "5.4.0", From a4d8619ba39329a0174275db6cfe2f635c637fdd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:47:24 -0400 Subject: [PATCH 441/828] chore(deps): update dependency @eslint/js to v9.36.0 (#13670) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5bfc78b537..992d1f7c4b 100644 --- a/install/package.json +++ b/install/package.json @@ -163,7 +163,7 @@ "@commitlint/cli": "20.0.0", "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", - "@eslint/js": "9.35.0", + "@eslint/js": "9.36.0", "@stylistic/eslint-plugin": "5.4.0", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 30ca00002ac3758979641d0983060d6aad271359 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:49:28 -0400 Subject: [PATCH 442/828] chore(deps): update actions/download-artifact action to v5 (#13646) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a5ce6a5d56..fb2bd3ccce 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -96,7 +96,7 @@ jobs: 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@v4 + uses: actions/download-artifact@v5 with: path: ${{ runner.temp }}/digests pattern: digests-* From d6e7e168bae945c13fb7004cacc0e590918688c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 25 Sep 2025 21:35:01 -0400 Subject: [PATCH 443/828] test: fix message --- test/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/topics.js b/test/topics.js index a29f0a84fc..7136280339 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1421,7 +1421,7 @@ describe('Topic\'s', () => { const result = await topics.post({ uid: adminUid, title: 'deleted unread', content: 'not unread', cid: categoryObj.cid }); await topics.delete(result.topicData.tid, adminUid); const unreadTids = await topics.getUnreadTids({ cid: 0, uid: uid }); - assert(!unreadTids.includes(result.topicData.tid), { unreadTids, tid: result.topicData.tid }); + assert(!unreadTids.includes(result.topicData.tid), JSON.stringify({ unreadTids, tid: result.topicData.tid })); }); }); From 160907d0fa843279c9b9c36884fd77af8011cf01 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 26 Sep 2025 09:20:35 +0000 Subject: [PATCH 444/828] Latest translations and fallbacks --- public/language/vi/admin/manage/categories.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index 915e334af3..d5775e1fe0 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -4,7 +4,7 @@ "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": "Rename", + "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", @@ -112,9 +112,9 @@ "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": "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.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!", From 675bec331c4fe4749fb372d2fa49a034c46ccc84 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sat, 27 Sep 2025 09:20:16 +0000 Subject: [PATCH 445/828] Latest translations and fallbacks --- public/language/pl/admin/manage/categories.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index 34d5839353..ba3cc5a058 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -4,7 +4,7 @@ "add-local-category": "Dodaj lokalną kategorię", "add-remote-category": "Dodaj zdalną kategorię", "remove": "Usuń", - "rename": "Rename", + "rename": "Przemianuj", "jump-to": "Skocz do...", "settings": "Ustawienia kategorii", "edit-category": "Edytuj kategorię", @@ -112,9 +112,9 @@ "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": "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.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!", From f644974a9b49fdd6b20514e11b9a3b99f5d3193a Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 28 Sep 2025 09:20:03 +0000 Subject: [PATCH 446/828] Latest translations and fallbacks --- .../language/it/admin/manage/categories.json | 12 +++++----- public/language/it/admin/manage/users.json | 2 +- .../it/admin/settings/activitypub.json | 24 +++++++++---------- public/language/it/error.json | 6 ++--- public/language/it/topic.json | 6 ++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index dd4562df02..d88533b81a 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -4,7 +4,7 @@ "add-local-category": "Aggiungi categoria locale", "add-remote-category": "Aggiungi categoria remota", "remove": "Rimuovi", - "rename": "Rename", + "rename": "Rinomina", "jump-to": "Vai a...", "settings": "Impostazioni Categoria", "edit-category": "Modifica categoria", @@ -18,8 +18,8 @@ "federatedDescription": "Descrizione federazione", "federatedDescription.help": "Questo testo sarà aggiunto alla descrizione della categoria quando interrogato da altri siti web/app.", "federatedDescription.default": "Questa è una categoria del forum che contiene discussioni di attualità. Puoi iniziare nuove discussioni menzionando questa categoria.", - "topic-template": "Topic Template", - "topic-template.help": "Define a template for new topics created in 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", @@ -112,9 +112,9 @@ "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": "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.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/users.json b/public/language/it/admin/manage/users.json index 689d129570..9f6a986e84 100644 --- a/public/language/it/admin/manage/users.json +++ b/public/language/it/admin/manage/users.json @@ -59,7 +59,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", diff --git a/public/language/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json index 764085b800..faaf555e92 100644 --- a/public/language/it/admin/settings/activitypub.json +++ b/public/language/it/admin/settings/activitypub.json @@ -23,22 +23,22 @@ "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": "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.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": "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", + "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", diff --git a/public/language/it/error.json b/public/language/it/error.json index 0fb310b196..1c4a16adf9 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -9,7 +9,7 @@ "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", @@ -145,8 +145,8 @@ "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", "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", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index bf3382d63a..35a3644c44 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", @@ -151,7 +151,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", @@ -194,7 +194,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)", From 30ba8e82476e992dcaf228c41083a68f89d28314 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 29 Sep 2025 14:04:07 +0000 Subject: [PATCH 447/828] chore: incrementing version number - v4.5.2 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index f9e5b8b0ed..ea5908ca1d 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.5.1", + "version": "4.5.2", "homepage": "https://www.nodebb.org", "repository": { "type": "git", @@ -201,4 +201,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file From 9a596d67f34153cbd0cdaf8443fbcce7cbdee7d3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 29 Sep 2025 14:04:08 +0000 Subject: [PATCH 448/828] chore: update changelog for v4.5.2 --- CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index beaab4c40c..c35feb4d24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,75 @@ +#### 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 From c3df68f2ed34cca590946a19182bd29c924f5075 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 30 Sep 2025 11:05:42 -0400 Subject: [PATCH 449/828] fix: don\'t begin processing local login if the passed-in username isn't even valid --- src/controllers/authentication.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index fef6f088b6..d5a0965d7b 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -420,6 +420,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([ From 4776d012812f1b250fe24687c51128dc5c752ad8 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Oct 2025 11:00:03 -0400 Subject: [PATCH 450/828] sec: disallow checkHeader from returning a URL from a different origin than the passed-in URL --- src/activitypub/index.js | 80 ++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 11f16322e2..1c0f4ce13d 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -565,42 +565,58 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { ActivityPub.checkHeader = async (url, timeout) => { timeout = timeout || meta.config.activitypubProbeTimeout || 2000; - const { response } = await request.head(url, { - timeout, - }); - const { headers } = response; - 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; - } + 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; + } - return apLink; - } + 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; + } + }); - return false; + 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 }) => { From 17dba0b0385e1156f413ed929a05a4051e559b83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:12:42 -0400 Subject: [PATCH 451/828] fix(deps): update dependency webpack to v5.102.0 (#13683) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 992d1f7c4b..77966f994c 100644 --- a/install/package.json +++ b/install/package.json @@ -149,7 +149,7 @@ "tough-cookie": "6.0.0", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.101.3", + "webpack": "5.102.0", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.3.4", From d7e93a5d757853f9e9d847e0b458545b9344cabf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:31:16 -0400 Subject: [PATCH 452/828] chore(deps): update dependency lint-staged to v16.2.3 (#13681) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 77966f994c..a609ed462d 100644 --- a/install/package.json +++ b/install/package.json @@ -171,7 +171,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "27.0.0", - "lint-staged": "16.2.1", + "lint-staged": "16.2.3", "mocha": "11.7.2", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 954e7bc8e3034baaa159366c55482c42155809f3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Oct 2025 11:43:23 -0400 Subject: [PATCH 453/828] fix: update outgoing page to match 404 design --- src/controllers/index.js | 1 + src/views/outgoing.tpl | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index 879774c17f..831619fc9e 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -8,6 +8,7 @@ const user = require('../user'); const plugins = require('../plugins'); const privilegesHelpers = require('../privileges/helpers'); const helpers = require('./helpers'); +const { secureRandom } = require('../utils'); const Controllers = module.exports; diff --git a/src/views/outgoing.tpl b/src/views/outgoing.tpl index 5f5b45a77d..8d52b1d3b3 100644 --- a/src/views/outgoing.tpl +++ b/src/views/outgoing.tpl @@ -1,7 +1,16 @@ -

    -

    - [[notifications:outgoing-link-message, {title}]] -

    - [[notifications:continue-to, {outgoing}]] - [[notifications:return-to, {title}]] -
    +
    +

    [[notifications:outgoing-link-message, {title}]]

    + + +
    \ No newline at end of file From 9cee799937c369acb300c37e702a7779f8e80f9f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Oct 2025 11:53:57 -0400 Subject: [PATCH 454/828] fix: force outgoing page on direct access to `/ap` handler --- src/controllers/activitypub/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index 121c8536aa..c64ef9ef57 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -48,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) { From 675178aca4ed41f70c7be069526214a5a8fb300f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Oct 2025 12:13:57 -0400 Subject: [PATCH 455/828] fix: allow quote-inline class in mocks sanitizer so quote-post fallback elements can be detected and removed during title generation, fixes #13688 --- src/activitypub/mocks.js | 1 + src/activitypub/notes.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 5cbd7876a7..a88fed09dc 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -33,6 +33,7 @@ const sanitizeConfig = { allowedTags: sanitize.defaults.allowedTags.concat(['img', 'picture', 'source']), allowedClasses: { '*': [], + 'p': ['quote-inline'], }, allowedAttributes: { a: ['href', 'rel'], diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 1418e412ec..c7e7b332c3 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -156,7 +156,10 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - const prettified = pretty(content || sourceContent); + let prettified = pretty(content || sourceContent); + + // Remove any lines that contain quote-post fallbacks + prettified = prettified.split('\n').filter(line => !line.startsWith('

    Date: Wed, 1 Oct 2025 12:15:07 -0400 Subject: [PATCH 456/828] chore: remove unneeded secureRandom require --- src/controllers/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index 831619fc9e..879774c17f 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -8,7 +8,6 @@ const user = require('../user'); const plugins = require('../plugins'); const privilegesHelpers = require('../privileges/helpers'); const helpers = require('./helpers'); -const { secureRandom } = require('../utils'); const Controllers = module.exports; From 56a9336611cc19788686c63b5d2768e37859fd9e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Oct 2025 12:52:09 -0400 Subject: [PATCH 457/828] docs: update openapi schema to refer to try.nodebb.org instead of example.org --- public/openapi/read/ap.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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: From 5ed19ef8a9372453a89c73140d10944bc46090a1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Oct 2025 13:51:04 -0400 Subject: [PATCH 458/828] fix: login handler to handle if non-confirmed email is entered --- src/controllers/authentication.js | 2 ++ test/authentication.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index d5a0965d7b..581f7d931c 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -262,6 +262,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) { diff --git a/test/authentication.js b/test/authentication.js index 193d617435..4f72a9a705 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -5,8 +5,8 @@ const assert = require('assert'); const url = require('url'); const nconf = require('nconf'); -const request = require('../src/request'); const db = require('./mocks/databasemock'); +const request = require('../src/request'); const user = require('../src/user'); const utils = require('../src/utils'); const meta = require('../src/meta'); @@ -52,8 +52,8 @@ describe('authentication', () => { meta.config.allowLoginWith = 'username-email'; const uid = await user.create({ username: '2nduser', password: '2ndpassword', email: '2nduser@nodebb.org' }); const { response, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword'); - assert.strictEqual(response.statusCode, 403); - assert.strictEqual(body, '[[error:invalid-login-credentials]]'); + assert.strictEqual(response.statusCode, 400); + assert.strictEqual(body, '[[error:invalid-email]]'); meta.config.allowLoginWith = oldValue; }); From 367f66caa4bb2cb64fabc06c894c861e86d1bc8f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 1 Oct 2025 18:12:05 +0000 Subject: [PATCH 459/828] chore: incrementing version number - v4.6.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e57a59d011..9f957c9ae5 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.5.2", + "version": "4.6.0", "homepage": "https://www.nodebb.org", "repository": { "type": "git", From c0d9bb0723713b5b0f1cc69e239d50322d41d6f3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 1 Oct 2025 18:12:06 +0000 Subject: [PATCH 460/828] chore: update changelog for v4.6.0 --- CHANGELOG.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c35feb4d24..cbb3067783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,129 @@ +#### 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 From 19dc1025d4cb7c7081948e55d1a62c3c83aa34b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:04:26 -0400 Subject: [PATCH 461/828] fix(deps): update dependency winston to v3.18.3 (#13687) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 9f957c9ae5..5d8a2b5443 100644 --- a/install/package.json +++ b/install/package.json @@ -151,7 +151,7 @@ "validator": "13.15.15", "webpack": "5.102.0", "webpack-merge": "6.0.1", - "winston": "3.17.0", + "winston": "3.18.3", "workerpool": "9.3.4", "xml": "1.0.1", "xregexp": "5.1.2", From eb06bda8d8d1c70040b8c8b1300ddf8089276a5a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:04:55 -0400 Subject: [PATCH 462/828] chore(deps): update dependency @commitlint/cli to v20.1.0 (#13686) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5d8a2b5443..46e67d5d56 100644 --- a/install/package.json +++ b/install/package.json @@ -160,7 +160,7 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "10.1.0", - "@commitlint/cli": "20.0.0", + "@commitlint/cli": "20.1.0", "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", "@eslint/js": "9.36.0", From c7696667372858e0d6c65246c1360035b79715de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:05:14 -0400 Subject: [PATCH 463/828] chore(deps): update dependency mocha to v11.7.4 (#13685) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 46e67d5d56..27bb269af0 100644 --- a/install/package.json +++ b/install/package.json @@ -172,7 +172,7 @@ "husky": "8.0.3", "jsdom": "27.0.0", "lint-staged": "16.2.3", - "mocha": "11.7.2", + "mocha": "11.7.4", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", From 4640a63e4bbbfdc6e844be5a1ba9945eb8d772b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:47:23 -0400 Subject: [PATCH 464/828] chore(deps): update redis docker tag to v8.2.2 (#13692) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a94157f446..a405e8a749 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.2.1' + image: 'redis:8.2.2' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 3e32023939..f7a9e0bc0a 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.2.1-alpine + image: redis:8.2.2-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 98bceb6721..2cd7197231 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:8.2.1-alpine + image: redis:8.2.2-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 ec096eb060..32b02159f8 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:8.2.1-alpine + image: redis:8.2.2-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 From 9b6e9b2ac39520b51270fb1cc2ec92008bd3bef4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:50:50 -0400 Subject: [PATCH 465/828] fix(deps): update dependency redis to v5.8.3 (#13691) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 27bb269af0..fb6a3ca3ec 100644 --- a/install/package.json +++ b/install/package.json @@ -123,7 +123,7 @@ "pretty": "^2.0.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.8.2", + "redis": "5.8.3", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 66285ef53eba7aec0152eab5c642477ccde18094 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sun, 5 Oct 2025 09:20:02 +0000 Subject: [PATCH 466/828] Latest translations and fallbacks --- public/language/zh-CN/admin/manage/categories.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 07c53a18aa..410f44b0c8 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -4,7 +4,7 @@ "add-local-category": "添加本地版块", "add-remote-category": "添加远程版块", "remove": "移除", - "rename": "Rename", + "rename": "重命名", "jump-to": "跳转…", "settings": "版块设置", "edit-category": "编辑版块", @@ -112,9 +112,9 @@ "alert.create": "创建一个版块", "alert.add": "添加一个版块", "alert.add-help": "可以通过指定其句柄将远程版块添加到版块列表中。

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

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

    警告! 版块将被清除!

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

    ", "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", From 5dc9f2c5d45a0a11ef06b7f16feee71e0dc682e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:13:26 -0400 Subject: [PATCH 467/828] fix(deps): update dependency nodemailer to v7.0.7 (#13694) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fb6a3ca3ec..45ca7d4109 100644 --- a/install/package.json +++ b/install/package.json @@ -111,7 +111,7 @@ "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.14", "nodebb-widget-essentials": "7.0.40", - "nodemailer": "7.0.6", + "nodemailer": "7.0.7", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", From d73892aedab0eb2e0d2e6a2bd5cc2a935c966cb8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:13:40 -0400 Subject: [PATCH 468/828] chore(deps): update dependency @eslint/js to v9.37.0 (#13693) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 45ca7d4109..79cc502eda 100644 --- a/install/package.json +++ b/install/package.json @@ -163,7 +163,7 @@ "@commitlint/cli": "20.1.0", "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", - "@eslint/js": "9.36.0", + "@eslint/js": "9.37.0", "@stylistic/eslint-plugin": "5.4.0", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 923ddbc1f1a464772a5f858b3318fae07106ce17 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:28:44 -0400 Subject: [PATCH 469/828] chore(deps): update postgres docker tag to v18 (#13679) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a405e8a749..89e7d91fe7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -48,7 +48,7 @@ jobs: services: postgres: - image: 'postgres:17-alpine' + image: 'postgres:18-alpine' env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index f7a9e0bc0a..3c55eb6c3b 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.6-alpine + image: postgres:18.0-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb diff --git a/docker-compose.yml b/docker-compose.yml index 32b02159f8..ee7a18ceb0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: - redis postgres: - image: postgres:17.6-alpine + image: postgres:18.0-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb From 93b6cb598402c5ba430a46712fc2459c654dcdd1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 6 Oct 2025 13:45:40 -0400 Subject: [PATCH 470/828] feat: federate Delete on post delete as well as purge, topic deletion federates Announce(Delete(Object)) --- src/api/activitypub.js | 59 +++++++++++++++++++++++++++++++++++++++++- src/posts/delete.js | 1 + src/topics/delete.js | 2 ++ src/topics/tools.js | 8 ++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/api/activitypub.js b/src/api/activitypub.js index 7e4ae7da18..19e9087539 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -350,7 +350,7 @@ 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 + // Only remote posts can be announced to local categories if (utils.isNumber(pid) || parseInt(cid, 10) === -1) { return; } @@ -379,6 +379,63 @@ activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => { }); }); +activitypubApi.announce.delete = enabledCheck(async ({ uid }, { tid }) => { + const now = new Date(); + const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); + + // 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 announce of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + to: [activitypub._constants.publicAddress], + cc: [`${nconf.get('url')}/category/${cid}/followers`], + }, { cid }); + + const deleteTpl = { + id: `${nconf.get('url')}/topic/${tid}#activity/delete/${now.getTime()}`, + type: 'Delete', + actor: `${nconf.get('url')}/category/${cid}`, + to, + cc, + origin: `${nconf.get('url')}/category/${cid}`, + }; + + // 7888 variant + await activitypub.send('cid', cid, Array.from(targets), { + id: `${nconf.get('url')}/topic/${tid}#activity/announce/delete/${now.getTime()}`, + type: 'Announce', + actor: `${nconf.get('url')}/category/${cid}`, + to, + cc, + object: { + ...deleteTpl, + object: `${nconf.get('url')}/topic/${tid}`, + }, + }); + + // 1b12 variant + await activitypub.send('cid', cid, Array.from(targets), { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/delete/${now.getTime()}`, + type: 'Announce', + actor: `${nconf.get('url')}/category/${cid}`, + to, + cc, + object: { + ...deleteTpl, + actor: `${nconf.get('url')}/uid/${uid}`, + object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, + }, + }); +}); + activitypubApi.undo = {}; // activitypubApi.undo.follow = diff --git a/src/posts/delete.js b/src/posts/delete.js index 5668ac3750..dddb33e6a9 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), diff --git a/src/topics/delete.js b/src/topics/delete.js index e97fd0a98e..d07a3aee5b 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 api = require('../api'); const utils = require('../utils'); module.exports = function (Topics) { @@ -80,6 +81,7 @@ module.exports = function (Topics) { } deletedTopic.tags = tags; await deleteFromFollowersIgnorers(tid); + await api.activitypub.announce.delete({ uid }, { tid }), await Promise.all([ db.deleteAll([ diff --git a/src/topics/tools.js b/src/topics/tools.js index 294615b38a..65505bde67 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -7,6 +7,7 @@ const topics = require('.'); const categories = require('../categories'); const user = require('../user'); const plugins = require('../plugins'); +const api = require('../api'); const privileges = require('../privileges'); const utils = require('../utils'); @@ -277,6 +278,13 @@ module.exports = function (Topics) { const oldCid = topicData.cid; await categories.moveRecentReplies(tid, oldCid, cid); + // AP: Announce(Delete(Object)) + if (cid === -1) { + await api.activitypub.announce.delete({ uid: data.uid }, { tid }); + } else { + // tbd: api.activitypub.announce.move + } + await Promise.all([ Topics.setTopicFields(tid, { cid: cid, From ec3998974c6484116852418254dbb61f9fcce6b1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 6 Oct 2025 22:17:35 -0400 Subject: [PATCH 471/828] fix: omg what. --- src/activitypub/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index c7e7b332c3..130cb1874f 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -159,7 +159,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { let prettified = pretty(content || sourceContent); // Remove any lines that contain quote-post fallbacks - prettified = prettified.split('\n').filter(line => !line.startsWith('

    !line.startsWith('

    Date: Tue, 7 Oct 2025 11:35:36 -0400 Subject: [PATCH 472/828] feat: federate topic deletion on topic deletion as well as purge --- src/topics/delete.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/topics/delete.js b/src/topics/delete.js index d07a3aee5b..3557349d9d 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -25,6 +25,7 @@ module.exports = function (Topics) { deleterUid: uid, deletedTimestamp: Date.now(), }), + api.activitypub.announce.delete({ uid }, { tid }), ]); await categories.updateRecentTidForCid(cid); From 79327e6cace72c280e00b2fdc03fec7d93714b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 7 Oct 2025 17:34:55 -0400 Subject: [PATCH 473/828] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 9f957c9ae5..3fcc3f33ef 100644 --- a/install/package.json +++ b/install/package.json @@ -106,7 +106,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.20", + "nodebb-theme-harmony": "2.1.21", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.14", From 623cec9d910c03dd67c94db3ce45bf334dafd869 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 8 Oct 2025 11:07:43 -0400 Subject: [PATCH 474/828] fix: logic error in image mime type checking --- src/activitypub/mocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index a88fed09dc..1e04fb99ab 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -104,7 +104,7 @@ Mocks._normalize = async (object) => { if (image) { const parsed = new URL(image); const type = mime.getType(parsed.pathname); - if (!type || type.startsWith('image/')) { + if (!type || !type.startsWith('image/')) { activitypub.helpers.log(`[activitypub/mocks.post] Received image not identified as image due to MIME type: ${image}`); image = null; } From d3b3720915f5846e8f5a8e0bee9c17b3ff233902 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 9 Oct 2025 13:56:59 -0400 Subject: [PATCH 475/828] refactor: move post attachment handling directly into posts.create --- src/activitypub/notes.js | 10 +++------- src/posts/create.js | 5 ++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 130cb1874f..8e11898f26 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -210,7 +210,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); if (!hasTid) { - const { to, cc, attachment } = mainPost._activitypub; + const { to, cc } = mainPost._activitypub; const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); try { @@ -239,7 +239,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { id: tid, path: mainPost._activitypub.image, }) : null, - posts.attachments.update(mainPid, attachment), ]); if (context) { @@ -249,16 +248,13 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } for (const post of unprocessed) { - const { to, cc, attachment } = post._activitypub; + const { to, cc } = post._activitypub; try { // eslint-disable-next-line no-await-in-loop await topics.reply(post); // eslint-disable-next-line no-await-in-loop - await Promise.all([ - Notes.updateLocalRecipients(post.pid, { to, cc }), - posts.attachments.update(post.pid, attachment), - ]); + await Notes.updateLocalRecipients(post.pid, { to, cc }); } catch (e) { activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); } diff --git a/src/posts/create.js b/src/posts/create.js index 656ae68ab0..5d56c05d26 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -71,6 +71,8 @@ module.exports = function (Posts) { const topicData = await topics.getTopicFields(tid, ['cid', 'pinned']); postData.cid = topicData.cid; + const hasAttachment = _activitypub.attachment && _activitypub.attachment.length; + await Promise.all([ db.sortedSetAdd('posts:pid', timestamp, postData.pid), utils.isNumber(pid) ? db.incrObjectField('global', 'postCount') : null, @@ -79,7 +81,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 }); From 07bed55e333805921463b1415f4317a4b1313e83 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 9 Oct 2025 13:57:21 -0400 Subject: [PATCH 476/828] fix: add attachments to retrieved post data onNewPost --- src/topics/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/create.js b/src/topics/create.js index 8f347c736a..098fba2d41 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -248,7 +248,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([ From e7bdf6bc31b7c7ae60e1cc38b78aec6c393e66bc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 9 Oct 2025 14:00:30 -0400 Subject: [PATCH 477/828] feat: bundle link-preview plugin --- install/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 79cc502eda..5402c223ef 100644 --- a/install/package.json +++ b/install/package.json @@ -101,6 +101,7 @@ "nodebb-plugin-dbsearch": "6.3.2", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", + "nodebb-plugin-link-preview": "2.1.5", "nodebb-plugin-markdown": "13.2.1", "nodebb-plugin-mentions": "4.7.6", "nodebb-plugin-spam-be-gone": "2.3.2", @@ -202,4 +203,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} From b153941cf389732b3a93ca30e5e8de65aaae2809 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 9 Oct 2025 14:01:08 -0400 Subject: [PATCH 478/828] feat: auto-enable link-preview plugin on new installations --- src/install.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/install.js b/src/install.js index 064d5d77ff..23346cbdee 100644 --- a/src/install.js +++ b/src/install.js @@ -534,6 +534,7 @@ async function enableDefaultPlugins() { 'nodebb-rewards-essentials', 'nodebb-plugin-emoji', 'nodebb-plugin-emoji-android', + 'nodebb-plugin-link-preview', ]; let customDefaults = nconf.get('defaultplugins') || nconf.get('defaultPlugins'); From b309a672a8f102265f430112c8321089ee518cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 10 Oct 2025 12:19:58 -0400 Subject: [PATCH 479/828] chore: up persona --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3fcc3f33ef..6e48066aed 100644 --- a/install/package.json +++ b/install/package.json @@ -109,7 +109,7 @@ "nodebb-theme-harmony": "2.1.21", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", - "nodebb-theme-persona": "14.1.14", + "nodebb-theme-persona": "14.1.15", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.6", "nprogress": "0.2.0", From bb7b65eaa13f9ff6fc8a284f50161f7be2a97165 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:02:20 -0400 Subject: [PATCH 480/828] fix(deps): update dependency webpack to v5.102.1 (#13698) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5402c223ef..3fc5172197 100644 --- a/install/package.json +++ b/install/package.json @@ -150,7 +150,7 @@ "tough-cookie": "6.0.0", "undici": "^7.10.0", "validator": "13.15.15", - "webpack": "5.102.0", + "webpack": "5.102.1", "webpack-merge": "6.0.1", "winston": "3.18.3", "workerpool": "9.3.4", From a2892f60bc018d16bb6794f32398141fa80324d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:02:30 -0400 Subject: [PATCH 481/828] fix(deps): update dependency semver to v7.7.3 (#13697) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3fc5172197..61336c75c0 100644 --- a/install/package.json +++ b/install/package.json @@ -132,7 +132,7 @@ "sass": "1.93.2", "satori": "0.18.3", "sbd": "^1.0.19", - "semver": "7.7.2", + "semver": "7.7.3", "serve-favicon": "2.5.1", "sharp": "0.34.4", "sitemap": "8.0.0", From 5d3709f002c089679f148eeedd50e148d069f6d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:02:42 -0400 Subject: [PATCH 482/828] fix(deps): update dependency nodemailer to v7.0.9 (#13695) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 61336c75c0..6c46043871 100644 --- a/install/package.json +++ b/install/package.json @@ -112,7 +112,7 @@ "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.14", "nodebb-widget-essentials": "7.0.40", - "nodemailer": "7.0.7", + "nodemailer": "7.0.9", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", From d7657538faeefbe70a237b61a1f6414bc018acf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 11 Oct 2025 20:39:14 -0400 Subject: [PATCH 483/828] Revert "feat: auto-enable link-preview plugin on new installations" This reverts commit b153941cf389732b3a93ca30e5e8de65aaae2809. --- src/install.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/install.js b/src/install.js index 23346cbdee..064d5d77ff 100644 --- a/src/install.js +++ b/src/install.js @@ -534,7 +534,6 @@ async function enableDefaultPlugins() { 'nodebb-rewards-essentials', 'nodebb-plugin-emoji', 'nodebb-plugin-emoji-android', - 'nodebb-plugin-link-preview', ]; let customDefaults = nconf.get('defaultplugins') || nconf.get('defaultPlugins'); From 6c2100684b7a1630e618560a13901e28a2750bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 11 Oct 2025 20:54:00 -0400 Subject: [PATCH 484/828] fix: crash in tests --- src/posts/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts/create.js b/src/posts/create.js index 5d56c05d26..e74299c27c 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -71,7 +71,7 @@ module.exports = function (Posts) { const topicData = await topics.getTopicFields(tid, ['cid', 'pinned']); postData.cid = topicData.cid; - const hasAttachment = _activitypub.attachment && _activitypub.attachment.length; + const hasAttachment = _activitypub && _activitypub.attachment && _activitypub.attachment.length; await Promise.all([ db.sortedSetAdd('posts:pid', timestamp, postData.pid), From 49a293259435b9db640737abfe278f8d6dae739e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 21:03:34 -0400 Subject: [PATCH 485/828] fix(deps): update dependency nodebb-theme-harmony to v2.1.21 (#13700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6c46043871..6237eae76b 100644 --- a/install/package.json +++ b/install/package.json @@ -107,7 +107,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.20", + "nodebb-theme-harmony": "2.1.21", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.14", From fa18287d037759a91ba11adc499b36fc2a5e24f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 21:03:42 -0400 Subject: [PATCH 486/828] fix(deps): update dependency nodebb-theme-persona to v14.1.15 (#13701) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6237eae76b..bc0b9481a9 100644 --- a/install/package.json +++ b/install/package.json @@ -110,7 +110,7 @@ "nodebb-theme-harmony": "2.1.21", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", - "nodebb-theme-persona": "14.1.14", + "nodebb-theme-persona": "14.1.15", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.9", "nprogress": "0.2.0", From f608c7c7a78e85d8cec27ad118f06f5c3945decf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 21:03:52 -0400 Subject: [PATCH 487/828] chore(deps): update dependency lint-staged to v16.2.4 (#13699) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index bc0b9481a9..526909d7e8 100644 --- a/install/package.json +++ b/install/package.json @@ -172,7 +172,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "27.0.0", - "lint-staged": "16.2.3", + "lint-staged": "16.2.4", "mocha": "11.7.4", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 238600a0ec85f45e3306c1877168d1b8ff92ebad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 21:04:03 -0400 Subject: [PATCH 488/828] chore(deps): update dependency smtp-server to v3.15.0 (#13702) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 526909d7e8..1154fa0451 100644 --- a/install/package.json +++ b/install/package.json @@ -177,7 +177,7 @@ "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.14.0" + "smtp-server": "3.15.0" }, "optionalDependencies": { "sass-embedded": "1.93.2" From 499c50a485eb6db4b8600f253846591caa909a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 13 Oct 2025 13:45:11 -0400 Subject: [PATCH 489/828] fix: #13705, don't cover link if preview is opening up --- public/src/client/topic.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index cce6f1f99a..5ee1f91f60 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -358,7 +358,6 @@ define('forum/topic', [ const postContent = link.parents('[component="topic"]').find('[component="post/content"]').first(); const postRect = postContent.offset(); const postWidth = postContent.width(); - const linkRect = link.offset(); const { top } = link.get(0).getBoundingClientRect(); const dropup = top > window.innerHeight / 2; tooltip.on('mouseenter', function () { @@ -366,11 +365,16 @@ define('forum/topic', [ }); tooltip.one('mouseleave', destroyTooltip); $(window).off('click', onClickOutside).one('click', onClickOutside); - tooltip.css({ - top: dropup ? linkRect.top - tooltip.outerHeight() : linkRect.top + 30, + 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); } } From af5efbd71d6ad9bedbf1ad74e1ede162f3a7fe31 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 14 Oct 2025 11:21:39 -0400 Subject: [PATCH 490/828] fix: regression caused by d3b3720915f5846e8f5a8e0bee9c17b3ff233902 --- src/posts/create.js | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/posts/create.js b/src/posts/create.js index e74299c27c..fa7ca1d071 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -18,6 +18,7 @@ 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]]'); @@ -46,23 +47,25 @@ 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}:`; - } - - postData.content = postData.content.replace(new RegExp(tag.name, 'g'), ``); - }); + // 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}:`; + } + + 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 })); @@ -71,8 +74,6 @@ module.exports = function (Posts) { const topicData = await topics.getTopicFields(tid, ['cid', 'pinned']); postData.cid = topicData.cid; - const hasAttachment = _activitypub && _activitypub.attachment && _activitypub.attachment.length; - await Promise.all([ db.sortedSetAdd('posts:pid', timestamp, postData.pid), utils.isNumber(pid) ? db.incrObjectField('global', 'postCount') : null, From bf37c7bd77cd4a391d8b9c80d16915ba5d1f0608 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:24:26 -0400 Subject: [PATCH 491/828] fix(deps): update dependency chart.js to v4.5.1 (#13704) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1154fa0451..0dcf15858c 100644 --- a/install/package.json +++ b/install/package.json @@ -50,7 +50,7 @@ "bootstrap": "5.3.8", "bootswatch": "5.3.8", "chalk": "4.1.2", - "chart.js": "4.5.0", + "chart.js": "4.5.1", "cli-graph": "3.2.2", "clipboard": "2.0.11", "commander": "14.0.1", From febe0ae01aa14db94e2ac7c2f41f6ca99f90c5ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:24:44 -0400 Subject: [PATCH 492/828] chore(deps): update actions/setup-node action to v6 (#13708) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 89e7d91fe7..03dbbf8f3c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -86,7 +86,7 @@ jobs: - run: cp install/package.json package.json - name: Install Node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} From 41b7a91d8f3e02cf7836086aee8a189f8a35df3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:10:55 -0400 Subject: [PATCH 493/828] fix(deps): update dependency esbuild to v0.25.11 (#13710) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0dcf15858c..7da467536c 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.10", + "esbuild": "0.25.11", "express": "4.21.2", "express-session": "1.18.2", "express-useragent": "1.0.15", From 9583f0d49b040d82130051f9958b64fb50c476a9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 11:24:08 -0400 Subject: [PATCH 494/828] feat: execute 1b12 rebroadcast logic on all tids even if not posted to a local cid --- src/activitypub/feps.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index b8bf765bef..cc49f2a8aa 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -3,6 +3,7 @@ const nconf = require('nconf'); const posts = require('../posts'); +const topics = require('../topics'); const utils = require('../utils'); const activitypub = module.parent.exports; @@ -13,8 +14,16 @@ 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 || !utils.isNumber(cid)) { // local cids only + + /** + * 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 shouldAnnounce = (utils.isNumber(cid) && cid > 0) || utils.isNumber(tid); + if (!shouldAnnounce) { // inverse conditionals can kiss my ass. return; } From c25c629023959e078630bcaa0d2fa618936b3fc8 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 11:52:47 -0400 Subject: [PATCH 495/828] fix(deps): bump dbsearch --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7da467536c..90d5f97518 100644 --- a/install/package.json +++ b/install/package.json @@ -98,7 +98,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.6.0", "nodebb-plugin-composer-default": "10.3.1", - "nodebb-plugin-dbsearch": "6.3.2", + "nodebb-plugin-dbsearch": "6.3.3", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-link-preview": "2.1.5", From 79d088536a590671765bebdf08f90218c28ba445 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 12:03:26 -0400 Subject: [PATCH 496/828] fix: update 1b12 rebroadcast logic to send as application actor if post is in remote cid --- src/activitypub/feps.js | 9 +++++++-- src/activitypub/helpers.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index cc49f2a8aa..36b600283f 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -21,12 +21,17 @@ Feps.announce = async function announce(id, activity) { * - local tids (posted to remote cids) only */ const tid = await posts.getPostField(localId || id, 'tid'); - const cid = await topics.getTopicField(tid, 'cid'); - const shouldAnnounce = (utils.isNumber(cid) && cid > 0) || utils.isNumber(tid); + let cid = await topics.getTopicField(tid, 'cid'); + const localCid = utils.isNumber(cid) && cid > 0; + const shouldAnnounce = localCid || utils.isNumber(tid); if (!shouldAnnounce) { // inverse conditionals can kiss my ass. return; } + if (!localCid) { + cid = 0; // override cid to 0 so application actor sends the announce + } + let relays = await activitypub.relays.list(); relays = relays.reduce((memo, { state, url }) => { if (state === 2) { diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index e6eb2e1c08..90701dd58d 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -225,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: From 58a9e1c4f9ec6cba94d5422b36005cf508f7a09d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 12:08:29 -0400 Subject: [PATCH 497/828] fix: update targets in 1b12 rebroadcast when cid is remote --- src/activitypub/feps.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index 36b600283f..12b99d8fc8 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -21,17 +21,13 @@ Feps.announce = async function announce(id, activity) { * - local tids (posted to remote cids) only */ const tid = await posts.getPostField(localId || id, 'tid'); - let cid = await topics.getTopicField(tid, 'cid'); + const cid = await topics.getTopicField(tid, 'cid'); const localCid = utils.isNumber(cid) && cid > 0; const shouldAnnounce = localCid || utils.isNumber(tid); if (!shouldAnnounce) { // inverse conditionals can kiss my ass. return; } - if (!localCid) { - cid = 0; // override cid to 0 so application actor sends the announce - } - let relays = await activitypub.relays.list(); relays = relays.reduce((memo, { state, url }) => { if (state === 2) { @@ -39,7 +35,7 @@ Feps.announce = async function announce(id, activity) { } return memo; }, []); - const followers = await activitypub.notes.getCategoryFollowers(cid); + const followers = localCid ? await activitypub.notes.getCategoryFollowers(cid) : [cid]; const targets = relays.concat(followers); if (!targets.length) { return; @@ -54,7 +50,7 @@ Feps.announce = async function announce(id, activity) { 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} and ${relays.length} relays`); - await activitypub.send('cid', cid, targets, { + 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}`, @@ -66,7 +62,7 @@ Feps.announce = async function announce(id, activity) { } 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', cid, targets, { + await activitypub.send('cid', localCid ? cid : 0, targets, { id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now + 1}`, type: 'Announce', actor: `${nconf.get('url')}/category/${cid}`, From a45f6f9c4cd1014202dd4900051068d87c836cbb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 12:24:42 -0400 Subject: [PATCH 498/828] fix: update getPrivateKey to send application actor key when cid 0 --- src/activitypub/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 5971c69326..7b84148600 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -201,7 +201,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 }; From d4695f1085e356507ab6a1b17329abc4b082cc72 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 12:31:55 -0400 Subject: [PATCH 499/828] fix: broken category urls in to, cc --- src/activitypub/feps.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index 12b99d8fc8..a84600ca65 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -46,6 +46,12 @@ Feps.announce = async function announce(id, activity) { 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) { @@ -53,9 +59,9 @@ Feps.announce = async function announce(id, activity) { 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.object, }); } @@ -65,9 +71,9 @@ Feps.announce = async function announce(id, activity) { await activitypub.send('cid', localCid ? cid : 0, targets, { id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now + 1}`, 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, }); }; From 3fa74d4cecc130fc584a6f9989fbaeb2da5a47dc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 12:33:57 -0400 Subject: [PATCH 500/828] fix: do not include actor from reflected activity when rebroadcasting remote cid --- src/activitypub/feps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index a84600ca65..9fb0e28680 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -42,7 +42,7 @@ Feps.announce = async function announce(id, activity) { } const { actor } = activity; - if (actor && !actor.startsWith(nconf.get('url'))) { + if (localCid && actor && !actor.startsWith(nconf.get('url'))) { targets.unshift(actor); } const now = Date.now(); From fadac6165e362e4bbfd53ac364a9d18e32d27451 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Oct 2025 15:02:23 -0400 Subject: [PATCH 501/828] fix: move Announce(Delete) out of topics.move and into topics API method --- src/api/topics.js | 11 ++++++++++- src/topics/tools.js | 8 -------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/api/topics.js b/src/api/topics.js index d44fffae04..e0f149e392 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -320,7 +320,16 @@ 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 }); + + // AP: Announce(Delete(Object)) + if (cid === -1) { + await activitypubApi.announce.delete({ uid: caller.uid }, { tid }); + // tbd: activitypubApi.undo.announce? + } else { + // tbd: some kind of plain object announce by the category... + activitypubApi.announce.note(caller, { tid }); // user announce, remove when discrete announces are a thing + // tbd: api.activitypub.announce.move + } } await events.log({ diff --git a/src/topics/tools.js b/src/topics/tools.js index 65505bde67..294615b38a 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -7,7 +7,6 @@ const topics = require('.'); const categories = require('../categories'); const user = require('../user'); const plugins = require('../plugins'); -const api = require('../api'); const privileges = require('../privileges'); const utils = require('../utils'); @@ -278,13 +277,6 @@ module.exports = function (Topics) { const oldCid = topicData.cid; await categories.moveRecentReplies(tid, oldCid, cid); - // AP: Announce(Delete(Object)) - if (cid === -1) { - await api.activitypub.announce.delete({ uid: data.uid }, { tid }); - } else { - // tbd: api.activitypub.announce.move - } - await Promise.all([ Topics.setTopicFields(tid, { cid: cid, From 4d5005b972664150ba1f38b652304d4c30b2856a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 16 Oct 2025 11:12:00 -0400 Subject: [PATCH 502/828] feat: handle incoming Announce(Delete), closes #13712 --- src/activitypub/inbox.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 9b5f85db69..9f8f3c5ebe 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -321,6 +321,12 @@ inbox.announce = async (req) => { break; } + case object.type === 'Delete': { + req.body = object; + await inbox.delete(req); + break; + } + case object.type === 'Create': { object = object.object; // falls through From 2b2028e4469702547184784aa43568b820dae86f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 16 Oct 2025 11:27:21 -0400 Subject: [PATCH 503/828] refactor: inbox announce(delete) handling to also handle context deletion, #13712 --- src/activitypub/contexts.js | 4 ++++ src/activitypub/inbox.js | 34 ++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/activitypub/contexts.js b/src/activitypub/contexts.js index 9d6c948a5e..2e346facc0 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); diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 9f8f3c5ebe..77fd2bf4b5 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -188,14 +188,14 @@ 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') { @@ -208,27 +208,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) { 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 }), // db.isSortedSetMember('usersRemote:lastCrawled', object.id), ]); switch (true) { case isNote: { - const cid = await posts.getCidByPid(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(pid, 'uid'); - await activitypub.feps.announce(pid, req.body); - await api.posts[method]({ uid }, { pid }); + 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; } @@ -238,7 +252,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; } } From 1d529473b4ca1123ddc73bd480044e32f63042d3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 16 Oct 2025 12:17:52 -0400 Subject: [PATCH 504/828] fix: rebroadcasting logic should only execute for local tids if the remote cid is not addressed already --- src/activitypub/feps.js | 3 ++- src/activitypub/helpers.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index 9fb0e28680..01fc3d46d0 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -23,7 +23,8 @@ Feps.announce = async function announce(id, activity) { const tid = await posts.getPostField(localId || id, 'tid'); const cid = await topics.getTopicField(tid, 'cid'); const localCid = utils.isNumber(cid) && cid > 0; - const shouldAnnounce = localCid || utils.isNumber(tid); + const addressed = activitypub.helpers.addressed(cid, activity); + const shouldAnnounce = localCid || (utils.isNumber(tid) && !addressed); if (!shouldAnnounce) { // inverse conditionals can kiss my ass. return; } diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 90701dd58d..6628b3ec55 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -526,3 +526,20 @@ 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); +}; From e09bb8b611acca5348a18bd19d3ed6c2a70129b9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 16 Oct 2025 15:57:01 -0400 Subject: [PATCH 505/828] refactor: user announces no longer occur on topic move. Instead, the new category announces. Only occurs when topic moved to local categories. --- src/api/activitypub.js | 35 ++++++++++++++++++++++++++++++++++- src/api/topics.js | 3 +-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/api/activitypub.js b/src/api/activitypub.js index 19e9087539..3d1d3ff546 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -347,7 +347,40 @@ activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { activitypubApi.announce = {}; -activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => { +activitypubApi.announce.category = enabledCheck(async (_, { tid }) => { + const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); + + // Only local categories can announce + if (!utils.isNumber(cid) || 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')}/category/${cid}/followers`, uid], + }, { cid, uid: utils.isNumber(uid) ? uid : undefined }); + + await activitypub.send('cid', cid, Array.from(targets), { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${Date.now()}`, + type: 'Announce', + actor: `${nconf.get('url')}/category/${cid}`, + to, + cc, + object: pid, + target: `${nconf.get('url')}/category/${cid}`, + }); +}); + +activitypubApi.announce.user = enabledCheck(async (caller, { tid }) => { + // ORPHANED, but will re-use when user announces are a thing. const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); // Only remote posts can be announced to local categories diff --git a/src/api/topics.js b/src/api/topics.js index e0f149e392..38468f5956 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -326,8 +326,7 @@ topicsAPI.move = async (caller, { tid, cid }) => { await activitypubApi.announce.delete({ uid: caller.uid }, { tid }); // tbd: activitypubApi.undo.announce? } else { - // tbd: some kind of plain object announce by the category... - activitypubApi.announce.note(caller, { tid }); // user announce, remove when discrete announces are a thing + activitypubApi.announce.category(caller, { tid }); // tbd: api.activitypub.announce.move } } From f98a7216a3feed00c80a91ee6276126bfc51158b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 16 Oct 2025 16:23:27 -0400 Subject: [PATCH 506/828] feat: handle Delete(Context) as a move to cid -1 if the remote context still exists --- src/activitypub/inbox.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 77fd2bf4b5..9fe4a52a4d 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -199,7 +199,9 @@ inbox.delete = async (req) => { } if (type === 'Tombstone') { - method = 'delete'; + method = 'delete'; // soft delete + } else if (activitypub._constants.acceptable.contextTypes.includes(type)) { + method = 'move'; // move to cid -1 } } catch (e) { // probably 410/404 @@ -215,10 +217,15 @@ inbox.delete = async (req) => { const [isNote, isContext/* , isActor */] = await Promise.all([ posts.exists(id), - activitypub.contexts.getItems(0, id, { returnRootId: true }), + activitypub.contexts.getItems(0, id, { returnRootId: true }), // ⚠️ unreliable, needs better logic (Contexts.is?) // db.isSortedSetMember('usersRemote:lastCrawled', object.id), ]); + // 'move' method only applicable for contexts + if (method === 'move' && !isContext) { + return reject('Delete', object, actor); + } + switch (true) { case isNote: { const cid = await posts.getCidByPid(id); @@ -241,8 +248,13 @@ inbox.delete = async (req) => { 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] }); + if (method === 'move') { + activitypub.helpers.log(`[activitypub/inbox.delete] Moving tid ${tid} to cid -1.`); + await api.topics.move({ uid }, { tid, cid: -1 }); + } else { + activitypub.helpers.log(`[activitypub/inbox.delete] Deleting tid ${tid}.`); + await api.topics[method]({ uid }, { tids: [tid] }); + } break; } From 603068aebb792dfbf4696c5e7a158bedf9484289 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 17 Oct 2025 11:11:04 -0400 Subject: [PATCH 507/828] fix: do not include image or icon props if they are falsy values --- src/activitypub/mocks.js | 58 +++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 1e04fb99ab..81db4b7a01 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -486,35 +486,37 @@ 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`, - - 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, - - publicKey: { - id: `${nconf.get('url')}/uid/${uid}#key`, - owner: `${nconf.get('url')}/uid/${uid}`, - publicKeyPem: publicKey, - }, - - endpoints: { - sharedInbox: `${nconf.get('url')}/inbox`, + ...{ + '@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, + 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`, + }, }, + ...(picture && { icon: picture }), + ...(cover && { image: cover }), }; }; From ecf95d1898347d3d8f0c6799d6c2ea9c92d4818b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 17 Oct 2025 11:11:04 -0400 Subject: [PATCH 508/828] fix: do not include image or icon props if they are falsy values --- src/activitypub/mocks.js | 58 +++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 1e04fb99ab..81db4b7a01 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -486,35 +486,37 @@ 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`, - - 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, - - publicKey: { - id: `${nconf.get('url')}/uid/${uid}#key`, - owner: `${nconf.get('url')}/uid/${uid}`, - publicKeyPem: publicKey, - }, - - endpoints: { - sharedInbox: `${nconf.get('url')}/inbox`, + ...{ + '@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, + 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`, + }, }, + ...(picture && { icon: picture }), + ...(cover && { image: cover }), }; }; From 351c9abc6f3016a55b34b6c791b85a7bb7f9a8d7 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 17 Oct 2025 15:21:57 +0000 Subject: [PATCH 509/828] chore: incrementing version number - v4.6.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6e48066aed..d49977d6fb 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.6.0", + "version": "4.6.1", "homepage": "https://www.nodebb.org", "repository": { "type": "git", From 655c858b5de68e5e439ecb96600ed0aa99bf6caf Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 17 Oct 2025 15:21:57 +0000 Subject: [PATCH 510/828] chore: update changelog for v4.6.1 --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb3067783..d4de66ec7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +#### 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 From 2425f3b671ef1f286740f21cac788c206cef8546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 17 Oct 2025 16:23:50 -0400 Subject: [PATCH 511/828] https://github.com/NodeBB/NodeBB/issues/13713 --- public/src/client/topic.js | 6 ++++++ public/src/modules/helpers.common.js | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 5ee1f91f60..bdebc50264 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -206,6 +206,12 @@ define('forum/topic', [ }); } }); + + $('[component="topic/thumb/list/expand"]').on('click', function () { + const btn = $(this); + btn.parents('[component="topic/thumb/list"]').removeClass('thumbs-collapsed'); + btn.remove(); + }); } function addBlockQuoteHandler() { diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index 896d0b485b..6df32ea76e 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, @@ -328,6 +330,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); From 52c56bc5453bd68d64082cf4ce5bfcd728a9cb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 17 Oct 2025 22:02:57 -0400 Subject: [PATCH 512/828] chore: up themes --- install/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/package.json b/install/package.json index 90d5f97518..7706ad1192 100644 --- a/install/package.json +++ b/install/package.json @@ -101,16 +101,16 @@ "nodebb-plugin-dbsearch": "6.3.3", "nodebb-plugin-emoji": "6.0.3", "nodebb-plugin-emoji-android": "4.1.1", - "nodebb-plugin-link-preview": "2.1.5", + "nodebb-plugin-link-preview": "2.1.5", "nodebb-plugin-markdown": "13.2.1", "nodebb-plugin-mentions": "4.7.6", "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.21", + "nodebb-theme-harmony": "2.1.22", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", - "nodebb-theme-persona": "14.1.15", + "nodebb-theme-persona": "14.1.16", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.9", "nprogress": "0.2.0", From 27a0dc731bddf58db8406a0bf9f619d5a69e056e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 22:10:24 -0400 Subject: [PATCH 513/828] fix(deps): update dependency ace-builds to v1.43.4 (#13714) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 97897399eb..6563a7f2d9 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.43.3", + "ace-builds": "1.43.4", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", From 7fd9e89495cebba1fca0aa63b1ba1585572129c0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 22:25:08 -0400 Subject: [PATCH 514/828] chore(deps): update dependency @eslint/js to v9.38.0 (#13716) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6563a7f2d9..4489a095a3 100644 --- a/install/package.json +++ b/install/package.json @@ -164,7 +164,7 @@ "@commitlint/cli": "20.1.0", "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", - "@eslint/js": "9.37.0", + "@eslint/js": "9.38.0", "@stylistic/eslint-plugin": "5.4.0", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 1d9d7fc56b222ddd26e2661f5d0407fc8d99c9d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:32:52 -0400 Subject: [PATCH 515/828] fix(deps): update dependency sitemap to v8.0.1 (#13720) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4489a095a3..589f1d75ad 100644 --- a/install/package.json +++ b/install/package.json @@ -135,7 +135,7 @@ "semver": "7.7.3", "serve-favicon": "2.5.1", "sharp": "0.34.4", - "sitemap": "8.0.0", + "sitemap": "8.0.1", "socket.io": "4.8.1", "socket.io-client": "4.8.1", "@socket.io/redis-adapter": "8.3.0", From 9d2b83f5636f88befa9f5a6a7e45bf0db16aca8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:33:11 -0400 Subject: [PATCH 516/828] chore(deps): update dependency jsdom to v27.0.1 (#13718) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 589f1d75ad..ab61a659e3 100644 --- a/install/package.json +++ b/install/package.json @@ -171,7 +171,7 @@ "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", - "jsdom": "27.0.0", + "jsdom": "27.0.1", "lint-staged": "16.2.4", "mocha": "11.7.4", "mocha-lcov-reporter": "1.3.0", From 93d46c842ea4f94218783bb35e19da283b8c86ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:44:31 -0400 Subject: [PATCH 517/828] chore(deps): update dependency @stylistic/eslint-plugin to v5.5.0 (#13717) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index ab61a659e3..adc2a69305 100644 --- a/install/package.json +++ b/install/package.json @@ -165,7 +165,7 @@ "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", "@eslint/js": "9.38.0", - "@stylistic/eslint-plugin": "5.4.0", + "@stylistic/eslint-plugin": "5.5.0", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", From 97e59fbe0430f40ec28ee8df0668b2d1218b7fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 21 Oct 2025 10:11:18 -0400 Subject: [PATCH 518/828] feat: add new setting to control posts uploads being shown as thumbs --- install/data/defaults.json | 1 + public/language/en-GB/admin/settings/uploads.json | 1 + src/topics/thumbs.js | 5 +++-- src/views/admin/settings/uploads.tpl | 5 +++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index 574a8bfe01..67557efabe 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -38,6 +38,7 @@ "maximumTagLength": 15, "undoTimeout": 0, "allowTopicsThumbnail": 1, + "showPostUploadsAsThumbnails": 1, "registrationType": "normal", "registrationApprovalType": "normal", "allowAccountDelete": 1, diff --git a/public/language/en-GB/admin/settings/uploads.json b/public/language/en-GB/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/en-GB/admin/settings/uploads.json +++ b/public/language/en-GB/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/src/topics/thumbs.js b/src/topics/thumbs.js index 22fa2c48bd..3239ab0fdc 100644 --- a/src/topics/thumbs.js +++ b/src/topics/thumbs.js @@ -33,8 +33,9 @@ Thumbs.load = async function (topicData) { const topicsWithThumbs = topicData.filter((tid, idx) => hasThumbs[idx]); const tidsWithThumbs = topicsWithThumbs.map(t => t.tid); - - const thumbs = await loadFromTopicData(topicsWithThumbs); + 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] || []) : [])); diff --git a/src/views/admin/settings/uploads.tpl b/src/views/admin/settings/uploads.tpl index aa138c8c67..400058eb20 100644 --- a/src/views/admin/settings/uploads.tpl +++ b/src/views/admin/settings/uploads.tpl @@ -88,6 +88,11 @@

    +
    + + +
    +
    From e7498e8fb5d2c852752a0d1128279655985420e3 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Tue, 21 Oct 2025 14:11:49 +0000 Subject: [PATCH 519/828] chore(i18n): fallback strings for new resources: nodebb.admin-settings-uploads --- public/language/ar/admin/settings/uploads.json | 1 + public/language/az/admin/settings/uploads.json | 1 + public/language/bg/admin/settings/uploads.json | 1 + public/language/bn/admin/settings/uploads.json | 1 + public/language/cs/admin/settings/uploads.json | 1 + public/language/da/admin/settings/uploads.json | 1 + public/language/de/admin/settings/uploads.json | 1 + public/language/el/admin/settings/uploads.json | 1 + public/language/en-US/admin/settings/uploads.json | 1 + public/language/en-x-pirate/admin/settings/uploads.json | 1 + public/language/es/admin/settings/uploads.json | 1 + public/language/et/admin/settings/uploads.json | 1 + public/language/fa-IR/admin/settings/uploads.json | 1 + public/language/fi/admin/settings/uploads.json | 1 + public/language/fr/admin/settings/uploads.json | 1 + public/language/gl/admin/settings/uploads.json | 1 + public/language/he/admin/settings/uploads.json | 1 + public/language/hr/admin/settings/uploads.json | 1 + public/language/hu/admin/settings/uploads.json | 1 + public/language/hy/admin/settings/uploads.json | 1 + public/language/id/admin/settings/uploads.json | 1 + public/language/it/admin/settings/uploads.json | 1 + public/language/ja/admin/settings/uploads.json | 1 + public/language/ko/admin/settings/uploads.json | 1 + public/language/lt/admin/settings/uploads.json | 1 + public/language/lv/admin/settings/uploads.json | 1 + public/language/ms/admin/settings/uploads.json | 1 + public/language/nb/admin/settings/uploads.json | 1 + public/language/nl/admin/settings/uploads.json | 1 + public/language/nn-NO/admin/settings/uploads.json | 1 + public/language/pl/admin/settings/uploads.json | 1 + public/language/pt-BR/admin/settings/uploads.json | 1 + public/language/pt-PT/admin/settings/uploads.json | 1 + public/language/ro/admin/settings/uploads.json | 1 + public/language/ru/admin/settings/uploads.json | 1 + public/language/rw/admin/settings/uploads.json | 1 + public/language/sc/admin/settings/uploads.json | 1 + public/language/sk/admin/settings/uploads.json | 1 + public/language/sl/admin/settings/uploads.json | 1 + public/language/sq-AL/admin/settings/uploads.json | 1 + public/language/sr/admin/settings/uploads.json | 1 + public/language/sv/admin/settings/uploads.json | 1 + public/language/th/admin/settings/uploads.json | 1 + public/language/tr/admin/settings/uploads.json | 1 + public/language/uk/admin/settings/uploads.json | 1 + public/language/ur/admin/settings/uploads.json | 1 + public/language/vi/admin/settings/uploads.json | 1 + public/language/zh-CN/admin/settings/uploads.json | 1 + public/language/zh-TW/admin/settings/uploads.json | 1 + 49 files changed, 49 insertions(+) diff --git a/public/language/ar/admin/settings/uploads.json b/public/language/ar/admin/settings/uploads.json index b8d85be443..af3efb16f7 100644 --- a/public/language/ar/admin/settings/uploads.json +++ b/public/language/ar/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "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/az/admin/settings/uploads.json b/public/language/az/admin/settings/uploads.json index ceed4ae396..a403a54e34 100644 --- a/public/language/az/admin/settings/uploads.json +++ b/public/language/az/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "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/bg/admin/settings/uploads.json b/public/language/bg/admin/settings/uploads.json index 4820730824..0cbfc9f7b4 100644 --- a/public/language/bg/admin/settings/uploads.json +++ b/public/language/bg/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Максимална височина на изображенията (в пиксели)", "reject-image-height-help": "Изображенията, чиято височина е по-голяма от тази стойност, ще бъдат отхвърляни.", "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/bn/admin/settings/uploads.json b/public/language/bn/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/bn/admin/settings/uploads.json +++ b/public/language/bn/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/cs/admin/settings/uploads.json b/public/language/cs/admin/settings/uploads.json index dea6aa44df..90d14e88d8 100644 --- a/public/language/cs/admin/settings/uploads.json +++ b/public/language/cs/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximální výška obrázku (v pixelech)", "reject-image-height-help": "Vyšší obrázek než tato hodnota bude zamítnut.", "allow-topic-thumbnails": "Povolit uživatelům nahrát miniatury témat", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "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/da/admin/settings/uploads.json b/public/language/da/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/da/admin/settings/uploads.json +++ b/public/language/da/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/de/admin/settings/uploads.json b/public/language/de/admin/settings/uploads.json index 269bfb8178..75a5223588 100644 --- a/public/language/de/admin/settings/uploads.json +++ b/public/language/de/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximale Bildhöhe (in Pixeln)", "reject-image-height-help": "Höhere Bilder werden abgelehnt.", "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/el/admin/settings/uploads.json b/public/language/el/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/el/admin/settings/uploads.json +++ b/public/language/el/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/en-US/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/en-US/admin/settings/uploads.json +++ b/public/language/en-US/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/en-x-pirate/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/en-x-pirate/admin/settings/uploads.json +++ b/public/language/en-x-pirate/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/es/admin/settings/uploads.json b/public/language/es/admin/settings/uploads.json index 60273aa8ac..d92fe07210 100644 --- a/public/language/es/admin/settings/uploads.json +++ b/public/language/es/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "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/et/admin/settings/uploads.json b/public/language/et/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/et/admin/settings/uploads.json +++ b/public/language/et/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/fa-IR/admin/settings/uploads.json index baf90ba0e4..507f64c898 100644 --- a/public/language/fa-IR/admin/settings/uploads.json +++ b/public/language/fa-IR/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/fi/admin/settings/uploads.json index a1412b0ed2..792ccd9d3d 100644 --- a/public/language/fi/admin/settings/uploads.json +++ b/public/language/fi/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Kuvan suurin sallittu korkeus (kuvapisteinä)", "reject-image-height-help": "Images taller than this value will be rejected.", "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/fr/admin/settings/uploads.json b/public/language/fr/admin/settings/uploads.json index 453b6be283..6662c0d074 100644 --- a/public/language/fr/admin/settings/uploads.json +++ b/public/language/fr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Hauteur maximale des images (en pixels)", "reject-image-height-help": "Les images plus grandes que cette valeur seront rejetées.", "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/gl/admin/settings/uploads.json b/public/language/gl/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/gl/admin/settings/uploads.json +++ b/public/language/gl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/he/admin/settings/uploads.json b/public/language/he/admin/settings/uploads.json index 55286bd76d..3b4c72a82b 100644 --- a/public/language/he/admin/settings/uploads.json +++ b/public/language/he/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "גובה תמונה מקסימלי (בפיקסלים)", "reject-image-height-help": "תמונות גבוהות יותר מערך זה יידחו", "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/hr/admin/settings/uploads.json b/public/language/hr/admin/settings/uploads.json index 1efba861e4..e206adf803 100644 --- a/public/language/hr/admin/settings/uploads.json +++ b/public/language/hr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "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/hu/admin/settings/uploads.json b/public/language/hu/admin/settings/uploads.json index fb103aea58..8a575d432f 100644 --- a/public/language/hu/admin/settings/uploads.json +++ b/public/language/hu/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "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/hy/admin/settings/uploads.json b/public/language/hy/admin/settings/uploads.json index 6fa6f789fa..388ddc3c09 100644 --- a/public/language/hy/admin/settings/uploads.json +++ b/public/language/hy/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Նկարի առավելագույն բարձրությունը (պիքսելներով)", "reject-image-height-help": "Այս արժեքից բարձր նկարները կմերժվեն:", "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/id/admin/settings/uploads.json b/public/language/id/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/id/admin/settings/uploads.json +++ b/public/language/id/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/it/admin/settings/uploads.json b/public/language/it/admin/settings/uploads.json index a2ba8602e9..f063b36ac9 100644 --- a/public/language/it/admin/settings/uploads.json +++ b/public/language/it/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Lunghezza Massima Immagine (in pixel)", "reject-image-height-help": "Le immagini più alte di questo valore saranno rifiutate.", "allow-topic-thumbnails": "Consenti agli utenti di caricare le miniature degli argomenti", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "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/ja/admin/settings/uploads.json b/public/language/ja/admin/settings/uploads.json index 84b216ba93..1e7ff65241 100644 --- a/public/language/ja/admin/settings/uploads.json +++ b/public/language/ja/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "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/uploads.json b/public/language/ko/admin/settings/uploads.json index f222e5fe80..c6ae4c5a87 100644 --- a/public/language/ko/admin/settings/uploads.json +++ b/public/language/ko/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "최대 이미지 높이(픽셀 단위)", "reject-image-height-help": "이 값보다 큰 이미지는 등록할 수 없습니다.", "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/lt/admin/settings/uploads.json b/public/language/lt/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/lt/admin/settings/uploads.json +++ b/public/language/lt/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/lv/admin/settings/uploads.json b/public/language/lv/admin/settings/uploads.json index 0b79f6bb9a..94082ee915 100644 --- a/public/language/lv/admin/settings/uploads.json +++ b/public/language/lv/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "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/ms/admin/settings/uploads.json b/public/language/ms/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/ms/admin/settings/uploads.json +++ b/public/language/ms/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/nb/admin/settings/uploads.json b/public/language/nb/admin/settings/uploads.json index ff5386fedb..8209e108e4 100644 --- a/public/language/nb/admin/settings/uploads.json +++ b/public/language/nb/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksimal bildehøyde (i piksler)", "reject-image-height-help": "Bilder høyere enn denne verdien vil bli avvist.", "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/nl/admin/settings/uploads.json b/public/language/nl/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/nl/admin/settings/uploads.json +++ b/public/language/nl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/nn-NO/admin/settings/uploads.json b/public/language/nn-NO/admin/settings/uploads.json index c729541aff..c7730e9264 100644 --- a/public/language/nn-NO/admin/settings/uploads.json +++ b/public/language/nn-NO/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Avvis bilete høgde", "reject-image-height-help": "Angi maksimal høgde for bilete som vert avvist ved opplasting.", "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/pl/admin/settings/uploads.json b/public/language/pl/admin/settings/uploads.json index 10d0606239..561a29afc0 100644 --- a/public/language/pl/admin/settings/uploads.json +++ b/public/language/pl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksymalna wysokość obrazu (w pikselach)", "reject-image-height-help": "Obrazy o wysokości przekraczającej tę wartość zostaną odrzucone.", "allow-topic-thumbnails": "Zezwalaj użytkownikom na ustawianie miniaturek tematów", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "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/pt-BR/admin/settings/uploads.json b/public/language/pt-BR/admin/settings/uploads.json index c203d2cd23..5f0b8362c9 100644 --- a/public/language/pt-BR/admin/settings/uploads.json +++ b/public/language/pt-BR/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "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-PT/admin/settings/uploads.json b/public/language/pt-PT/admin/settings/uploads.json index 91c89d7a46..593df275f8 100644 --- a/public/language/pt-PT/admin/settings/uploads.json +++ b/public/language/pt-PT/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "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/ro/admin/settings/uploads.json b/public/language/ro/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/ro/admin/settings/uploads.json +++ b/public/language/ro/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/ru/admin/settings/uploads.json b/public/language/ru/admin/settings/uploads.json index 8725c70db8..c26cbe27da 100644 --- a/public/language/ru/admin/settings/uploads.json +++ b/public/language/ru/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Макс. высота изображения (в пикселях)", "reject-image-height-help": "Загрузка изображений выше указанного значения будет отклонена.", "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/rw/admin/settings/uploads.json b/public/language/rw/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/rw/admin/settings/uploads.json +++ b/public/language/rw/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/sc/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sc/admin/settings/uploads.json +++ b/public/language/sc/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/sk/admin/settings/uploads.json b/public/language/sk/admin/settings/uploads.json index f9a18dcb4c..fdabe75093 100644 --- a/public/language/sk/admin/settings/uploads.json +++ b/public/language/sk/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "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/sl/admin/settings/uploads.json b/public/language/sl/admin/settings/uploads.json index fc43ca3793..8d185fe531 100644 --- a/public/language/sl/admin/settings/uploads.json +++ b/public/language/sl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/sq-AL/admin/settings/uploads.json b/public/language/sq-AL/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sq-AL/admin/settings/uploads.json +++ b/public/language/sq-AL/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/sr/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sr/admin/settings/uploads.json +++ b/public/language/sr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/sv/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sv/admin/settings/uploads.json +++ b/public/language/sv/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/uploads.json b/public/language/th/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/th/admin/settings/uploads.json +++ b/public/language/th/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "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/tr/admin/settings/uploads.json b/public/language/tr/admin/settings/uploads.json index f9b369f66a..b9d2884b62 100644 --- a/public/language/tr/admin/settings/uploads.json +++ b/public/language/tr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksimum Görsel Yüksekliği (piksel)", "reject-image-height-help": "Bu değerden daha uzun olan görseller reddedilecektir.", "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/uk/admin/settings/uploads.json b/public/language/uk/admin/settings/uploads.json index f54640cb29..18dda7a1ca 100644 --- a/public/language/uk/admin/settings/uploads.json +++ b/public/language/uk/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "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/ur/admin/settings/uploads.json b/public/language/ur/admin/settings/uploads.json index f44da0e354..289d295862 100644 --- a/public/language/ur/admin/settings/uploads.json +++ b/public/language/ur/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "تصاویر کی زیادہ سے زیادہ اونچائی (پکسلز میں)", "reject-image-height-help": "جن تصاویر کی اونچائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔", "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/vi/admin/settings/uploads.json b/public/language/vi/admin/settings/uploads.json index f89c13a955..055f3dcf47 100644 --- a/public/language/vi/admin/settings/uploads.json +++ b/public/language/vi/admin/settings/uploads.json @@ -22,6 +22,7 @@ "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.", "allow-topic-thumbnails": "Cho phép người dùng tải lên ảnh thumbnails chủ đề", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "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/zh-CN/admin/settings/uploads.json b/public/language/zh-CN/admin/settings/uploads.json index 382195d94a..0c3cee593d 100644 --- a/public/language/zh-CN/admin/settings/uploads.json +++ b/public/language/zh-CN/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "图片最大高度值(单位:像素)", "reject-image-height-help": "高于此数值大小的图片将会被拒绝", "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/uploads.json b/public/language/zh-TW/admin/settings/uploads.json index fc3bddd9ca..a72caffbc2 100644 --- a/public/language/zh-TW/admin/settings/uploads.json +++ b/public/language/zh-TW/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "圖片最大高度值(單位:像素)", "reject-image-height-help": "高於此數值大小的圖片將會被拒絕", "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 )。 為空則表示允許所有副檔名。", From 83a172c9a49bb499e6bb84dd42bdf63829ad313d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:13:58 -0400 Subject: [PATCH 520/828] chore(deps): update dependency lint-staged to v16.2.5 (#13721) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index adc2a69305..45db72c361 100644 --- a/install/package.json +++ b/install/package.json @@ -172,7 +172,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "27.0.1", - "lint-staged": "16.2.4", + "lint-staged": "16.2.5", "mocha": "11.7.4", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 34e95e6d46b670df2a3973c842d7b667bbe9d0ea Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 21 Oct 2025 12:00:01 -0400 Subject: [PATCH 521/828] 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 --- src/activitypub/inbox.js | 59 +++++++++++++++++--------- src/api/activitypub.js | 92 +++++++++++++++------------------------- src/api/topics.js | 8 ++-- src/topics/delete.js | 3 +- 4 files changed, 79 insertions(+), 83 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 9fe4a52a4d..9a9381f43f 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'); @@ -78,6 +79,42 @@ inbox.add = async (req) => { } }; +inbox.remove = async (req) => { + const { actor, object } = req.body; + + const isContext = activitypub._constants.acceptable.contextTypes.has(object.type); + if (!isContext) { + return; // don't know how to handle other types + } + console.log('isContext?', isContext); + + const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); + const exists = await posts.exists(mainPid); + if (!exists) { + return; // post not cached; do nothing. + } + console.log('mainPid is', mainPid); + + // 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; what?? + 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.update = async (req) => { const { actor, object } = req.body; const isPublic = publiclyAddressed([...(object.to || []), ...(object.cc || [])]); @@ -200,8 +237,6 @@ inbox.delete = async (req) => { if (type === 'Tombstone') { method = 'delete'; // soft delete - } else if (activitypub._constants.acceptable.contextTypes.includes(type)) { - method = 'move'; // move to cid -1 } } catch (e) { // probably 410/404 @@ -221,11 +256,6 @@ inbox.delete = async (req) => { // db.isSortedSetMember('usersRemote:lastCrawled', object.id), ]); - // 'move' method only applicable for contexts - if (method === 'move' && !isContext) { - return reject('Delete', object, actor); - } - switch (true) { case isNote: { const cid = await posts.getCidByPid(id); @@ -248,13 +278,8 @@ inbox.delete = async (req) => { return; } const { tid, uid } = await posts.getPostFields(pid, ['tid', 'uid']); - if (method === 'move') { - activitypub.helpers.log(`[activitypub/inbox.delete] Moving tid ${tid} to cid -1.`); - await api.topics.move({ uid }, { tid, cid: -1 }); - } else { - activitypub.helpers.log(`[activitypub/inbox.delete] Deleting tid ${tid}.`); - await api.topics[method]({ uid }, { tids: [tid] }); - } + activitypub.helpers.log(`[activitypub/inbox.delete] Deleting tid ${tid}.`); + await api.topics[method]({ uid }, { tids: [tid] }); break; } @@ -347,12 +372,6 @@ inbox.announce = async (req) => { break; } - case object.type === 'Delete': { - req.body = object; - await inbox.delete(req); - break; - } - case object.type === 'Create': { object = object.object; // falls through diff --git a/src/api/activitypub.js b/src/api/activitypub.js index 3d1d3ff546..12c1989238 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -412,63 +412,6 @@ activitypubApi.announce.user = enabledCheck(async (caller, { tid }) => { }); }); -activitypubApi.announce.delete = enabledCheck(async ({ uid }, { tid }) => { - const now = new Date(); - const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); - - // 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 announce of pid ${pid} to the fediverse due to privileges.`); - return; - } - - const { to, cc, targets } = await activitypub.buildRecipients({ - to: [activitypub._constants.publicAddress], - cc: [`${nconf.get('url')}/category/${cid}/followers`], - }, { cid }); - - const deleteTpl = { - id: `${nconf.get('url')}/topic/${tid}#activity/delete/${now.getTime()}`, - type: 'Delete', - actor: `${nconf.get('url')}/category/${cid}`, - to, - cc, - origin: `${nconf.get('url')}/category/${cid}`, - }; - - // 7888 variant - await activitypub.send('cid', cid, Array.from(targets), { - id: `${nconf.get('url')}/topic/${tid}#activity/announce/delete/${now.getTime()}`, - type: 'Announce', - actor: `${nconf.get('url')}/category/${cid}`, - to, - cc, - object: { - ...deleteTpl, - object: `${nconf.get('url')}/topic/${tid}`, - }, - }); - - // 1b12 variant - await activitypub.send('cid', cid, Array.from(targets), { - id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/delete/${now.getTime()}`, - type: 'Announce', - actor: `${nconf.get('url')}/category/${cid}`, - to, - cc, - object: { - ...deleteTpl, - actor: `${nconf.get('url')}/uid/${uid}`, - object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, - }, - }); -}); - activitypubApi.undo = {}; // activitypubApi.undo.follow = @@ -573,3 +516,38 @@ activitypubApi.undo.flag = enabledCheck(async (caller, flag) => { }); await db.sortedSetRemove(`flag:${flag.flagId}:remote`, caller.uid); }); + +activitypubApi.remove = {}; + +activitypubApi.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: [`${nconf.get('url')}/category/${cid}/followers`], + }, { cid }); + + // Remove(Context) + 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}`, + origin: `${nconf.get('url')}/category/${cid}`, + }); +}); \ No newline at end of file diff --git a/src/api/topics.js b/src/api/topics.js index 38468f5956..964c49e2ec 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -8,6 +8,7 @@ const meta = require('../meta'); const privileges = require('../privileges'); const events = require('../events'); const batch = require('../batch'); +const utils = require('../utils'); const activitypubApi = require('./activitypub'); const apiHelpers = require('./helpers'); @@ -321,13 +322,12 @@ topicsAPI.move = async (caller, { tid, cid }) => { if (!topicData.deleted) { socketHelpers.sendNotificationToTopicOwner(tid, caller.uid, 'move', 'notifications:moved-your-topic'); - // AP: Announce(Delete(Object)) - if (cid === -1) { - await activitypubApi.announce.delete({ uid: caller.uid }, { tid }); + if (utils.isNumber(cid) && parseInt(cid, 10) === -1) { + activitypubApi.remove.context(caller, { tid }); // tbd: activitypubApi.undo.announce? } else { + // tbd: activitypubApi.move activitypubApi.announce.category(caller, { tid }); - // tbd: api.activitypub.announce.move } } diff --git a/src/topics/delete.js b/src/topics/delete.js index 3557349d9d..ee9d39e107 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -25,7 +25,7 @@ module.exports = function (Topics) { deleterUid: uid, deletedTimestamp: Date.now(), }), - api.activitypub.announce.delete({ uid }, { tid }), + api.activitypub.remove.context({ uid }, { tid }), ]); await categories.updateRecentTidForCid(cid); @@ -82,7 +82,6 @@ module.exports = function (Topics) { } deletedTopic.tags = tags; await deleteFromFollowersIgnorers(tid); - await api.activitypub.announce.delete({ uid }, { tid }), await Promise.all([ db.deleteAll([ From 3df4970ce112970cba895253465186403c8e3849 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 21 Oct 2025 12:16:20 -0400 Subject: [PATCH 522/828] fix: call api.topics method on topic move during note assertion, have category announce new topic on note assertion --- src/activitypub/notes.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 8e11898f26..14e0976986 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -15,6 +15,7 @@ const notifications = require('../notifications'); const user = require('../user'); const topics = require('../topics'); const posts = require('../posts'); +const api = require('../api'); const utils = require('../utils'); const activitypub = module.parent.exports; @@ -106,7 +107,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (options.cid && cid === -1) { // Move topic if currently uncategorized - await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); + await api.topics.move({ uid: 'system' }, { tid, cid: options.cid }); } const exists = await posts.exists(chain.map(p => p.pid)); @@ -261,6 +262,12 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } await Notes.syncUserInboxes(tid, uid); + + if (!hasTid && options.cid) { + // New topic, have category announce it + api.activitypub.announce.category({}, { tid }); + } + return { tid, count }; } catch (e) { winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}).`); From 5a6c209770c3439edf7564f6a18ed693a6b6b683 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:23:22 -0400 Subject: [PATCH 523/828] fix(deps): update dependency workerpool to v10 (#13723) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 45db72c361..c0b6bc44f8 100644 --- a/install/package.json +++ b/install/package.json @@ -153,7 +153,7 @@ "webpack": "5.102.1", "webpack-merge": "6.0.1", "winston": "3.18.3", - "workerpool": "9.3.4", + "workerpool": "10.0.0", "xml": "1.0.1", "xregexp": "5.1.2", "yargs": "17.7.2", From bb34b8c7a3b2fd617bc7bb8000df38ae897baea0 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 22 Oct 2025 09:20:27 +0000 Subject: [PATCH 524/828] Latest translations and fallbacks --- public/language/bg/admin/settings/uploads.json | 2 +- public/language/it/admin/settings/uploads.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/bg/admin/settings/uploads.json b/public/language/bg/admin/settings/uploads.json index 0cbfc9f7b4..0cb47ec123 100644 --- a/public/language/bg/admin/settings/uploads.json +++ b/public/language/bg/admin/settings/uploads.json @@ -22,7 +22,7 @@ "reject-image-height": "Максимална височина на изображенията (в пиксели)", "reject-image-height-help": "Изображенията, чиято височина е по-голяма от тази стойност, ще бъдат отхвърляни.", "allow-topic-thumbnails": "Позволяване на потребителите да качват миниатюрни изображения за темите", - "show-post-uploads-as-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/it/admin/settings/uploads.json b/public/language/it/admin/settings/uploads.json index f063b36ac9..d40563b47f 100644 --- a/public/language/it/admin/settings/uploads.json +++ b/public/language/it/admin/settings/uploads.json @@ -22,7 +22,7 @@ "reject-image-height": "Lunghezza Massima Immagine (in pixel)", "reject-image-height-help": "Le immagini più alte di questo valore saranno rifiutate.", "allow-topic-thumbnails": "Consenti agli utenti di caricare le miniature degli argomenti", - "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", + "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.", From 3ede64d8a12a1ecc1970f0d9218ea7bcae9100e1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 22 Oct 2025 12:51:50 -0400 Subject: [PATCH 525/828] refactor: move all methods in src/api/activitypub.js to src/activitypub.out.js --- src/activitypub/index.js | 1 + src/activitypub/notes.js | 2 +- .../activitypub.js => activitypub/out.js} | 388 ++++++++---------- src/api/categories.js | 5 +- src/api/helpers.js | 6 +- src/api/index.js | 1 - src/api/posts.js | 6 +- src/api/topics.js | 10 +- src/controllers/write/categories.js | 15 +- src/controllers/write/users.js | 13 +- src/flags.js | 10 +- src/messaging/delete.js | 4 +- src/messaging/edit.js | 4 +- src/messaging/notifications.js | 4 +- src/topics/delete.js | 4 +- src/topics/scheduled.js | 4 +- src/user/categories.js | 10 +- src/user/profile.js | 4 +- 18 files changed, 202 insertions(+), 289 deletions(-) rename src/{api/activitypub.js => activitypub/out.js} (65%) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 7b84148600..e215d68c5d 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -68,6 +68,7 @@ ActivityPub.instances = require('./instances'); ActivityPub.feps = require('./feps'); ActivityPub.rules = require('./rules'); ActivityPub.relays = require('./relays'); +ActivityPub.out = require('./out'); ActivityPub.startJobs = () => { ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 14e0976986..b0efa76e08 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -265,7 +265,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (!hasTid && options.cid) { // New topic, have category announce it - api.activitypub.announce.category({}, { tid }); + activitypub.out.announce.category(tid); } return { tid, count }; diff --git a/src/api/activitypub.js b/src/activitypub/out.js similarity index 65% rename from src/api/activitypub.js rename to src/activitypub/out.js index 12c1989238..da6c62f361 100644 --- a/src/api/activitypub.js +++ b/src/activitypub/out.js @@ -1,34 +1,34 @@ 'use strict'; /** - * DEVELOPMENT NOTE + * 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() * - * 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. + * YMMV. */ -const nconf = require('nconf'); 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 activitypub = require('../activitypub'); -const posts = require('../posts'); const topics = require('../topics'); +const posts = require('../posts'); const messaging = require('../messaging'); const utils = require('../utils'); +const activitypub = module.parent.exports; -const activitypubApi = module.exports; +const Out = module.exports; function enabledCheck(next) { - return async function (caller, params) { + return async function (...args) { if (meta.config.activitypubEnabled) { try { - await next(caller, params); + await next.apply(null, args); } catch (e) { winston.error(`[activitypub/api] Error\n${e.stack}`); } @@ -36,7 +36,7 @@ function enabledCheck(next) { }; } -activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => { +Out.follow = enabledCheck(async (type, id, actor) => { // Privilege checks should be done upstream const acceptedTypes = ['uid', 'cid']; const assertion = await activitypub.actors.assert(actor); @@ -73,100 +73,32 @@ activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => } }); -// should be .undo.follow -activitypubApi.unfollow = enabledCheck(async (caller, { 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}`), - ]); - } -}); - -activitypubApi.create = {}; +Out.create = {}; -activitypubApi.create.note = enabledCheck(async (caller, { pid, post }) => { - if (!post) { - post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); +Out.create.note = enabledCheck(async (uid, post) => { + if (utils.isNumber(post)) { + post = (await posts.getPostSummaryByPids([post], uid, { stripTags: false })).pop(); if (!post) { return; } - } else { - pid = post.pid; } - + 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, caller.uid, post); + const { activity, targets } = await activitypub.mocks.activities.create(pid, uid, post); await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), activity), + activitypub.send('uid', uid, Array.from(targets), activity), activitypub.feps.announce(pid, activity), // utils.isNumber(post.cid) ? activitypubApi.add(caller, { pid }) : undefined, ]); }); -activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj }) => { +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 @@ -184,9 +116,9 @@ activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj }) await activitypub.send('uid', messageObj.fromuid, targets, payload); }); -activitypubApi.update = {}; +Out.update = {}; -activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { +Out.update.profile = enabledCheck(async (uid, actorUid) => { // Local users only if (!utils.isNumber(uid)) { return; @@ -194,10 +126,10 @@ activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { const [object, targets] = await Promise.all([ activitypub.mocks.actors.user(uid), - db.getSortedSetMembers(`followersRemote:${caller.uid}`), + db.getSortedSetMembers(`followersRemote:${uid}`), ]); - await activitypub.send('uid', caller.uid, targets, { + await activitypub.send('uid', actorUid || uid, targets, { id: `${object.id}#activity/update/${Date.now()}`, type: 'Update', actor: object.id, @@ -207,7 +139,7 @@ activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { }); }); -activitypubApi.update.category = enabledCheck(async (caller, { cid }) => { +Out.update.category = enabledCheck(async (cid) => { // Local categories only if (!utils.isNumber(cid)) { return; @@ -228,7 +160,7 @@ activitypubApi.update.category = enabledCheck(async (caller, { cid }) => { }); }); -activitypubApi.update.note = enabledCheck(async (caller, { post }) => { +Out.update.note = enabledCheck(async (uid, post) => { // Only applies to local posts if (!utils.isNumber(post.pid)) { return; @@ -255,12 +187,12 @@ activitypubApi.update.note = enabledCheck(async (caller, { post }) => { }; await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), payload), + activitypub.send('uid', uid, Array.from(targets), payload), activitypub.feps.announce(post.pid, payload), ]); }); -activitypubApi.update.privateNote = enabledCheck(async (caller, { messageObj }) => { +Out.update.privateNote = enabledCheck(async (uid, messageObj) => { if (!utils.isNumber(messageObj.mid)) { return; } @@ -281,19 +213,19 @@ activitypubApi.update.privateNote = enabledCheck(async (caller, { messageObj }) object, }; - await activitypub.send('uid', caller.uid, targets, payload); + await activitypub.send('uid', uid, targets, payload); }); -activitypubApi.delete = {}; +Out.delete = {}; -activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => { +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], caller.uid, { stripTags: false })).pop(); + 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 }); @@ -314,18 +246,18 @@ activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => { }; await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), payload), + activitypub.send('uid', uid, Array.from(targets), payload), activitypub.feps.announce(pid, payload), ]); }); -activitypubApi.like = {}; +Out.like = {}; -activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { +Out.like.note = enabledCheck(async (uid, pid) => { const payload = { - id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, + id: `${nconf.get('url')}/uid/${uid}#activity/like/${encodeURIComponent(pid)}`, type: 'Like', - actor: `${nconf.get('url')}/uid/${caller.uid}`, + actor: `${nconf.get('url')}/uid/${uid}`, object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, }; @@ -334,20 +266,20 @@ activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { return; } - const uid = await posts.getPostField(pid, 'uid'); - if (!activitypub.helpers.isUri(uid)) { + const recipient = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(recipient)) { return; } await Promise.all([ - activitypub.send('uid', caller.uid, [uid], payload), + activitypub.send('uid', uid, [recipient], payload), activitypub.feps.announce(pid, payload), ]); }); -activitypubApi.announce = {}; +Out.announce = {}; -activitypubApi.announce.category = enabledCheck(async (_, { tid }) => { +Out.announce.category = enabledCheck(async (tid) => { const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); // Only local categories can announce @@ -379,72 +311,157 @@ activitypubApi.announce.category = enabledCheck(async (_, { tid }) => { }); }); -activitypubApi.announce.user = enabledCheck(async (caller, { tid }) => { - // ORPHANED, but will re-use when user announces are a thing. - const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); +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); +}); - // Only remote posts can be announced to local categories - if (utils.isNumber(pid) || parseInt(cid, 10) === -1) { +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 uid = await posts.getPostField(pid, 'uid'); // author - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + const allowed = await privileges.categories.can('topics:read', cid, activitypub._constants.uid); if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); + 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({ - id: pid, to: [activitypub._constants.publicAddress], - cc: [`${nconf.get('url')}/uid/${caller.uid}/followers`, uid], - }, { uid: caller.uid }); + cc: [`${nconf.get('url')}/category/${cid}/followers`], + }, { cid }); - 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}`, + // Remove(Context) + 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: pid, - target: `${nconf.get('url')}/category/${cid}`, + object: `${nconf.get('url')}/topic/${tid}`, + origin: `${nconf.get('url')}/category/${cid}`, }); }); -activitypubApi.undo = {}; +Out.undo = {}; -// activitypubApi.undo.follow = +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]]'); + } -activitypubApi.undo.like = enabledCheck(async (caller, { pid }) => { + 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}`), + ]); + } +}); + +Out.undo.like = enabledCheck(async (uid, pid) => { if (!activitypub.helpers.isUri(pid)) { return; } - const uid = await posts.getPostField(pid, 'uid'); - if (!activitypub.helpers.isUri(uid)) { + const author = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(author)) { return; } const payload = { - id: `${nconf.get('url')}/uid/${caller.uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, + id: `${nconf.get('url')}/uid/${uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, type: 'Undo', - actor: `${nconf.get('url')}/uid/${caller.uid}`, + actor: `${nconf.get('url')}/uid/${uid}`, object: { - actor: `${nconf.get('url')}/uid/${caller.uid}`, - id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, + 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', caller.uid, [uid], payload), + activitypub.send('uid', uid, [author], payload), activitypub.feps.announce(pid, payload), ]); }); -activitypubApi.flag = enabledCheck(async (caller, flag) => { +Out.undo.flag = enabledCheck(async (uid, flag) => { if (!activitypub.helpers.isUri(flag.targetId)) { return; } @@ -453,101 +470,18 @@ activitypubApi.flag = enabledCheck(async (caller, flag) => { 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()}`, + (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/${caller.uid}`, + actor: `${nconf.get('url')}/uid/${uid}`, object: { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, - actor: `${nconf.get('url')}/uid/${caller.uid}`, + 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`, caller.uid); -}); - -activitypubApi.remove = {}; - -activitypubApi.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: [`${nconf.get('url')}/category/${cid}/followers`], - }, { cid }); - - // Remove(Context) - 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}`, - origin: `${nconf.get('url')}/category/${cid}`, - }); + await db.sortedSetRemove(`flag:${flag.flagId}:remote`, uid); }); \ No newline at end of file diff --git a/src/api/categories.js b/src/api/categories.js index 693f8f15ee..476a0d4d9d 100644 --- a/src/api/categories.js +++ b/src/api/categories.js @@ -7,10 +7,9 @@ const events = require('../events'); const user = require('../user'); const groups = require('../groups'); const privileges = require('../privileges'); +const activitypub = require('../activitypub'); const utils = require('../utils'); -const activitypubApi = require('./activitypub'); - const categoriesAPI = module.exports; const hasAdminPrivilege = async (uid, privilege = 'categories') => { @@ -66,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 }) { diff --git a/src/api/helpers.js b/src/api/helpers.js index 7df860a569..3422b4a6f9 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -6,6 +6,7 @@ const topics = require('../topics'); const posts = require('../posts'); const privileges = require('../privileges'); const plugins = require('../plugins'); +const activitypub = require('../activitypub'); const socketHelpers = require('../socket.io/helpers'); const websockets = require('../socket.io'); const events = require('../events'); @@ -129,7 +130,6 @@ 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); @@ -137,12 +137,12 @@ async function executeCommand(caller, command, eventName, notification, data) { } if (result && command === 'upvote') { socketHelpers.upvote(result, notification); - await api.activitypub.like.note(caller, { pid: data.pid }); + await activitypub.out.like.note(caller.uid, 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); - await api.activitypub.undo.like(caller, { pid: data.pid }); + await activitypub.out.undo.like(caller.uid, data.pid); } 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 54454e6e52..74c02a210e 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -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; @@ -208,7 +208,7 @@ async function deleteOrRestore(caller, data, params) { // Explicitly non-awaited posts.getPostSummaryByPids([data.pid], caller.uid, { extraFields: ['edited'] }).then(([post]) => { - require('.').activitypub.update.note(caller, { post }); + activitypub.out.update.note(caller.uid, post); }); } @@ -254,7 +254,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); diff --git a/src/api/topics.js b/src/api/topics.js index 964c49e2ec..9d3c149397 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -8,9 +8,9 @@ const meta = require('../meta'); 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; @@ -80,7 +80,7 @@ topicsAPI.create = async function (caller, data) { socketHelpers.notifyNew(caller.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); if (!isScheduling) { - await activitypubApi.create.note(caller, { pid: result.postData.pid }); + await activitypub.out.create.note(caller.uid, result.postData.pid); } return result.topicData; @@ -116,7 +116,7 @@ topicsAPI.reply = async function (caller, data) { } socketHelpers.notifyNew(caller.uid, 'newPost', result); - await activitypubApi.create.note(caller, { post: postData }); + await activitypub.out.create.note(caller.uid, postData); return postData; }; @@ -323,11 +323,11 @@ topicsAPI.move = async (caller, { tid, cid }) => { socketHelpers.sendNotificationToTopicOwner(tid, caller.uid, 'move', 'notifications:moved-your-topic'); if (utils.isNumber(cid) && parseInt(cid, 10) === -1) { - activitypubApi.remove.context(caller, { tid }); + activitypub.out.remove.context(caller.uid, tid); // tbd: activitypubApi.undo.announce? } else { // tbd: activitypubApi.move - activitypubApi.announce.category(caller, { tid }); + activitypub.out.announce.category(tid); } } 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/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/flags.js b/src/flags.js index 8ae9a6c595..435bc10c42 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'); @@ -477,8 +476,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 }); @@ -531,7 +529,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); } }) ), @@ -569,7 +567,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 }); @@ -599,7 +597,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([ 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/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/topics/delete.js b/src/topics/delete.js index ee9d39e107..466d25a0dd 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -8,7 +8,7 @@ const categories = require('../categories'); const flags = require('../flags'); const plugins = require('../plugins'); const batch = require('../batch'); -const api = require('../api'); +const activitypub = require('../activitypub'); const utils = require('../utils'); module.exports = function (Topics) { @@ -25,7 +25,7 @@ module.exports = function (Topics) { deleterUid: uid, deletedTimestamp: Date.now(), }), - api.activitypub.remove.context({ uid }, { tid }), + activitypub.out.remove.context(uid, tid), ]); await categories.updateRecentTidForCid(cid); 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/user/categories.js b/src/user/categories.js index 09356eac5b..4248def8bd 100644 --- a/src/user/categories.js +++ b/src/user/categories.js @@ -5,8 +5,8 @@ const _ = require('lodash'); const db = require('../database'); const meta = require('../meta'); const categories = require('../categories'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); -const api = require('../api'); const utils = require('../utils'); module.exports = function (User) { @@ -30,12 +30,8 @@ module.exports = function (User) { throw new Error('[[error:no-category]]'); } - const apiMethod = state >= categories.watchStates.tracking ? 'follow' : 'unfollow'; - const follows = cids.filter(cid => !utils.isNumber(cid)).map(cid => api.activitypub[apiMethod]({ uid }, { - type: 'uid', - id: uid, - actor: cid, - })); // returns promises + 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), diff --git a/src/user/profile.js b/src/user/profile.js index 3009d0a3d5..ec4a23d3a3 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', From d02e188a5f7d6fb271d28653a82159ea9802d18c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 22 Oct 2025 15:04:47 -0400 Subject: [PATCH 526/828] feat: update Remove(Context) to use target instead of origin, federate out Move(Context) on topic move between local cids --- src/activitypub/inbox.js | 2 -- src/activitypub/index.js | 17 ++++++++++------- src/activitypub/out.js | 41 +++++++++++++++++++++++++++++++++++++--- src/api/topics.js | 2 +- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 9a9381f43f..23ac85c844 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -86,14 +86,12 @@ inbox.remove = async (req) => { if (!isContext) { return; // don't know how to handle other types } - console.log('isContext?', isContext); const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); const exists = await posts.exists(mainPid); if (!exists) { return; // post not cached; do nothing. } - console.log('mainPid is', mainPid); // Ensure that cid is same-origin as the actor const tid = await posts.getPostField(mainPid, 'tid'); diff --git a/src/activitypub/index.js b/src/activitypub/index.js index e215d68c5d..767c88ce23 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -533,7 +533,7 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { * - 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 */ @@ -551,12 +551,15 @@ 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]); diff --git a/src/activitypub/out.js b/src/activitypub/out.js index da6c62f361..7fb77c9140 100644 --- a/src/activitypub/out.js +++ b/src/activitypub/out.js @@ -351,10 +351,9 @@ Out.remove.context = enabledCheck(async (uid, tid) => { const { to, cc, targets } = await activitypub.buildRecipients({ to: [activitypub._constants.publicAddress], - cc: [`${nconf.get('url')}/category/${cid}/followers`], + cc: [], }, { cid }); - // Remove(Context) await activitypub.send('uid', uid, Array.from(targets), { id: `${nconf.get('url')}/topic/${tid}#activity/remove/${now.getTime()}`, type: 'Remove', @@ -362,7 +361,43 @@ Out.remove.context = enabledCheck(async (uid, tid) => { to, cc, object: `${nconf.get('url')}/topic/${tid}`, - origin: `${nconf.get('url')}/category/${cid}`, + 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 isNotLocal = id => !utils.isNumber(cid) || parseInt(cid, 10) < 1; + if ([cid, oldCid].some(isNotLocal)) { + 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}`, }); }); diff --git a/src/api/topics.js b/src/api/topics.js index 9d3c149397..d7f2f4836f 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -326,7 +326,7 @@ topicsAPI.move = async (caller, { tid, cid }) => { activitypub.out.remove.context(caller.uid, tid); // tbd: activitypubApi.undo.announce? } else { - // tbd: activitypubApi.move + activitypub.out.move.context(caller.uid, tid); activitypub.out.announce.category(tid); } } From 22868d3f97be1807a53872d3cade8f735f83064d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 22 Oct 2025 15:05:06 -0400 Subject: [PATCH 527/828] fix: bad var --- src/activitypub/out.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/out.js b/src/activitypub/out.js index 7fb77c9140..014531c310 100644 --- a/src/activitypub/out.js +++ b/src/activitypub/out.js @@ -373,7 +373,7 @@ Out.move.context = enabledCheck(async (uid, tid) => { const { cid, oldCid } = await topics.getTopicFields(tid, ['cid', 'oldCid']); // This check may be revised if inter-community moderation becomes real. - const isNotLocal = id => !utils.isNumber(cid) || parseInt(cid, 10) < 1; + const isNotLocal = id => !utils.isNumber(id) || parseInt(id, 10) < 1; if ([cid, oldCid].some(isNotLocal)) { return; } From 4f2f872bf993313694701981e7d01b3d22fe671b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 22 Oct 2025 15:15:19 -0400 Subject: [PATCH 528/828] fix: update logic re: federating out topic moves --- src/activitypub/out.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/activitypub/out.js b/src/activitypub/out.js index 014531c310..40d597db51 100644 --- a/src/activitypub/out.js +++ b/src/activitypub/out.js @@ -373,8 +373,10 @@ Out.move.context = enabledCheck(async (uid, tid) => { const { cid, oldCid } = await topics.getTopicFields(tid, ['cid', 'oldCid']); // This check may be revised if inter-community moderation becomes real. - const isNotLocal = id => !utils.isNumber(id) || parseInt(id, 10) < 1; - if ([cid, oldCid].some(isNotLocal)) { + 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)) || [cid, oldCid].every(!isLocal)) { // stealing or remote-to-remote return; } From c1f6e52ba5c6fd16dff1b55afb0c214d8d3f4690 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:36:33 -0400 Subject: [PATCH 529/828] fix(deps): update dependency nodemailer to v7.0.10 (#13726) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c0b6bc44f8..f66a79f512 100644 --- a/install/package.json +++ b/install/package.json @@ -112,7 +112,7 @@ "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.16", "nodebb-widget-essentials": "7.0.40", - "nodemailer": "7.0.9", + "nodemailer": "7.0.10", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", From e3c55f76c1b6aacf31d1c9807ce79ad04f40907c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:36:55 -0400 Subject: [PATCH 530/828] chore(deps): update dependency lint-staged to v16.2.6 (#13725) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f66a79f512..8249e4e99a 100644 --- a/install/package.json +++ b/install/package.json @@ -172,7 +172,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", "jsdom": "27.0.1", - "lint-staged": "16.2.5", + "lint-staged": "16.2.6", "mocha": "11.7.4", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", From 194cedb4d7e637b017c6c414a2f76e7dc795ab04 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 23 Oct 2025 12:02:59 -0400 Subject: [PATCH 531/828] fix: cross-check remove(context) target prop against cid --- src/activitypub/inbox.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 23ac85c844..8a9e5c7e93 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -80,7 +80,7 @@ inbox.add = async (req) => { }; inbox.remove = async (req) => { - const { actor, object } = req.body; + const { actor, object, target } = req.body; const isContext = activitypub._constants.acceptable.contextTypes.has(object.type); if (!isContext) { @@ -88,16 +88,17 @@ inbox.remove = async (req) => { } const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); + const fromCid = target || object.audience; const exists = await posts.exists(mainPid); - if (!exists) { + 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)) { - // remote removal of topic in local cid; what?? + 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; From 8ca52c7e78e9eaa314e2fe84b4f073b3cd7ceb39 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 23 Oct 2025 12:15:36 -0400 Subject: [PATCH 532/828] feat: handle Move(Context) activity --- src/activitypub/inbox.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 8a9e5c7e93..d7dbcf6834 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -114,6 +114,43 @@ inbox.remove = async (req) => { }); }; +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 = publiclyAddressed([...(object.to || []), ...(object.cc || [])]); From 25c088b228c12aa233764e2bc96edde602736b68 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Fri, 24 Oct 2025 09:21:02 +0000 Subject: [PATCH 533/828] Latest translations and fallbacks --- public/language/pl/admin/settings/uploads.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/pl/admin/settings/uploads.json b/public/language/pl/admin/settings/uploads.json index 561a29afc0..87d598de8b 100644 --- a/public/language/pl/admin/settings/uploads.json +++ b/public/language/pl/admin/settings/uploads.json @@ -22,7 +22,7 @@ "reject-image-height": "Maksymalna wysokość obrazu (w pikselach)", "reject-image-height-help": "Obrazy o wysokości przekraczającej tę wartość zostaną odrzucone.", "allow-topic-thumbnails": "Zezwalaj użytkownikom na ustawianie miniaturek tematów", - "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", + "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.", From 418717fdff7606651f9bc66ad9a597bd024a823d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:16:38 -0400 Subject: [PATCH 534/828] fix(deps): update dependency redis to v5.9.0 (#13727) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8249e4e99a..7f46a6804a 100644 --- a/install/package.json +++ b/install/package.json @@ -124,7 +124,7 @@ "pretty": "^2.0.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.8.3", + "redis": "5.9.0", "rimraf": "6.0.1", "rss": "1.2.2", "rtlcss": "4.3.0", From 9410f466d80b30f52e5f926e5d17a513beec1084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 24 Oct 2025 11:04:29 -0400 Subject: [PATCH 535/828] fix: closes #13729, fix filename encoding --- src/middleware/multer.js | 16 ++++++++++++++++ src/routes/admin.js | 4 +--- src/routes/api.js | 4 +--- src/routes/authentication.js | 5 +---- src/routes/helpers.js | 4 +--- 5 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 src/middleware/multer.js diff --git a/src/middleware/multer.js b/src/middleware/multer.js new file mode 100644 index 0000000000..e15c146ff3 --- /dev/null +++ b/src/middleware/multer.js @@ -0,0 +1,16 @@ +'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/routes/admin.js b/src/routes/admin.js index 5421b4d0ef..4788593c5a 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -85,9 +85,7 @@ function apiRoutes(router, name, middleware, controllers) { 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 multer = require('multer'); - const storage = multer.diskStorage({}); - const upload = multer({ storage }); + const upload = require('../middleware/multer'); const middlewares = [ upload.array('files[]', 20), diff --git a/src/routes/api.js b/src/routes/api.js index 4424d9a979..79fb83b811 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -23,9 +23,7 @@ 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 multer = require('multer'); - const storage = multer.diskStorage({}); - const upload = multer({ storage }); + const upload = require('../middleware/multer'); const postMiddlewares = [ middleware.maintenanceMode, diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 720675b29d..9836cf22a5 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -154,10 +154,7 @@ Auth.reloadRoutes = async function (params) { }); }); - - const multer = require('multer'); - const storage = multer.diskStorage({}); - const upload = multer({ storage }); + const upload = require('../middleware/multer') const middlewares = [ upload.any(), Auth.middleware.applyCSRF, diff --git a/src/routes/helpers.js b/src/routes/helpers.js index 2109a0bd9c..079eb36536 100644 --- a/src/routes/helpers.js +++ b/src/routes/helpers.js @@ -54,9 +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 multer = require('multer'); - const storage = multer.diskStorage({}); - const upload = multer({ storage }); + const upload = require('../middleware/multer'); middlewares = [ middleware.autoLocale, middleware.applyBlacklist, From 430a3e81130ac6c1393f9854e1ac78de484e8c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 24 Oct 2025 11:12:20 -0400 Subject: [PATCH 536/828] test: add test for #13729 --- "test/files/\346\265\213\350\257\225.jpg" | Bin 0 -> 1010 bytes test/uploads.js | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 "test/files/\346\265\213\350\257\225.jpg" diff --git "a/test/files/\346\265\213\350\257\225.jpg" "b/test/files/\346\265\213\350\257\225.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..905e886eef32d9fa695e35e69f69314c0d5850cc GIT binary patch literal 1010 zcmex=5A1R0qH8UG()kY-?DVrFDyWIzCRpldmpn1Pb|0t}4IV8Fxa4 zIw}PwvKKar8i_dtB~4uP5U4?dkpXBJ!YCk%iG>+tkCGq*6B837kOP-xWD;Zsnxka+ z!6Sa$#(TjgQ|Y5V{SLOjx6Mv%A%bW#uXH*X0MjL*yQWsYu~uNGq;8Qn&jcU-r>jO z0N>0fJIgE7jT`f&oHmJP-g$9v$~L9Sw`zMM9~hcvYgAtMnqw67Mqu%?+TUTq@*UTv z|J2^TRqbe``R=8c3fZ%EH#JQ1;dquE_A1wOo8X@`Zmn%aj0?g(2`qV|X~^~Nub7&$IkY{)M;AjwMxmbk&%?_R~_AZtI8Y8)^gWuXVk5 z{ryKge}BE((ajvW2cF%${fc?dtl0@ucl|7N5f58@X7ZNm$+z~c-O}65^>?XyNp-09 zb^qH_Hr;N|&l6^1_`dF0S80e@D=ntK7x@s)sqJ;7)a)%=m*(4xQ?(^`>2pc#5zlvwe3C1}W%@YztC*O(R{nQ> do1crieDwF!%ojGi?EiKChm1vCJLdeq2>^^Eisk?S literal 0 HcmV?d00001 diff --git a/test/uploads.js b/test/uploads.js index 1e5b3d19e3..8a57c0d06e 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -194,6 +194,12 @@ describe('Upload Controllers', () => { assert(body.response.images[0].url); }); + it('should upload a file with utf8 characters in the name to a post', async () => { + const { body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/测试.jpg'), {}, jar, csrf_token); + + assert(body.response.images[0].url.endsWith('测试.jpg')); + }); + it('should fail to upload image to post if image dimensions are too big', async () => { const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/toobig.png'), {}, jar, csrf_token); assert.strictEqual(response.statusCode, 500); From 008e1ae4e497f2ea8aa1f1eeab8533567dbad586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 24 Oct 2025 11:27:43 -0400 Subject: [PATCH 537/828] lint: fix lint --- src/middleware/multer.js | 5 +++-- src/routes/authentication.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/middleware/multer.js b/src/middleware/multer.js index e15c146ff3..e288fe309e 100644 --- a/src/middleware/multer.js +++ b/src/middleware/multer.js @@ -2,14 +2,15 @@ const multer = require('multer'); const storage = multer.diskStorage({}); -const upload = multer({ storage, +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/routes/authentication.js b/src/routes/authentication.js index 9836cf22a5..a4290544a1 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -154,7 +154,7 @@ Auth.reloadRoutes = async function (params) { }); }); - const upload = require('../middleware/multer') + const upload = require('../middleware/multer'); const middlewares = [ upload.any(), Auth.middleware.applyCSRF, From ab9154aa49a132b28b2a938b19c143e1a638b041 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 24 Oct 2025 13:32:04 -0400 Subject: [PATCH 538/828] fix: logic error in out.remove.context --- src/activitypub/out.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/activitypub/out.js b/src/activitypub/out.js index 40d597db51..9c9bdb5dba 100644 --- a/src/activitypub/out.js +++ b/src/activitypub/out.js @@ -376,7 +376,10 @@ Out.move.context = enabledCheck(async (uid, tid) => { 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)) || [cid, oldCid].every(!isLocal)) { // stealing or remote-to-remote + } else if ( + (isLocal(cid) && !isLocal(oldCid)) || // stealing, or + [cid, oldCid].every(id => !isLocal(id)) // remote-to-remote + ) { return; } From ff5f65bfa1cbdcab9738c8d488fb2ff601af2a07 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Mon, 27 Oct 2025 09:21:45 +0000 Subject: [PATCH 539/828] Latest translations and fallbacks --- .../zh-CN/admin/settings/activitypub.json | 6 ++-- .../language/zh-CN/admin/settings/chat.json | 12 +++---- .../zh-CN/admin/settings/uploads.json | 8 ++--- public/language/zh-CN/aria.json | 12 +++---- public/language/zh-CN/category.json | 18 +++++----- public/language/zh-CN/email.json | 22 ++++++------ public/language/zh-CN/error.json | 10 +++--- public/language/zh-CN/global.json | 16 ++++----- public/language/zh-CN/groups.json | 2 +- public/language/zh-CN/modules.json | 36 +++++++++---------- public/language/zh-CN/notifications.json | 2 +- public/language/zh-CN/register.json | 2 +- public/language/zh-CN/themes/persona.json | 2 +- 13 files changed, 74 insertions(+), 74 deletions(-) diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 5a0ef40eca..4effab4baf 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -30,9 +30,9 @@ "rules.cid": "版块", "relays": "中继服务", - "relays.intro": "中继服务能提升您NodeBB论坛内容的发现效率。订阅中继服务意味着:中继服务接收的内容将转发至此处,而本论坛发布的内容则由中继服务向外同步传播。", - "relays.warning": "注:中继服务可能接收大量流量,并可能增加存储和处理成本。", - "relays.litepub": "NodeBB 遵循 LitePub 风格的中继标准。您在此处输入的URL应以 /actor 结尾。", + "relays.intro": "中继服务能提升您 NodeBB 论坛内容的发现效率。订阅中继服务意味着:中继服务接收的内容将转发至此处,而本论坛发布的内容则由中继服务向外同步传播。", + "relays.warning": "注意:中继服务可能接收大量传入流量,并可能增加存储和处理成本。", + "relays.litepub": "NodeBB 遵循 LitePub 风格的中继标准。您在此处输入的 URL 应以 /actor 结尾。", "relays.add": "添加新中继服务", "relays.relay": "中继服务", "relays.state": "状态", diff --git a/public/language/zh-CN/admin/settings/chat.json b/public/language/zh-CN/admin/settings/chat.json index d7c8f595a3..ac92fca1e4 100644 --- a/public/language/zh-CN/admin/settings/chat.json +++ b/public/language/zh-CN/admin/settings/chat.json @@ -5,13 +5,13 @@ "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": "聊天信息保持可撤回状态的秒数。" + "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/zh-CN/admin/settings/uploads.json b/public/language/zh-CN/admin/settings/uploads.json index 0c3cee593d..c2cc7c587e 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)", @@ -22,7 +22,7 @@ "reject-image-height": "图片最大高度值(单位:像素)", "reject-image-height-help": "高于此数值大小的图片将会被拒绝", "allow-topic-thumbnails": "允许用户上传主题缩略图", - "show-post-uploads-as-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-CN/aria.json b/public/language/zh-CN/aria.json index 9df98227c1..2467cf769b 100644 --- a/public/language/zh-CN/aria.json +++ b/public/language/zh-CN/aria.json @@ -1,9 +1,9 @@ { - "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 的群组页面链接" } \ No newline at end of file diff --git a/public/language/zh-CN/category.json b/public/language/zh-CN/category.json index 27a3f239b0..a03dcf42c6 100644 --- a/public/language/zh-CN/category.json +++ b/public/language/zh-CN/category.json @@ -2,12 +2,12 @@ "category": "版块", "subcategories": "子版块", "uncategorized": "未分类的", - "uncategorized.description": "不完全符合任何现有类别的主题", - "handle.description": "您可以通过 %1 标签在开放的社交网络上关注这一版块。", + "uncategorized.description": "不严格符合任何现有版块的主题", + "handle.description": "此版块可通过社交网络公开平台使用用户名 %1 进行关注", "new-topic-button": "发表主题", "guest-login-post": "登录以发布", - "no-topics": "此版块还没有任何内容。
    赶紧来发帖吧!", - "no-followers": "本网站无人跟踪或关注此版块。跟踪或关注此版块,以便开始接收更新。", + "no-topics": "此版块下尚无主题。
    何不尝试发布一个?", + "no-followers": "本网站上没有用户正在关注或订阅此版块。请关注或订阅此版块,以便开始接收更新。", "browsing": "正在浏览", "no-replies": "尚无回复", "no-new-posts": "没有新主题", @@ -17,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 e91848b78d..39fabf9c1b 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -39,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 分钟后再试。", @@ -105,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": "请选择一个未来的日期。", @@ -120,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": "在删除此账号之前,请先移除其管理权限。", @@ -222,7 +222,7 @@ "no-groups-selected": "没有用户组被选中", "invalid-home-page-route": "无效的首页路径", "invalid-session": "无效的会话", - "invalid-session-text": "您的登录会话似乎不再处于活动状态。请刷新此页面。", + "invalid-session-text": "您的登录会话似乎已失效。请刷新此页面。", "session-mismatch": "会话不匹配", "session-mismatch-text": "您的登录会话似乎与服务器不再匹配。请刷新此页面。", "no-topics-selected": "没有主题被选中!", @@ -247,7 +247,7 @@ "cant-set-self-as-parent": "无法将自身设置为父版块", "api.master-token-no-uid": "收到一个在请求体中没有对应 `_uid` 的主令牌", "api.400": "您传入的请求某些地方出错了。", - "api.401": "找不到有效的登录会话。请登录后再试。", + "api.401": "未找到有效的登录会话。请登录后重试。", "api.403": "您没有权限使用此调用", "api.404": "无效 API 调用", "api.426": "Write API 的请求需要 HTTPS,请用 HTTPS 重新发送您的请求", diff --git a/public/language/zh-CN/global.json b/public/language/zh-CN/global.json index a98d700a71..0a9fbbfff8 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,8 @@ "users": "用户", "topics": "主题", "posts": "帖子", - "x-posts": "%1 个帖子", - "x-topics": "%1 个主题", + "x-posts": "%1 个帖子", + "x-topics": "%1 个主题", "x-reputation": "%1声望", "best": "最佳", "controversial": "有争议的", diff --git a/public/language/zh-CN/groups.json b/public/language/zh-CN/groups.json index 74b4fbb097..3f9ba5a0f7 100644 --- a/public/language/zh-CN/groups.json +++ b/public/language/zh-CN/groups.json @@ -23,7 +23,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 d9ae48b598..21683139a5 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.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 正在输入...", @@ -57,26 +57,26 @@ "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": "隐藏预览", @@ -85,7 +85,7 @@ "composer.user-said": "%1 说:", "composer.discard": "确定想要取消此帖?", "composer.submit-and-lock": "提交并锁定", - "composer.toggle-dropdown": "标为 Dropdown", + "composer.toggle-dropdown": "切换下拉菜单", "composer.uploading": "正在上传 %1", "composer.formatting.bold": "加粗", "composer.formatting.italic": "倾斜", @@ -106,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": "时间", @@ -114,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": "确认", @@ -128,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 7df8ab7fd0..533651060d 100644 --- a/public/language/zh-CN/notifications.json +++ b/public/language/zh-CN/notifications.json @@ -71,7 +71,7 @@ "users-csv-exported": "用户列表 CSV 已导出,点击以下载", "post-queue-accepted": "您先前提交的帖子已通过查验,点击这里查看您的帖子。", "post-queue-rejected": "您先前提交的帖子已被拒绝", - "post-queue-notify": "您先前提交的帖子收到了通知:“%1”", + "post-queue-notify": "您先前提交的帖子收到了通知:
    “%1”", "email-confirmed": "电子邮箱已确认", "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已完全激活。", "email-confirm-error-message": "验证的您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。", 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/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": "非常小的屏幕(如纵向模式的手机)。", From a49efe49ea7dbfb724e26a10ff66720893a2f328 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:57:57 -0400 Subject: [PATCH 540/828] fix(deps): update dependency commander to v14.0.2 (#13731) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7f46a6804a..3a6155f6d7 100644 --- a/install/package.json +++ b/install/package.json @@ -53,7 +53,7 @@ "chart.js": "4.5.1", "cli-graph": "3.2.2", "clipboard": "2.0.11", - "commander": "14.0.1", + "commander": "14.0.2", "compare-versions": "6.1.1", "compression": "1.8.1", "connect-flash": "0.1.1", From 07eb16150c4e4d14757be1964faa5bafd28d58b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 27 Oct 2025 20:07:33 -0400 Subject: [PATCH 541/828] center user count in chat, add commas to usercount, make last user image full width --- src/views/partials/chats/options.tpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/partials/chats/options.tpl b/src/views/partials/chats/options.tpl index 0c651d503c..88deba08d2 100644 --- a/src/views/partials/chats/options.tpl +++ b/src/views/partials/chats/options.tpl @@ -82,7 +82,7 @@ {{{ if users.length }}} -
    +
    {{{ if ./users.0 }}} {buildAvatar(./users.0, "24px", true)} @@ -91,10 +91,10 @@ {buildAvatar(./users.1, "24px", true)} {{{ end }}} {{{ if ./users.2 }}} - {buildAvatar(./users.2, "24px", true)} + {buildAvatar(./users.2, "24px", true)} {{{ end }}}
    - {./userCount} + {formattedNumber(./userCount)}
    {{{ end }}}
    From 5cfec5b1a9003aae2ce851688336775b5d2932af Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 28 Oct 2025 11:51:02 -0400 Subject: [PATCH 542/828] fix: order of operations when updating category handle --- src/categories/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categories/update.js b/src/categories/update.js index bf32317ae2..ff4d6e4d11 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -165,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), ]); } From 964a5388b72195381d0017ca15b1eca1a63926cc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 28 Oct 2025 13:40:35 -0400 Subject: [PATCH 543/828] fix(deps): bump mentions to 4.8.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 3a6155f6d7..d5883420fa 100644 --- a/install/package.json +++ b/install/package.json @@ -103,7 +103,7 @@ "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-link-preview": "2.1.5", "nodebb-plugin-markdown": "13.2.1", - "nodebb-plugin-mentions": "4.7.6", + "nodebb-plugin-mentions": "4.8.0", "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", From 6f448ce2f6d433ad22dd9d8158da1c3793333039 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:47:10 -0400 Subject: [PATCH 544/828] fix(deps): update dependency validator to v13.15.20 (#13733) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d5883420fa..d225704739 100644 --- a/install/package.json +++ b/install/package.json @@ -149,7 +149,7 @@ "toobusy-js": "0.5.1", "tough-cookie": "6.0.0", "undici": "^7.10.0", - "validator": "13.15.15", + "validator": "13.15.20", "webpack": "5.102.1", "webpack-merge": "6.0.1", "winston": "3.18.3", From 524df6e5485aa833fc7ec55333908e99e1a7a0b0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Oct 2025 12:32:21 -0400 Subject: [PATCH 545/828] fix: update category mock to save full handle --- src/activitypub/mocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 81db4b7a01..d83df6e1e1 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -320,7 +320,7 @@ Mocks.category = async (actors) => { const payload = { cid, name, - handle: preferredUsername, + handle: `${preferredUsername}@${hostname}`, slug: `${preferredUsername}@${hostname}`, description: summary, descriptionParsed: posts.sanitize(summary), From 5c3b126166e839c289453b9b6cad1dfb63356a8a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Oct 2025 12:32:36 -0400 Subject: [PATCH 546/828] fix(deps): update mentions --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d225704739..197f6c2513 100644 --- a/install/package.json +++ b/install/package.json @@ -103,7 +103,7 @@ "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-link-preview": "2.1.5", "nodebb-plugin-markdown": "13.2.1", - "nodebb-plugin-mentions": "4.8.0", + "nodebb-plugin-mentions": "4.8.1", "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", From 07d169d29e2665fa26141661a09f04a8c7624070 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:07:34 -0400 Subject: [PATCH 547/828] chore(deps): update dependency smtp-server to v3.16.0 (#13737) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 197f6c2513..6d2c10594d 100644 --- a/install/package.json +++ b/install/package.json @@ -177,7 +177,7 @@ "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.15.0" + "smtp-server": "3.16.0" }, "optionalDependencies": { "sass-embedded": "1.93.2" From b5c1e8e7f624aa5dc4991de64c722b115771ba66 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:07:55 -0400 Subject: [PATCH 548/828] fix(deps): update dependency sitemap to v8.0.2 (#13736) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6d2c10594d..4425799caf 100644 --- a/install/package.json +++ b/install/package.json @@ -135,7 +135,7 @@ "semver": "7.7.3", "serve-favicon": "2.5.1", "sharp": "0.34.4", - "sitemap": "8.0.1", + "sitemap": "8.0.2", "socket.io": "4.8.1", "socket.io-client": "4.8.1", "@socket.io/redis-adapter": "8.3.0", From 97e5aa1d1823ea1fae4cb8cd126687e654fcdefe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:08:05 -0400 Subject: [PATCH 549/828] chore(deps): update mongo docker tag to v8.2 (#13738) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 03dbbf8f3c..6304098eca 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -75,7 +75,7 @@ jobs: - 6379:6379 mongo: - image: 'mongo:8.0' + image: 'mongo:8.2' ports: # Maps port 27017 on service container to the host - 27017:27017 From a0a10c8b5cd667e699b1e1aef15e6a67d3f2b6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 29 Oct 2025 13:16:34 -0400 Subject: [PATCH 550/828] chore: up ttlcache to 2.x --- install/package.json | 2 +- src/cache/ttl.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 3a6155f6d7..ff5d94af5e 100644 --- a/install/package.json +++ b/install/package.json @@ -33,7 +33,7 @@ "@fontsource/inter": "5.2.8", "@fontsource/poppins": "5.2.7", "@fortawesome/fontawesome-free": "6.7.2", - "@isaacs/ttlcache": "1.4.1", + "@isaacs/ttlcache": "2.0.1", "@nodebb/spider-detector": "2.0.3", "@popperjs/core": "2.11.8", "@textcomplete/contenteditable": "0.1.13", diff --git a/src/cache/ttl.js b/src/cache/ttl.js index c8ed90af57..61cd4c07f4 100644 --- a/src/cache/ttl.js +++ b/src/cache/ttl.js @@ -1,7 +1,7 @@ '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'); From f6219d0026bd977f96dc021c8bf35707ce7059e8 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Oct 2025 14:49:53 -0400 Subject: [PATCH 551/828] fix: update logic so that purging a post does not remove toPid fields from children, updated addParentPosts so that post existence is checked --- src/posts/delete.js | 15 +++++---------- src/topics/posts.js | 3 +++ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/posts/delete.js b/src/posts/delete.js index dddb33e6a9..ba637ccff5 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -197,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/topics/posts.js b/src/topics/posts.js index 8201bcad02..41b819d219 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -184,6 +184,9 @@ module.exports = function (Topics) { .filter(p => p && p.hasOwnProperty('toPid') && (activitypub.helpers.isUri(p.toPid) || utils.isNumber(p.toPid))) .map(postObj => postObj.toPid); + const exists = await posts.exists(parentPids); + parentPids = parentPids.filter((_, idx) => exists[idx]); + if (!parentPids.length) { return; } From 30b1212a0ae0e40e6c18cd7cf6af7ce37e9a80c5 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Oct 2025 14:52:59 -0400 Subject: [PATCH 552/828] fix: relax toPid assertion checks so that it only checks that it is a number or uri --- src/posts/create.js | 20 ++------------------ test/topics.js | 45 --------------------------------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/src/posts/create.js b/src/posts/create.js index fa7ca1d071..064e337a94 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'); @@ -24,8 +23,8 @@ module.exports = function (Posts) { 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'); @@ -101,19 +100,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/test/topics.js b/test/topics.js index 7136280339..9f6b2c719b 100644 --- a/test/topics.js +++ b/test/topics.js @@ -314,51 +314,6 @@ describe('Topic\'s', () => { }); }); - it('should fail to create new reply with toPid that has been purged', async () => { - const { postData } = await topics.post({ - uid: topic.userId, - cid: topic.categoryId, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - await posts.purge(postData.pid, topic.userId); - - await assert.rejects( - topics.reply({ uid: topic.userId, content: 'test post', tid: postData.topic.tid, toPid: postData.pid }), - { message: '[[error:invalid-pid]]' } - ); - }); - - it('should fail to create a new reply with toPid that has been deleted (user cannot view_deleted)', async () => { - const { postData } = await topics.post({ - uid: topic.userId, - cid: topic.categoryId, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - await posts.delete(postData.pid, topic.userId); - const uid = await User.create({ username: utils.generateUUID().slice(0, 10) }); - - await assert.rejects( - topics.reply({ uid, content: 'test post', tid: postData.topic.tid, toPid: postData.pid }), - { message: '[[error:invalid-pid]]' } - ); - }); - - it('should properly create a new reply with toPid that has been deleted (user\'s own deleted post)', async () => { - const { postData } = await topics.post({ - uid: topic.userId, - cid: topic.categoryId, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - await posts.delete(postData.pid, topic.userId); - const uid = await User.create({ username: utils.generateUUID().slice(0, 10) }); - - const { pid } = await topics.reply({ uid: topic.userId, content: 'test post', tid: postData.topic.tid, toPid: postData.pid }); - assert(pid); - }); - it('should delete nested relies properly', async () => { const result = await topics.post({ uid: fooUid, title: 'nested test', content: 'main post', cid: topic.categoryId }); const reply1 = await topics.reply({ uid: fooUid, content: 'reply post 1', tid: result.topicData.tid }); From 748cc5eecda87f2f0c4335f2318c00689cf52a04 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Oct 2025 15:15:01 -0400 Subject: [PATCH 553/828] fix: logic error in context generation --- src/controllers/activitypub/actors.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/activitypub/actors.js b/src/controllers/activitypub/actors.js index 850585e379..b4f4ddd055 100644 --- a/src/controllers/activitypub/actors.js +++ b/src/controllers/activitypub/actors.js @@ -178,7 +178,9 @@ Actors.topic = async function (req, res, next) { } // Convert pids to urls - collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + if (collection.orderedItems) { + collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + } const object = { '@context': 'https://www.w3.org/ns/activitystreams', From 4858abe1498a6c75d3f3b7108484d32f7e7439c6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Oct 2025 15:18:13 -0400 Subject: [PATCH 554/828] fix: add replies in parallel during note assertion --- src/activitypub/notes.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index b0efa76e08..0673a99b0d 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -248,18 +248,16 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } } - for (const post of unprocessed) { + await Promise.all(unprocessed.map(async (post) => { const { to, cc } = post._activitypub; try { - // eslint-disable-next-line no-await-in-loop await topics.reply(post); - // eslint-disable-next-line no-await-in-loop await Notes.updateLocalRecipients(post.pid, { to, cc }); } catch (e) { activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); } - } + })); await Notes.syncUserInboxes(tid, uid); From 425d2eb2954f6c32aec6cb8c3d58712965ab49e9 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 30 Oct 2025 09:20:53 +0000 Subject: [PATCH 555/828] Latest translations and fallbacks --- public/language/ro/admin/admin.json | 26 +++--- .../language/ro/admin/advanced/database.json | 2 +- public/language/ro/admin/dashboard.json | 6 +- .../language/ro/admin/development/info.json | 4 +- .../language/ro/admin/manage/categories.json | 64 +++++++-------- .../language/ro/admin/manage/privileges.json | 2 +- .../ro/admin/manage/user-custom-fields.json | 52 ++++++------ public/language/ro/admin/menu.json | 2 +- .../ro/admin/settings/activitypub.json | 82 +++++++++---------- public/language/ro/admin/settings/chat.json | 4 +- public/language/ro/admin/settings/email.json | 4 +- .../language/ro/admin/settings/general.json | 4 +- .../ro/admin/settings/navigation.json | 2 +- public/language/ro/admin/settings/post.json | 6 +- .../language/ro/admin/settings/uploads.json | 6 +- public/language/ro/admin/settings/user.json | 2 +- 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 | 8 +- public/language/ro/tags.json | 2 +- public/language/ro/topic.json | 36 ++++---- public/language/ro/unread.json | 2 +- public/language/ro/users.json | 2 +- public/language/ro/world.json | 32 ++++---- 26 files changed, 185 insertions(+), 185 deletions(-) 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/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 0be6d5866c..9e4b880154 100644 --- a/public/language/ro/admin/dashboard.json +++ b/public/language/ro/admin/dashboard.json @@ -75,7 +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.page-views-ap": "Vizualizări Pagină ActivityPub", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", @@ -96,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..782b9133e0 100644 --- a/public/language/ro/admin/development/info.json +++ b/public/language/ro/admin/development/info.json @@ -3,7 +3,7 @@ "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", @@ -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 38037f7206..11e06b840f 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -1,25 +1,25 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", - "rename": "Rename", + "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": "Category ID", + "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": "Topic Template", - "topic-template.help": "Define a template for new topics created in this category.", + "federatedDescription": "Descriere Federată", + "federatedDescription.help": "Acest text va fi adăugat la descrierea categoriei atunci când va fi interogat de alte site-uri web/aplicații.", + "federatedDescription.default": "Aceasta este o categorie de forum care conține discuții pe teme. Puteți începe discuții noi menționând această categorie.", + "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", @@ -49,7 +49,7 @@ "disable": "Disable", "edit": "Edit", "analytics": "Analytics", - "federation": "Federation", + "federation": "Federație", "view-category": "View category", "set-order": "Set order", @@ -89,32 +89,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": "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.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/privileges.json b/public/language/ro/admin/manage/privileges.json index 240cff6aa5..1b0a547853 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", 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/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 5ab4fa43e8..e94dfe6b6d 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -1,48 +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.", - "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", + "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": "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", + "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": "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" + "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..0ffa932af3 100644 --- a/public/language/ro/admin/settings/chat.json +++ b/public/language/ro/admin/settings/chat.json @@ -5,8 +5,8 @@ "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)", diff --git a/public/language/ro/admin/settings/email.json b/public/language/ro/admin/settings/email.json index 0310939cb3..ac3d5eb0f9 100644 --- a/public/language/ro/admin/settings/email.json +++ b/public/language/ro/admin/settings/email.json @@ -28,8 +28,8 @@ "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.", "template": "Edit Email Template", "template.select": "Select Email Template", 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/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 e91a7bee36..da12502884 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)", @@ -22,7 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", - "show-post-uploads-as-thumbnails": "Show post uploads as 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 c8cc3c9c34..7f87942d2a 100644 --- a/public/language/ro/admin/settings/user.json +++ b/public/language/ro/admin/settings/user.json @@ -64,7 +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", + "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/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 5b8dd99a46..40dc8ec5ec 100644 --- a/public/language/ro/social.json +++ b/public/language/ro/social.json @@ -7,8 +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-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "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/topic.json b/public/language/ro/topic.json index 5d44fe17d7..30691b771b 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,8 +61,8 @@ "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", @@ -106,7 +106,7 @@ "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", @@ -137,7 +137,7 @@ "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", @@ -162,7 +162,7 @@ "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.", "composer.title-placeholder": "Introdu numele subiectului aici ...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -188,8 +188,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 +214,15 @@ "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)" } \ 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/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 7fdb1569f2..33a94dd925 100644 --- a/public/language/ro/world.json +++ b/public/language/ro/world.json @@ -1,21 +1,21 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Lumea", + "popular": "Subiecte Populare", + "recent": "Toate subiectele", + "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!", - "show-categories": "Show categories", - "hide-categories": "Hide categories" + "show-categories": "Afișează categoriile", + "hide-categories": "Ascunde categoriile" } \ No newline at end of file From b5ea20898e40c93f4dc125a948ba2d016af755b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 30 Oct 2025 20:32:24 -0400 Subject: [PATCH 556/828] chore: up express-useragent --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 71545ac52f..76abd3e20c 100644 --- a/install/package.json +++ b/install/package.json @@ -69,7 +69,7 @@ "esbuild": "0.25.11", "express": "4.21.2", "express-session": "1.18.2", - "express-useragent": "1.0.15", + "express-useragent": "2.0.1", "fetch-cookie": "3.1.0", "file-loader": "6.2.0", "fs-extra": "11.3.2", From 179440372aef747f824b91e5fa023d1e269f22eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 30 Oct 2025 20:34:01 -0400 Subject: [PATCH 557/828] refactor: get rid of post.exists check, if post doesnt exist content is falsy --- src/topics/posts.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/topics/posts.js b/src/topics/posts.js index 41b819d219..a8535939e9 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -184,9 +184,6 @@ module.exports = function (Topics) { .filter(p => p && p.hasOwnProperty('toPid') && (activitypub.helpers.isUri(p.toPid) || utils.isNumber(p.toPid))) .map(postObj => postObj.toPid); - const exists = await posts.exists(parentPids); - parentPids = parentPids.filter((_, idx) => exists[idx]); - if (!parentPids.length) { return; } @@ -212,7 +209,7 @@ module.exports = function (Topics) { parentPost.content = foundPost.content; return; } - parentPost = await posts.parsePost(parentPost); + await posts.parsePost(parentPost); })); const parents = {}; @@ -230,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]; } }); From 9d3e8179600482ce7cb175466e14042378908ec3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 31 Oct 2025 09:40:59 -0400 Subject: [PATCH 558/828] fix: bump themes for cross-post support, #13396 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 76abd3e20c..8d1f84abc2 100644 --- a/install/package.json +++ b/install/package.json @@ -107,10 +107,10 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.22", + "nodebb-theme-harmony": "2.1.23", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", - "nodebb-theme-persona": "14.1.16", + "nodebb-theme-persona": "14.1.17", "nodebb-widget-essentials": "7.0.40", "nodemailer": "7.0.10", "nprogress": "0.2.0", From 98a1101d40a52c778cc6e3d111d447b2552c89d2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 31 Oct 2025 09:44:06 -0400 Subject: [PATCH 559/828] test: update test for toPid logic to reflect that toPid stays even if parent is purged --- test/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/topics.js b/test/topics.js index 9f6b2c719b..fb3f9cb835 100644 --- a/test/topics.js +++ b/test/topics.js @@ -327,7 +327,7 @@ describe('Topic\'s', () => { replies = await apiPosts.getReplies({ uid: fooUid }, { pid: reply1.pid }); assert.strictEqual(replies, null); toPid = await posts.getPostField(reply2.pid, 'toPid'); - assert.strictEqual(toPid, null); + assert.strictEqual(parseInt(toPid, 10), parseInt(reply1.pid, 10)); }); }); From 4ce4e773cb4c23dbacc6b5c26f9cad8f74799162 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:17:35 -0400 Subject: [PATCH 560/828] chore(deps): update dependency jsdom to v27.1.0 (#13743) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8d1f84abc2..6ad10567e6 100644 --- a/install/package.json +++ b/install/package.json @@ -171,7 +171,7 @@ "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", - "jsdom": "27.0.1", + "jsdom": "27.1.0", "lint-staged": "16.2.6", "mocha": "11.7.4", "mocha-lcov-reporter": "1.3.0", From cb96701b470bb6bc95062b4e76a526629008841d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:27:01 -0400 Subject: [PATCH 561/828] chore(deps): update dependency sass-embedded to v1.93.3 (#13745) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6ad10567e6..1dd6ecaf15 100644 --- a/install/package.json +++ b/install/package.json @@ -180,7 +180,7 @@ "smtp-server": "3.16.0" }, "optionalDependencies": { - "sass-embedded": "1.93.2" + "sass-embedded": "1.93.3" }, "resolutions": { "*/jquery": "3.7.1" From ba1230735f7d23cb930a53b5e243bd9ba6b33b3b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:27:10 -0400 Subject: [PATCH 562/828] fix(deps): update dependency sass to v1.93.3 (#13746) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1dd6ecaf15..bfdcdaadf8 100644 --- a/install/package.json +++ b/install/package.json @@ -129,7 +129,7 @@ "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.93.2", + "sass": "1.93.3", "satori": "0.18.3", "sbd": "^1.0.19", "semver": "7.7.3", From a36d89fcdaa0759e31ee2a38fbbe2f04784ac6a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:27:53 -0400 Subject: [PATCH 563/828] fix(deps): update dependency rimraf to v6.1.0 (#13744) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index bfdcdaadf8..c5352230ed 100644 --- a/install/package.json +++ b/install/package.json @@ -125,7 +125,7 @@ "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", "redis": "5.9.0", - "rimraf": "6.0.1", + "rimraf": "6.1.0", "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", From 85d2667215dd944405b0054ef39907416ad00b64 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Sat, 1 Nov 2025 09:20:27 +0000 Subject: [PATCH 564/828] Latest translations and fallbacks --- public/language/zh-CN/modules.json | 2 +- public/language/zh-CN/search.json | 18 +++++++++--------- public/language/zh-CN/topic.json | 6 +++--- public/language/zh-CN/user.json | 12 ++++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index 21683139a5..6d29de3341 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -1,5 +1,5 @@ { - "chat.room-id": "房间 %1", + "chat.room-id": "聊天室 %1", "chat.chatting-with": "与...聊天", "chat.placeholder": "在此处输入聊天信息,拖放图片", "chat.placeholder.mobile": "输入聊天信息", 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/topic.json b/public/language/zh-CN/topic.json index a9830d1883..4a68353e43 100644 --- a/public/language/zh-CN/topic.json +++ b/public/language/zh-CN/topic.json @@ -66,16 +66,16 @@ "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 上 分支了 这个主题", "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": "当有人回复此主题时,您会收到通知。", diff --git a/public/language/zh-CN/user.json b/public/language/zh-CN/user.json index e8eafaad63..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": "您确定要删除账户内容(帖子/主题/上传)吗?
    此操作不可逆,而且您无法恢复任何数据

    ", @@ -204,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": "授予许可", From be4d0e811e925ff19a30afd912139f959e6afd14 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 4 Nov 2025 11:09:15 -0500 Subject: [PATCH 565/828] fix: wrong auto-categorization if group actor is explicitly included in `audience` --- src/activitypub/notes.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 130cb1874f..973e97a7b6 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -137,7 +137,11 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { const { hostname } = new URL(mainPid); remoteCid = Array.from(set).filter((id, idx) => { const { hostname: cidHostname } = new URL(id); - return assertedGroups[idx] && cidHostname === hostname; + const explicitAudience = Array.isArray(_activitypub.audience) ? + _activitypub.audience.includes(id) : + _activitypub.audience === id; + + return assertedGroups[idx] && (explicitAudience || cidHostname === hostname); }).shift(); } catch (e) { // noop From 090eb0884527b7a36a84985b04dc3aea40d3bd4b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:55:48 -0500 Subject: [PATCH 566/828] fix(deps): update dependency esbuild to v0.25.12 (#13748) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c5352230ed..e28e0d5fd8 100644 --- a/install/package.json +++ b/install/package.json @@ -66,7 +66,7 @@ "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.11", + "esbuild": "0.25.12", "express": "4.21.2", "express-session": "1.18.2", "express-useragent": "2.0.1", From 4e7867a95d7cca8b244773c897d3e94ffc08b1d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:56:01 -0500 Subject: [PATCH 567/828] chore(deps): update dependency @eslint/js to v9.39.1 (#13747) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e28e0d5fd8..b0308a82f2 100644 --- a/install/package.json +++ b/install/package.json @@ -164,7 +164,7 @@ "@commitlint/cli": "20.1.0", "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", - "@eslint/js": "9.38.0", + "@eslint/js": "9.39.1", "@stylistic/eslint-plugin": "5.5.0", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", From 13c23fddd7508388a54216f4bc63203bdf5625c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:56:41 -0500 Subject: [PATCH 568/828] chore(deps): update github artifact actions (#13730) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fb2bd3ccce..e74e58c07b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -80,7 +80,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -96,7 +96,7 @@ jobs: 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@v5 + uses: actions/download-artifact@v6 with: path: ${{ runner.temp }}/digests pattern: digests-* From 4e33c1dfd3a59e15f194659b8f810c258839a6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 4 Nov 2025 12:42:08 -0500 Subject: [PATCH 569/828] chore: up harmony, closes #13753 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b0308a82f2..f385cd7b64 100644 --- a/install/package.json +++ b/install/package.json @@ -107,7 +107,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.5", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.23", + "nodebb-theme-harmony": "2.1.24", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.48", "nodebb-theme-persona": "14.1.17", From 1921ccaa101baff4b777d995b3a686e56a9ab619 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:43:34 -0500 Subject: [PATCH 570/828] fix(deps): update dependency sitemap to v9 (#13752) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f385cd7b64..38e7a2b3a7 100644 --- a/install/package.json +++ b/install/package.json @@ -135,7 +135,7 @@ "semver": "7.7.3", "serve-favicon": "2.5.1", "sharp": "0.34.4", - "sitemap": "8.0.2", + "sitemap": "9.0.0", "socket.io": "4.8.1", "socket.io-client": "4.8.1", "@socket.io/redis-adapter": "8.3.0", From a34284df834b2fc5ea50655b528e05d80ca22cf0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:44:03 -0500 Subject: [PATCH 571/828] fix(deps): update dependency bcryptjs to v3.0.3 (#13751) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 38e7a2b3a7..875f46d00c 100644 --- a/install/package.json +++ b/install/package.json @@ -43,7 +43,7 @@ "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.21", - "bcryptjs": "3.0.2", + "bcryptjs": "3.0.3", "benchpressjs": "2.5.5", "body-parser": "2.2.0", "bootbox": "6.0.4", From 4c5f7f6060c87a6528545a26f95cd05a79357638 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:54:57 -0500 Subject: [PATCH 572/828] chore(deps): update redis docker tag to v8.2.3 (#13750) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docker-compose-pgsql.yml | 2 +- docker-compose-redis.yml | 2 +- docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6304098eca..7be9ff42da 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.2.2' + image: 'redis:8.2.3' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 3c55eb6c3b..27ab3cdbb8 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.2.2-alpine + image: redis:8.2.3-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 2cd7197231..9dcc03ac29 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:8.2.2-alpine + image: redis:8.2.3-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 ee7a18ceb0..37decabf23 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:8.2.2-alpine + image: redis:8.2.3-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 From a8e45587bc8015974d80a76d37e60ff026726827 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 5 Nov 2025 09:22:28 +0000 Subject: [PATCH 573/828] Latest translations and fallbacks --- public/language/ar/error.json | 1 + public/language/az/error.json | 1 + public/language/bg/error.json | 1 + public/language/bn/error.json | 1 + public/language/cs/error.json | 1 + public/language/da/error.json | 1 + public/language/de/error.json | 1 + public/language/el/error.json | 1 + public/language/en-US/error.json | 1 + public/language/en-x-pirate/error.json | 1 + public/language/es/error.json | 1 + public/language/et/error.json | 1 + public/language/fa-IR/error.json | 1 + public/language/fi/error.json | 1 + public/language/fr/error.json | 1 + public/language/gl/error.json | 1 + public/language/he/error.json | 1 + public/language/hr/error.json | 1 + public/language/hu/error.json | 1 + public/language/hy/error.json | 1 + public/language/id/error.json | 1 + public/language/it/error.json | 1 + public/language/ja/error.json | 1 + public/language/ko/error.json | 1 + public/language/lt/error.json | 1 + public/language/lv/error.json | 1 + public/language/ms/error.json | 1 + public/language/nb/error.json | 1 + public/language/nl/error.json | 1 + public/language/nn-NO/error.json | 1 + public/language/pl/error.json | 1 + public/language/pt-BR/error.json | 1 + public/language/pt-PT/error.json | 1 + public/language/ro/error.json | 1 + public/language/ru/error.json | 1 + public/language/rw/error.json | 1 + public/language/sc/error.json | 1 + public/language/sk/error.json | 1 + public/language/sl/error.json | 1 + public/language/sq-AL/error.json | 1 + public/language/sr/error.json | 1 + public/language/sv/error.json | 1 + public/language/th/error.json | 1 + public/language/tr/error.json | 1 + public/language/uk/error.json | 1 + public/language/ur/error.json | 1 + public/language/vi/error.json | 1 + public/language/zh-CN/error.json | 1 + public/language/zh-TW/error.json | 1 + 49 files changed, 49 insertions(+) diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 4d2ea94cba..8e52b61425 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -147,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": "ملف غير مقبول", diff --git a/public/language/az/error.json b/public/language/az/error.json index 62ca9934ff..5cbd2b5af8 100644 --- a/public/language/az/error.json +++ b/public/language/az/error.json @@ -147,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", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 94d12a36b1..7d8d8dd869 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -147,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": "Грешен файл", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 3dfb852227..c5ccfa7b0f 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -147,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": "ভুল ফাইল", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 4295b7d97a..6c4c0c2833 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -147,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", diff --git a/public/language/da/error.json b/public/language/da/error.json index 9418e8663c..d421ec2c4e 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -147,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", diff --git a/public/language/de/error.json b/public/language/de/error.json index b292fdf3f1..c1af918b65 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -147,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", diff --git a/public/language/el/error.json b/public/language/el/error.json index 70bedd30c8..b09940f7f8 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -147,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": "Άκυρο Αρχείο", diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index c3bb2dc892..eb42797f04 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -147,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", diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json index c3bb2dc892..eb42797f04 100644 --- a/public/language/en-x-pirate/error.json +++ b/public/language/en-x-pirate/error.json @@ -147,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", diff --git a/public/language/es/error.json b/public/language/es/error.json index 27a8776929..5e3b3c58b3 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -147,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", diff --git a/public/language/et/error.json b/public/language/et/error.json index 7fd4027cec..37508f05f0 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -147,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", diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json index 7660c66770..34498fc1f0 100644 --- a/public/language/fa-IR/error.json +++ b/public/language/fa-IR/error.json @@ -147,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": "فایل نامعتبر است.", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index d7d1af89ea..771da7a5c1 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -147,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", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index d22c59af08..1c1329390a 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -147,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", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 7669c41b8f..e2c0414ede 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -147,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", diff --git a/public/language/he/error.json b/public/language/he/error.json index a492795106..013df27b96 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -147,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": "קובץ לא תקין", diff --git a/public/language/hr/error.json b/public/language/hr/error.json index 4e2d576d5c..fbf67edcc9 100644 --- a/public/language/hr/error.json +++ b/public/language/hr/error.json @@ -147,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", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index e337533e57..2483c26e37 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -147,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", diff --git a/public/language/hy/error.json b/public/language/hy/error.json index e6c4a3c18b..7db353d01a 100644 --- a/public/language/hy/error.json +++ b/public/language/hy/error.json @@ -147,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": "Անվավեր ֆայլ", diff --git a/public/language/id/error.json b/public/language/id/error.json index 21d344ce72..5bade5dae2 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -147,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", diff --git a/public/language/it/error.json b/public/language/it/error.json index 1c4a16adf9..50bfa7a26b 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -147,6 +147,7 @@ "post-already-restored": "Questo post è 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 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", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index be0b4396d1..3941c3d08c 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -147,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": "無効なファイル", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 80ee9f08e5..5a651299c2 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -147,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": "잘못된 파일", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index 5fbf1188c4..787374e85c 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -147,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", diff --git a/public/language/lv/error.json b/public/language/lv/error.json index 6195541875..e75b8a9019 100644 --- a/public/language/lv/error.json +++ b/public/language/lv/error.json @@ -147,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", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 26e1b5a310..b0a08abe48 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -147,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", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index a497b4f446..295fc7a7f9 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -147,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", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index 662a161197..586c81e656 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -147,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", diff --git a/public/language/nn-NO/error.json b/public/language/nn-NO/error.json index c25a7f8cb2..2a1c581a74 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -147,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", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 27cb6c9d42..02721106dd 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -147,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", diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json index 2fc1039862..d02827700d 100644 --- a/public/language/pt-BR/error.json +++ b/public/language/pt-BR/error.json @@ -147,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", diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json index fa0418c41c..68eacd4407 100644 --- a/public/language/pt-PT/error.json +++ b/public/language/pt-PT/error.json @@ -147,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", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index abc63f3b06..5ffde39996 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -147,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", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index d741a098a1..4a553e390d 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -147,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": "Некорректный файл", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 0452b4fc11..1756938782 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -147,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", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index c3bb2dc892..eb42797f04 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -147,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", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index cd21ec05a3..d567ee3556 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -147,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", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index ac9775d30b..31a71083ec 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -147,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", diff --git a/public/language/sq-AL/error.json b/public/language/sq-AL/error.json index b1aab456a0..5597b14d32 100644 --- a/public/language/sq-AL/error.json +++ b/public/language/sq-AL/error.json @@ -147,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", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index ec8ef67b20..3c011d4b3a 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -147,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": "Неисправна датотека", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 19a3580440..2d91974915 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -147,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", diff --git a/public/language/th/error.json b/public/language/th/error.json index 529a74d404..e6cf42d581 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -147,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": "ไฟล์ไม่ถูกต้อง", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index ea93e9abe6..b16b2d15ba 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -147,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", diff --git a/public/language/uk/error.json b/public/language/uk/error.json index 002b6a8471..9d871644e7 100644 --- a/public/language/uk/error.json +++ b/public/language/uk/error.json @@ -147,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": "Невірний файл", diff --git a/public/language/ur/error.json b/public/language/ur/error.json index 21e92491e6..279e5d997c 100644 --- a/public/language/ur/error.json +++ b/public/language/ur/error.json @@ -147,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": "غلط فائل", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index ec481020a1..2293f2de02 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -147,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ệ", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index 39fabf9c1b..ee9f6b6a21 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -147,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": "无效文件", diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index 14a9bcdbbb..4cbbafcffd 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -147,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": "無效檔案", From ed83bc5b83f243e68faa56f1c1852b613aa08c2b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 5 Nov 2025 12:55:03 -0500 Subject: [PATCH 574/828] revert: remove `federatedDescription` category field, closes #13757 --- public/language/en-GB/admin/manage/categories.json | 3 --- src/activitypub/mocks.js | 10 +++------- src/categories/data.js | 2 +- src/views/admin/manage/category.tpl | 10 ---------- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -15,9 +15,6 @@ "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", diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index d83df6e1e1..3fe103f3b1 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -521,11 +521,11 @@ Mocks.actors.user = async (uid) => { }; Mocks.actors.category = async (cid) => { - let { + const { name, handle: preferredUsername, slug, - descriptionParsed: summary, federatedDescription, backgroundImage, + descriptionParsed: summary, backgroundImage, } = await categories.getCategoryFields(cid, - ['name', 'handle', 'slug', 'description', 'descriptionParsed', 'federatedDescription', 'backgroundImage']); + ['name', 'handle', 'slug', 'description', 'descriptionParsed', 'backgroundImage']); const publicKey = await activitypub.getPublicKey('cid', cid); let icon; @@ -546,10 +546,6 @@ 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', diff --git a/src/categories/data.js b/src/categories/data.js index dc4467ffa3..bf2ddac25f 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -117,7 +117,7 @@ function modifyCategory(category, fields) { db.parseIntFields(category, intFields, fields); - const escapeFields = ['name', 'nickname', '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)) { category[field] = validator.escape(String(category[field] || '')); diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index b935638ac4..19b0efa116 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -36,16 +36,6 @@
    -
    - - -

    - [[admin/manage/categories:federatedDescription.help]] -

    -
    -
    diff --git a/src/views/partials/topic/topic-menu-list.tpl b/src/views/partials/topic/topic-menu-list.tpl index 147b5cbbf4..f32ea80ec5 100644 --- a/src/views/partials/topic/topic-menu-list.tpl +++ b/src/views/partials/topic/topic-menu-list.tpl @@ -21,10 +21,6 @@ {{{ end }}} -
  • - [[topic:thread-tools.crosspost]] -
  • -
  • [[topic:thread-tools.merge]]
  • From 0a0a7da9ba3c76cdcd22e6a2b4b0635e8da3eef2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 29 Dec 2025 14:20:25 -0500 Subject: [PATCH 793/828] fix: bug where privileges users could not uncrosspost others' crossposts. Tests --- src/topics/crossposts.js | 5 +- test/topics/crossposts.js | 120 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index 20ed086d71..ce2bcda93a 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -2,6 +2,7 @@ const db = require('../database'); const topics = require('.'); +const user = require('../user'); const categories = require('../categories'); const posts = require('../posts'); const activitypub = require('../activitypub'); @@ -87,8 +88,10 @@ Crossposts.add = async function (tid, cid, uid) { 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) && String(uid) === String(_uid)) { + if (String(cid) === String(_cid) && (isPrivileged || isMod || String(uid) === String(_uid))) { id = _id; } diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 878dc864d2..a018973a50 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -8,9 +8,11 @@ const db = require('../mocks/databasemock'); const meta = require('../../src/meta'); const install = require('../../src/install'); const user = require('../../src/user'); +const groups = require('../../src/groups'); const categories = require('../../src/categories'); const topics = require('../../src/topics'); const posts = require('../../src/posts'); +const privileges = require('../../src/privileges'); const activitypub = require('../../src/activitypub'); const utils = require('../../src/utils'); @@ -142,6 +144,14 @@ describe('Crossposting (& related logic)', () => { await topics.crossposts.add(tid, cid2, uid); }); + it('should not let another user uncrosspost', async () => { + const uid2 = await user.create({ username: utils.generateUUID().slice(0, 8) }); + assert.rejects( + topics.crossposts.remove(tid, cid2, uid2), + '[[error:invalid-data]]', + ); + }); + it('should successfully uncrosspost from a cid', async () => { const crossposts = await topics.crossposts.remove(tid, cid2, uid); @@ -168,6 +178,116 @@ describe('Crossposting (& related logic)', () => { }); }); + describe('uncrosspost (as administrator)', () => { + let tid; + let cid1; + let cid2; + let uid; + let privUid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + privUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + await groups.join('administrators', privUid); + + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + }); + + it('should successfully uncrosspost from a cid', async () => { + const crossposts = await topics.crossposts.remove(tid, cid2, privUid); + + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + + describe('uncrosspost (as global moderator)', () => { + let tid; + let cid1; + let cid2; + let uid; + let privUid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + privUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + await groups.join('Global Moderators', privUid); + + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + }); + + it('should successfully uncrosspost from a cid', async () => { + const crossposts = await topics.crossposts.remove(tid, cid2, privUid); + + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + + describe('uncrosspost (as category moderator)', () => { + let tid; + let cid1; + let cid2; + let uid; + let privUid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + privUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + }); + + it('should fail to uncrosspost if not mod of passed-in category', async () => { + await privileges.categories.give(['moderate'], cid1, [privUid]); + assert.rejects( + topics.crossposts.remove(tid, cid2, privUid), + '[[error:invalid-data]]', + ); + }); + + it('should successfully uncrosspost from a cid if proper mod', async () => { + await privileges.categories.give(['moderate'], cid2, [privUid]); + const crossposts = await topics.crossposts.remove(tid, cid2, privUid); + + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + describe('ActivityPub effects (or lack thereof)', () => { describe('local canonical category', () => { let tid; From f6cc556d37bd2289709b79f9dd70212b8656342e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 29 Dec 2025 14:32:34 -0500 Subject: [PATCH 794/828] fix: topic crosspost delete and purge handling --- src/topics/crossposts.js | 10 +++++++ src/topics/delete.js | 1 + test/topics/crossposts.js | 60 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index ce2bcda93a..72cff44424 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -127,4 +127,14 @@ Crossposts.remove = async function (tid, cid, uid) { 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/delete.js b/src/topics/delete.js index 466d25a0dd..03e756e1fd 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -102,6 +102,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 }); diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index a018973a50..542e5b22a7 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -288,6 +288,66 @@ describe('Crossposting (& related logic)', () => { }); }); + describe('Deletion', () => { + let tid; + let cid1; + let cid2; + let uid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + await topics.delete(tid, uid); + }); + + it('should maintain crossposts when topic is deleted', async () => { + const crossposts = await topics.crossposts.get(tid); + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 1); + }); + }); + + describe('Purging', () => { + let tid; + let cid1; + let cid2; + let uid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + await topics.purge(tid, uid); + }); + + it('should remove crossposts when topic is purged', async () => { + const crossposts = await topics.crossposts.get(tid); + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + describe('ActivityPub effects (or lack thereof)', () => { describe('local canonical category', () => { let tid; From 148663c5367173fcc1f6faefbdaa80dddf2e6737 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 29 Dec 2025 14:57:47 -0500 Subject: [PATCH 795/828] fix: update auto-categorization rules to also handle already-categorized topics via crosspost --- src/activitypub/notes.js | 9 ++++++++- src/topics/crossposts.js | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 3771a72d36..a66adb0dc6 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -104,6 +104,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { const hasTid = !!tid; const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; + let crosspostCid = false; if (options.cid && cid === -1) { // Move topic if currently uncategorized @@ -155,8 +156,10 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } // Auto-categorization (takes place only if all other categorization efforts fail) + crosspostCid = await assignCategory(mainPost); if (!options.cid) { - options.cid = await assignCategory(mainPost); + options.cid = crosspostCid; + crosspostCid = false; } // mainPid ok to leave as-is @@ -265,6 +268,10 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { 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); diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index 72cff44424..26aec2bf8a 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -76,7 +76,7 @@ Crossposts.add = async function (tid, cid, uid) { db.sortedSetAdd(`cid:${cid}:pids`, pidTimestamps, pids), db.setObject(`crosspost:${crosspostId}`, { uid, tid, cid, timestamp: now }), db.sortedSetAdd(`tid:${tid}:crossposts`, now, crosspostId), - db.sortedSetAdd(`uid:${uid}:crossposts`, now, crosspostId), + uid > 0 ? db.sortedSetAdd(`uid:${uid}:crossposts`, now, crosspostId) : false, ]); await categories.onTopicsMoved([cid]); } else { @@ -121,7 +121,7 @@ Crossposts.remove = async function (tid, cid, uid) { db.sortedSetRemoveBulk(bulkRemove), db.delete(`crosspost:${crosspostId}`), db.sortedSetRemove(`tid:${tid}:crossposts`, crosspostId), - db.sortedSetRemove(`uid:${uid}:crossposts`, crosspostId), + uid > 0 ? db.sortedSetRemove(`uid:${uid}:crossposts`, crosspostId) : false, ]); await categories.onTopicsMoved([cid]); From 28249efbe674093e987a0f86f8cdac0644e83e85 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 29 Dec 2025 15:07:47 -0500 Subject: [PATCH 796/828] fix: remove old remote user to remote category migration logic + tests --- src/activitypub/actors.js | 36 -------------- test/activitypub/actors.js | 99 -------------------------------------- 2 files changed, 135 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 1962294a24..c6894b7c3b 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -411,7 +411,6 @@ Actors.assertGroup = async (ids, options = {}) => { db.sortedSetAdd('usersRemote:lastCrawled', groups.map(() => now), groups.map(p => p.id)), db.sortedSetAddBulk(queries.searchAdd), db.setObject('handle:cid', queries.handleAdd), - _migratePersonToGroup(categoryObjs), db.setsAdd(masksAdd, 'topics:create'), db.setsRemove(masksRemove, 'topics:create'), ]); @@ -419,41 +418,6 @@ Actors.assertGroup = async (ids, options = {}) => { return categoryObjs; }; -async function _migratePersonToGroup(categoryObjs) { - // 4.0.0-4.1.x asserted as:Group as users. This moves relevant stuff over and deletes the now-duplicate user. - let ids = categoryObjs.map(category => category.cid); - const slugs = categoryObjs.map(category => category.slug); - const isUser = await db.isObjectFields('handle:uid', slugs); - ids = ids.filter((id, idx) => isUser[idx]); - if (!ids.length) { - return; - } - - await Promise.all(ids.map(async (id) => { - const shares = await db.getSortedSetMembers(`uid:${id}:shares`); - let cids = await topics.getTopicsFields(shares, ['cid']); - cids = cids.map(o => o.cid); - await Promise.all(shares.map(async (share, idx) => { - const cid = cids[idx]; - if (cid === -1) { - await topics.tools.move(share, { - cid: id, - uid: 'system', - }); - } - })); - - const followers = await db.getSortedSetMembersWithScores(`followersRemote:${id}`); - await db.sortedSetAdd( - `cid:${id}:uid:watch:state`, - followers.map(() => categories.watchStates.tracking), - followers.map(({ value }) => value), - ); - await user.deleteAccount(id); - })); - await categories.onTopicsMoved(ids); -} - Actors.getLocalFollowers = async (id) => { // Returns local uids and cids that follow a remote actor (by id) const response = { diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index 7106dced69..2e2375e2ce 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -76,105 +76,6 @@ describe('Actor asserton', () => { assert.strictEqual(assertion.length, 1); assert.strictEqual(assertion[0].cid, actor.id); }); - - describe('remote user to remote category migration', () => { - it('should not migrate a user to a category if .assert is called', async () => { - // ... because the user isn't due for an update and so is filtered out during qualification - const { id } = helpers.mocks.person(); - await activitypub.actors.assert([id]); - - const { actor } = helpers.mocks.group({ id }); - const assertion = await activitypub.actors.assertGroup([id]); - - assert(assertion.length, 0); - - const exists = await user.exists(id); - assert.strictEqual(exists, false); - }); - - it('should migrate a user to a category if on re-assertion it identifies as an as:Group', async () => { - // This is to handle previous behaviour that saved all as:Group actors as NodeBB users. - const { id } = helpers.mocks.person(); - await activitypub.actors.assert([id]); - - helpers.mocks.group({ id }); - const assertion = await activitypub.actors.assertGroup([id]); - - assert(assertion && Array.isArray(assertion) && assertion.length === 1); - - const exists = await user.exists(id); - assert.strictEqual(exists, false); - }); - - it('should migrate any shares by that user, into topics in the category', async () => { - const { id } = helpers.mocks.person(); - await activitypub.actors.assert([id]); - - // Two shares - for (let x = 0; x < 2; x++) { - const { id: pid } = helpers.mocks.note(); - // eslint-disable-next-line no-await-in-loop - const { tid } = await activitypub.notes.assert(0, pid, { skipChecks: 1 }); - // eslint-disable-next-line no-await-in-loop - await db.sortedSetAdd(`uid:${id}:shares`, Date.now(), tid); - } - - helpers.mocks.group({ id }); - await activitypub.actors.assertGroup([id]); - - const { topic_count, post_count } = await categories.getCategoryData(id); - assert.strictEqual(topic_count, 2); - assert.strictEqual(post_count, 2); - }); - - it('should not migrate shares by that user that already belong to a local category', async () => { - const { id } = helpers.mocks.person(); - await activitypub.actors.assert([id]); - - const { cid } = await categories.create({ name: utils.generateUUID() }); - - // Two shares, one moved to local cid - for (let x = 0; x < 2; x++) { - const { id: pid } = helpers.mocks.note(); - // eslint-disable-next-line no-await-in-loop - const { tid } = await activitypub.notes.assert(0, pid, { skipChecks: 1 }); - // eslint-disable-next-line no-await-in-loop - await db.sortedSetAdd(`uid:${id}:shares`, Date.now(), tid); - - if (!x) { - // eslint-disable-next-line no-await-in-loop - await topics.tools.move(tid, { - cid, - uid: 'system', - }); - } - } - - helpers.mocks.group({ id }); - await activitypub.actors.assertGroup([id]); - - const { topic_count, post_count } = await categories.getCategoryData(id); - assert.strictEqual(topic_count, 1); - assert.strictEqual(post_count, 1); - }); - - it('should migrate any local followers into category watches', async () => { - const { id } = helpers.mocks.person(); - await activitypub.actors.assert([id]); - - const followerUid = await user.create({ username: utils.generateUUID() }); - await Promise.all([ - db.sortedSetAdd(`followingRemote:${followerUid}`, Date.now(), id), - db.sortedSetAdd(`followersRemote:${id}`, Date.now(), followerUid), - ]); - - helpers.mocks.group({ id }); - await activitypub.actors.assertGroup([id]); - - const states = await categories.getWatchState([id], followerUid); - assert.strictEqual(states[0], categories.watchStates.tracking); - }); - }); }); describe('less happy paths', () => { From e5ee52e5da5763c9c5e1dc2be18d6026e95068a9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 29 Dec 2025 15:08:04 -0500 Subject: [PATCH 797/828] fix: update category sync logic to utilise crossposts instead --- src/activitypub/inbox.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 91e9bfab4a..f3413f3f51 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -388,12 +388,10 @@ inbox.announce = async (req) => { // Category sync, remove when cross-posting available const { cids } = await activitypub.actors.getLocalFollowers(actor); - let cid = null; - if (cids.size > 0) { - cid = Array.from(cids)[0]; - } + const syncedCids = Array.from(cids); // 1b12 announce + let cid = null; const categoryActor = await categories.exists(actor); if (categoryActor) { cid = actor; @@ -448,7 +446,7 @@ inbox.announce = async (req) => { socketHelpers.sendNotificationToPostOwner(pid, actor, 'announce', 'notifications:activitypub.announce'); } else { // Remote object // Follower check - if (!fromRelay && !cid) { + 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`); @@ -471,6 +469,12 @@ inbox.announce = async (req) => { ({ 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 From ea417b062b8ebff6d80abfb8f831c6eb31d4460f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 31 Dec 2025 10:08:12 -0500 Subject: [PATCH 798/828] fix: client-side handling of category selector when cross-posting so only local cids are sent to backend --- public/src/client/topic/crosspost.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic/crosspost.js b/public/src/client/topic/crosspost.js index 268a5f9446..9242708a87 100644 --- a/public/src/client/topic/crosspost.js +++ b/public/src/client/topic/crosspost.js @@ -49,7 +49,7 @@ define('forum/topic/crosspost', [ } function onCategoriesSelected(data) { - ({ selectedCids } = data); + selectedCids = data.selectedCids.filter(utils.isNumber); if (data.changed) { modal.find('#crosspost_thread_commit').prop('disabled', false); } From add163a42db9b103e8c85d4118ed367071fdccba Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 31 Dec 2025 10:54:57 -0500 Subject: [PATCH 799/828] test: ensure auto-cat and cat sync logic properly integrates with crossposts --- src/activitypub/actors.js | 9 +++++ test/topics/crossposts.js | 85 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index c6894b7c3b..15128e82f0 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -445,10 +445,19 @@ Actors.getLocalFollowers = async (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)); + } + }); } return response; diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 542e5b22a7..3becd9868d 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -348,6 +348,91 @@ describe('Crossposting (& related logic)', () => { }); }); + describe('category sync; integration with', () => { + let cid; + let remoteCid; + let pid; + let post; + + const helpers = require('../activitypub/helpers'); + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ id: remoteCid } = helpers.mocks.group()); + ({ id: pid, note: post } = helpers.mocks.note({ + audience: [remoteCid], + })); + + // Mock a group follow/accept + const timestamp = Date.now(); + console.log('saving', remoteCid); + await Promise.all([ + db.sortedSetAdd(`cid:${cid}:following`, timestamp, remoteCid), + db.sortedSetAdd(`followersRemote:${remoteCid}`, timestamp, `cid|${cid}`), + ]); + }); + + it('should automatically cross-post the topic when the remote category announces', async () => { + const { activity: body } = helpers.mocks.announce({ + actor: remoteCid, + object: post, + }); + + await activitypub.inbox.announce({ body }); + + const tid = await posts.getPostField(pid, 'tid'); + const crossposts = await topics.crossposts.get(tid); + + assert.strictEqual(crossposts.length, 1); + assert.partialDeepStrictEqual(crossposts[0], { + uid: '0', + tid, + cid: String(cid), + }); + }); + }); + + describe('auto-categorization; integration with', () => { + let cid; + let remoteCid; + let pid; + let post; + + const helpers = require('../activitypub/helpers'); + + before(async () => { + const preferredUsername = utils.generateUUID().slice(0, 8); + ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ id: remoteCid } = helpers.mocks.group({ + preferredUsername, + })); + ({ id: pid, note: post } = helpers.mocks.note({ + audience: [remoteCid], + tag: [ + { + type: 'Hashtag', + name: `#${preferredUsername}`, + }, + ], + })); + + await activitypub.rules.add('hashtag', preferredUsername, cid); + }); + + it('note assertion should automatically cross-post', async () => { + await activitypub.notes.assert(0, pid, { skipChecks: true }); + + const tid = await posts.getPostField(pid, 'tid'); + const crossposts = await topics.crossposts.get(tid); + assert.strictEqual(crossposts.length, 1); + assert.partialDeepStrictEqual(crossposts[0], { + uid: '0', + tid, + cid: String(cid), + }); + }); + }); + describe('ActivityPub effects (or lack thereof)', () => { describe('local canonical category', () => { let tid; From d81b644d7f177011cf40c04aa0904ddbe8c206f4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 5 Jan 2026 12:24:00 -0500 Subject: [PATCH 800/828] docs: update openapi schema for missing routes related to crossposting --- .../components/schemas/CrosspostObject.yaml | 74 ++++++++++++------- public/openapi/read/topic/topic_id.yaml | 4 + .../openapi/write/topics/tid/crossposts.yaml | 28 +++++++ 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/public/openapi/components/schemas/CrosspostObject.yaml b/public/openapi/components/schemas/CrosspostObject.yaml index 53a4183055..54f36a4f5f 100644 --- a/public/openapi/components/schemas/CrosspostObject.yaml +++ b/public/openapi/components/schemas/CrosspostObject.yaml @@ -1,32 +1,50 @@ CrosspostObject: - 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 + 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 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/write/topics/tid/crossposts.yaml b/public/openapi/write/topics/tid/crossposts.yaml index 212a1f6469..f292292e0a 100644 --- a/public/openapi/write/topics/tid/crossposts.yaml +++ b/public/openapi/write/topics/tid/crossposts.yaml @@ -1,3 +1,31 @@ +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 From 0677689a7541d4f88976d9b57fdb4e4b8104037a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 5 Jan 2026 15:07:50 -0500 Subject: [PATCH 801/828] test: stop using partialDeepStrictEqual for now --- test/topics/crossposts.js | 41 +++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 3becd9868d..ce3be1fbc5 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -89,7 +89,13 @@ describe('Crossposting (& related logic)', () => { assert(Array.isArray(crossposts)); assert.strictEqual(crossposts.length, 1); - assert.partialDeepStrictEqual(crossposts[0], { + + const actual = crossposts[0]; + assert.deepStrictEqual({ + uid: actual.uid, + tid: actual.tid, + cid: actual.cid, + }, { uid, tid, cid: cid2, @@ -365,7 +371,6 @@ describe('Crossposting (& related logic)', () => { // Mock a group follow/accept const timestamp = Date.now(); - console.log('saving', remoteCid); await Promise.all([ db.sortedSetAdd(`cid:${cid}:following`, timestamp, remoteCid), db.sortedSetAdd(`followersRemote:${remoteCid}`, timestamp, `cid|${cid}`), @@ -384,7 +389,13 @@ describe('Crossposting (& related logic)', () => { const crossposts = await topics.crossposts.get(tid); assert.strictEqual(crossposts.length, 1); - assert.partialDeepStrictEqual(crossposts[0], { + + const actual = crossposts[0]; + assert.deepStrictEqual({ + uid: actual.uid, + tid: actual.tid, + cid: actual.cid, + }, { uid: '0', tid, cid: String(cid), @@ -425,7 +436,13 @@ describe('Crossposting (& related logic)', () => { const tid = await posts.getPostField(pid, 'tid'); const crossposts = await topics.crossposts.get(tid); assert.strictEqual(crossposts.length, 1); - assert.partialDeepStrictEqual(crossposts[0], { + + const actual = crossposts[0]; + assert.deepStrictEqual({ + uid: actual.uid, + tid: actual.tid, + cid: actual.cid, + }, { uid: '0', tid, cid: String(cid), @@ -497,7 +514,13 @@ describe('Crossposting (& related logic)', () => { }); assert.strictEqual(activitypub._sent.size, 1); - assert.partialDeepStrictEqual(Array.from(activitypub._sent).pop()[1], { + + const actual = Array.from(activitypub._sent).pop()[1]; + assert.deepStrictEqual({ + type: actual.type, + actor: actual.actor, + object: actual.object, + }, { type: 'Announce', actor: `${nconf.get('url')}/category/${cid1}`, object: activity, @@ -513,7 +536,13 @@ describe('Crossposting (& related logic)', () => { await activitypub.inbox.like({ body }); assert.strictEqual(activitypub._sent.size, 1); - assert.partialDeepStrictEqual(Array.from(activitypub._sent).pop()[1], { + + const actual = Array.from(activitypub._sent).pop()[1]; + assert.deepStrictEqual({ + type: actual.type, + actor: actual.actor, + object: actual.object, + }, { type: 'Announce', actor: `${nconf.get('url')}/category/${cid1}`, object: body, From 47e37ed5716207ba655fbf1a06c58b6fd4c9c1e5 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 6 Jan 2026 10:13:04 -0500 Subject: [PATCH 802/828] test: intify uid/cid if they are numbers (when getting crossposts) --- src/topics/crossposts.js | 3 +++ test/topics/crossposts.js | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index 26aec2bf8a..c02f24e934 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -27,6 +27,9 @@ Crossposts.get = async function (tid) { crossposts = crossposts.map((crosspost, idx) => { crosspost.id = crosspostIds[idx]; crosspost.category = categoriesData.get(parseInt(crosspost.cid, 10)); + crosspost.uid = utils.isNumber(crosspost.uid) ? parseInt(crosspost.uid) : crosspost.uid; + crosspost.cid = utils.isNumber(crosspost.cid) ? parseInt(crosspost.cid) : crosspost.cid; + return crosspost; }); diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index ce3be1fbc5..5b5a4b0d67 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -396,9 +396,9 @@ describe('Crossposting (& related logic)', () => { tid: actual.tid, cid: actual.cid, }, { - uid: '0', + uid: 0, tid, - cid: String(cid), + cid: cid, }); }); }); @@ -443,9 +443,9 @@ describe('Crossposting (& related logic)', () => { tid: actual.tid, cid: actual.cid, }, { - uid: '0', + uid: 0, tid, - cid: String(cid), + cid: cid, }); }); }); From 273bc68c468cdc6b174269939a414c6f35461b82 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 7 Jan 2026 10:48:22 -0500 Subject: [PATCH 803/828] feat: user crossposts federate as:Announce --- src/controllers/write/topics.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index 5bca992074..f26964c13d 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'); @@ -222,6 +223,7 @@ Topics.getCrossposts = async (req, res) => { 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 }); }; @@ -229,6 +231,7 @@ Topics.crosspost = async (req, res) => { 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.parms.tid); helpers.formatApiResponse(200, res, { crossposts }); }; From 7465762d8731aff82cc3f3a538c6cd4fa7906ef4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 7 Jan 2026 11:50:00 -0500 Subject: [PATCH 804/828] fix: typo, client-side handling of crossposts as pertains to uncategorized topics --- public/src/client/topic/crosspost.js | 59 ++++++++++++++-------------- src/controllers/write/topics.js | 2 +- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/public/src/client/topic/crosspost.js b/public/src/client/topic/crosspost.js index 9242708a87..473450f3d1 100644 --- a/public/src/client/topic/crosspost.js +++ b/public/src/client/topic/crosspost.js @@ -20,27 +20,44 @@ define('forum/topic/crosspost', [ }; function showModal() { - app.parseAndTranslate('modals/crosspost-topic', { - selectedCategory: ajaxify.data.crossposts.length ? - { - icon: 'fa-plus', - name: '[[unread:multiple-categories-selected]]', - bgColor: '#ddd', - } : - ajaxify.data.category, - }, function (html) { + 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([ajaxify.data.cid, ...ajaxify.data.crossposts.map(c => c.cid)])), + selectedCids: Array.from(new Set(selectedCids)), }); modal.find('#crosspost_thread_commit').on('click', onCommitClicked); @@ -49,7 +66,7 @@ define('forum/topic/crosspost', [ } function onCategoriesSelected(data) { - selectedCids = data.selectedCids.filter(utils.isNumber); + selectedCids = data.selectedCids.filter(cid => utils.isNumber(cid) && cid > 0); if (data.changed) { modal.find('#crosspost_thread_commit').prop('disabled', false); } @@ -58,31 +75,13 @@ define('forum/topic/crosspost', [ function onCommitClicked() { const commitEl = modal.find('#crosspost_thread_commit'); - if (!commitEl.prop('disabled') && selectedCids && selectedCids.length) { + if (!commitEl.prop('disabled')) { commitEl.prop('disabled', true); const data = { tid: Crosspost.tid, cids: selectedCids, }; - // TODO - // if (config.undoTimeout > 0) { - // return alerts.alert({ - // alert_id: 'tids_move_' + (Crosspost.tid ? Crosspost.tid.join('-') : 'all'), - // title: '[[topic:thread-tools.move]]', - // message: message, - // type: 'success', - // timeout: config.undoTimeout, - // timeoutfn: function () { - // moveTopics(data); - // }, - // clickfn: function (alert, params) { - // delete params.timeoutfn; - // alerts.success('[[topic:topic-move-undone]]'); - // }, - // }); - // } - crosspost(data); } } diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index f26964c13d..868fc08e8e 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -231,7 +231,7 @@ Topics.crosspost = async (req, res) => { 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.parms.tid); + await activitypub.out.undo.announce('uid', req.uid, req.params.tid); helpers.formatApiResponse(200, res, { crossposts }); }; From d20906b59228900eb1ca380c97efc3cb1de2ac92 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 8 Jan 2026 15:59:09 -0500 Subject: [PATCH 805/828] tests: fix... tests --- test/topics/crossposts.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 5b5a4b0d67..174fda26d0 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -517,9 +517,9 @@ describe('Crossposting (& related logic)', () => { const actual = Array.from(activitypub._sent).pop()[1]; assert.deepStrictEqual({ - type: actual.type, - actor: actual.actor, - object: actual.object, + type: actual.payload.type, + actor: actual.payload.actor, + object: actual.payload.object, }, { type: 'Announce', actor: `${nconf.get('url')}/category/${cid1}`, @@ -539,9 +539,9 @@ describe('Crossposting (& related logic)', () => { const actual = Array.from(activitypub._sent).pop()[1]; assert.deepStrictEqual({ - type: actual.type, - actor: actual.actor, - object: actual.object, + type: actual.payload.type, + actor: actual.payload.actor, + object: actual.payload.object, }, { type: 'Announce', actor: `${nconf.get('url')}/category/${cid1}`, From b9b33f9f8d0dc845a73ac3da178b4f857a0c79d0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 8 Jan 2026 16:47:00 -0500 Subject: [PATCH 806/828] fix: unused values --- src/activitypub/actors.js | 1 - src/topics/tools.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 15128e82f0..4720390ed3 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -9,7 +9,6 @@ const meta = require('../meta'); const batch = require('../batch'); const categories = require('../categories'); const user = require('../user'); -const topics = require('../topics'); const utils = require('../utils'); const TTLCache = require('../cache/ttl'); diff --git a/src/topics/tools.js b/src/topics/tools.js index 8fb0474788..f5e9b13dc7 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -5,11 +5,9 @@ const _ = require('lodash'); const db = require('../database'); const topics = require('.'); const categories = require('../categories'); -const posts = require('../posts'); const user = require('../user'); const plugins = require('../plugins'); const privileges = require('../privileges'); -const activitypub = require('../activitypub'); const utils = require('../utils'); From 82507c0fb1675b29fb97462e35f3da7088e592de Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 12 Jan 2026 12:29:53 -0500 Subject: [PATCH 807/828] fix: calling sortedSetRemove to remove multiple values, instead of baking it into sortedSetRemoveBulk --- src/topics/crossposts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index c02f24e934..d71d83f714 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -118,12 +118,12 @@ Crossposts.remove = async function (tid, cid, uid) { `cid:${cid}:tids:views`, ]; bulkRemove = bulkRemove.map(zset => [zset, tid]); - bulkRemove.push([`cid:${cid}:pids`, pids]); 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]); From 943b53b0bc0bb9613e0c2740ebf7cd13c8d3c800 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 12 Jan 2026 12:45:49 -0500 Subject: [PATCH 808/828] fix: bump themes --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 1f079c4379..0d32e53d31 100644 --- a/install/package.json +++ b/install/package.json @@ -107,10 +107,10 @@ "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.1.31", + "nodebb-theme-harmony": "2.1.32", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", - "nodebb-theme-persona": "14.1.23", + "nodebb-theme-persona": "14.1.24", "nodebb-widget-essentials": "7.0.41", "nodemailer": "7.0.12", "nprogress": "0.2.0", From 2f96eed4aff41beef9c74f447f668e736e41bbe2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 12 Jan 2026 14:07:45 -0500 Subject: [PATCH 809/828] fix: guard against negative uids crossposting --- src/topics/crossposts.js | 10 ++++++++++ test/topics/crossposts.js | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index d71d83f714..2308242220 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -37,6 +37,13 @@ Crossposts.get = async function (tid) { }; Crossposts.add = async function (tid, cid, uid) { + console.log('ADD WAS CALLED!!', tid, cid, uid); + return; + /** + * 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); @@ -45,6 +52,9 @@ Crossposts.add = async function (tid, cid, uid) { if (!exists) { throw new Error('[[error:invalid-cid]]'); } + if (uid < 0) { + throw new Error('[[error:invalid-uid]]'); + } const crossposts = await Crossposts.get(tid); const crosspostedCids = crossposts.map(crosspost => String(crosspost.cid)); diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 174fda26d0..ec6fe66aa5 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -84,6 +84,13 @@ describe('Crossposting (& related logic)', () => { tid = topicData.tid; }); + it('should not allow a spider (uid -1) to crosspost', async () => { + await assert.rejects( + topics.crossposts.add(tid, cid2, -1), + { message: '[[error:invalid-uid]]' } + ); + }); + it('should successfully crosspost to another cid', async () => { const crossposts = await topics.crossposts.add(tid, cid2, uid); From a4c470ffa909c912b6d2a83870a71ef8ef281536 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 12 Jan 2026 14:10:31 -0500 Subject: [PATCH 810/828] fix: bump themes --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 0d32e53d31..2e08d61768 100644 --- a/install/package.json +++ b/install/package.json @@ -107,10 +107,10 @@ "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.1.32", + "nodebb-theme-harmony": "2.1.33", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", - "nodebb-theme-persona": "14.1.24", + "nodebb-theme-persona": "14.1.25", "nodebb-widget-essentials": "7.0.41", "nodemailer": "7.0.12", "nprogress": "0.2.0", From bcc204fa932822f063c2df944e1c91ba5b2a271c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 12 Jan 2026 15:42:33 -0500 Subject: [PATCH 811/828] fix: derp --- src/topics/crossposts.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index 2308242220..b4ae973ad2 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -37,8 +37,6 @@ Crossposts.get = async function (tid) { }; Crossposts.add = async function (tid, cid, uid) { - console.log('ADD WAS CALLED!!', tid, cid, uid); - return; /** * NOTE: If uid is 0, the assumption is that it is a "system" crosspost, not a guest! * (Normally guest uid is 0) From 0e1ccfc988f6dcd3ba77fcd96dbd350f353875ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 Jan 2026 20:49:31 -0500 Subject: [PATCH 812/828] refactor: check if tid is truthy --- src/privileges/topics.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 508f989f5a..5538e626d9 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -94,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); From 6eea4df5efda5f86f0342a258ddf2f5549067c36 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 13 Jan 2026 10:36:03 -0500 Subject: [PATCH 813/828] fix: #13888, decode html entities for AP category name and description --- src/activitypub/mocks.js | 4 ++-- test/activitypub/actors.js | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 660cb223f9..051b17f96e 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -576,9 +576,9 @@ 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, diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index 2e2375e2ce..e659713de3 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -16,7 +16,7 @@ const slugify = require('../../src/slugify'); const helpers = require('./helpers'); -describe('Actor asserton', () => { +describe('as:Person (Actor asserton)', () => { before(async () => { meta.config.activitypubEnabled = 1; await install.giveWorldPrivileges(); @@ -514,6 +514,26 @@ describe('Controllers', () => { url: `${nconf.get('url')}/assets/uploads/files/test.png`, }); }); + + it('should not contain html entities in name and summary', async () => {const payload = {}; + payload[cid] = { + name: 'One & Two', + description: 'This is a category for one & two', + }; + await categories.update(payload); + + const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, { + headers: { + Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + }, + }); + + const { name, summary } = body; + assert.deepStrictEqual({ name, summary }, { + name: 'One & Two', + summary: 'This is a category for one & two', + }); + }); }); describe('Instance Actor endpoint', () => { From 0c75934adf4040ad83f10b454aa8dbc9bd7c2f80 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 13 Jan 2026 11:25:18 -0500 Subject: [PATCH 814/828] fix: #13889, custom emoji from Piefed --- src/posts/create.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/posts/create.js b/src/posts/create.js index 064e337a94..044b07d699 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -60,7 +60,8 @@ module.exports = function (Posts) { tag.name = `${tag.name}:`; } - postData.content = postData.content.replace(new RegExp(tag.name, 'g'), ``); + const property = postData.sourceContent && !postData.content ? 'sourceContent' : 'content'; + postData[property] = postData[property].replace(new RegExp(tag.name, 'g'), ``); }); } From 974ab1f8bc226738dbc7bdbf04c611ced5351225 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:41:04 -0500 Subject: [PATCH 815/828] fix(deps): update dependency diff to v8.0.3 (#13882) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2e08d61768..32ccd06600 100644 --- a/install/package.json +++ b/install/package.json @@ -65,7 +65,7 @@ "cropperjs": "1.6.2", "csrf-sync": "4.2.1", "daemon": "1.1.0", - "diff": "8.0.2", + "diff": "8.0.3", "esbuild": "0.27.2", "express": "4.22.1", "express-session": "1.18.2", From 9b1c32b1845ba8e72f7329d8221349dfa0162775 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:42:41 -0500 Subject: [PATCH 816/828] fix(deps): update dependency spdx-license-list to v6.11.0 (#13890) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 32ccd06600..30e9433b4e 100644 --- a/install/package.json +++ b/install/package.json @@ -140,7 +140,7 @@ "socket.io-client": "4.8.3", "@socket.io/redis-adapter": "8.3.0", "sortablejs": "1.15.6", - "spdx-license-list": "6.10.0", + "spdx-license-list": "6.11.0", "terser-webpack-plugin": "5.3.16", "textcomplete": "0.18.2", "textcomplete.contenteditable": "0.1.1", From a73ab8ee1e674d4b3b37ef58221b4869d5010946 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 14 Jan 2026 12:46:14 -0500 Subject: [PATCH 817/828] fix: i18n fallbacks --- public/language/ar/aria.json | 3 +- public/language/ar/error.json | 2 + public/language/ar/global.json | 1 + public/language/ar/topic.json | 6 ++ public/language/az/aria.json | 3 +- public/language/az/error.json | 2 + public/language/az/global.json | 1 + public/language/az/topic.json | 6 ++ public/language/bg/aria.json | 3 +- public/language/bg/error.json | 2 + public/language/bg/global.json | 1 + public/language/bg/topic.json | 6 ++ public/language/bn/aria.json | 3 +- public/language/bn/error.json | 2 + public/language/bn/global.json | 1 + public/language/bn/topic.json | 6 ++ public/language/cs/aria.json | 15 +-- public/language/cs/error.json | 64 +++++------ public/language/cs/global.json | 27 ++--- public/language/cs/topic.json | 138 ++++++++++++------------ public/language/da/aria.json | 3 +- public/language/da/error.json | 2 + public/language/da/global.json | 1 + public/language/da/topic.json | 6 ++ public/language/de/aria.json | 3 +- public/language/de/error.json | 2 + public/language/de/global.json | 1 + public/language/de/topic.json | 6 ++ public/language/el/aria.json | 3 +- public/language/el/error.json | 2 + public/language/el/global.json | 1 + public/language/el/topic.json | 6 ++ public/language/en-US/aria.json | 3 +- public/language/en-US/error.json | 2 + public/language/en-US/global.json | 1 + public/language/en-US/topic.json | 6 ++ public/language/en-x-pirate/aria.json | 3 +- public/language/en-x-pirate/error.json | 2 + public/language/en-x-pirate/global.json | 1 + public/language/en-x-pirate/topic.json | 6 ++ public/language/es/aria.json | 3 +- public/language/es/error.json | 2 + public/language/es/global.json | 1 + public/language/es/topic.json | 6 ++ public/language/et/aria.json | 3 +- public/language/et/error.json | 2 + public/language/et/global.json | 1 + public/language/et/topic.json | 6 ++ public/language/fa-IR/aria.json | 3 +- public/language/fa-IR/error.json | 2 + public/language/fa-IR/global.json | 1 + public/language/fa-IR/topic.json | 6 ++ public/language/fi/aria.json | 3 +- public/language/fi/error.json | 2 + public/language/fi/global.json | 1 + public/language/fi/topic.json | 6 ++ public/language/fr/aria.json | 5 +- public/language/fr/error.json | 8 +- public/language/fr/global.json | 7 +- public/language/fr/topic.json | 16 ++- public/language/gl/aria.json | 3 +- public/language/gl/error.json | 2 + public/language/gl/global.json | 1 + public/language/gl/topic.json | 6 ++ public/language/he/aria.json | 3 +- public/language/he/error.json | 2 + public/language/he/global.json | 1 + public/language/he/topic.json | 6 ++ public/language/hr/aria.json | 3 +- public/language/hr/error.json | 2 + public/language/hr/global.json | 1 + public/language/hr/topic.json | 6 ++ public/language/hu/aria.json | 3 +- public/language/hu/error.json | 2 + public/language/hu/global.json | 1 + public/language/hu/topic.json | 6 ++ public/language/hy/aria.json | 3 +- public/language/hy/error.json | 2 + public/language/hy/global.json | 1 + public/language/hy/topic.json | 6 ++ public/language/id/aria.json | 3 +- public/language/id/error.json | 2 + public/language/id/global.json | 1 + public/language/id/topic.json | 6 ++ public/language/it/aria.json | 3 +- public/language/it/error.json | 2 + public/language/it/global.json | 1 + public/language/it/topic.json | 6 ++ public/language/ja/aria.json | 3 +- public/language/ja/error.json | 2 + public/language/ja/global.json | 1 + public/language/ja/topic.json | 6 ++ public/language/ko/aria.json | 3 +- public/language/ko/error.json | 2 + public/language/ko/global.json | 1 + public/language/ko/topic.json | 6 ++ public/language/lt/aria.json | 3 +- public/language/lt/error.json | 2 + public/language/lt/global.json | 1 + public/language/lt/topic.json | 6 ++ public/language/lv/aria.json | 3 +- public/language/lv/error.json | 2 + public/language/lv/global.json | 1 + public/language/lv/topic.json | 6 ++ public/language/ms/aria.json | 3 +- public/language/ms/error.json | 2 + public/language/ms/global.json | 1 + public/language/ms/topic.json | 6 ++ public/language/nb/aria.json | 3 +- public/language/nb/error.json | 2 + public/language/nb/global.json | 1 + public/language/nb/topic.json | 6 ++ public/language/nl/aria.json | 3 +- public/language/nl/error.json | 2 + public/language/nl/global.json | 1 + public/language/nl/topic.json | 6 ++ public/language/nn-NO/aria.json | 3 +- public/language/nn-NO/error.json | 2 + public/language/nn-NO/global.json | 1 + public/language/nn-NO/topic.json | 6 ++ public/language/pl/aria.json | 3 +- public/language/pl/error.json | 2 + public/language/pl/global.json | 1 + public/language/pl/topic.json | 6 ++ public/language/pt-BR/aria.json | 3 +- public/language/pt-BR/error.json | 2 + public/language/pt-BR/global.json | 1 + public/language/pt-BR/topic.json | 6 ++ public/language/pt-PT/aria.json | 3 +- public/language/pt-PT/error.json | 2 + public/language/pt-PT/global.json | 1 + public/language/pt-PT/topic.json | 6 ++ public/language/ro/aria.json | 3 +- public/language/ro/error.json | 2 + public/language/ro/global.json | 1 + public/language/ro/topic.json | 6 ++ public/language/ru/aria.json | 3 +- public/language/ru/error.json | 2 + public/language/ru/global.json | 1 + public/language/ru/topic.json | 6 ++ public/language/rw/aria.json | 3 +- public/language/rw/error.json | 2 + public/language/rw/global.json | 1 + public/language/rw/topic.json | 6 ++ public/language/sc/aria.json | 3 +- public/language/sc/error.json | 2 + public/language/sc/global.json | 1 + public/language/sc/topic.json | 6 ++ public/language/sk/aria.json | 3 +- public/language/sk/error.json | 2 + public/language/sk/global.json | 1 + public/language/sk/topic.json | 6 ++ public/language/sl/aria.json | 3 +- public/language/sl/error.json | 2 + public/language/sl/global.json | 1 + public/language/sl/topic.json | 6 ++ public/language/sq-AL/aria.json | 3 +- public/language/sq-AL/error.json | 2 + public/language/sq-AL/global.json | 1 + public/language/sq-AL/topic.json | 6 ++ public/language/sr/aria.json | 3 +- public/language/sr/error.json | 2 + public/language/sr/global.json | 1 + public/language/sr/topic.json | 6 ++ public/language/sv/aria.json | 3 +- public/language/sv/error.json | 2 + public/language/sv/global.json | 1 + public/language/sv/topic.json | 6 ++ public/language/th/aria.json | 3 +- public/language/th/error.json | 2 + public/language/th/global.json | 1 + public/language/th/topic.json | 6 ++ public/language/tr/aria.json | 3 +- public/language/tr/error.json | 2 + public/language/tr/global.json | 1 + public/language/tr/topic.json | 6 ++ public/language/uk/aria.json | 3 +- public/language/uk/error.json | 2 + public/language/uk/global.json | 1 + public/language/uk/topic.json | 6 ++ public/language/ur/aria.json | 3 +- public/language/ur/error.json | 2 + public/language/ur/global.json | 1 + public/language/ur/topic.json | 6 ++ public/language/vi/aria.json | 3 +- public/language/vi/error.json | 2 + public/language/vi/global.json | 1 + public/language/vi/topic.json | 6 ++ public/language/zh-CN/aria.json | 3 +- public/language/zh-CN/error.json | 2 + public/language/zh-CN/global.json | 1 + public/language/zh-CN/topic.json | 6 ++ public/language/zh-TW/aria.json | 3 +- public/language/zh-TW/error.json | 2 + public/language/zh-TW/global.json | 1 + public/language/zh-TW/topic.json | 6 ++ 196 files changed, 667 insertions(+), 177 deletions(-) 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/error.json b/public/language/ar/error.json index 4d2ea94cba..9d0edec4bd 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -147,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": "ملف غير مقبول", @@ -228,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", diff --git a/public/language/ar/global.json b/public/language/ar/global.json index 5790feee44..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", diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json index fffdfd022c..1f5d03920f 100644 --- a/public/language/ar/topic.json +++ b/public/language/ar/topic.json @@ -103,6 +103,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 +133,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 +143,7 @@ "loading-more-posts": "تحميل المزيد من المشاركات", "move-topic": "نقل الموضوع", "move-topics": "نقل المواضيع", + "crosspost-topic": "Cross-post Topic", "move-post": "نقل المشاركة", "post-moved": "تم نقل المشاركة", "fork-topic": "فرع الموضوع", @@ -163,6 +166,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", 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/error.json b/public/language/az/error.json index 62ca9934ff..37d0761e28 100644 --- a/public/language/az/error.json +++ b/public/language/az/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/az/global.json b/public/language/az/global.json index 9430259eb9..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", diff --git a/public/language/az/topic.json b/public/language/az/topic.json index d8993f4098..5a856f5992 100644 --- a/public/language/az/topic.json +++ b/public/language/az/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/bg/error.json index 94d12a36b1..45bdc6a503 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -147,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": "Грешен файл", @@ -228,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": "Гостите не могат да блокират други потребители", diff --git a/public/language/bg/global.json b/public/language/bg/global.json index fefc17cb77..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 репутация", diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json index 35a1958077..baa01a79d9 100644 --- a/public/language/bg/topic.json +++ b/public/language/bg/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "Ако желаете, тук можете да посочите дата на давност за закачените теми. Можете и да оставите полето празно, при което темата ще остане закачена, докато не бъде откачена ръчно.", "load-categories": "Зареждане на категориите", "confirm-move": "Преместване", + "confirm-crosspost": "Cross-post", "confirm-fork": "Разделяне", "bookmark": "Отметка", "bookmarks": "Отметки", @@ -141,6 +143,7 @@ "loading-more-posts": "Зареждане на още публикации", "move-topic": "Преместване на темата", "move-topics": "Преместване на темите", + "crosspost-topic": "Cross-post Topic", "move-post": "Преместване на публикацията", "post-moved": "Публикацията беше преместена!", "fork-topic": "Разделяне на темата", @@ -163,6 +166,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": "Скриване", 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/error.json b/public/language/bn/error.json index 3dfb852227..b0466f064c 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -147,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": "ভুল ফাইল", @@ -228,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", diff --git a/public/language/bn/global.json b/public/language/bn/global.json index df59f05d33..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", diff --git a/public/language/bn/topic.json b/public/language/bn/topic.json index 6a89846c81..4ee9c37159 100644 --- a/public/language/bn/topic.json +++ b/public/language/bn/topic.json @@ -103,6 +103,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 +133,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 +143,7 @@ "loading-more-posts": "আরো পোষ্ট লোড করা হচ্ছে", "move-topic": "টপিক সরান", "move-topics": "টপিক সমূহ সরান", + "crosspost-topic": "Cross-post Topic", "move-post": "পোষ্ট সরান", "post-moved": "পোষ্ট সরানো হয়েছে", "fork-topic": "টপিক ফর্ক করুন", @@ -163,6 +166,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", 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/error.json b/public/language/cs/error.json index 4295b7d97a..5e8ea072e0 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -3,7 +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.", + "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.", @@ -33,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.", @@ -68,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í", @@ -93,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ů", @@ -147,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", @@ -155,9 +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-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-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", @@ -172,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", @@ -186,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", @@ -228,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.", @@ -235,14 +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", - "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin", - "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", @@ -256,11 +258,11 @@ "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 e02fc73a56..a69cd6e1de 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -24,15 +24,15 @@ "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ší", @@ -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,7 +83,7 @@ "downvoted": "Nesouhlasů", "views": "Zobrazení", "posters": "Přispěvatelé", - "watching": "Watching", + "watching": "Sleduji", "reputation": "Reputace", "lastpost": "Poslední příspěvek", "firstpost": "První příspěvek", @@ -112,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", @@ -143,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": "Ř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/topic.json b/public/language/cs/topic.json index 0d788c1eec..a84be0982a 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,54 +15,54 @@ "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", @@ -103,10 +103,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 +119,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 +133,53 @@ "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": "Zkopírovat odkaz", - "go-to-original": "View Original Post", + "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.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.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", @@ -203,26 +209,26 @@ "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)" } \ 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/error.json b/public/language/da/error.json index 9418e8663c..5be58578d4 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/da/global.json b/public/language/da/global.json index 1530a7cd55..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", diff --git a/public/language/da/topic.json b/public/language/da/topic.json index 4b3720e0ca..7bb88e5473 100644 --- a/public/language/da/topic.json +++ b/public/language/da/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/de/error.json index b292fdf3f1..27ce16174c 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -147,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", @@ -228,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.", diff --git a/public/language/de/global.json b/public/language/de/global.json index 47604413e5..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", diff --git a/public/language/de/topic.json b/public/language/de/topic.json index 67a22712f3..7ddf907569 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -103,6 +103,7 @@ "thread-tools.lock": "Thema schließen", "thread-tools.unlock": "Thema öffnen", "thread-tools.move": "Thema verschieben", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Beiträge verschieben", "thread-tools.move-all": "Alle verschieben", "thread-tools.change-owner": "Besitzer ändern", @@ -132,6 +133,7 @@ "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", @@ -141,6 +143,7 @@ "loading-more-posts": "Lade mehr Beiträge", "move-topic": "Thema verschieben", "move-topics": "Themen verschieben", + "crosspost-topic": "Cross-post Topic", "move-post": "Beitrag verschieben", "post-moved": "Beitrag wurde verschoben!", "fork-topic": "Thema aufspalten", @@ -163,6 +166,9 @@ "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.", + "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": "Hier den Titel des Themas eingeben...", "composer.handle-placeholder": "Gib deinen Namen/Nick hier ein", "composer.hide": "Verstecken", 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/error.json b/public/language/el/error.json index 70bedd30c8..f8eb99ac4f 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -147,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": "Άκυρο Αρχείο", @@ -228,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", diff --git a/public/language/el/global.json b/public/language/el/global.json index 1fa9b54024..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", diff --git a/public/language/el/topic.json b/public/language/el/topic.json index d7acb0c3ae..783bf09fb3 100644 --- a/public/language/el/topic.json +++ b/public/language/el/topic.json @@ -103,6 +103,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 +133,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 +143,7 @@ "loading-more-posts": "Φόρτωση περισσότερων δημοσιεύσεων", "move-topic": "Μετακίνηση Θέματος", "move-topics": "Μετακίνηση Θεμάτων", + "crosspost-topic": "Cross-post Topic", "move-post": "Μετακίνηση Δημοσίευσης", "post-moved": "Η δημοσίευση μετακινήθηκε!", "fork-topic": "Διαχωρισμός Θέματος", @@ -163,6 +166,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", 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/error.json b/public/language/en-US/error.json index c3bb2dc892..ea28a9a51c 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/en-US/global.json b/public/language/en-US/global.json index 71b4d886e3..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", diff --git a/public/language/en-US/topic.json b/public/language/en-US/topic.json index 25ff8a6dc9..42c3c17e66 100644 --- a/public/language/en-US/topic.json +++ b/public/language/en-US/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/en-x-pirate/error.json index c3bb2dc892..ea28a9a51c 100644 --- a/public/language/en-x-pirate/error.json +++ b/public/language/en-x-pirate/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/en-x-pirate/global.json b/public/language/en-x-pirate/global.json index 00e084c8c6..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", diff --git a/public/language/en-x-pirate/topic.json b/public/language/en-x-pirate/topic.json index 25ff8a6dc9..42c3c17e66 100644 --- a/public/language/en-x-pirate/topic.json +++ b/public/language/en-x-pirate/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/es/error.json index 27a8776929..8cc79669d9 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/es/global.json b/public/language/es/global.json index 79a12f7364..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", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index 440bc2254a..10fac46e19 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/et/error.json index 7fd4027cec..b19da887ce 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/et/global.json b/public/language/et/global.json index 4d5ec65539..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", diff --git a/public/language/et/topic.json b/public/language/et/topic.json index a1cede1835..8dce5b3405 100644 --- a/public/language/et/topic.json +++ b/public/language/et/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/fa-IR/error.json index 7660c66770..75e694bfda 100644 --- a/public/language/fa-IR/error.json +++ b/public/language/fa-IR/error.json @@ -147,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": "فایل نامعتبر است.", @@ -228,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", diff --git a/public/language/fa-IR/global.json b/public/language/fa-IR/global.json index 37cae2906a..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", diff --git a/public/language/fa-IR/topic.json b/public/language/fa-IR/topic.json index cb940c2db5..c1a12ef12a 100644 --- a/public/language/fa-IR/topic.json +++ b/public/language/fa-IR/topic.json @@ -103,6 +103,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 +133,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 +143,7 @@ "loading-more-posts": "بارگذاری پست‌های بیش‌تر", "move-topic": "جابه‌جایی موضوع", "move-topics": "انتقال موضوع", + "crosspost-topic": "Cross-post Topic", "move-post": "جابه‌جایی موضوع", "post-moved": "پست جابه‌جا شد!", "fork-topic": "شاخه ساختن از موضوع", @@ -163,6 +166,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": "پیش نویس", 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/error.json b/public/language/fi/error.json index d7d1af89ea..1d94258881 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/fi/global.json b/public/language/fi/global.json index 2d4cf5edb8..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", diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json index 0ebb73285d..ccfcc5d418 100644 --- a/public/language/fi/topic.json +++ b/public/language/fi/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/fr/error.json index d22c59af08..1000046ef5 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -68,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é.", @@ -147,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", @@ -228,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", @@ -256,7 +258,7 @@ "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 ef41d31500..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,7 +83,7 @@ "downvoted": "Vote(s) négatif(s)", "views": "Vues", "posters": "Publieurs", - "watching": "Watching", + "watching": "Abonné", "reputation": "Réputation", "lastpost": "Dernier message", "firstpost": "Premier message", @@ -112,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/topic.json b/public/language/fr/topic.json index 35fe9b1dc4..6a28ab9fec 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -27,7 +27,7 @@ "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", @@ -103,10 +103,11 @@ "thread-tools.lock": "Verrouiller le sujet", "thread-tools.unlock": "Déverouiller le sujet", "thread-tools.move": "Déplacer le sujet", + "thread-tools.crosspost": "Crosspost Topic", "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 +133,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": "Cross-post", "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": "Cross-post Topic", "move-post": "Déplacer", "post-moved": "Message déplacé !", "fork-topic": "Scinder le sujet", @@ -163,6 +166,9 @@ "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.", + "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": "Entrer le titre du sujet ici…", "composer.handle-placeholder": "Entrez votre nom/identifiant ici", "composer.hide": "Cacher", @@ -223,6 +229,6 @@ "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": "Partager", + "announcers-x": "Partages (%1)" } \ 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/error.json b/public/language/gl/error.json index 7669c41b8f..dc3e5bff65 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/gl/global.json b/public/language/gl/global.json index 3d966b4b34..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", diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json index 1422a1cd53..d29158105c 100644 --- a/public/language/gl/topic.json +++ b/public/language/gl/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/he/error.json index a492795106..8ede669d3e 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -147,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": "קובץ לא תקין", @@ -228,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": "אורחים אינם יכולים לחסום משתמשים אחרים", diff --git a/public/language/he/global.json b/public/language/he/global.json index 7ec0571727..884b522e0c 100644 --- a/public/language/he/global.json +++ b/public/language/he/global.json @@ -68,6 +68,7 @@ "users": "משתמשים", "topics": "נושאים", "posts": "פוסטים", + "crossposts": "Cross-posts", "x-posts": "%1 פוסטים", "x-topics": "%1 נושאים", "x-reputation": "%1 מוניטין", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 9e479a2da8..f63e796f95 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "באפשרותכם להגדיר כאן תאריך תפוגה לנושאים המוצמדים. לחלופין, ביכולתכם להשאיר שדה זו ריקה, כדי שהנושא יישאר נעוץ עד לביטול ההצמדה ידנית.", "load-categories": "טוען קטגוריות", "confirm-move": "העברה", + "confirm-crosspost": "Cross-post", "confirm-fork": "פיצול", "bookmark": "הוספה למועדפים", "bookmarks": "מועדפים", @@ -141,6 +143,7 @@ "loading-more-posts": "טוען פוסטים נוספים", "move-topic": "העברת נושא", "move-topics": "העברת נושאים", + "crosspost-topic": "Cross-post Topic", "move-post": "העבר פוסט", "post-moved": "הפוסט הועבר!", "fork-topic": "פיצול נושא", @@ -163,6 +166,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": "הסתרה", 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/error.json b/public/language/hr/error.json index 4e2d576d5c..c4f777e0aa 100644 --- a/public/language/hr/error.json +++ b/public/language/hr/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/hr/global.json b/public/language/hr/global.json index 20c3fd9b17..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", diff --git a/public/language/hr/topic.json b/public/language/hr/topic.json index fdf37e5614..b57a83c3bd 100644 --- a/public/language/hr/topic.json +++ b/public/language/hr/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/hu/error.json index e337533e57..859581ec70 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/hu/global.json b/public/language/hu/global.json index 09f66b03df..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", diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json index 9e887e5264..dfad41e707 100644 --- a/public/language/hu/topic.json +++ b/public/language/hu/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/hy/error.json index e6c4a3c18b..29adcb6fe0 100644 --- a/public/language/hy/error.json +++ b/public/language/hy/error.json @@ -147,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": "Անվավեր ֆայլ", @@ -228,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": "Հյուրը չի կարող արգելափակել այլ օգտատերին", diff --git a/public/language/hy/global.json b/public/language/hy/global.json index f3a6813f6b..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 հեղինակություն", diff --git a/public/language/hy/topic.json b/public/language/hy/topic.json index fc8d08db95..1fe2851d2f 100644 --- a/public/language/hy/topic.json +++ b/public/language/hy/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "Դուք կարող եք ըստ ցանկության սահմանել ամրացված թեմայի (թեմայի) պիտանելիության ժամկետը այստեղ: Որպես այլընտրանք, դուք կարող եք թողնել այս դաշտը դատարկ, որպեսզի թեման մնա ամրացված, մինչև այն ձեռքով չապամրացվի:", "load-categories": "Կատեգորիաների բեռնում", "confirm-move": "Տեղափոխել", + "confirm-crosspost": "Cross-post", "confirm-fork": "Մասնատել", "bookmark": "Էջանիշ", "bookmarks": "Էջանիշեր", @@ -141,6 +143,7 @@ "loading-more-posts": "Լրացուցիչ գրառումների բեռնում", "move-topic": "Տեղափոխել թեման", "move-topics": "Տեղափոխել թեմաները", + "crosspost-topic": "Cross-post Topic", "move-post": "Տեղափոխել գրառումը", "post-moved": "Գրառումը տեղափոխված է։", "fork-topic": "Մասնատել թեման", @@ -163,6 +166,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": "Թաքցնել", 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/error.json b/public/language/id/error.json index 21d344ce72..314ef3bfbd 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/id/global.json b/public/language/id/global.json index 65c9c601c9..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", diff --git a/public/language/id/topic.json b/public/language/id/topic.json index 7483052649..599fd67b52 100644 --- a/public/language/id/topic.json +++ b/public/language/id/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/it/error.json index 1c4a16adf9..aac05e3ae5 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -147,6 +147,7 @@ "post-already-restored": "Questo post è già stato ripristinato", "topic-already-deleted": "Questa discussione è già stata eliminata", "topic-already-restored": "Questa discussione è già stata ripristinata", + "topic-already-crossposted": "This topic has already been cross-posted there.", "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", @@ -228,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": "You cannot move topics in or out of remote categories; consider cross-posting instead.", "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", diff --git a/public/language/it/global.json b/public/language/it/global.json index 23c607afac..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", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index 35a3644c44..96f61aa439 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -103,6 +103,7 @@ "thread-tools.lock": "Blocca Discussione", "thread-tools.unlock": "Sblocca Discussione", "thread-tools.move": "Sposta Discussione", + "thread-tools.crosspost": "Crosspost Topic", "thread-tools.move-posts": "Sposta Post", "thread-tools.move-all": "Sposta Tutto", "thread-tools.change-owner": "Cambia proprietario", @@ -132,6 +133,7 @@ "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": "Cross-post", "confirm-fork": "Dividi", "bookmark": "Favorito", "bookmarks": "Segnalibri", @@ -141,6 +143,7 @@ "loading-more-posts": "Caricamento altri post", "move-topic": "Sposta Discussione", "move-topics": "Sposta Discussioni", + "crosspost-topic": "Cross-post Topic", "move-post": "Sposta Post", "post-moved": "Post spostato!", "fork-topic": "Dividi Discussione", @@ -163,6 +166,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": "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": "Inserisci qui il titolo della discussione...", "composer.handle-placeholder": "Inserisci qui il tuo nome/pseudonimo", "composer.hide": "Nascondi", 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/error.json b/public/language/ja/error.json index be0b4396d1..130607f24a 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -147,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": "無効なファイル", @@ -228,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": "ゲストは他のユーザーをブロックできません", diff --git a/public/language/ja/global.json b/public/language/ja/global.json index eb6ce87591..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", diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index b05507a7f1..895440aeea 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -103,6 +103,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 +133,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 +143,7 @@ "loading-more-posts": "もっと見る", "move-topic": "スレッドを移動", "move-topics": "スレッドを移動する", + "crosspost-topic": "Cross-post Topic", "move-post": "投稿を移動", "post-moved": "投稿を移動しました!", "fork-topic": "スレッドをフォーク", @@ -163,6 +166,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", 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/error.json b/public/language/ko/error.json index 80ee9f08e5..8b950fadc8 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -147,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": "잘못된 파일", @@ -228,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": "비회원는 다른 사용자를 차단할 수 없습니다", diff --git a/public/language/ko/global.json b/public/language/ko/global.json index f58db1beb3..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 평판", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index f626bea9d8..f943483ad1 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "여기에서 고정된 토픽에 대한 만료일을 선택적으로 설정할 수 있습니다. 또는 토픽이 수동으로 고정 해제될 때까지 이 필드를 비워 둘 수도 있습니다.", "load-categories": "카테고리 로드 중", "confirm-move": "이동", + "confirm-crosspost": "Cross-post", "confirm-fork": "포크", "bookmark": "북마크", "bookmarks": "북마크", @@ -141,6 +143,7 @@ "loading-more-posts": "게시물 더 불러오는 중", "move-topic": "토픽 이동", "move-topics": "토픽 이동", + "crosspost-topic": "Cross-post Topic", "move-post": "게시물 이동", "post-moved": "게시물이 이동되었습니다!", "fork-topic": "토픽 포크", @@ -163,6 +166,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": "숨기기", 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/error.json b/public/language/lt/error.json index 5fbf1188c4..fd7ae6e0db 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/lt/global.json b/public/language/lt/global.json index 12e8cb089f..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", diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json index 4146e1bce6..b551ce6eb2 100644 --- a/public/language/lt/topic.json +++ b/public/language/lt/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/lv/error.json index 6195541875..76f62525c8 100644 --- a/public/language/lv/error.json +++ b/public/language/lv/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/lv/global.json b/public/language/lv/global.json index 85f1c6f3d2..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", diff --git a/public/language/lv/topic.json b/public/language/lv/topic.json index d55b6c985c..e20f510da6 100644 --- a/public/language/lv/topic.json +++ b/public/language/lv/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/ms/error.json index 26e1b5a310..fac32a33a4 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/ms/global.json b/public/language/ms/global.json index e0466de405..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", diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json index 0493a3172b..11e3dd2e2a 100644 --- a/public/language/ms/topic.json +++ b/public/language/ms/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/nb/error.json index a497b4f446..c795bb4168 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/nb/global.json b/public/language/nb/global.json index 6e48052df8..54b9f793ab 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -68,6 +68,7 @@ "users": "Brukere", "topics": "Emner", "posts": "Innlegg", + "crossposts": "Cross-posts", "x-posts": "%1 innlegg", "x-topics": "%1 emner", "x-reputation": "%1 omdømme", diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 81f986bc40..8e7ccf54df 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -103,6 +103,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 +133,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,6 +143,7 @@ "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", @@ -163,6 +166,9 @@ "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", 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/error.json b/public/language/nl/error.json index 662a161197..6436fd1251 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/nl/global.json b/public/language/nl/global.json index 0e4fc76a89..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", diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index ff3e53c91a..a4ead8ccd1 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/nn-NO/error.json index c25a7f8cb2..c9c43c0299 100644 --- a/public/language/nn-NO/error.json +++ b/public/language/nn-NO/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/nn-NO/global.json b/public/language/nn-NO/global.json index 73fa0477eb..383dc061ab 100644 --- a/public/language/nn-NO/global.json +++ b/public/language/nn-NO/global.json @@ -68,6 +68,7 @@ "users": "Brukarar", "topics": "Emne", "posts": "Innlegg", + "crossposts": "Cross-posts", "x-posts": "%1 innlegg", "x-topics": "%1 emne", "x-reputation": "%1 omdømme", diff --git a/public/language/nn-NO/topic.json b/public/language/nn-NO/topic.json index 14f21172dd..1e757aa136 100644 --- a/public/language/nn-NO/topic.json +++ b/public/language/nn-NO/topic.json @@ -103,6 +103,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 +133,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,6 +143,7 @@ "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", @@ -163,6 +166,9 @@ "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", 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/error.json b/public/language/pl/error.json index 27cb6c9d42..f42a3cfdbb 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/pl/global.json b/public/language/pl/global.json index d6b71fd3d3..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", diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 4311bb4586..7817083a3a 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/pt-BR/error.json index 2fc1039862..d97687f27f 100644 --- a/public/language/pt-BR/error.json +++ b/public/language/pt-BR/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/pt-BR/global.json b/public/language/pt-BR/global.json index d3b69cd790..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", diff --git a/public/language/pt-BR/topic.json b/public/language/pt-BR/topic.json index 42c79c8bd0..785decb74c 100644 --- a/public/language/pt-BR/topic.json +++ b/public/language/pt-BR/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/pt-PT/error.json index fa0418c41c..7efe48d837 100644 --- a/public/language/pt-PT/error.json +++ b/public/language/pt-PT/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/pt-PT/global.json b/public/language/pt-PT/global.json index 80fd97f2f3..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", diff --git a/public/language/pt-PT/topic.json b/public/language/pt-PT/topic.json index 535f260664..b32e1a9889 100644 --- a/public/language/pt-PT/topic.json +++ b/public/language/pt-PT/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/ro/error.json index abc63f3b06..837bbfc82f 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/ro/global.json b/public/language/ro/global.json index c446072424..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", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index 30691b771b..b52960064a 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -103,6 +103,7 @@ "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", @@ -132,6 +133,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": "Se Încarcă Categoriile", "confirm-move": "Mută", + "confirm-crosspost": "Cross-post", "confirm-fork": "Bifurcă", "bookmark": "Bookmark", "bookmarks": "Bookmarks", @@ -141,6 +143,7 @@ "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", @@ -163,6 +166,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": "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", 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/error.json b/public/language/ru/error.json index d741a098a1..23a4344b14 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -147,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": "Некорректный файл", @@ -228,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": "Гости не могут блокировать пользователей", diff --git a/public/language/ru/global.json b/public/language/ru/global.json index d4381e5558..da1bcc699c 100644 --- a/public/language/ru/global.json +++ b/public/language/ru/global.json @@ -68,6 +68,7 @@ "users": "Пользователи", "topics": "Темы", "posts": "Сообщения", + "crossposts": "Cross-posts", "x-posts": "%1 posts", "x-topics": "%1 topics", "x-reputation": "%1 reputation", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index af877fb070..40153c951d 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "При желании вы можете установить дату истечения срока для закрепленных тем здесь. Кроме того, вы можете оставить это поле пустым, чтобы тема оставалась закрепленной до тех пор, пока она не будет откреплена вручную.", "load-categories": "Загружаем категории", "confirm-move": "Перенести", + "confirm-crosspost": "Cross-post", "confirm-fork": "Разделить", "bookmark": "Добавить в закладки", "bookmarks": "Закладки", @@ -141,6 +143,7 @@ "loading-more-posts": "Загружаем больше сообщений", "move-topic": "Перенести тему", "move-topics": "Перенести темы", + "crosspost-topic": "Cross-post Topic", "move-post": "Перенести сообщение", "post-moved": "Сообщение перенесено!", "fork-topic": "Создать дополнительную ветвь дискуссии", @@ -163,6 +166,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": "Скрыть", 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/error.json b/public/language/rw/error.json index 0452b4fc11..4937ae160a 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/rw/global.json b/public/language/rw/global.json index c1f710d15a..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", diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json index ffc4c4084f..cc9f436d2c 100644 --- a/public/language/rw/topic.json +++ b/public/language/rw/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/sc/error.json index c3bb2dc892..ea28a9a51c 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/sc/global.json b/public/language/sc/global.json index 943d34ccbe..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", diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json index 36b50ac704..37cc4037ad 100644 --- a/public/language/sc/topic.json +++ b/public/language/sc/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/sk/error.json index cd21ec05a3..1ce1df745b 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/sk/global.json b/public/language/sk/global.json index f8f1af5516..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", diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json index 1f20e2e431..7323338171 100644 --- a/public/language/sk/topic.json +++ b/public/language/sk/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/sl/error.json index ac9775d30b..f1ce1ae7ea 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/sl/global.json b/public/language/sl/global.json index c26caab00c..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", diff --git a/public/language/sl/topic.json b/public/language/sl/topic.json index 18861a85ce..02ec954c1d 100644 --- a/public/language/sl/topic.json +++ b/public/language/sl/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/sq-AL/error.json index b1aab456a0..b5e7ead539 100644 --- a/public/language/sq-AL/error.json +++ b/public/language/sq-AL/error.json @@ -147,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", @@ -228,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ë", diff --git a/public/language/sq-AL/global.json b/public/language/sq-AL/global.json index 89ff10d050..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", diff --git a/public/language/sq-AL/topic.json b/public/language/sq-AL/topic.json index d3313d7967..a4aeccae0a 100644 --- a/public/language/sq-AL/topic.json +++ b/public/language/sq-AL/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/sr/error.json index ec8ef67b20..28eaae07cc 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -147,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": "Неисправна датотека", @@ -228,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": "Гости нису у могућности да блокирају друге кориснике", diff --git a/public/language/sr/global.json b/public/language/sr/global.json index 7be67acafd..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 угледа", diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json index 27775992d9..18fd58fd83 100644 --- a/public/language/sr/topic.json +++ b/public/language/sr/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "Овде можете по жељи да одредите датум истека закачених тема. Можете и да ово поље оставите празно да би тема остала закачена док се ручно не откачи.", "load-categories": "Учитавање категорија", "confirm-move": "Премести", + "confirm-crosspost": "Cross-post", "confirm-fork": "Раздвоји", "bookmark": "Обележивач", "bookmarks": "Обележивачи", @@ -141,6 +143,7 @@ "loading-more-posts": "Учитавање још порука", "move-topic": "Премести тему", "move-topics": "Премести теме", + "crosspost-topic": "Cross-post Topic", "move-post": "Премести поруку", "post-moved": "Порука је премештена!", "fork-topic": "Раздвоји тему", @@ -163,6 +166,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": "Сакриј", 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/error.json b/public/language/sv/error.json index 19a3580440..ee85209229 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/sv/global.json b/public/language/sv/global.json index 2d89399d59..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", diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json index 483b26e254..1097c11638 100644 --- a/public/language/sv/topic.json +++ b/public/language/sv/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/th/error.json index 529a74d404..ad793ee21f 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -147,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": "ไฟล์ไม่ถูกต้อง", @@ -228,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": "ผู้มาเยือนไม่สามารถบล็อกผู้ใช้งานอื่น", diff --git a/public/language/th/global.json b/public/language/th/global.json index 554d2ad47c..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", diff --git a/public/language/th/topic.json b/public/language/th/topic.json index 6d4e92b082..5562e20295 100644 --- a/public/language/th/topic.json +++ b/public/language/th/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "คุณสามารถเลือกจะตั้งค่าวันหมดอายุสำหรับกระทู้ปักหมุดที่นี่ คูณยังสามารถปล่อยให้ฟิลด์นี้ว่างเพื่อให้กระทู้ยังคงถูกปักหมดจนกว่าจะยกเลิกด้วยมือ", "load-categories": "กำลังโหลดหมวดหมู่", "confirm-move": "ย้าย", + "confirm-crosspost": "Cross-post", "confirm-fork": "แยก", "bookmark": "บุ๊กมาร์ก", "bookmarks": "บุ๊กมาร์ก", @@ -141,6 +143,7 @@ "loading-more-posts": "โหลดโพสเพิ่มเติม", "move-topic": "ย้ายกระทู้", "move-topics": "ย้ายกระทู้", + "crosspost-topic": "Cross-post Topic", "move-post": "ย้ายโพส", "post-moved": "โพสต์ถูกย้ายแล้ว!", "fork-topic": "แยกกระทู้", @@ -163,6 +166,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": "ซ่อน", 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/error.json b/public/language/tr/error.json index ea93e9abe6..46464288ad 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -147,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", @@ -228,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", diff --git a/public/language/tr/global.json b/public/language/tr/global.json index 4aa5ebf25d..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", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index d423571f7e..0bddc42542 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -103,6 +103,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 +133,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 +143,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 +166,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", 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/error.json b/public/language/uk/error.json index 002b6a8471..0a9bb8c3f7 100644 --- a/public/language/uk/error.json +++ b/public/language/uk/error.json @@ -147,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": "Невірний файл", @@ -228,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": "Гості не можуть блокувати інших користувачів", diff --git a/public/language/uk/global.json b/public/language/uk/global.json index d5c11eabe8..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", diff --git a/public/language/uk/topic.json b/public/language/uk/topic.json index d7648e7b15..70128938ef 100644 --- a/public/language/uk/topic.json +++ b/public/language/uk/topic.json @@ -103,6 +103,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 +133,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 +143,7 @@ "loading-more-posts": "Завантажуємо більше постів", "move-topic": "Перемістити тему", "move-topics": "Перемістити теми", + "crosspost-topic": "Cross-post Topic", "move-post": "Перемістити пост", "post-moved": "Пост переміщено!", "fork-topic": "Відгалужити тему", @@ -163,6 +166,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", diff --git a/public/language/ur/aria.json b/public/language/ur/aria.json index 04f277985d..93035daebb 100644 --- a/public/language/ur/aria.json +++ b/public/language/ur/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/ur/error.json b/public/language/ur/error.json index 21e92491e6..29de1b061f 100644 --- a/public/language/ur/error.json +++ b/public/language/ur/error.json @@ -147,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": "غلط فائل", @@ -228,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": "مہمان دوسرے صارفین کو بلاک نہیں کر سکتے", diff --git a/public/language/ur/global.json b/public/language/ur/global.json index fe2671f98d..e7932799d2 100644 --- a/public/language/ur/global.json +++ b/public/language/ur/global.json @@ -68,6 +68,7 @@ "users": "صارفین", "topics": "موضوعات", "posts": "پوسٹس", + "crossposts": "Cross-posts", "x-posts": "%1 پوسٹس", "x-topics": "%1 موضوعات", "x-reputation": "%1 ساکھ", diff --git a/public/language/ur/topic.json b/public/language/ur/topic.json index bcc6b33aae..5c6e9f7991 100644 --- a/public/language/ur/topic.json +++ b/public/language/ur/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "اگر آپ چاہیں تو یہاں پن کیے گئے موضوعات کے لیے ختم ہونے کی تاریخ بتا سکتے ہیں۔ آپ اس فیلڈ کو خالی بھی چھوڑ سکتے ہیں، اس صورت میں موضوع اس وقت تک پن رہے گا جب تک اسے دستی طور پر ان پن نہ کیا جائے۔", "load-categories": "زمرہ جات لوڈ کریں", "confirm-move": "منتقل کریں", + "confirm-crosspost": "Cross-post", "confirm-fork": "تقسیم کریں", "bookmark": "بک مارک", "bookmarks": "بک مارکس", @@ -141,6 +143,7 @@ "loading-more-posts": "مزید پوسٹس لوڈ ہو رہی ہیں", "move-topic": "موضوع منتقل کریں", "move-topics": "موضوعات منتقل کریں", + "crosspost-topic": "Cross-post Topic", "move-post": "پوسٹ منتقل کریں", "post-moved": "پوسٹ منتقل کر دی گئی!", "fork-topic": "موضوع تقسیم کریں", @@ -163,6 +166,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": "چھپائیں", 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/error.json b/public/language/vi/error.json index ec481020a1..8924d37160 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -147,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ệ", @@ -228,6 +229,7 @@ "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", diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 5a066d9b0e..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", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index ddb40f03a3..63c5f43ae1 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -103,6 +103,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,6 +133,7 @@ "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-crosspost": "Cross-post", "confirm-fork": "Chia nhánh", "bookmark": "Dấu trang", "bookmarks": "Dấu trang", @@ -141,6 +143,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ủ đề", @@ -163,6 +166,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", diff --git a/public/language/zh-CN/aria.json b/public/language/zh-CN/aria.json index 2467cf769b..44960de6dc 100644 --- a/public/language/zh-CN/aria.json +++ b/public/language/zh-CN/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/zh-CN/error.json b/public/language/zh-CN/error.json index 39fabf9c1b..0a98e71bc2 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -147,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": "无效文件", @@ -228,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": "游客无法屏蔽其他用户", diff --git a/public/language/zh-CN/global.json b/public/language/zh-CN/global.json index 0a9fbbfff8..af3507b43e 100644 --- a/public/language/zh-CN/global.json +++ b/public/language/zh-CN/global.json @@ -68,6 +68,7 @@ "users": "用户", "topics": "主题", "posts": "帖子", + "crossposts": "Cross-posts", "x-posts": "%1 个帖子", "x-topics": "%1 个主题", "x-reputation": "%1声望", diff --git a/public/language/zh-CN/topic.json b/public/language/zh-CN/topic.json index 4a68353e43..8439c99bee 100644 --- a/public/language/zh-CN/topic.json +++ b/public/language/zh-CN/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "您可以在此处选择为置顶主题设置一个失效日期。或者您也可以选择不设置,则该主题将会一直被置顶,直到管理员取消置顶。", "load-categories": "正在载入版块", "confirm-move": "移动", + "confirm-crosspost": "Cross-post", "confirm-fork": "分割", "bookmark": "书签", "bookmarks": "书签", @@ -141,6 +143,7 @@ "loading-more-posts": "正在加载更多帖子", "move-topic": "移动主题", "move-topics": "移动主题", + "crosspost-topic": "Cross-post Topic", "move-post": "移动帖子", "post-moved": "帖子已移动!", "fork-topic": "分割主题", @@ -163,6 +166,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": "隐藏", 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/error.json b/public/language/zh-TW/error.json index 14a9bcdbbb..98893b9921 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -147,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": "無效檔案", @@ -228,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": "訪客無法封鎖其他使用者", diff --git a/public/language/zh-TW/global.json b/public/language/zh-TW/global.json index d1ce88efbf..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", diff --git a/public/language/zh-TW/topic.json b/public/language/zh-TW/topic.json index 927d486876..ab27551f77 100644 --- a/public/language/zh-TW/topic.json +++ b/public/language/zh-TW/topic.json @@ -103,6 +103,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 +133,7 @@ "pin-modal-help": "您可在這裏設定置頂話題的有效日期,也可放著不動它直到被手動將置頂取消。", "load-categories": "正在載入版面", "confirm-move": "移動", + "confirm-crosspost": "Cross-post", "confirm-fork": "分割", "bookmark": "書籤", "bookmarks": "書籤", @@ -141,6 +143,7 @@ "loading-more-posts": "正在載入更多貼文", "move-topic": "移動主題", "move-topics": "移動主題", + "crosspost-topic": "Cross-post Topic", "move-post": "移動貼文", "post-moved": "回覆已移動!", "fork-topic": "分割主題", @@ -163,6 +166,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": "隱藏", From 8d6a4ed8757b942b7f0d53f0c7394e6f29259a2d Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 14 Jan 2026 17:54:33 +0000 Subject: [PATCH 818/828] chore: incrementing version number - v4.8.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 30e9433b4e..040fac1dee 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.7.2", + "version": "4.8.0", "homepage": "https://www.nodebb.org", "repository": { "type": "git", @@ -203,4 +203,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file From fe15becf17fde1185f09df9f390c9b6b7e1da885 Mon Sep 17 00:00:00 2001 From: MichaelHilton Date: Fri, 16 Jan 2026 16:13:06 +0000 Subject: [PATCH 819/828] added .devcontainer and Dockerfile --- .devcontainer/Dockerfile | 12 +++++++++++ .devcontainer/devcontainer.json | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..acf590a0d3 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm + +RUN apt-get update \ + && apt-get install -y \ + iputils-ping \ + redis-server \ + && rm -rf /var/lib/apt/lists/* + +RUN curl https://qlty.sh | /bin/bash \ + && mv ~/.qlty/bin/qlty /usr/local/bin/qlty \ + && rm -rf ~/.qlty \ + && qlty --version diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..ad24c08a78 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,38 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node +{ + "name": "NodeBB", + "build": { + "dockerfile": "Dockerfile" + }, + "postCreateCommand": "redis-server --daemonize yes", + "postStartCommand": "redis-server --daemonize yes || true", + + "forwardPorts": [4567], + "portsAttributes": { + "4567": { + "label": "NodeBB" + } + }, + + "customizations": { + "vscode": { + "extensions": [ + "42crunch.vscode-openapi", + "ms-vscode.live-server", + "redis.redis-for-vscode" + ] + } + }, + "containerEnv": { + "NODEBB_PORT": "4567", + "NODEBB_DB": "redis", + "NODEBB_REDIS_HOST": "localhost", + "NODEBB_REDIS_PORT": "6379", + "NODEBB_REDIS_PASSWORD": "", + "NODEBB_REDIS_DB": "0", + "NODEBB_ADMIN_USERNAME": "admin", + "NODEBB_ADMIN_PASSWORD": "password123!", + "NODEBB_ADMIN_EMAIL": "admin@admin.admin" + } +} From 68151808ccb5045a5f841c767d98aa243d0252dd Mon Sep 17 00:00:00 2001 From: MichaelHilton Date: Fri, 16 Jan 2026 16:13:26 +0000 Subject: [PATCH 820/828] gitignore database --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 42a1b3c705..c965816b6a 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,5 @@ link-plugins.sh test.sh .docker/** -!**/.gitkeep \ No newline at end of file +!**/.gitkeep +dump.rdb From f7d55965f2d2eea14d2d2f91b2e3f63d742f22fa Mon Sep 17 00:00:00 2001 From: MichaelHilton Date: Fri, 16 Jan 2026 16:35:41 +0000 Subject: [PATCH 821/828] add harmony as vendor package --- install/package.json | 2 +- vendor/nodebb-theme-harmony-2.1.35/.gitignore | 8 + vendor/nodebb-theme-harmony-2.1.35/.npmignore | 5 + vendor/nodebb-theme-harmony-2.1.35/README.md | 22 ++ .../eslint.config.mjs | 10 + .../languages/harmony.json | 1 + .../lib/controllers.js | 29 ++ vendor/nodebb-theme-harmony-2.1.35/library.js | 187 +++++++++++ .../nodebb-theme-harmony-2.1.35/package.json | 47 +++ .../nodebb-theme-harmony-2.1.35/plugin.json | 25 ++ .../public/.eslintrc | 3 + .../public/admin.js | 15 + .../public/harmony.js | 298 ++++++++++++++++++ .../public/settings.js | 31 ++ .../nodebb-theme-harmony-2.1.35/renovate.json | 6 + .../screenshot.png | Bin 0 -> 54442 bytes .../screenshots/categories.png | Bin 0 -> 360009 bytes .../screenshots/recent.png | Bin 0 -> 368684 bytes .../screenshots/topic.png | Bin 0 -> 257175 bytes .../scss/account.scss | 27 ++ .../scss/category.scss | 4 + .../scss/chats.scss | 12 + .../scss/common.scss | 132 ++++++++ .../scss/fonts.scss | 24 ++ .../scss/groups.scss | 22 ++ .../scss/harmony.scss | 26 ++ .../scss/header.scss | 16 + .../scss/mixins.scss | 167 ++++++++++ .../scss/mobilebar.scss | 64 ++++ .../scss/modals.scss | 6 + .../scss/modules/breadcrumbs.scss | 16 + .../scss/modules/cover.scss | 104 ++++++ .../scss/modules/filters.scss | 8 + .../scss/modules/nprogress.scss | 80 +++++ .../scss/modules/paginator.scss | 24 ++ .../scss/modules/tags.scss | 6 + .../scss/modules/topic-navigator.scss | 53 ++++ .../scss/modules/topics-list.scss | 40 +++ .../scss/modules/user-menu.scss | 11 + .../scss/overrides.scss | 66 ++++ .../scss/sidebar.scss | 123 ++++++++ .../scss/skins.scss | 65 ++++ .../scss/status.scss | 25 ++ .../scss/topic.scss | 149 +++++++++ .../templates/account/best.tpl | 1 + .../templates/account/blocks.tpl | 51 +++ .../templates/account/bookmarks.tpl | 1 + .../templates/account/categories.tpl | 64 ++++ .../templates/account/consent.tpl | 73 +++++ .../templates/account/controversial.tpl | 1 + .../templates/account/downvoted.tpl | 1 + .../templates/account/edit.tpl | 138 ++++++++ .../templates/account/edit/password.tpl | 35 ++ .../templates/account/edit/username.tpl | 30 ++ .../templates/account/followers.tpl | 15 + .../templates/account/following.tpl | 16 + .../templates/account/groups.tpl | 15 + .../templates/account/ignored.tpl | 1 + .../templates/account/info.tpl | 271 ++++++++++++++++ .../templates/account/posts.tpl | 35 ++ .../templates/account/profile.tpl | 92 ++++++ .../templates/account/read.tpl | 1 + .../templates/account/sessions.tpl | 9 + .../templates/account/settings.tpl | 268 ++++++++++++++++ .../templates/account/shares.tpl | 20 ++ .../templates/account/tags.tpl | 13 + .../templates/account/theme.tpl | 82 +++++ .../templates/account/topics.tpl | 45 +++ .../templates/account/uploads.tpl | 37 +++ .../templates/account/upvoted.tpl | 1 + .../templates/account/watched.tpl | 1 + .../templates/admin/plugins/harmony.tpl | 72 +++++ .../templates/categories.tpl | 29 ++ .../templates/category.tpl | 92 ++++++ .../templates/footer.tpl | 24 ++ .../templates/groups/details.tpl | 86 +++++ .../templates/groups/list.tpl | 58 ++++ .../templates/groups/members.tpl | 10 + .../templates/header.tpl | 39 +++ .../templates/notifications.tpl | 32 ++ .../templates/partials/account/admin-menu.tpl | 36 +++ .../partials/account/category-item.tpl | 22 ++ .../templates/partials/account/footer.tpl | 3 + .../templates/partials/account/header.tpl | 98 ++++++ .../partials/account/session-list.tpl | 18 ++ .../partials/account/sidebar-left.tpl | 126 ++++++++ .../partials/breadcrumbs-json-ld.tpl | 16 + .../templates/partials/breadcrumbs.tpl | 12 + .../templates/partials/buttons/newTopic.tpl | 22 ++ .../partials/categories/children.tpl | 12 + .../templates/partials/categories/item.tpl | 60 ++++ .../partials/categories/lastpost.tpl | 24 ++ .../templates/partials/categories/link.tpl | 5 + .../partials/category/subcategory.tpl | 19 ++ .../templates/partials/category/tags.tpl | 3 + .../templates/partials/cookie-consent.tpl | 6 + .../templates/partials/groups/admin.tpl | 95 ++++++ .../templates/partials/groups/badge.tpl | 1 + .../templates/partials/groups/invited.tpl | 33 ++ .../templates/partials/groups/list.tpl | 16 + .../templates/partials/groups/memberlist.tpl | 47 +++ .../templates/partials/groups/pending.tpl | 29 ++ .../partials/groups/sidebar-left.tpl | 27 ++ .../templates/partials/header/brand.tpl | 28 ++ .../templates/partials/mobile-footer.tpl | 4 + .../templates/partials/mobile-header.tpl | 14 + .../templates/partials/mobile-nav.tpl | 91 ++++++ .../templates/partials/notifications_list.tpl | 44 +++ .../templates/partials/paginator.tpl | 47 +++ .../templates/partials/post_bar.tpl | 22 ++ .../templates/partials/posts_list.tpl | 8 + .../templates/partials/posts_list_item.tpl | 28 ++ .../quick-category-search-results.tpl | 50 +++ .../partials/quick-search-results.tpl | 50 +++ .../templates/partials/search-filters.tpl | 184 +++++++++++ .../templates/partials/search-results.tpl | 55 ++++ .../templates/partials/sidebar-left.tpl | 40 +++ .../templates/partials/sidebar-right.tpl | 17 + .../templates/partials/sidebar/chats.tpl | 45 +++ .../templates/partials/sidebar/drafts.tpl | 63 ++++ .../partials/sidebar/logged-in-menu.tpl | 23 ++ .../partials/sidebar/logged-out-menu.tpl | 44 +++ .../partials/sidebar/notifications.tpl | 49 +++ .../partials/sidebar/search-mobile.tpl | 29 ++ .../templates/partials/sidebar/search.tpl | 30 ++ .../templates/partials/sidebar/user-menu.tpl | 103 ++++++ .../templates/partials/skin-switcher.tpl | 50 +++ .../templates/partials/tags_list.tpl | 8 + .../templates/partials/toast.tpl | 19 ++ .../templates/partials/topic-filters.tpl | 16 + .../templates/partials/topic-list-bar.tpl | 55 ++++ .../templates/partials/topic-terms.tpl | 16 + .../partials/topic/browsing-users.tpl | 1 + .../templates/partials/topic/event.tpl | 40 +++ .../templates/partials/topic/mark-unread.tpl | 6 + .../partials/topic/navigation-post.tpl | 11 + .../partials/topic/navigator-mobile.tpl | 62 ++++ .../templates/partials/topic/navigator.tpl | 22 ++ .../templates/partials/topic/necro-post.tpl | 6 + .../templates/partials/topic/post-editor.tpl | 1 + .../templates/partials/topic/post-menu.tpl | 4 + .../partials/topic/post-placeholder.tpl | 15 + .../templates/partials/topic/post.tpl | 152 +++++++++ .../templates/partials/topic/quickreply.tpl | 29 ++ .../templates/partials/topic/reactions.tpl | 1 + .../templates/partials/topic/reply-button.tpl | 17 + .../partials/topic/selection-tooltip.tpl | 3 + .../templates/partials/topic/sort.tpl | 27 ++ .../templates/partials/topic/stats.tpl | 30 ++ .../templates/partials/topic/tag.tpl | 1 + .../templates/partials/topic/tags.tpl | 1 + .../templates/partials/topic/thumbs.tpl | 7 + .../templates/partials/topic/tools.tpl | 9 + .../templates/partials/topic/watch.tpl | 60 ++++ .../templates/partials/topics_list.tpl | 131 ++++++++ .../templates/partials/users/item.tpl | 39 +++ .../templates/partials/users_list.tpl | 5 + .../templates/partials/users_list_menu.tpl | 15 + .../templates/popular.tpl | 35 ++ .../templates/recent.tpl | 43 +++ .../templates/search.tpl | 46 +++ .../templates/tag.tpl | 35 ++ .../templates/tags.tpl | 50 +++ .../templates/top.tpl | 35 ++ .../templates/topic.tpl | 158 ++++++++++ .../templates/unread.tpl | 32 ++ .../templates/users.tpl | 39 +++ .../templates/world.tpl | 96 ++++++ vendor/nodebb-theme-harmony-2.1.35/theme.json | 7 + vendor/nodebb-theme-harmony-2.1.35/theme.scss | 1 + 170 files changed, 6951 insertions(+), 1 deletion(-) create mode 100644 vendor/nodebb-theme-harmony-2.1.35/.gitignore create mode 100644 vendor/nodebb-theme-harmony-2.1.35/.npmignore create mode 100644 vendor/nodebb-theme-harmony-2.1.35/README.md create mode 100644 vendor/nodebb-theme-harmony-2.1.35/eslint.config.mjs create mode 100644 vendor/nodebb-theme-harmony-2.1.35/languages/harmony.json create mode 100644 vendor/nodebb-theme-harmony-2.1.35/lib/controllers.js create mode 100644 vendor/nodebb-theme-harmony-2.1.35/library.js create mode 100644 vendor/nodebb-theme-harmony-2.1.35/package.json create mode 100644 vendor/nodebb-theme-harmony-2.1.35/plugin.json create mode 100644 vendor/nodebb-theme-harmony-2.1.35/public/.eslintrc create mode 100644 vendor/nodebb-theme-harmony-2.1.35/public/admin.js create mode 100644 vendor/nodebb-theme-harmony-2.1.35/public/harmony.js create mode 100644 vendor/nodebb-theme-harmony-2.1.35/public/settings.js create mode 100644 vendor/nodebb-theme-harmony-2.1.35/renovate.json create mode 100644 vendor/nodebb-theme-harmony-2.1.35/screenshot.png create mode 100644 vendor/nodebb-theme-harmony-2.1.35/screenshots/categories.png create mode 100644 vendor/nodebb-theme-harmony-2.1.35/screenshots/recent.png create mode 100644 vendor/nodebb-theme-harmony-2.1.35/screenshots/topic.png create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/account.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/category.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/chats.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/common.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/fonts.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/groups.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/harmony.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/header.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/mixins.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/mobilebar.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modals.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/breadcrumbs.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/cover.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/filters.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/nprogress.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/paginator.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/tags.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/topic-navigator.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/topics-list.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/modules/user-menu.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/overrides.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/sidebar.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/skins.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/status.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/scss/topic.scss create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/best.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/blocks.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/bookmarks.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/categories.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/consent.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/controversial.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/downvoted.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/edit.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/password.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/username.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/followers.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/following.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/groups.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/ignored.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/info.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/posts.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/profile.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/read.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/sessions.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/settings.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/shares.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/tags.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/theme.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/topics.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/uploads.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/upvoted.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/account/watched.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/admin/plugins/harmony.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/categories.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/category.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/footer.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/groups/details.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/groups/list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/groups/members.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/header.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/notifications.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/admin-menu.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/category-item.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/footer.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/header.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/session-list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/sidebar-left.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/breadcrumbs-json-ld.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/breadcrumbs.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/buttons/newTopic.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/categories/children.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/categories/item.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/categories/lastpost.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/categories/link.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/category/subcategory.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/category/tags.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/cookie-consent.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/admin.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/badge.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/invited.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/memberlist.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/pending.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/groups/sidebar-left.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/header/brand.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/mobile-footer.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/mobile-header.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/mobile-nav.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/notifications_list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/paginator.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/post_bar.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/posts_list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/posts_list_item.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/quick-category-search-results.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/quick-search-results.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/search-filters.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/search-results.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar-left.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar-right.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/chats.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/drafts.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/logged-in-menu.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/logged-out-menu.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/notifications.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/search-mobile.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/search.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/sidebar/user-menu.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/skin-switcher.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/tags_list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/toast.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic-filters.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic-list-bar.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic-terms.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/browsing-users.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/event.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/mark-unread.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/navigation-post.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/navigator-mobile.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/navigator.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/necro-post.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/post-editor.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/post-menu.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/post-placeholder.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/post.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/quickreply.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/reactions.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/reply-button.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/selection-tooltip.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/sort.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/stats.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/tag.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/tags.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/thumbs.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/tools.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topic/watch.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/topics_list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/users/item.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/users_list.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/partials/users_list_menu.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/popular.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/recent.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/search.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/tag.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/tags.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/top.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/topic.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/unread.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/users.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/templates/world.tpl create mode 100644 vendor/nodebb-theme-harmony-2.1.35/theme.json create mode 100644 vendor/nodebb-theme-harmony-2.1.35/theme.scss diff --git a/install/package.json b/install/package.json index 040fac1dee..5ec1e3d209 100644 --- a/install/package.json +++ b/install/package.json @@ -107,7 +107,7 @@ "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.1.33", + "nodebb-theme-harmony": "file:vendor/nodebb-theme-harmony-2.1.35", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", "nodebb-theme-persona": "14.1.25", diff --git a/vendor/nodebb-theme-harmony-2.1.35/.gitignore b/vendor/nodebb-theme-harmony-2.1.35/.gitignore new file mode 100644 index 0000000000..270508c166 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/.gitignore @@ -0,0 +1,8 @@ +*.css +npm-debug.log +sftp-config.json +*.sublime-project +*.sublime-workspace +.idea +.vscode +node_modules/ \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/.npmignore b/vendor/nodebb-theme-harmony-2.1.35/.npmignore new file mode 100644 index 0000000000..2eb0848797 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/.npmignore @@ -0,0 +1,5 @@ +*.css +npm-debug.log +sftp-config.json +*.sublime-project +*.sublime-workspace diff --git a/vendor/nodebb-theme-harmony-2.1.35/README.md b/vendor/nodebb-theme-harmony-2.1.35/README.md new file mode 100644 index 0000000000..94e0fea1d1 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/README.md @@ -0,0 +1,22 @@ +Harmony theme for NodeBB +==================== + +The Harmony theme is the default theme for NodeBB for versions spanning v3.0.0 onwards. + +## Issues + +Issues are tracked in [the main project issue tracker](https://github.com/NodeBB/NodeBB/issues?q=is%3Aopen+is%3Aissue+label%3Athemes). + +## Screenshots + +### Categories + +_The cards in the header are added by the recent cards plugin. https://github.com/NodeBB-Community/nodebb-plugin-recent-cards_ + + + +### Recent + + +### Topic + diff --git a/vendor/nodebb-theme-harmony-2.1.35/eslint.config.mjs b/vendor/nodebb-theme-harmony-2.1.35/eslint.config.mjs new file mode 100644 index 0000000000..f0da2045c6 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/eslint.config.mjs @@ -0,0 +1,10 @@ +'use strict'; + +import serverConfig from 'eslint-config-nodebb'; +import publicConfig from 'eslint-config-nodebb/public'; + +export default [ + ...publicConfig, + ...serverConfig, +]; + diff --git a/vendor/nodebb-theme-harmony-2.1.35/languages/harmony.json b/vendor/nodebb-theme-harmony-2.1.35/languages/harmony.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/languages/harmony.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/lib/controllers.js b/vendor/nodebb-theme-harmony-2.1.35/lib/controllers.js new file mode 100644 index 0000000000..379ec06509 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/lib/controllers.js @@ -0,0 +1,29 @@ +'use strict'; + +const Controllers = module.exports; + +const accountHelpers = require.main.require('./src/controllers/accounts/helpers'); +const helpers = require.main.require('./src/controllers/helpers'); + +Controllers.renderAdminPage = (req, res) => { + res.render('admin/plugins/harmony', { + title: '[[themes/harmony:theme-name]]', + }); +}; + +Controllers.renderThemeSettings = async (req, res, next) => { + const userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query); + if (!userData) { + return next(); + } + const lib = require('../library'); + userData.theme = await lib.loadThemeConfig(userData.uid); + + userData.title = '[[themes/harmony:settings.title]]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([ + { text: userData.username, url: `/user/${userData.userslug}` }, + { text: '[[themes/harmony:settings.title]]' }, + ]); + + res.render('account/theme', userData); +}; diff --git a/vendor/nodebb-theme-harmony-2.1.35/library.js b/vendor/nodebb-theme-harmony-2.1.35/library.js new file mode 100644 index 0000000000..45f8bd2707 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/library.js @@ -0,0 +1,187 @@ +'use strict'; + +const nconf = require.main.require('nconf'); +const meta = require.main.require('./src/meta'); +const _ = require.main.require('lodash'); +const user = require.main.require('./src/user'); + +const controllers = require('./lib/controllers'); + +const library = module.exports; + +const defaults = { + enableQuickReply: 'on', + enableBreadcrumbs: 'on', + centerHeaderElements: 'off', + mobileTopicTeasers: 'off', + stickyToolbar: 'on', + topicSidebarTools: 'on', + topMobilebar: 'off', + autohideBottombar: 'on', + openSidebars: 'off', + chatModals: 'off', +}; + +library.init = async function (params) { + const { router, middleware } = params; + const routeHelpers = require.main.require('./src/routes/helpers'); + + routeHelpers.setupAdminPageRoute(router, '/admin/plugins/harmony', [], controllers.renderAdminPage); + + routeHelpers.setupPageRoute(router, '/user/:userslug/theme', [ + middleware.exposeUid, + middleware.ensureLoggedIn, + middleware.canViewUsers, + middleware.checkAccountPermissions, + ], controllers.renderThemeSettings); + + if (nconf.get('isPrimary') && process.env.NODE_ENV === 'production') { + setTimeout(buildSkins, 0); + } +}; + +async function buildSkins() { + try { + const plugins = require.main.require('./src/plugins'); + await plugins.prepareForBuild(['client side styles']); + for (const skin of meta.css.supportedSkins) { + await meta.css.buildBundle(`client-${skin}`, true); + } + require.main.require('./src/meta/minifier').killAll(); + } catch (err) { + console.error(err.stack); + } +} + +library.addAdminNavigation = async function (header) { + header.plugins.push({ + route: '/plugins/harmony', + icon: 'fa-paint-brush', + name: '[[themes/harmony:theme-name]]', + }); + return header; +}; + +library.addProfileItem = async (data) => { + data.links.push({ + id: 'theme', + route: 'theme', + icon: 'fa-paint-brush', + name: '[[themes/harmony:settings.title]]', + visibility: { + self: true, + other: false, + moderator: false, + globalMod: false, + admin: false, + }, + }); + + return data; +}; + +library.defineWidgetAreas = async function (areas) { + const locations = ['header', 'sidebar', 'footer']; + const templates = [ + 'categories.tpl', 'category.tpl', 'topic.tpl', 'users.tpl', + 'unread.tpl', 'recent.tpl', 'popular.tpl', 'top.tpl', 'tags.tpl', 'tag.tpl', + 'login.tpl', 'register.tpl', 'world.tpl', + ]; + function capitalizeFirst(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + templates.forEach((template) => { + locations.forEach((location) => { + areas.push({ + name: `${capitalizeFirst(template.split('.')[0])} ${capitalizeFirst(location)}`, + template: template, + location: location, + }); + }); + }); + + areas = areas.concat([ + { + name: 'Main post header', + template: 'topic.tpl', + location: 'mainpost-header', + }, + { + name: 'Main post footer', + template: 'topic.tpl', + location: 'mainpost-footer', + }, + { + name: 'Sidebar Footer', + template: 'global', + location: 'sidebar-footer', + }, + { + name: 'Brand Header', + template: 'global', + location: 'brand-header', + }, + { + name: 'About me (before)', + template: 'account/profile.tpl', + location: 'profile-aboutme-before', + }, + { + name: 'About me (after)', + template: 'account/profile.tpl', + location: 'profile-aboutme-after', + }, + ]); + + return areas; +}; + +library.loadThemeConfig = async function (uid) { + const [themeConfig, userConfig] = await Promise.all([ + meta.settings.get('harmony'), + user.getSettings(uid), + ]); + + const config = { ...defaults, ...themeConfig, ...(_.pick(userConfig, Object.keys(defaults))) }; + config.enableQuickReply = config.enableQuickReply === 'on'; + config.enableBreadcrumbs = config.enableBreadcrumbs === 'on'; + config.centerHeaderElements = config.centerHeaderElements === 'on'; + config.mobileTopicTeasers = config.mobileTopicTeasers === 'on'; + config.stickyToolbar = config.stickyToolbar === 'on'; + config.topicSidebarTools = config.topicSidebarTools === 'on'; + config.autohideBottombar = config.autohideBottombar === 'on'; + config.topMobilebar = config.topMobilebar === 'on'; + config.openSidebars = config.openSidebars === 'on'; + config.chatModals = config.chatModals === 'on'; + return config; +}; + +library.getThemeConfig = async function (config) { + config.theme = await library.loadThemeConfig(config.uid); + config.openDraftsOnPageLoad = false; + return config; +}; + +library.getAdminSettings = async function (hookData) { + if (hookData.plugin === 'harmony') { + hookData.values = { + ...defaults, + ...hookData.values, + }; + } + return hookData; +}; + +library.saveUserSettings = async function (hookData) { + Object.keys(defaults).forEach((key) => { + if (hookData.data.hasOwnProperty(key)) { + hookData.settings[key] = hookData.data[key] || undefined; + } + }); + return hookData; +}; + +library.filterMiddlewareRenderHeader = async function (hookData) { + hookData.templateData.bootswatchSkinOptions = await meta.css.getSkinSwitcherOptions(hookData.req.uid); + return hookData; +}; \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/package.json b/vendor/nodebb-theme-harmony-2.1.35/package.json new file mode 100644 index 0000000000..dd7f309c43 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/package.json @@ -0,0 +1,47 @@ +{ + "name": "nodebb-theme-harmony", + "version": "2.1.35", + "nbbpm": { + "compatibility": "^4.0.0" + }, + "description": "Harmony theme for NodeBB", + "main": "library.js", + "repository": { + "type": "git", + "url": "https://github.com/NodeBB/nodebb-theme-harmony" + }, + "scripts": { + "lint": "eslint ." + }, + "keywords": [ + "nodebb", + "theme", + "forum", + "bootstrap", + "responsive" + ], + "contributors": [ + { + "name": "Julian Lam", + "email": "julian@nodebb.org", + "url": "https://github.com/julianlam" + }, + { + "name": "Barış Soner Uşaklı", + "email": "baris@nodebb.org", + "url": "https://github.com/barisusakli" + } + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/NodeBB/nodebb-theme-harmony/issues" + }, + "dependencies": { + "@fontsource/inter": "5.2.8", + "@fontsource/poppins": "5.2.7" + }, + "devDependencies": { + "eslint": "9.39.2", + "eslint-config-nodebb": "^1.1.4" + } +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/plugin.json b/vendor/nodebb-theme-harmony-2.1.35/plugin.json new file mode 100644 index 0000000000..96d5e2acf4 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/plugin.json @@ -0,0 +1,25 @@ +{ + "id": "nodebb-theme-harmony", + "hooks": [ + { "hook": "static:app.load", "method": "init" }, + { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, + { "hook": "filter:widgets.getAreas", "method": "defineWidgetAreas" }, + { "hook": "filter:config.get", "method": "getThemeConfig" }, + { "hook": "filter:settings.get", "method": "getAdminSettings"}, + { "hook": "filter:user.saveSettings", "method": "saveUserSettings" }, + { "hook": "filter:user.profileMenu", "method": "addProfileItem" }, + { "hook": "filter:middleware.renderHeader", "method": "filterMiddlewareRenderHeader" } + ], + "scripts": [ + "public/harmony.js" + ], + "modules": { + "../admin/plugins/harmony.js": "public/admin.js", + "../client/account/theme.js": "public/settings.js" + }, + "staticDirs": { + "inter": "node_modules/@fontsource/inter/files", + "poppins": "node_modules/@fontsource/poppins/files" + }, + "languages": "languages" +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/public/.eslintrc b/vendor/nodebb-theme-harmony-2.1.35/public/.eslintrc new file mode 100644 index 0000000000..a3ce8297a6 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/public/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "nodebb/public" +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/public/admin.js b/vendor/nodebb-theme-harmony-2.1.35/public/admin.js new file mode 100644 index 0000000000..6df37a7156 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/public/admin.js @@ -0,0 +1,15 @@ +'use strict'; + +define('admin/plugins/harmony', ['settings'], function (Settings) { + var ACP = {}; + + ACP.init = function () { + Settings.load('harmony', $('.harmony-settings')); + + $('#save').on('click', function () { + Settings.save('harmony', $('.harmony-settings')); + }); + }; + + return ACP; +}); diff --git a/vendor/nodebb-theme-harmony-2.1.35/public/harmony.js b/vendor/nodebb-theme-harmony-2.1.35/public/harmony.js new file mode 100644 index 0000000000..35c0bb98d2 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/public/harmony.js @@ -0,0 +1,298 @@ +'use strict'; + +$(document).ready(function () { + setupSkinSwitcher(); + setupNProgress(); + setupMobileMenu(); + setupSearch(); + setupDrafts(); + handleMobileNavigator(); + setupNavTooltips(); + fixPlaceholders(); + fixSidebarOverflow(); + + function setupSkinSwitcher() { + $('[component="skinSwitcher"]').on('click', '.dropdown-item', function () { + const skin = $(this).attr('data-value'); + $('[component="skinSwitcher"] .dropdown-item .fa-check').addClass('invisible'); + $(this).find('.fa-check').removeClass('invisible'); + require(['forum/account/settings', 'hooks'], function (accountSettings, hooks) { + hooks.one('action:skin.change', function () { + $('[component="skinSwitcher"] [component="skinSwitcher/icon"]').removeClass('fa-fade'); + }); + $('[component="skinSwitcher"] [component="skinSwitcher/icon"]').addClass('fa-fade'); + accountSettings.changeSkin(skin); + }); + }); + } + + require(['hooks'], function (hooks) { + $(window).on('action:composer.resize action:sidebar.toggle', function () { + const isRtl = $('html').attr('data-dir') === 'rtl'; + const css = { + width: $('#panel').width(), + }; + const sidebarEl = $('.sidebar-left'); + css[isRtl ? 'right' : 'left'] = sidebarEl.is(':visible') ? sidebarEl.outerWidth(true) : 0; + $('[component="composer"]').css(css); + }); + + hooks.on('filter:chat.openChat', function (hookData) { + // disables chat modals & goes straight to chat page based on user setting + hookData.modal = config.theme.chatModals && !utils.isMobile(); + return hookData; + }); + }); + + function setupMobileMenu() { + require(['hooks', 'api', 'navigator'], function (hooks, api, navigator) { + $('[component="sidebar/toggle"]').on('click', async function () { + const sidebarEl = $('.sidebar'); + sidebarEl.toggleClass('open'); + if (app.user.uid) { + await api.put(`/users/${app.user.uid}/settings`, { + settings: { + openSidebars: sidebarEl.hasClass('open') ? 'on' : 'off', + }, + }); + } + $(window).trigger('action:sidebar.toggle'); + if (ajaxify.data.template.topic) { + hooks.fire('action:navigator.update', { newIndex: navigator.getIndex() }); + } + }); + + const bottomBar = $('[component="bottombar"]'); + let stickyTools = null; + const location = config.theme.topMobilebar ? 'top' : 'bottom'; + const $body = $('body'); + const $window = $(window); + $body.on('shown.bs.dropdown hidden.bs.dropdown', '.sticky-tools', function () { + bottomBar.toggleClass('hidden', $(this).find('.dropdown-menu.show').length); + }); + function isSearchVisible() { + return !!$('[component="bottombar"] [component="sidebar/search"] .search-dropdown.show').length; + } + + let lastScrollTop = $window.scrollTop(); + let newPostsLoaded = false; + + function onWindowScroll() { + const st = $window.scrollTop(); + if (newPostsLoaded) { + newPostsLoaded = false; + lastScrollTop = st; + return; + } + if (st !== lastScrollTop && !navigator.scrollActive && !isSearchVisible()) { + const diff = Math.abs(st - lastScrollTop); + const scrolledDown = st > lastScrollTop; + const scrolledUp = st < lastScrollTop; + const isHiding = !scrolledUp && scrolledDown; + if (diff > 10) { + bottomBar.css({ + [location]: isHiding ? + -bottomBar.find('.bottombar-nav').outerHeight(true) : + 0, + }); + if (stickyTools && config.theme.topMobilebar && config.theme.autohideBottombar) { + stickyTools.css({ + top: isHiding ? 0 : 'var(--panel-offset)', + }); + } + } + } + lastScrollTop = st; + } + + const delayedScroll = utils.throttle(onWindowScroll, 250); + function enableAutohide() { + $window.off('scroll', delayedScroll); + if (config.theme.autohideBottombar) { + lastScrollTop = $window.scrollTop(); + $window.on('scroll', delayedScroll); + } + } + + hooks.on('action:posts.loading', function () { + $window.off('scroll', delayedScroll); + }); + hooks.on('action:posts.loaded', function () { + newPostsLoaded = true; + setTimeout(enableAutohide, 250); + }); + hooks.on('action:ajaxify.end', function () { + bottomBar.removeClass('hidden'); + const { template } = ajaxify.data; + stickyTools = (template.category || template.topic) ? $('.sticky-tools') : null; + $window.off('scroll', delayedScroll); + if (config.theme.autohideBottombar) { + bottomBar.css({ [location]: 0 }); + setTimeout(enableAutohide, 250); + } + }); + }); + } + + function setupSearch() { + $('[component="sidebar/search"]').on('shown.bs.dropdown', function () { + $(this).find('[component="search/fields"] input[name="query"]').trigger('focus'); + }); + } + + function setupDrafts() { + require(['composer/drafts', 'bootbox'], function (drafts, bootbox) { + const draftsEl = $('[component="sidebar/drafts"]'); + + function updateBadgeCount() { + const count = drafts.getAvailableCount(); + if (count > 0) { + draftsEl.removeClass('hidden'); + } + $('[component="drafts/count"]').toggleClass('hidden', count <= 0).text(count); + } + + async function renderDraftList() { + const draftListEl = $('[component="drafts/list"]'); + const draftItems = drafts.listAvailable(); + if (!draftItems.length) { + draftListEl.find('.no-drafts').removeClass('hidden'); + draftListEl.find('.placeholder-wave').addClass('hidden'); + draftListEl.find('.draft-item-container').html(''); + return; + } + draftItems.reverse().forEach((draft) => { + if (draft) { + if (draft.title) { + draft.title = utils.escapeHTML(String(draft.title)); + } + draft.text = utils.escapeHTML( + draft.text + ).replace(/(?:\r\n|\r|\n)/g, '
    '); + } + }); + + const html = await app.parseAndTranslate('partials/sidebar/drafts', 'drafts', { drafts: draftItems }); + draftListEl.find('.no-drafts').addClass('hidden'); + draftListEl.find('.placeholder-wave').addClass('hidden'); + draftListEl.find('.draft-item-container').html(html).find('.timeago').timeago(); + } + + + draftsEl.on('shown.bs.dropdown', renderDraftList); + + draftsEl.on('click', '[component="drafts/open"]', function () { + drafts.open($(this).attr('data-save-id')); + }); + + draftsEl.on('click', '[component="drafts/delete"]', function () { + const save_id = $(this).attr('data-save-id'); + bootbox.confirm('[[modules:composer.discard-draft-confirm]]', function (ok) { + if (ok) { + drafts.removeDraft(save_id); + renderDraftList(); + } + }); + return false; + }); + + $(window).on('action:composer.drafts.save', updateBadgeCount); + $(window).on('action:composer.drafts.remove', updateBadgeCount); + updateBadgeCount(); + }); + } + + function setupNProgress() { + require(['nprogress'], function (NProgress) { + window.nprogress = NProgress; + if (NProgress) { + $(window).on('action:ajaxify.start', function () { + NProgress.set(0.7); + }); + + $(window).on('action:ajaxify.end', function () { + NProgress.done(true); + }); + } + }); + } + + function handleMobileNavigator() { + const paginationBlockEl = $('.pagination-block'); + require(['hooks'], function (hooks) { + hooks.on('action:ajaxify.end', function () { + paginationBlockEl.find('.dropdown-menu.show').removeClass('show'); + }); + hooks.on('filter:navigator.scroll', function (hookData) { + paginationBlockEl.find('.dropdown-menu.show').removeClass('show'); + return hookData; + }); + }); + } + + function setupNavTooltips() { + // remove title from user icon in sidebar to prevent double tooltip + $('.sidebar [component="header/avatar"] .avatar').removeAttr('title'); + const tooltipEls = $('.sidebar [title]'); + const lefttooltipEls = $('.sidebar-left [title]'); + const rightooltipEls = $('.sidebar-right [title]'); + const isRtl = $('html').attr('data-dir') === 'rtl'; + lefttooltipEls.tooltip({ + trigger: 'manual', + animation: false, + placement: isRtl ? 'left' : 'right', + }); + rightooltipEls.tooltip({ + trigger: 'manual', + animation: false, + placement: isRtl ? 'right' : 'left', + }); + + tooltipEls.on('mouseenter', function (ev) { + const target = $(ev.target); + const isDropdown = target.hasClass('dropdown-menu') || !!target.parents('.dropdown-menu').length; + if (!$('.sidebar').hasClass('open') && !isDropdown) { + $(this).tooltip('show'); + } + }); + tooltipEls.on('click mouseleave', function () { + $(this).tooltip('hide'); + }); + } + + function fixPlaceholders() { + if (!config.loggedIn) { + return; + } + ['notifications', 'chat'].forEach((type) => { + const countEl = $(`nav.sidebar [component="${type}/count"]`).first(); + if (!countEl.length) { + return; + } + const count = parseInt(countEl.text(), 10); + if (count > 1) { + const listEls = $(`.dropdown-menu [component="${type}/list"]`); + listEls.each((index, el) => { + const placeholder = $(el).children().first(); + for (let x = 0; x < count - 1; x++) { + const cloneEl = placeholder.clone(true); + cloneEl.insertAfter(placeholder); + } + }); + } + }); + } + + function fixSidebarOverflow() { + // overflow-y-auto needs to be removed on main-nav when dropdowns are opened + const mainNavEl = $('#main-nav'); + function toggleOverflow() { + mainNavEl.toggleClass( + 'overflow-y-auto', + !mainNavEl.find('.dropdown-menu.show').length + ); + } + mainNavEl.on('shown.bs.dropdown', toggleOverflow) + .on('hidden.bs.dropdown', toggleOverflow); + } +}); diff --git a/vendor/nodebb-theme-harmony-2.1.35/public/settings.js b/vendor/nodebb-theme-harmony-2.1.35/public/settings.js new file mode 100644 index 0000000000..e677db7fe0 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/public/settings.js @@ -0,0 +1,31 @@ +'use strict'; + +define('forum/account/theme', ['forum/account/header', 'api', 'settings', 'alerts'], function (header, api, settings, alerts) { + const Theme = {}; + + Theme.init = () => { + header.init(); + Theme.setupForm(); + }; + + Theme.setupForm = () => { + const saveEl = document.getElementById('save'); + if (saveEl) { + const formEl = document.getElementById('theme-settings'); + saveEl.addEventListener('click', async () => { + const themeSettings = settings.helper.serializeForm($(formEl)); + await api.put(`/users/${ajaxify.data.uid}/settings`, { + settings: { + ...themeSettings, + }, + }); + if (ajaxify.data.isSelf) { + config.theme = (await api.get('/api/config')).theme; + } + alerts.success('[[success:settings-saved]]'); + }); + } + }; + + return Theme; +}); diff --git a/vendor/nodebb-theme-harmony-2.1.35/renovate.json b/vendor/nodebb-theme-harmony-2.1.35/renovate.json new file mode 100644 index 0000000000..39a2b6e9a5 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/screenshot.png b/vendor/nodebb-theme-harmony-2.1.35/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8908da191b76bba5d8c89bc7875edbfa069687 GIT binary patch literal 54442 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 )Lw_~mZ#PQ@9ho>^s3H*k3- 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 Aj{pDw literal 0 HcmV?d00001 diff --git a/vendor/nodebb-theme-harmony-2.1.35/screenshots/categories.png b/vendor/nodebb-theme-harmony-2.1.35/screenshots/categories.png new file mode 100644 index 0000000000000000000000000000000000000000..7cae6309a15788936eb88ebb0727b4ce7e81688e GIT binary patch literal 360009 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

    lA`y^OFp`}B5S^+()WoIk~SAp45tP5~Of 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

    54<#zeQjo+EI#9su(Rj##9ss=A8VF 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 yBIhC+uU5w-C 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 z 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
  • ak=hVU+^k}n< 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 L06B9jpdCxH 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ⅅ+d&<(u$Ws@7vpwb{h_iDpP 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 X-OOQ-aLfuR9_~?5)KsW>V;TBC)+d+G literal 0 HcmV?d00001 diff --git a/vendor/nodebb-theme-harmony-2.1.35/screenshots/topic.png b/vendor/nodebb-theme-harmony-2.1.35/screenshots/topic.png new file mode 100644 index 0000000000000000000000000000000000000000..3e5c92cf55c284db1999c591a0f7e2f9196fbf1b GIT binary patch literal 257175 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�BIOpGEdtFAN}x+#hNwfodi26CmzV_u4`2k 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%0D&!{oOR^HxXuX>*cP>P#6SPt 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%0A&GGvZWW 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
    +

    [[pages:account/blocks, {username}]]

    +
    + +
    +
    +
    + + +
    + {{{ each users }}} +
    + + +
    + {{{ end }}} +
    + +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/bookmarks.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/bookmarks.tpl new file mode 100644 index 0000000000..21b7d5b195 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/bookmarks.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/categories.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/categories.tpl new file mode 100644 index 0000000000..576caf5ae8 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/categories.tpl @@ -0,0 +1,64 @@ + + + + +
    +
      + {{{each categories}}} + + {{{end}}} +
    + +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/consent.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/consent.tpl new file mode 100644 index 0000000000..f472e9ee99 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/consent.tpl @@ -0,0 +1,73 @@ + +

    [[user:consent.title]]

    + +

    [[user:consent.lead]]

    +

    [[user:consent.intro]]

    + +
    + +
    +
    + {{{ if gdpr_consent }}} +
    + [[user:consent.received]] + +
    + {{{ else }}} +
    + [[user:consent.not-received]] +

    +
    + +
    +
    + {{{ end }}} +
    +
    +

    [[user:consent.email-intro]]

    + {{{ if digest.enabled }}} +

    [[user:consent.digest-frequency, {digest.frequency}]]

    + {{{ else }}} +

    [[user:consent.digest-off]]

    + {{{ end }}} + + +
    +
    +
    +
    +
    +
    +

    [[user:consent.right-of-access]]

    +

    [[user:consent.right-of-access-description]]

    +

    [[user:consent.right-to-rectification]]

    +

    [[user:consent.right-to-rectification-description]]

    +

    [[user:consent.right-to-erasure]]

    +

    [[user:consent.right-to-erasure-description]]

    +

    [[user:consent.right-to-data-portability]]

    +

    [[user:consent.right-to-data-portability-description]]

    + +
    + + +
    +
    +
    +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/controversial.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/controversial.tpl new file mode 100644 index 0000000000..21b7d5b195 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/controversial.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/downvoted.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/downvoted.tpl new file mode 100644 index 0000000000..21b7d5b195 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/downvoted.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit.tpl new file mode 100644 index 0000000000..dcdb867ff9 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit.tpl @@ -0,0 +1,138 @@ + + +
    +

    {{{ if isSelf }}}[[user:edit-profile]]{{{ else }}}[[pages:account/edit, {username}]]{{{ end }}}

    + +
    +
    +
    +
    +
    + + +
    + +
    + + +
    + + {{{ each customUserFields }}} +
    + + {{{ if ((./type == "input-text") || (./type == "input-link")) }}} + + {{{ end }}} + + {{{ if (./type == "input-number") }}} + + {{{ end }}} + + {{{ if (./type == "input-date") }}} + + {{{ end }}} + + {{{ if ((./type == "select") || (./type == "select-multi")) }}} + + {{{ end }}} +
    + {{{ end }}} + + {{{ if groups.length }}} +
    + + +
    + {{{ each groups }}} +
    + +
    + + + {{{ if allowMultipleBadges }}} + + + {{{ end }}} +
    +
    + {{{ end }}} +
    +
    + {{{ end }}} + + {{{ if allowAboutMe }}} +
    + + +
    + {{{ end }}} + + {{{ if (allowSignature && !disableSignatures) }}} +
    + + +
    + {{{ end }}} +
    +
    +
    + +
    +
    + + + {{{ if config.requireEmailConfirmation }}} + {{{ if (email && isSelf) }}} + [[user:confirm-email]]

    + {{{ end }}} + {{{ end }}} +
    + + {{{ if sso.length }}} + + + {{{ end }}} + +
    + {{{ if (allowAccountDelete && isSelf) }}} +
    + +
    + {{{ end }}} +
    +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/password.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/password.tpl new file mode 100644 index 0000000000..4505529fa3 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/password.tpl @@ -0,0 +1,35 @@ + + +

    {{{ if isSelf }}}[[user:change-password]]{{{ else }}}[[pages:{template.name}, {username}]]{{{ end }}}

    +
    +
    +
    + + + {{{ if isSelf }}} +
    + + +
    + {{{ end }}} + +
    + + + +
    + +
    + + + +
    + +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/username.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/username.tpl new file mode 100644 index 0000000000..a98f78ebd9 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/edit/username.tpl @@ -0,0 +1,30 @@ + + +

    {{{ if isSelf }}}[[user:change-username]]{{{ else }}}[[pages:{template.name}, {username}]]{{{ end }}}

    +
    +
    +
    +
    + + +
    + + + + {{{ if isSelf }}} +
    + + +
    + {{{ end }}} + + + +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/followers.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/followers.tpl new file mode 100644 index 0000000000..68fe6de65d --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/followers.tpl @@ -0,0 +1,15 @@ + +

    [[pages:{template.name}, {username}]]

    + +{{{ if !users.length }}} +
    [[user:has-no-follower]]
    +{{{ end }}} + +
    +{{{ each users }}} + +{{{end}}} +
    + + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/following.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/following.tpl new file mode 100644 index 0000000000..73fd4913db --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/following.tpl @@ -0,0 +1,16 @@ + + +

    [[pages:{template.name}, {username}]]

    + +{{{ if !users.length }}} +
    [[user:follows-no-one]]
    +{{{ end }}} + +
    +{{{ each users }}} + +{{{end}}} +
    + + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/groups.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/groups.tpl new file mode 100644 index 0000000000..c1faf80836 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/groups.tpl @@ -0,0 +1,15 @@ + + +

    [[pages:{template.name}, {username}]]

    + +
    +
    + {{{ if !groups.length }}} +
    [[groups:no-groups-found]]
    + {{{ else }}} + + {{{ end }}} +
    +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/ignored.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/ignored.tpl new file mode 100644 index 0000000000..238b943cd5 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/ignored.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/info.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/info.tpl new file mode 100644 index 0000000000..8bdd0a3ec7 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/info.tpl @@ -0,0 +1,271 @@ + + +{{{ if sessions.length }}} +
    +

    [[global:sessions]]

    +
      + +
    +
    +{{{ end }}} + +
    +
    +
    +
    + [[global:recentips]] +
    +
    +
      + {{{each ips}}} +
    • {@value}
    • + {{{end}}} +
    +
    +
    + +
    +
    + [[user:info.username-history]] +
    +
    + +
    +
    + +
    +
    + [[user:info.email-history]] +
    +
    + +
    +
    + + {{{ if isAdminOrGlobalModerator }}} +
    +
    + [[user:info.moderation-note]] +
    +
    + + + +
    +
    + {{{ each moderationNotes }}} +
    + +
    + + + +
    +
    + {./note} +
    + +
    + +
    + +
    + + +
    +
    +
    + {{{ end }}} +
    + +
    +
    + {{{ end }}} +
    +
    +
    +
    + [[user:info.latest-flags]] +
    +
    + {{{ if history.flags.length }}} +
      + {{{ each history.flags }}} +
    • +
      +
      + {{{ if (./type == "user")}}} + [[user:info.profile]] + {{{ else }}} + [[user:info.post]] + {{{ end }}} + +
      + + [[user:info.view-flag]] +
      + + {{{ if (./type == "post") }}} +

      + {{{ if history.flags.targetPurged }}} +

      [[flags:target-purged]]
      + {{{ else }}} + {./title} + {{{ end }}} +

      + {{{ end }}} + +
      + [[user:info.reported-by]] +
      + {{{ each ./reports }}} + {buildAvatar(./reporter, "24px", true)} + {{{ end }}} +
      +
      +
    • + {{{ end }}} +
    + {{{ else }}} +
    [[user:info.no-flags]]
    + {{{ end }}} +
    +
    + +
    +
    + [[user:info.ban-history]] + + {{{ if (!banned && !isSelf) }}} + + {{{ end }}} + {{{ if (banned && !isSelf) }}} + + {{{ end }}} +
    +
    + {{{ if history.bans.length }}} +
      + {{{ each history.bans }}} +
    • +
      + + {{{ if (./type != "unban") }}} + [[user:banned]] + {{{ else }}} + [[user:unbanned]] + {{{ end }}} +
      +

      + [[user:info.banned-reason-label]]: {./reason} +

      +

      + {{{ if ./until }}} + [[user:info.banned-until, {isoTimeToLocaleString(./untilISO, config.userLang)}]] + {{{ else }}} + {{{ if (./type != "unban") }}} + [[user:info.banned-permanently]] + {{{ end }}} + {{{ end }}} +

      +
    • + {{{ end }}} +
    + {{{ else }}} +
    [[user:info.no-ban-history]]
    + {{{ end }}} +
    +
    + +
    +
    + [[user:info.mute-history]] + + {{{ if !muted }}} + {{{ if !isSelf }}} + + {{{ end }}} + {{{ else }}} + {{{ if !isSelf }}} + + {{{ end }}} + {{{ end }}} +
    +
    + {{{ if history.mutes.length }}} +
      + {{{ each history.mutes }}} +
    • +
      + + {{{ if (./type != "unmute") }}} + [[user:muted]] + {{{ else }}} + [[user:unmuted]] + {{{ end }}} +
      +

      + [[user:info.banned-reason-label]]: {./reason} +

      +

      + {{{ if ./until }}} + [[user:info.muted-until, {isoTimeToLocaleString(./untilISO, config.userLang)}]] + {{{ end }}} +

      +
    • + {{{ end }}} +
    + {{{ else }}} +
    [[user:info.no-mute-history]]
    + {{{ end }}} +
    +
    +
    +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/posts.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/posts.tpl new file mode 100644 index 0000000000..d155ad3f4f --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/posts.tpl @@ -0,0 +1,35 @@ + + +
    +

    [[global:posts]]

    +
    + [[global:header.recent]] + {{{ if !reputation:disabled }}} + [[global:best]] + [[global:controversial]] + {{{ if canEdit }}} + [[global:upvoted]] + {{{ if !downvote:disabled }}} + [[global:downvoted]] + {{{ end }}} + {{{ end }}} + {{{ end }}} + {{{ if canEdit }}} + [[user:bookmarks]] + {{{ end }}} +
    +
    + +{{{ if !posts.length }}} +
    {noItemsFoundKey}
    +{{{ end }}} + +
    + + + {{{ if config.usePagination }}} + + {{{ end }}} +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/profile.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/profile.tpl new file mode 100644 index 0000000000..be1b5aead7 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/profile.tpl @@ -0,0 +1,92 @@ + + +{{{ if widgets.profile-aboutme-before.length }}} +
    +{{{each widgets.profile-aboutme-before}}} +{./html} +{{{end}}} +
    +{{{ end }}} + +{{{ if aboutme }}} +
    +{aboutmeParsed} +
    +{{{ end }}} + +{{{ if widgets.profile-aboutme-after.length }}} +
    +{{{each widgets.profile-aboutme-after}}} +{./html} +{{{end}}} +
    +{{{ end }}} + + + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/read.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/read.tpl new file mode 100644 index 0000000000..238b943cd5 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/read.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/sessions.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/sessions.tpl new file mode 100644 index 0000000000..679d087729 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/sessions.tpl @@ -0,0 +1,9 @@ + + +

    [[user:sessions.description]]

    +
    +
      + +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/settings.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/settings.tpl new file mode 100644 index 0000000000..70374d5a74 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/settings.tpl @@ -0,0 +1,268 @@ + + +
    +

    {{{ if isSelf }}}[[pages:account/settings]]{{{ else }}}[[pages:account/settings-of, {username}]]{{{ end }}}

    + +
    +
    +
    + {{{ if !disableCustomUserSkins }}} + + + +
    + {{{ end }}} + + {{{ if allowUserHomePage }}} + + +
    + +

    [[user:homepage-description]]

    +
    + + +
    + {{{ end }}} + +
    [[global:privacy]]
    + + {{{ if !hideEmail }}} +
    + + +
    + {{{ end }}} + + {{{ if !hideFullname }}} +
    + + +
    + {{{ end }}} + + {{{ if !config.disableChat }}} +
    + + +
    + + +
    + + +
    + {{{ each settings.chatAllowListUsers }}} +
    + {buildAvatar(@value, "16px", true)} {./username} + +
    + {{{ end }}} +
    + + +
    + +
    + + +
    + {{{ each settings.chatDenyListUsers }}} +
    + {buildAvatar(@value, "16px", true)} {./username} + +
    + {{{ end }}} +
    + + +
    + + {{{ end }}} + +
    + +
    [[user:browsing]]
    + +
    + + +
    + + {{{ if inTopicSearchAvailable }}} +
    + + +
    +

    [[user:topic-search-help]]

    + {{{ end }}} + +
    + + +
    + +
    + + +
    + +
    + +
    [[global:pagination]]
    + +
    + + +
    +
    + + +
    +
    + + +
    + +
    + +
    [[global:sort]]
    + +
    + + +
    +
    + + +
    + + + {{{ if !disableEmailSubscriptions }}} +
    +
    [[global:email]]
    +
    +
    + + +

    [[user:digest-description]]

    +
    +
    + {{{ end }}} + + {{{ each customSettings}}} +
    +
    {./title}
    +
    + {./content} +
    + {{{end}}} +
    +
    + +
    + + + +
    + + {{{ if (isAdmin && isSelf) }}} + + + +
    + {{{ end }}} + +
    [[topic:watch]]
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + +
    [[user:notifications]]
    +
    + {{{ each notificationSettings }}} +
    +
    + +
    +
    + +
    +
    + {{{end}}} + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/shares.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/shares.tpl new file mode 100644 index 0000000000..82f471cec2 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/shares.tpl @@ -0,0 +1,20 @@ + + +
    +
    +

    [[pages:account/shares, {username}]]

    +
    +
    + +{{{ if !topics.length }}} +
    {noItemsFoundKey}
    +{{{ end }}} + +
    + + {{{ if config.usePagination }}} + + {{{ end }}} +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/tags.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/tags.tpl new file mode 100644 index 0000000000..c1007eb836 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/tags.tpl @@ -0,0 +1,13 @@ + + +
    +
    +

    {title}

    +
    +
    + +
    + +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/theme.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/theme.tpl new file mode 100644 index 0000000000..01206b1d5d --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/theme.tpl @@ -0,0 +1,82 @@ + + +
    +

    [[themes/harmony:settings.title]]

    + + +
    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/topics.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/topics.tpl new file mode 100644 index 0000000000..4a78a9534b --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/topics.tpl @@ -0,0 +1,45 @@ + + +
    +
    +

    [[global:topics]]

    + {{{ if showSort }}} +
    + + +
    + {{{ end }}} +
    + +
    + {{{ if canEdit }}} + [[global:header.recent]] + [[user:watched]] + [[user:ignored]] + [[user:read]] + {{{ end }}} +
    +
    + + +{{{ if !topics.length }}} +
    {noItemsFoundKey}
    +{{{ end }}} + +
    + + {{{ if config.usePagination }}} + + {{{ end }}} +
    + + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/uploads.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/uploads.tpl new file mode 100644 index 0000000000..dd5e79002e --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/uploads.tpl @@ -0,0 +1,37 @@ + + +

    {title}

    + +
    + {{{ if privateUploads }}}[[uploads:private-uploads-info]]{{{ else }}}[[uploads:public-uploads-info]]{{{ end }}} +
    + +{{{ if !uploads.length }}} +
    [[uploads:no-uploads-found]]
    +{{{ end }}} + + + + + + + + + + {{{ each uploads }}} + + + + + {{{ end }}} + +
    + {./url} + +
    + +
    +
    + + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/upvoted.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/upvoted.tpl new file mode 100644 index 0000000000..21b7d5b195 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/upvoted.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/watched.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/watched.tpl new file mode 100644 index 0000000000..238b943cd5 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/watched.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/admin/plugins/harmony.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/admin/plugins/harmony.tpl new file mode 100644 index 0000000000..0dc2e541ee --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/admin/plugins/harmony.tpl @@ -0,0 +1,72 @@ +
    + + +
    +
    +
    +
    + + +
    +
    + + +

    [[themes/harmony:settings.enableBreadcrumbs.why]]

    +
    +
    + + +
    +
    + + +
    +
    + +
    + [[themes/harmony:settings.stickyToolbar]] +

    + [[themes/harmony:settings.stickyToolbar.help]] +

    +
    +
    +
    + +
    + [[themes/harmony:settings.topicSidebarTools]] +

    + [[themes/harmony:settings.topicSidebarTools.help]] +

    +
    +
    +
    + +
    + [[themes/harmony:settings.autohideBottombar]] +

    + [[themes/harmony:settings.autohideBottombar.help]] +

    +
    +
    +
    + +
    + [[themes/harmony:settings.topMobilebar]] +
    +
    +
    + + +
    +
    + +
    + [[themes/harmony:settings.chatModals]] +
    +
    +
    +
    + + +
    +
    diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/categories.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/categories.tpl new file mode 100644 index 0000000000..0a5df5db97 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/categories.tpl @@ -0,0 +1,29 @@ +
    + {{{ each widgets.header }}} + {{widgets.header.html}} + {{{ end }}} +
    +
    +
    + {{{ if pagination.pages.length }}} +
    + {{{ end }}} +
      + {{{ each categories }}} + + {{{ end }}} +
    + + +
    +
    + {{{ each widgets.sidebar }}} + {{widgets.sidebar.html}} + {{{ end }}} +
    +
    +
    + {{{ each widgets.footer }}} + {{widgets.footer.html}} + {{{ end }}} +
    diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/category.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/category.tpl new file mode 100644 index 0000000000..82d84639c0 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/category.tpl @@ -0,0 +1,92 @@ + +{{{ if config.theme.enableBreadcrumbs }}} + +{{{ end }}} + +
    +
    + {buildCategoryIcon(@value, "40px", "rounded-1 flex-shrink-0")} +

    {./name}

    +
    + {{{ if ./descriptionParsed }}} +
    + {./descriptionParsed} +
    + {{{ end }}} + {{{ if ./handleFull }}} +

    + [[category:handle.description, {handleFull}]] + +

    + {{{ end }}} +
    + + {humanReadableNumber(totalTopicCount)} + [[global:topics]] + + + {humanReadableNumber(totalPostCount)} + [[global:posts]] + + {{{ if !isNumber(cid) }}} + + View Original + + + {{{ end }}} +
    +
    + +{{{ if widgets.header.length }}} +
    + {{{ each widgets.header }}} + {{widgets.header.html}} + {{{ end }}} +
    +{{{ end }}} + + +
    +
    + + {{{ if (topics.length || privileges.topics:create) }}} + + {{{ end }}} + + {{{ if (./inbox && (./hasFollowers == false)) }}} + + {{{ end }}} + + {{{ if (!topics.length && privileges.topics:create) }}} +
    + [[category:no-topics]] +
    + {{{ end }}} + + + + {{{ if config.usePagination }}} + + {{{ end }}} +
    +
    + {{{ each widgets.sidebar }}} + {{widgets.sidebar.html}} + {{{ end }}} +
    +
    +
    + {{{each widgets.footer}}} + {{widgets.footer.html}} + {{{end}}} +
    + +{{{ if !config.usePagination }}} + +{{{ end }}} diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/footer.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/footer.tpl new file mode 100644 index 0000000000..c62d38cb03 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/footer.tpl @@ -0,0 +1,24 @@ + +

    eDh|7(5qVMDZqp)3~ zolpce#`iYJPfEQTWt~OY% z*!d7XqM#nTF2(Wj>p(a(#A3>fP^j)Mv0As-2%1q78{3&~x7Xwd_+EC=&s&NssZgQz zTUuY;z3uOWV`E`d4t~Y)kc+u`_#ESHmS`u~_PkEgXQ9L@h5q$>Oe#*-4R|H7QQifAlVw)tCH5irWnqt_JX(UV=q&(EZmyLPW-g5Bj!FIyS+JP^%$(QX#W z8OE7Mf29S=s^J4+tC}G6o_90`riR2429q0;+qP|N%ggCU|jf(!i5`Ub{XZKm-K_0@^1vBe&DcmuhJhP59F?MI zj#0H9)UceyDMKrPz+(NtqJz_qjnk8g@a99BZLd}<5+HPrCS4E} zS;t~q*U87IKvKP4I=c7ipKSb9bfWJTCyH%!bSx7JleKilZNU+EwjOYiH^+s4tDl9+v!1_@T-}0 z=U&bo_0S}sv9C^pd*1@Ii92RkTU`4cz~k(^BG60d7uGMcVNaJA4hq}NpElPr2RodZ ziIMdWH-c}09|^X~&g6fuqqQrY!%zo(eUPwFKppGIxgXb(=ADp1xv|s%d<1-|b_Sn5 zd?FrS`E{sQEFPQZ^peDCK?*Wp&s00u=le0af*O#dZV3basWaos$M2)5_BJ=1dW zS`%|O#4GO2LoL@Grz%@p9l1>^0BfD9%+scfTuSq`$;iM6RO?d#&xgGjF2EkNvnR?Rc(^r6^>;>L8!%K`mM^{$~incIS3=ar|*2P7mGRZ;klV$OWfi6kgK|#bzrakj>lT* z2U&t?lv;u^u06j$QV=73+2<|%buS0Z6~q>UEyQVtKXj+bHqu$?45&OHIg}bBX9Y|n z>c0EiMRd$cSVujK=+6p>Vs$8yTPH8a(#6C2vMA(ia;M%vwxQ`V+T(o^_=cqH?!Q$+4^glzi{ZDYf<*)V@6 zcG0EcP`bFxPK*Pcqk>lB?)y`{mhPnVGnY}NkfteB9BLSy*(APv9PGA>R88+#qj4Uy zp0q(eS_81)#s2jhsq@QQWoLP;6ZNAWRtKTbh)`$TC$6iRXH_xxvR_zV``YWy43?{X zk)4^c4p;D#ra7zuEz2!z)wLvk+%ZdUz~(h-amE`79t)P!p&zGyNpcyCerbJ;QTiJ9 z&qaMR&Cs3%ky1ZIszN|fAP(~NRHYHoqw3pRvO#|i$NeW=r^}mbxN$_XB~OJ^pGHC#hutyT+Wg%a6rniWW2D?r+?u-8_WJb>TTht955& zJB{NjkEM?a+Yowqq3eWgM@IBflZcCrs8xw5Yoi2(KpR?6M2r~QN+B6O!1UBP4hueR ze^frgy%L^_c6}x1W<9e`()F77Bz2w+x%`QUri`U}c!NF5L_|yD=x$HQyu$BDKIx^i zBgQy#0`{!U)yDTLCv_WU1KYpiE?PID_hx##dgq!kewG>Z!6cL`TgOrAiv$R+Ju;Vf z`mjMeZ-V{$R8}ERelE-y4~;w2Z8XS!DNP8R6NE|$2>n)hU{cPmqmKV@f3y`e$}vzu zud=DSEaIZ?UzAZ;G62oFJll>Ph`!Sy%06u0Eo93DhM*H4h^Y@jmJXQtP%rsWrSUd! zi2ry(fpbH~pMo0+7^tkn>H>7g-dkFkkwoaO`<&2$&`mn#)^ znP-!}B-RWHLX^y?eSVVri<&lA8Yu!)E}y;VB&7=LGl!jaOc`ziDiG=)VuKwLuY>M| zE?NSblfbO00c~9zgB~q;dsc9?z>S>*ZZ|ZMCJ`(qUfU0Af>|&5wZ7l6ShI`mbYr>2 zOgEJ24lRXHXpJ|anU!evv+eB>b@FJY%yY8|bx`k6oVK!bUpMixV+Gg_d+KhoVRNgr zV4E_M3^%mTMCxxNrLt8x-D1TyFX?3=rdfa`jmvGz7Y_l~Y3%ka0)r25yFwrQO!nK? zLO0tQ;)&q#qx#oH9(vaM z@H5ExyZ>fj3iI{=KgZU4R?Dbwl`ec(oW}x^eDn}d4CbtA7^pv2u+|%AX$1@#Gdeq@ zgY#=eFy>;=I6|rg@>$W&uZDLxRH!i-yJxU=X-Xdo94}|WWBC}SrQX0>Ur6HYo{Y)l z0#&xZ!V-W$^>`%9re56lU80v8F+Kov$M46Y*qopc^Qk*{$D%|r*PC2b#cu?=sp*b( z(W^k}dLOdlz~vEa_>z88NeAH-S{7)QKyxX1S24~Cv$H{2vGMx1;p7-)wwW>XrZ!=L zfZkITnh?o&h85Lq#v;{6Hy$m9h1tcYM$u|7<>DEi+_*6uOt@@fWdT^?(py z=ckexR_?>&(oh`933`?S-ISGV7`B%c8aMdrHb~jM$PMa5u`-(%=X?-^uY#5(=S7hy&n4|Ut-@r@+J>K>EAZbB%muv?&+9`gzssFE^XF*9GlQ zm_0qnjqR}lTU!azL|)qtzqsD{BIx0O$*L{@Y3=;AwXNhZT@Yy)Z!i=GbREJNIhGdC zasjpd5f3_D4#{ec6$KZ$vyqV)F8RGvM7}~KXYbt|yof3Z?OP(hFL#kd#+4sH0uax0 z#XNO=OG+$6EyivWCXR~>9ORl&NsGKZZ)eCIc#BtBZqEkCHr3*YSq%K`q^>kDbOqzk zFnzS_dJkhfl7i}>1`AIj)MsZ8*aal7!H9_WqQ_#y%5evOaM(d4lxQ9Wtrv(acTO7X zGo)OKjg^TgbRT0{T@Rbp& zyhavWPH~>?QeRv&+wvRk>Fh760K??KpzUZ`YTHdWE*U0@@1y2i#SQXtkW( zQ8Ij+o2Vga$JBA>JG~RV#w{Lz=&9;A*-r-pU6j|} z3sD1~Pe^(l8H3LGuHu}Vh`&dP=@77El8L-n`$o`cuo%�|+C3#o(xef{=`I_h+b` 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 z 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 literal 0 HcmV?d00001 diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/account.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/account.scss new file mode 100644 index 0000000000..0a318b3e1e --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/account.scss @@ -0,0 +1,27 @@ +.page-user.page-status-200 #panel { + margin-top: 0px!important; +} +.account { + margin-top: 200px; + + @include media-breakpoint-up(md) { + margin-top: 300px; + } + + .categories { + [component="categories/category"] { + $category-pad: 50; + @for $i from 1 through 6 { + .depth-#{$i} { + padding-left: #{$category-pad * $i}px; + } + } + } + } +} + +[component="group/badge/item"]:first-child [component="group/order/up"], +[component="group/badge/item"]:last-child [component="group/order/down"] { + opacity: 0.65; + pointer-events: none; +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/category.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/category.scss new file mode 100644 index 0000000000..41bff4d4eb --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/category.scss @@ -0,0 +1,4 @@ +.category-header .description p { margin: 0; } +.page-category .breadcrumb .breadcrumb-item:last-child { + display: none; +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/chats.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/chats.scss new file mode 100644 index 0000000000..e0c5f49a96 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/chats.scss @@ -0,0 +1,12 @@ +// themes have a different layout so each one needs this block to set height to 100% +body.page-user-chats { + > .layout-container { + height: 100%; + > #panel { + height: 100%; + > .container { + height: 100%; + } + } + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/common.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/common.scss new file mode 100644 index 0000000000..2ba4e047a2 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/common.scss @@ -0,0 +1,132 @@ + +html { + height: 100%; +} + +body { + overflow-y: scroll; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +@include media-breakpoint-down(lg) { + body:not(.page-user-chats) { + min-height: 100vh; + .layout-container { + min-height: 100vh; + } + } +} + +// fixes chrome font boosting :/ https://stackoverflow.com/questions/13430897/how-to-override-font-boosting-in-mobile-chrome +body * { + max-height:1000000px; + text-size-adjust: none; + -webkit-text-size-adjust: none; + -moz-text-size-adjust: none; +} + +hr { + border-top-color: var(--bs-border-color); + opacity: 1; +} + +.ff-base { font-family: $font-family-base !important; } +.ff-sans { font-family: $font-family-sans-serif !important; } +.ff-secondary { font-family: $font-family-secondary; } +.tracking-tight { letter-spacing: -0.02em; } + +.caret { + &::after { + border: none; + font-family: "FontAwesome"; + content: "\f078"; + } +} + +.placeholder-wave { + opacity: 0.5; +} + +.bg-card-cap { + --bs-bg-opacity: 1; + background-color: $card-cap-bg!important; +} + +blockquote { + $bq-border-color: mix($light, $dark, 75%); + @extend .text-bg-light; + font-style: normal; + border-left: 2px solid $bq-border-color; + padding: 1rem; + p:last-child { + margin-bottom: 0; + } + .toggle { + border-color: $bq-border-color!important; + } +} + +body:not(.page-user) { + #content { + transition: opacity 150ms linear; + &.ajaxifying { + -moz-opacity: 0; + opacity: 0; + } + } +} +.page-user { + #content { + transition: opacity 150ms linear; + &.ajaxifying .account-content { + transition: opacity 150ms linear; + -moz-opacity: 0; + opacity: 0; + } + } +} + +.sticky-tools { + position: sticky; + z-index: 3; + top: 0; + padding: 0.25rem 0; +} +// quartz doesn't need body-bg for tool background +.skin-quartz .sticky-tools { + background-color: initial; +} + +.flex-basis-md-200 { + @include media-breakpoint-up(md) { + flex-basis: 200px!important; + } +} + +.markdown-highlight { + @extend .shadow-sm; + @extend .border; +} + +[component="chat/message/body"], [component="post/content"] { + .img-fluid { + @extend .shadow-sm; + padding: $spacer * 0.5; + margin: $spacer * 0.5 0; + border: 1px solid $border-color; + background-color: $light; + border-radius: $border-radius-sm; + max-height: 500px; + width: auto; + } +} + +[component="chat/message/body"], +[component="post/content"], +[component="topic/teaser"] .post-content, +[component="category/posts"] .post-content, +.post-queue.posts-list .post-content { + a { text-decoration: underline;} +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/fonts.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/fonts.scss new file mode 100644 index 0000000000..21a9bfca53 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/fonts.scss @@ -0,0 +1,24 @@ +@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); +$font-path: "./plugins/nodebb-theme-harmony" !default; + + +@include fontsource.faces( + $metadata: inter.$metadata, + $subsets: $subsets, + $weights: $weights, + $styles: all, + $directory: "#{$font-path}/inter" +); + +@include fontsource.faces( + $metadata: poppins.$metadata, + $subsets: $subsets, + $weights: $weights, + $styles: all, + $directory: "#{$font-path}/poppins" +); diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/groups.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/groups.scss new file mode 100644 index 0000000000..914e3fce90 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/groups.scss @@ -0,0 +1,22 @@ +.template-groups-details #panel { + margin-top: 0px!important; +} + +.group-hover-bg { + $hover-color: mix($light, $dark, 97%); + $border-color: mix($light, $dark, 90%); + .card-body { + border-color: $border-color!important; + } + &:hover { + background-color: $hover-color; + } +} + +.groups.details { + margin-top: 200px; + + @include media-breakpoint-up(md) { + margin-top: 300px; + } +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/harmony.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/harmony.scss new file mode 100644 index 0000000000..e4fd5a073e --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/harmony.scss @@ -0,0 +1,26 @@ +@import "fonts"; +@import "mixins"; +@import "common"; + +@import "header"; +@import "topic"; +@import "category"; +@import "chats"; +@import "sidebar"; +@import "mobilebar"; +@import "status"; +@import "account"; +@import "groups"; +@import "modals"; + +@import "modules/breadcrumbs"; +@import "modules/tags"; +@import "modules/user-menu"; +@import "modules/topic-navigator"; +@import "modules/topics-list"; +@import "modules/cover"; +@import "modules/nprogress"; +@import "modules/paginator"; +@import "modules/filters"; + +@import "skins"; \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/header.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/header.scss new file mode 100644 index 0000000000..a64d632c11 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/header.scss @@ -0,0 +1,16 @@ +// hide brand/title on user and group details pages so it doesnt break covers +body[class*="template-account-"], .template-chats, .template-groups-details { + .brand-container { + display: none; + } +} +[component="brand/wrapper"] { + &:hover { + background-color: $card-cap-bg; + } +} + +[component="brand/logo"] { + max-height: 48px; + width: auto; +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/mixins.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/mixins.scss new file mode 100644 index 0000000000..5ce0e55354 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/mixins.scss @@ -0,0 +1,167 @@ +@mixin topic-avatars() { + .icon .avatar, .timeline-badge { + z-index: 1; + + line-height: calc(var(--avatar-size) - 4px); + } + + [component="user/locality"] { + transform: translate(-75%, -100%); + z-index: 2; + background-color: $body-bg; + font-size: .5rem; + } + + [component="user/status"] { + transform: translate(-80%, -100%); + z-index: 2; + } + + @include media-breakpoint-up(sm) { + [component="user/status"] { + padding: 5px; + } + + [component="user/locality"] { + font-size: .75rem; + } + } +} + +@mixin timeline-style() { + > [component="post"], .timeline-event, > [component="post/placeholder"] { + position: relative; // for absolutely positioned pseudo-element, below + border: 0; + margin-left: 1.5rem; + transition: border-color 1s ease-out; + + &:last-child { + padding-bottom: 2rem; + + &:after { + content: ''; + position: absolute; + bottom: 0; + height: 16px; + width: 16px; + background-color: $border-color; + border-radius: 100%; + transform: translate(calc(-50% - .5px), 100%); + transition: background-color 1s ease-out; + } + + &.highlight:after { + background-color: $primary; + } + } + + >.post-container-parent { + margin-left: -1.5rem; + } + + &.highlight { + .bookmarked { + opacity: 1 !important; + } + } + + @include topic-avatars(); + } + + [component="topic/event"], [component="topic/necro-post"] { + &.timeline-event { + text-align: left; + justify-content: left; + font-size: 1em; + + .timeline-badge { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; + margin-right: 1rem; + color: $gray-500; + background-color: $body-bg; + } + + .timeline-text { + line-height: 32px; + text-transform: initial; + } + } + } + + @include media-breakpoint-down(sm) { + > [component="post"], .timeline-event { + margin-left: initial; + &:first-child:before, &:last-child:after { + display: none; + } + } + + [component="post"] { + margin-left: initial; + > div:not(.content) { + margin-left: 0; + } + } + + [component="post"]:last-child:after { + display: none; + } + + + [component="topic/event"], [component="topic/necro-post"] { + &.timeline-event { + .timeline-badge { + margin-right: initial; + } + .timeline-text { + line-height: 16px; + font-size: 0.75rem; + } + } + } + } + + @include media-breakpoint-up(sm) { + > [component="post"], .timeline-event, > [component="post/placeholder"] { + border-left: 2px solid $border-color; + + &.highlight { + border-left: 2px solid $primary; + } + } + + .timeline-event { + margin-left: 1.5rem; + + [component="topic/event/delete"] { + visibility: hidden; + } + + &:hover { + [component="topic/event/delete"] { + visibility: visible; + + &:hover { + color: $danger; + } + } + } + } + + [component="topic/event"], [component="topic/necro-post"] { + &.timeline-event .timeline-badge { + width: 24px; + height: 24px; + padding: 0; + margin-left: -0.75rem; + margin-right: 1.25rem; + border: 2px solid $border-color; + border-radius: 50%; + } + } + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/mobilebar.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/mobilebar.scss new file mode 100644 index 0000000000..facdd53c3c --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/mobilebar.scss @@ -0,0 +1,64 @@ +.navigator-mobile { + .pagination-block { + .scroller-container { + border-right: 3px solid; + margin-right: 5.5px; + .scroller-thumb { + right: -6px; + padding-right: 15px; + margin-right: -15px; + } + } + } +} + +.bottombar-nav { + #user_dropdown .avatar { + margin: 2px 0; // fixes the avatar so its height is same as the icons on right sidebar + } + .nav-text { + font-size: 1rem; + color: $body-color; + } + .nav-link { + padding: 8px; + border-radius: $border-radius-sm; + } + .usermenu, .chats, .notifications, .drafts, .search, .logged-out-menu { + .visible-open { display: none; } + } + .badge { + font-size: 9px; + line-height: 12px; + } + @include media-breakpoint-down(md) { + .dropdown-menu { + left: 0!important; + right: 0!important; + box-shadow: none!important; + border-left: 0; + border-right: 0; + border-radius: 0; + .list-container { + max-height: 60vh!important; + overflow-y: auto!important; + } + } + } + + .navigation-dropdown, .user-dropdown { + > li { + > a, .dropdown-item { + padding: 10px 20px!important; + } + } + + max-height: 60vh!important; + overflow: auto!important; + } + + .search-dropdown .quick-search-results { + max-height: 225px!important; + overflow-y: auto!important; + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modals.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modals.scss new file mode 100644 index 0000000000..201cfb5229 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modals.scss @@ -0,0 +1,6 @@ +.tool-modal { + @include media-breakpoint-up(md) { + bottom: $spacer * 3; + right: $spacer * 4; + } +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/breadcrumbs.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/breadcrumbs.scss new file mode 100644 index 0000000000..6f7d855138 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/breadcrumbs.scss @@ -0,0 +1,16 @@ +.breadcrumb .breadcrumb-item { + font-family: $font-family-secondary; + + &::before { + font-family: $font-family-sans-serif; + font-weight: $font-weight-semibold; + font-size: $small-font-size; + line-height: $h4-font-size; + } + + a, span { + color: $body-color; + font-size: $small-font-size; + line-height: 16px; + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/cover.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/cover.scss new file mode 100644 index 0000000000..33c6571263 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/cover.scss @@ -0,0 +1,104 @@ +// used in group and account pages +.cover { + background-size: cover; + background-repeat: no-repeat; + height: 200px; + position: absolute; + background-origin: content-box; + width: 100%; + top: var(--panel-offset)!important; + left: auto; + right: 0px; + + &:hover .controls, .controls:focus-within { + opacity: 1; + } + + .controls { + height: 200px; + line-height: 200px; + opacity: 0; + @include transition(opacity .15s linear); + cursor: pointer; + pointer-events: none; + + > * { + pointer-events: all; + } + } + + &.active { + &:hover { + cursor: move; + } + + .controls { + > * { + display: none; + } + } + + .save { + display: inline-block; + } + } + + &.saving { + .save { + display: none; + } + + .indicator { + display: inline-block; + } + } + + .save, .indicator { + display: inline-block; + position: absolute; + bottom: 1em; + left: 50%; + transform: translateX(-50%); + opacity: 1; + padding: 0.5em; + font-weight: bold; + + &:hover { + cursor: pointer; + } + } + + .save { + display: none; + } + + .indicator { + display: none; + } +} + +.cover > .container { + height: 200px; + position: relative; + pointer-events: none; + .save { + pointer-events: all; + } + .controls { + pointer-events: none; + > * { + pointer-events: all; + } + } +} + +@include media-breakpoint-up(md) { + .cover, .cover > .container { + height: 300px; + + .controls { + height: 300px; + line-height: 300px; + } + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/filters.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/filters.scss new file mode 100644 index 0000000000..1f70620d48 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/filters.scss @@ -0,0 +1,8 @@ +[component="search/filters"], [component="flags/filters"] { + .filter-btn { + border-color: $gray-300!important; + &.active-filter { + border-color: $primary!important; + } + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/nprogress.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/nprogress.scss new file mode 100644 index 0000000000..a576b32d44 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/nprogress.scss @@ -0,0 +1,80 @@ +#nprogress { + pointer-events: none; +} + +$nprogress-color: $primary; + +#nprogress .bar { + background: $nprogress-color; + position: fixed; + z-index: 1031; + top: 0; + left: 0; + width: 100%; + height: 2px; +} + +#nprogress .peg { + display: block; + position: absolute; + right: 0px; + width: 100px; + height: 100%; + box-shadow: 0 0 10px $nprogress-color, 0 0 5px $nprogress-color; + opacity: 1.0; + + -webkit-transform: rotate(3deg) translate(0px, -4px); + -ms-transform: rotate(3deg) translate(0px, -4px); + transform: rotate(3deg) translate(0px, -4px); +} + +#nprogress .spinner { + display: none; + position: fixed; + z-index: 1031; + top: 15px; + right: 15px; +} + +@include media-breakpoint-down(sm) { + #nprogress .spinner { + bottom: 15px; + right: 15px; + top: initial; + } +} + + +#nprogress .spinner-icon { + width: 18px; + height: 18px; + box-sizing: border-box; + + border: solid 2px transparent; + border-top-color: $nprogress-color; + border-left-color: $nprogress-color; + border-radius: 50%; + + -webkit-animation: nprogress-spinner 400ms linear infinite; + animation: nprogress-spinner 400ms linear infinite; +} + +.nprogress-custom-parent { + overflow: hidden; + position: relative; +} + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar { + position: absolute; +} + +@-webkit-keyframes nprogress-spinner { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} +@keyframes nprogress-spinner { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/paginator.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/paginator.scss new file mode 100644 index 0000000000..1689c8e49b --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/paginator.scss @@ -0,0 +1,24 @@ +.skin-noskin [component="pagination"] { + .page-item.active:not(.disabled) .page-link { + color: $body-color; + background-color: $gray-300; + border-color: $gray-300; + } + + .page-item:not(.disabled):hover .page-link { + color: $body-color; + } +} + +[component="pagination"] { + .page-item.active:not(.disabled) .page-link { + color: $pagination-active-color; + } + .page-item:not(.disabled):hover .page-link { + color: $pagination-hover-color; + background-color: $pagination-hover-bg; + } + .page-item:not(.disabled) .page-link { + color: $body-color; + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/tags.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/tags.scss new file mode 100644 index 0000000000..f1217aa621 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/tags.scss @@ -0,0 +1,6 @@ +.tag-list { + .tag { + background-color: $gray-200!important; + color: $gray-700!important; + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/topic-navigator.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/topic-navigator.scss new file mode 100644 index 0000000000..8d0f681c83 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/topic-navigator.scss @@ -0,0 +1,53 @@ +.pagination-block { display: none; } + +body.template-topic { + // used for both sidebar and bottom bar pagination-block + .pagination-block { + display: block; + transition: opacity 250ms ease-in; + opacity: 0; + &.ready { + opacity: 1; + } + &.noreplies { + pointer-events: none; + cursor: none; + } + } +} + +.topic .pagination-block { + .scroller-content { + min-width: 170px; + } + .scroller-container { + left: 16px; + height: 275px; + border-left: 2px solid $border-color; + + .scroller-thumb { + left: -5px; + &:not(.active) { + transition: top 100ms linear; + } + cursor: grab; + &.active { + cursor: grabbing; + } + } + + .unread { + width: 1px; + height: 0; // initial + bottom: 0; + background: $primary; + transition: $transition-base; + left: -1px; + + .meta { + left: 5px; + font-size: 13px; + } + } + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/topics-list.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/topics-list.scss new file mode 100644 index 0000000000..714010c1f8 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/topics-list.scss @@ -0,0 +1,40 @@ +ul.topics-list, ul.categories-list { + li { + &.deleted { + .meta, .topic-thumbs { visibility: hidden!important; } + opacity: 0.65; + } + + &.selected { + background-color: mix($body-bg, $body-color, 90%); + [component="topic/select"] { + color: $success!important; + visibility: visible; + } + } + p { + margin: 0 !important; + } + + // all other skins use link-color for unread titles + &.unread .title { + color: $link-color!important; + } + + .ui-sortable-handle { + cursor: move; + } + + // if only one thumb don't display + [data-numthumbs="1"] { display: none; } + } +} + +// on default skin use primary color for unread titles +.skin-noskin { + ul.topics-list, ul.categories-list { + li.unread .title { + color: $primary!important; + } + } +} diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/modules/user-menu.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/user-menu.scss new file mode 100644 index 0000000000..cda2ac064b --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/modules/user-menu.scss @@ -0,0 +1,11 @@ +[component="header/usercontrol"] { + [component="header/profilelink"] > div, .user-status > div { + min-width: 1.25em; // match fontawesome fixed width + } + .user-status i.fa-check { + display: none; + } + .user-status.selected i.fa-check { + display: block; + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/overrides.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/overrides.scss new file mode 100644 index 0000000000..11131a3fe5 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/overrides.scss @@ -0,0 +1,66 @@ +// only overrides to bs5 variables here + +// Harmony colours +$white: #fff !default; +$gray-100: #f8f9fa !default; +$gray-200: #e9ecef !default; +$gray-300: #dee2e6 !default; +$gray-400: #ced4da !default; +$gray-500: #adb5bd !default; +$gray-600: #6c757d !default; +$gray-700: #495057 !default; +$gray-800: #343a40 !default; +$gray-900: #212529 !default; +$black: #000 !default; + +$blue: #0d6efd !default; +$red: #dc3545 !default; +$yellow: #ffc107 !default; +$green: #198754 !default; +$cyan: #0dcaf0 !default; + +$primary: $blue !default; +$secondary: $gray-600 !default; +$success: $green !default; +$info: $cyan !default; +$warning: $yellow !default; +$danger: $red !default; +$light: $gray-100 !default; +$dark: $gray-900 !default; + +$body-color: $gray-800 !default; +$body-bg: $white !default; +$body-tertiary-bg: $gray-200 !default; +$text-muted: $gray-600 !default; +$border-color: $gray-200 !default; +$link-color: #0951be !default; + +$form-check-input-border: var(--bs-border-width) solid $gray-500 !default; + +$input-placeholder-color: $gray-600!important; + +// no caret on dropdown-toggle +$enable-caret: false; + +// disable smooth scroll, this makes window.scrollTo(0,0) in ajaxify.js take x milliseconds +$enable-smooth-scroll: false; + +$enable-shadows: true; + +$link-decoration: none; +$link-hover-decoration: underline; + +// Custom fonts +$font-family-sans-serif: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +$font-family-secondary: "Poppins", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; +$font-weight-semibold: 500 !default; +$small-font-size: 0.875rem !default; + +$breadcrumb-divider: quote("→"); +$breadcrumb-divider-color: $gray-500 !default; +$breadcrumb-active-color: $body-color !default; +$breadcrumb-item-padding-x: 12px !default; + +.form-control::placeholder, .bootstrap-tagsinput::placeholder { + color: $gray-500 !important; +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/sidebar.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/sidebar.scss new file mode 100644 index 0000000000..2f35291fc9 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/sidebar.scss @@ -0,0 +1,123 @@ +.skin-noskin { + // only using colors when there is no bootswatch skin applied + nav.sidebar, .bottombar-nav { + color: $secondary !important; + background-color: $light !important; + } + .bottombar-nav { + .dropdown-menu { + color: $secondary !important; + background-color: $light !important; + } + } +} + +.sidebar { + $hover-color: mix($light, $dark, 90%); + + @include media-breakpoint-up(lg) { + &.open { + min-width: 200px; + max-width: 200px; + width: 200px; + + .sidebar-toggle { + .fa-angles-right { display: none; } + .fa-angles-left { display: inline-block; } + } + .visible-open { display: initial; } + .visible-closed { display: none; } + hr.visible-open { display: block; } + .truncate-open { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .sidebar-toggle-container { + width: 100% + } + } + } + .visible-open { display: none; } + .visible-closed {display: initial; } + + .truncate-open { + overflow: initial; + text-overflow: initial; + white-space: initial; + } + + .nav-link { + @extend .ff-secondary; + padding: ($spacer * 0.25) ($spacer * 0.5); + border-radius: $border-radius-sm; + cursor: pointer; + &.active { + background-color: $hover-color; + } + &:hover { + background-color: $hover-color; + } + } + + .nav-item { + .dropdown-menu .dropdown-item { + @extend .rounded-1; + } + } + + #user_dropdown .avatar { + margin: 2px 0; // fixes the avatar so its height is same as the icons on right sidebar + } + + .sidebar-toggle { + justify-content: start; + .fa-angles-right { display: inline-block; } + .fa-angles-left { display: none; } + } + + .search-dropdown { + width: 300px; + } + + .chats-dropdown, .notifications-dropdown, .drafts-dropdown { + min-width: 300px; + width: 300px; + .list-container { + max-height: 400px; + overflow-y: auto; + } + } + + .badge { + font-size: 9px; + line-height: 12px; + &.visible-open { + font-size:12px; + line-height: 12px; + font-weight: normal; + } + } + + [data-widget-area="sidebar-footer"] { + font-size: $font-size-base * 0.75; + } +} + + /*rtl:begin:ignore*/ + html[data-dir="rtl"] { + .sidebar { + &.open { + .sidebar-toggle { + .fa-angles-right { display: none; } + .fa-angles-left { display: inline-block; } + } + } + .sidebar-toggle { + .fa-angles-right { display: inline-block; } + .fa-angles-left { display: none; } + } + } +} + /*rtl:end:ignore*/ + diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/skins.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/skins.scss new file mode 100644 index 0000000000..192f2d2636 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/skins.scss @@ -0,0 +1,65 @@ +.skin-quartz { + // $body-bg-image is gradient in quartz + [component="post"] .icon { + background-color: transparent !important; + } + .form-control::placeholder { + color:$gray-100!important; + } +} + +.skin-solar { + .form-control::placeholder { + color:$gray-100!important; + } +} + +.skin-quartz, .skin-lux, .skin-morph { + // $spacer being modified looks bad on this element + .topic-list-header .btn, .topic-main-buttons .btn { + padding: 6px 12px; + } +} + +.skin-yeti { + .badge { + padding-left: 0.5rem; + padding-right: 0.5rem; + } +} + +// table color fix, remove once https://github.com/thomaspark/bootswatch/issues/1276 is published +.skin-darkly, .skin-superhero, .skin-solar, .skin-quartz { + table > :not(caption) > * > * { + color: white; + } +} + +.skin-superhero { + // fix read button in dropdowns + .mark-read .read { + color: $primary!important; + } +} + +.skin-slate { + // fix unread button colors in dropdowns + .mark-read .unread { + color: $secondary!important; + } +} + +.skin-morph { + .text-secondary { + color: #7b8ab8!important; + } +} + +:root { + .skin-darkly, .skin-slate, .skin-cyborg { + --bs-border-color: #929292; + } + .skin-zephyr { + --bs-secondary-rgb: var(--bs-secondary-color); + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/status.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/status.scss new file mode 100644 index 0000000000..37c5f0a474 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/status.scss @@ -0,0 +1,25 @@ +.status { + padding: 3px; + + &.online { + background-color: $success; + } + + &.away { + background-color: $warning; + } + + &.dnd { + background-color: $danger; + } + + &.offline { + background-color: $gray-600; + } +} + +@include media-breakpoint-up(sm) { + .status { + padding: 5px; + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/scss/topic.scss b/vendor/nodebb-theme-harmony-2.1.35/scss/topic.scss new file mode 100644 index 0000000000..9f1abd43bf --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/scss/topic.scss @@ -0,0 +1,149 @@ +body.template-topic { + .breadcrumb .breadcrumb-item:last-child { + display: none; + } + .topic { + .posts-container { + max-width: 960px; + width: 960px; + } + + .posts { + &.timeline { + @include timeline-style; + } + + .post-header { + font-size: 0.8125rem; + line-height: 1.25rem; + + .bookmarked { + transition: $transition-fade; + } + } + + [component="post"] { + &.selected .post-container { + background-color: mix($body-bg, $body-color, 90%); + } + &.deleted .post-container .content { opacity: .65; } + + [component="post/content"] { + @include fix-lists(); + + > blockquote { + > blockquote { + > *:not(.blockquote) { + display: none; + } + } + + > blockquote.uncollapsed { + > *:not(.blockquote) { + display: block; + } + } + } + + @include media-breakpoint-up(lg) { + table { // text-break breaks table formatting + word-break:initial!important; + } + } + } + } + + [component="post/upvote"], [component="post/downvote"] { + &.upvoted, &.downvoted { + background-color: var(--btn-ghost-active-color); + + &:hover { + background-color: var(--btn-ghost-hover-color); + } + } + } + } + } + + .quick-reply { + @include topic-avatars(); + } + + [component="post/parent"] { + margin-left: 2.5rem; + [component="post/parent/content"] > p:last-child { + margin-bottom: 0; + } + &[data-collapsed="true"] { + > [component="post/parent/content"] > * { + font-size: 13px!important; + line-height: var(--bs-btn-line-height)!important; + } + } + } + + [component="post/replies/container"] { + .icon { + display: none !important; + } + + .post-header .icon { + display: initial !important; + + .status { + display: none; + } + } + + .timeline-event { + display: none !important; + } + + [component="post"] { + padding-top: 0 !important; + padding-bottom: $spacer; + &:last-of-type { + padding-bottom: 0; + .post-footer { + padding-bottom: 0!important; + border-bottom: none !important; + } + } + } + } + + + [component="topic/thumb/list"] { + &.thumbs-collapsed a.d-inline-block:nth-of-type(n+4) { + display: none!important; + } + > a { + height: calc($font-size-base * 4); + } + } +} + +@include media-breakpoint-up(lg) { + body.template-topic { + .topic .posts { + [component="post"] { + [component="post/actions"] { + opacity: 0; + transition: $transition-fade; + + &:has([aria-expanded="true"]) { + opacity: 1; + } + } + [component="post/actions"]:focus-within { + opacity: 1; + } + &:hover { + > div > .post-container > [component="post/footer"] > div > [component="post/actions"] { + opacity: 1; + } + } + } + } + } +} \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/best.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/best.tpl new file mode 100644 index 0000000000..21b7d5b195 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/best.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/account/blocks.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/account/blocks.tpl new file mode 100644 index 0000000000..fb7a60aabb --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/account/blocks.tpl @@ -0,0 +1,51 @@ + +

    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

  • + + +
    + {{{ if !config.theme.topMobilebar }}} + + {{{ else }}} +
    + +
    + {{{ end }}} + + {{{ if !isSpider }}} +
    +
    + +
    +
    + {{{ end }}} + + + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/groups/details.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/groups/details.tpl new file mode 100644 index 0000000000..fd392c7dbe --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/groups/details.tpl @@ -0,0 +1,86 @@ +
    +
    +
    + {{{ if group.isOwner }}} +
    + + + +
    + [[groups:cover-save]] +
    [[groups:cover-saving]]
    + {{{ end }}} +
    +
    + +
    +
    +
    +

    {group.displayName}

    +
    +
    + {group.descriptionParsed} +
    + {{{ if group.private }}}[[groups:details.private]]{{{ end }}} + {{{ if group.hidden }}}[[groups:details.hidden]]{{{ end }}} +
    +
    +
    +
    + {{{ if loggedIn }}} + {function.membershipBtn, group} + {{{ end }}} + {{{ if isAdmin }}} + [[user:edit]] + {{{ end }}} +
    +
    + +
    +
    + {{{each widgets.left}}} + {{widgets.left.html}} + {{{end}}} +
    + + +
    +
    +
    +

    [[global:posts]]

    + {{{ if !posts.length }}} +
    [[groups:details.has-no-posts]]
    + {{{ end }}} + +
    +
    +

    [[groups:details.members]]

    + + +
    + {{{ if group.isOwner }}} +
    +

    [[groups:details.pending]]

    + +
    + +
    +

    [[groups:details.invited]]

    + +
    + +
    +

    [[groups:details.owner-options]]

    + +
    + {{{ end }}} +
    +
    + +
    + {{{each widgets.right}}} + {{widgets.right.html}} + {{{end}}} +
    +
    +
    diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/groups/list.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/groups/list.tpl new file mode 100644 index 0000000000..6d62e93482 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/groups/list.tpl @@ -0,0 +1,58 @@ +
    + {{{each widgets.header}}} + {{widgets.header.html}} + {{{end}}} +
    +
    +

    [[pages:groups]]

    +
    + +
    +
    +
    + {{{ if allowGroupCreation }}} + + {{{ end }}} + +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    + +
    + {{{ if groups.length }}} + + {{{ else }}} +
    +
    + [[groups:no-groups-found]] +
    +
    + {{{ end }}} +
    + + +
    diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/groups/members.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/groups/members.tpl new file mode 100644 index 0000000000..ffec941ab3 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/groups/members.tpl @@ -0,0 +1,10 @@ + +
    +
    + {{{ each users }}} + + {{{ end }}} +
    + + +
    \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/header.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/header.tpl new file mode 100644 index 0000000000..412826c93a --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/header.tpl @@ -0,0 +1,39 @@ + + + + {browserTitle} + {{{each metaTags}}}{function.buildMetaTag}{{{end}}} + + {{{each linkTags}}}{function.buildLinkTag}{{{end}}} + + + + {{{if useCustomHTML}}} + {{customHTML}} + {{{end}}} + {{{if useCustomCSS}}} + + {{{end}}} + + + + [[global:skip-to-content]] + + {{{ if config.theme.topMobilebar }}} + + {{{ end }}} + +
    + + +
    + +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/notifications.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/notifications.tpl new file mode 100644 index 0000000000..3e6673d701 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/notifications.tpl @@ -0,0 +1,32 @@ +
    + + +
    + +
    +
    + +
    +
    +
      + +
    + +
    +
    +
    + + diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/admin-menu.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/admin-menu.tpl new file mode 100644 index 0000000000..2941c91505 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/admin-menu.tpl @@ -0,0 +1,36 @@ +
    + + +
    diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/category-item.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/category-item.tpl new file mode 100644 index 0000000000..e7f9568a44 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/category-item.tpl @@ -0,0 +1,22 @@ +
  • + +
    +
    +
    + {buildCategoryIcon(@value, "24px", "rounded-1")} +
    +
    +
    + +
    + {{{ if ./descriptionParsed }}} +
    {./descriptionParsed}
    + {{{ end }}} +
    +
    +
    + +
    +
    +
    +
  • diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/footer.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/footer.tpl new file mode 100644 index 0000000000..e3c5167c91 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/footer.tpl @@ -0,0 +1,3 @@ +
    +
    +
    \ No newline at end of file diff --git a/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/header.tpl b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/header.tpl new file mode 100644 index 0000000000..0d372d9c04 --- /dev/null +++ b/vendor/nodebb-theme-harmony-2.1.35/templates/partials/account/header.tpl @@ -0,0 +1,98 @@ +