From 093528ef17d6f2e7073b5b4eba13b97fb477b4c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:11:41 +0100 Subject: [PATCH 01/17] New Crowdin Translations (automated) (#37915) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 5 +++++ app/javascript/mastodon/locales/da.json | 7 ++++++- app/javascript/mastodon/locales/de.json | 5 +++++ app/javascript/mastodon/locales/el.json | 5 +++++ app/javascript/mastodon/locales/en-GB.json | 5 +++++ app/javascript/mastodon/locales/es-AR.json | 5 +++++ app/javascript/mastodon/locales/es-MX.json | 5 +++++ app/javascript/mastodon/locales/es.json | 7 +++++++ app/javascript/mastodon/locales/fi.json | 5 +++++ app/javascript/mastodon/locales/fr-CA.json | 7 +++++++ app/javascript/mastodon/locales/fr.json | 7 +++++++ app/javascript/mastodon/locales/ga.json | 7 +++++++ app/javascript/mastodon/locales/gl.json | 5 +++++ app/javascript/mastodon/locales/he.json | 5 +++++ app/javascript/mastodon/locales/is.json | 5 +++++ app/javascript/mastodon/locales/it.json | 5 +++++ app/javascript/mastodon/locales/ko.json | 4 ++++ app/javascript/mastodon/locales/nl.json | 16 ++++++++++++++++ app/javascript/mastodon/locales/sq.json | 5 +++++ app/javascript/mastodon/locales/sv.json | 1 + app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/vi.json | 5 +++++ app/javascript/mastodon/locales/zh-CN.json | 7 +++++++ app/javascript/mastodon/locales/zh-TW.json | 5 +++++ config/locales/da.yml | 6 +++--- config/locales/doorkeeper.es-AR.yml | 4 ++++ config/locales/doorkeeper.es.yml | 4 ++++ config/locales/doorkeeper.fr-CA.yml | 2 -- config/locales/doorkeeper.fr.yml | 2 -- config/locales/doorkeeper.nl.yml | 4 ++++ config/locales/doorkeeper.tr.yml | 4 ++++ config/locales/ko.yml | 7 +++++++ config/locales/simple_form.ko.yml | 2 ++ config/locales/simple_form.nl.yml | 1 + 34 files changed, 163 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 45acd59217..dbdf43ab60 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -263,6 +263,11 @@ "collections.create_collection": "Стварыць калекцыю", "collections.delete_collection": "Выдаліць калекцыю", "collections.description_length_hint": "Максімум 100 сімвалаў", + "collections.detail.accounts_heading": "Уліковыя запісы", + "collections.detail.curated_by_author": "Курыруе {author}", + "collections.detail.curated_by_you": "Курыруеце Вы", + "collections.detail.loading": "Загружаецца калекцыя…", + "collections.detail.share": "Падзяліцца гэтай калекцыяй", "collections.edit_details": "Змяніць асноўныя звесткі", "collections.edit_settings": "Змяніць налады", "collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 56b5910a38..095b3f916a 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -20,7 +20,7 @@ "account.add_or_remove_from_list": "Tilføj eller fjern fra lister", "account.badges.admin": "Admin", "account.badges.blocked": "Blokeret", - "account.badges.bot": "Automatisert", + "account.badges.bot": "Automatiseret", "account.badges.domain_blocked": "Blokeret domæne", "account.badges.group": "Gruppe", "account.badges.muted": "Skjult", @@ -263,6 +263,11 @@ "collections.create_collection": "Opret samling", "collections.delete_collection": "Slet samling", "collections.description_length_hint": "Begrænset til 100 tegn", + "collections.detail.accounts_heading": "Konti", + "collections.detail.curated_by_author": "Kurateret af {author}", + "collections.detail.curated_by_you": "Kurateret af dig", + "collections.detail.loading": "Indlæser samling…", + "collections.detail.share": "Del denne samling", "collections.edit_details": "Rediger grundlæggende oplysninger", "collections.edit_settings": "Rediger indstillinger", "collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 8c19179ba0..0f5945775e 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -263,6 +263,11 @@ "collections.create_collection": "Sammlung erstellen", "collections.delete_collection": "Sammlung löschen", "collections.description_length_hint": "Maximal 100 Zeichen", + "collections.detail.accounts_heading": "Konten", + "collections.detail.curated_by_author": "Kuratiert von {author}", + "collections.detail.curated_by_you": "Kuratiert von dir", + "collections.detail.loading": "Sammlung wird geladen …", + "collections.detail.share": "Sammlung teilen", "collections.edit_details": "Allgemeine Informationen bearbeiten", "collections.edit_settings": "Einstellungen bearbeiten", "collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 102cf7e7d5..5178130cf0 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -263,6 +263,11 @@ "collections.create_collection": "Δημιουργία συλλογής", "collections.delete_collection": "Διαγραφή συλλογής", "collections.description_length_hint": "Όριο 100 χαρακτήρων", + "collections.detail.accounts_heading": "Λογαριασμοί", + "collections.detail.curated_by_author": "Επιμέλεια από {author}", + "collections.detail.curated_by_you": "Επιμέλεια από εσάς", + "collections.detail.loading": "Γίνεται φόρτωση της συλλογής…", + "collections.detail.share": "Κοινοποιήστε αυτήν τη συλλογή", "collections.edit_details": "Επεξεργασία βασικών στοιχείων", "collections.edit_settings": "Επεξεργασία ρυθμίσεων", "collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 1e2acb5559..b7dd625311 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -263,6 +263,11 @@ "collections.create_collection": "Create collection", "collections.delete_collection": "Delete collection", "collections.description_length_hint": "100 characters limit", + "collections.detail.accounts_heading": "Accounts", + "collections.detail.curated_by_author": "Curated by {author}", + "collections.detail.curated_by_you": "Curated by you", + "collections.detail.loading": "Loading collection…", + "collections.detail.share": "Share this collection", "collections.edit_details": "Edit basic details", "collections.edit_settings": "Edit settings", "collections.error_loading_collections": "There was an error when trying to load your collections.", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index d6856b63ac..e2c41f6259 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Límite de 100 caracteres", + "collections.detail.accounts_heading": "Cuentas", + "collections.detail.curated_by_author": "Curado por {author}", + "collections.detail.curated_by_you": "Curado por vos", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar configuración", "collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 4f2110f181..2c5afab168 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Limitado a 100 caracteres", + "collections.detail.accounts_heading": "Cuentas", + "collections.detail.curated_by_author": "Seleccionado por {author}", + "collections.detail.curated_by_you": "Seleccionado por ti", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar configuración", "collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index e98c685ae5..bb0f07ea57 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -141,6 +141,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.column_button": "Hecho", + "account_edit.column_title": "Editar perfil", "account_note.placeholder": "Haz clic para añadir nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro", @@ -261,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Limitado a 100 caracteres", + "collections.detail.accounts_heading": "Cuentas", + "collections.detail.curated_by_author": "Seleccionado por {author}", + "collections.detail.curated_by_you": "Seleccionado por ti", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar datos básicos", "collections.edit_settings": "Cambiar ajustes", "collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 2ab912530a..ab8a2728ab 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -263,6 +263,11 @@ "collections.create_collection": "Luo kokoelma", "collections.delete_collection": "Poista kokoelma", "collections.description_length_hint": "100 merkin rajoitus", + "collections.detail.accounts_heading": "Tilit", + "collections.detail.curated_by_author": "Koonnut {author}", + "collections.detail.curated_by_you": "Itse kokoamasi", + "collections.detail.loading": "Ladataan kokoelmaa…", + "collections.detail.share": "Jaa tämä kokoelma", "collections.edit_details": "Muokkaa perustietoja", "collections.edit_settings": "Muokkaa asetuksia", "collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 7b83533de2..521008913b 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -141,6 +141,8 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Ne plus masquer les notifications", "account.unmute_short": "Ne plus masquer", + "account_edit.column_button": "Terminé", + "account_edit.column_title": "Modifier le profil", "account_note.placeholder": "Cliquez pour ajouter une note", "admin.dashboard.daily_retention": "Taux de rétention des comptes par jour après inscription", "admin.dashboard.monthly_retention": "Taux de rétention des comptes par mois après inscription", @@ -261,6 +263,11 @@ "collections.create_collection": "Créer une collection", "collections.delete_collection": "Supprimer la collection", "collections.description_length_hint": "Maximum 100 caractères", + "collections.detail.accounts_heading": "Comptes", + "collections.detail.curated_by_author": "Organisée par {author}", + "collections.detail.curated_by_you": "Organisée par vous", + "collections.detail.loading": "Chargement de la collection…", + "collections.detail.share": "Partager la collection", "collections.edit_details": "Modifier les informations générales", "collections.edit_settings": "Modifier les paramètres", "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index eee947ec48..1a581e9c9d 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -141,6 +141,8 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Réactiver les notifications", "account.unmute_short": "Ne plus masquer", + "account_edit.column_button": "Terminé", + "account_edit.column_title": "Modifier le profil", "account_note.placeholder": "Cliquez pour ajouter une note", "admin.dashboard.daily_retention": "Taux de rétention des utilisateur·rice·s par jour après inscription", "admin.dashboard.monthly_retention": "Taux de rétention des utilisateur·rice·s par mois après inscription", @@ -261,6 +263,11 @@ "collections.create_collection": "Créer une collection", "collections.delete_collection": "Supprimer la collection", "collections.description_length_hint": "Maximum 100 caractères", + "collections.detail.accounts_heading": "Comptes", + "collections.detail.curated_by_author": "Organisée par {author}", + "collections.detail.curated_by_you": "Organisée par vous", + "collections.detail.loading": "Chargement de la collection…", + "collections.detail.share": "Partager la collection", "collections.edit_details": "Modifier les informations générales", "collections.edit_settings": "Modifier les paramètres", "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 2165b5bf74..e05a85e0ae 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -141,6 +141,8 @@ "account.unmute": "Díbhalbhaigh @{name}", "account.unmute_notifications_short": "Díbhalbhaigh fógraí", "account.unmute_short": "Díbhalbhaigh", + "account_edit.column_button": "Déanta", + "account_edit.column_title": "Cuir Próifíl in Eagar", "account_note.placeholder": "Cliceáil chun nóta a chuir leis", "admin.dashboard.daily_retention": "Ráta coinneála an úsáideora de réir an lae tar éis clárú", "admin.dashboard.monthly_retention": "Ráta coinneála na n-úsáideoirí de réir na míosa tar éis dóibh clárú", @@ -261,6 +263,11 @@ "collections.create_collection": "Cruthaigh bailiúchán", "collections.delete_collection": "Scrios bailiúchán", "collections.description_length_hint": "Teorainn 100 carachtar", + "collections.detail.accounts_heading": "Cuntais", + "collections.detail.curated_by_author": "Curtha i dtoll a chéile ag {author}", + "collections.detail.curated_by_you": "Curtha i dtoll a chéile agatsa", + "collections.detail.loading": "Ag lódáil an bhailiúcháin…", + "collections.detail.share": "Comhroinn an bailiúchán seo", "collections.edit_details": "Cuir sonraí bunúsacha in eagar", "collections.edit_settings": "Socruithe a chur in eagar", "collections.error_loading_collections": "Tharla earráid agus iarracht á déanamh do bhailiúcháin a luchtú.", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index e9a5392676..54263a5d8c 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Límite de 100 caracteres", + "collections.detail.accounts_heading": "Contas", + "collections.detail.curated_by_author": "Seleccionadas por {author}", + "collections.detail.curated_by_you": "Seleccionadas por ti", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar axustes", "collections.error_loading_collections": "Houbo un erro ao intentar cargar as túas coleccións.", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 5a9bf30389..a5808b3ba1 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -263,6 +263,11 @@ "collections.create_collection": "יצירת אוסף", "collections.delete_collection": "מחיקת האוסף", "collections.description_length_hint": "מגבלה של 100 תווים", + "collections.detail.accounts_heading": "חשבונות", + "collections.detail.curated_by_author": "נאצר על ידי {author}", + "collections.detail.curated_by_you": "נאצר על ידיך", + "collections.detail.loading": "טוען אוסף…", + "collections.detail.share": "שיתוף אוסף", "collections.edit_details": "עריכת פרטים בסיסיים", "collections.edit_settings": "עריכת הגדרות", "collections.error_loading_collections": "חלה שגיאה בנסיון לטעון את אוספיך.", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 649128d9c5..92cdb3088b 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -263,6 +263,11 @@ "collections.create_collection": "Búa til safn", "collections.delete_collection": "Eyða safni", "collections.description_length_hint": "100 stafa takmörk", + "collections.detail.accounts_heading": "Aðgangar", + "collections.detail.curated_by_author": "Safnað saman af {author}", + "collections.detail.curated_by_you": "Safnað saman af þér", + "collections.detail.loading": "Hleð inn safni…", + "collections.detail.share": "Deila þessu safni", "collections.edit_details": "Breyta grunnupplýsingum", "collections.edit_settings": "Breyta stillingum", "collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index cf37f6b8e4..846a860fd5 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crea la collezione", "collections.delete_collection": "Cancella la collezione", "collections.description_length_hint": "Limite di 100 caratteri", + "collections.detail.accounts_heading": "Account", + "collections.detail.curated_by_author": "Curata da {author}", + "collections.detail.curated_by_you": "Curata da te", + "collections.detail.loading": "Caricamento della collezione…", + "collections.detail.share": "Condividi questa collezione", "collections.edit_details": "Modifica i dettagli di base", "collections.edit_settings": "Modifica impostazioni", "collections.error_loading_collections": "Si è verificato un errore durante il tentativo di caricare le tue collezioni.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index f5bda95ad2..9731c0756d 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -16,7 +16,9 @@ "account.about": "정보", "account.account_note_header": "개인 메모", "account.activity": "활동", + "account.add_note": "개인 메모 추가", "account.add_or_remove_from_list": "리스트에 추가 혹은 삭제", + "account.badges.admin": "관리자", "account.badges.blocked": "차단함", "account.badges.bot": "자동화됨", "account.badges.domain_blocked": "차단한 도메인", @@ -33,6 +35,7 @@ "account.direct": "@{name} 님에게 개인 멘션", "account.disable_notifications": "@{name} 의 게시물 알림 끄기", "account.domain_blocking": "도메인 차단함", + "account.edit_note": "개인 메모 편집", "account.edit_profile": "프로필 편집", "account.edit_profile_short": "수정", "account.enable_notifications": "@{name} 의 게시물 알림 켜기", @@ -45,6 +48,7 @@ "account.featured.hashtags": "해시태그", "account.featured_tags.last_status_at": "{date}에 마지막으로 게시", "account.featured_tags.last_status_never": "게시물 없음", + "account.filters.all": "모든 활동", "account.filters.boosts_toggle": "부스트 보기", "account.filters.replies_toggle": "답글 보기", "account.follow": "팔로우", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index ad10a2f1ca..a611068521 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -141,6 +141,8 @@ "account.unmute": "@{name} niet langer negeren", "account.unmute_notifications_short": "Meldingen niet langer negeren", "account.unmute_short": "Niet langer negeren", + "account_edit.column_button": "Klaar", + "account_edit.column_title": "Profiel bewerken", "account_note.placeholder": "Klik om een opmerking toe te voegen", "admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie", "admin.dashboard.monthly_retention": "Retentiegraad van gebruikers per maand, vanaf registratie", @@ -244,9 +246,12 @@ "closed_registrations_modal.preamble": "Mastodon is gedecentraliseerd. Op welke server je ook een account hebt, je kunt overal vandaan mensen op deze server volgen en er mee interactie hebben. Je kunt zelfs zelf een Mastodon-server hosten!", "closed_registrations_modal.title": "Registreren op Mastodon", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", + "collections.accounts.empty_description": "Voeg tot {count} accounts toe die je volgt", + "collections.accounts.empty_title": "Deze verzameling is leeg", "collections.collection_description": "Omschrijving", "collections.collection_name": "Naam", "collections.collection_topic": "Onderwerp", + "collections.confirm_account_removal": "Weet je zeker dat je dit account uit deze verzameling wilt verwijderen?", "collections.content_warning": "Inhoudswaarschuwing", "collections.continue": "Doorgaan", "collections.create.accounts_subtitle": "Alleen accounts die je volgt en ontdekt willen worden, kunnen worden toegevoegd.", @@ -258,9 +263,17 @@ "collections.create_collection": "Verzameling aanmaken", "collections.delete_collection": "Verzameling verwijderen", "collections.description_length_hint": "Maximaal 100 karakters", + "collections.detail.accounts_heading": "Accounts", + "collections.detail.curated_by_author": "Samengesteld door {author}", + "collections.detail.curated_by_you": "Samengesteld door jou", + "collections.detail.loading": "Verzameling laden…", + "collections.detail.share": "Deze verzameling delen", "collections.edit_details": "Basisgegevens bewerken", "collections.edit_settings": "Instellingen bewerken", "collections.error_loading_collections": "Er is een fout opgetreden bij het laden van je verzamelingen.", + "collections.hints.accounts_counter": "{count} / {max} accounts", + "collections.hints.add_more_accounts": "Voeg ten minste {count, plural, one {# account} other {# accounts}} toe om door te gaan", + "collections.hints.can_not_remove_more_accounts": "Verzamelingen moeten ten minste {count, plural, one {# account} other {# accounts}} bevatten. Meer accounts verwijderen is niet mogelijk.", "collections.last_updated_at": "Laatst bijgewerkt: {date}", "collections.manage_accounts": "Accounts beheren", "collections.manage_accounts_in_collection": "Accounts in deze verzameling beheren", @@ -269,6 +282,9 @@ "collections.name_length_hint": "100 tekens limiet", "collections.new_collection": "Nieuwe verzameling", "collections.no_collections_yet": "Nog geen verzamelingen.", + "collections.remove_account": "Deze account verwijderen", + "collections.search_accounts_label": "Zoek naar accounts om toe te voegen…", + "collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd", "collections.topic_hint": "Voeg een hashtag toe die anderen helpt het hoofdonderwerp van deze collectie te begrijpen.", "collections.view_collection": "Verzameling bekijken", "collections.visibility_public": "Openbaar", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index e0ddb7fbaf..51a2f78547 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -260,6 +260,11 @@ "collections.create_collection": "Krijoni koleksion", "collections.delete_collection": "Fshije koleksionin", "collections.description_length_hint": "Kufi prej 100 shenjash", + "collections.detail.accounts_heading": "Llogari", + "collections.detail.curated_by_author": "Në kujdesin e {author}", + "collections.detail.curated_by_you": "Nën kujdesin tuaj", + "collections.detail.loading": "Po ngarkohet koleksion…", + "collections.detail.share": "Ndajeni këtë koleksion me të tjerë", "collections.edit_details": "Përpunoni hollësi bazë", "collections.edit_settings": "Përpunoni rregullime", "collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 80a1fe8bf3..7d9b2d2024 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -200,6 +200,7 @@ "collections.create_a_collection_hint": "Skapa en samling för att rekommendera eller dela dina favoritkonton med andra.", "collections.create_collection": "Skapa samling", "collections.delete_collection": "Radera samling", + "collections.detail.accounts_heading": "Konton", "collections.error_loading_collections": "Det uppstod ett fel när dina samlingar skulle laddas.", "collections.hints.accounts_counter": "{count} / {max} konton", "collections.no_collections_yet": "Inga samlingar än.", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 6efc24ac74..34ee18014d 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -141,6 +141,8 @@ "account.unmute": "@{name} adlı kişinin sesini aç", "account.unmute_notifications_short": "Bildirimlerin sesini aç", "account.unmute_short": "Susturmayı kaldır", + "account_edit.column_button": "Tamamlandı", + "account_edit.column_title": "Profili Düzenle", "account_note.placeholder": "Not eklemek için tıklayın", "admin.dashboard.daily_retention": "Kayıttan sonra günlük kullanıcı saklama oranı", "admin.dashboard.monthly_retention": "Kayıttan sonra aylık kullanıcı saklama oranı", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 09deb8244f..a956d58b00 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -263,6 +263,11 @@ "collections.create_collection": "Tạo collection", "collections.delete_collection": "Xóa collection", "collections.description_length_hint": "Giới hạn 100 ký tự", + "collections.detail.accounts_heading": "Tài khoản", + "collections.detail.curated_by_author": "Tuyển chọn bởi {author}", + "collections.detail.curated_by_you": "Tuyển chọn bởi bạn", + "collections.detail.loading": "Đang tải collection…", + "collections.detail.share": "Chia sẻ collection này", "collections.edit_details": "Sửa thông tin cơ bản", "collections.edit_settings": "Sửa cài đặt", "collections.error_loading_collections": "Đã xảy ra lỗi khi tải những collection của bạn.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 749f2ee916..9ecfba8a6a 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -141,6 +141,8 @@ "account.unmute": "不再隐藏 @{name}", "account.unmute_notifications_short": "恢复通知", "account.unmute_short": "取消隐藏", + "account_edit.column_button": "完成", + "account_edit.column_title": "修改个人资料", "account_note.placeholder": "点击添加备注", "admin.dashboard.daily_retention": "注册后用户留存率(按日计算)", "admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)", @@ -261,6 +263,11 @@ "collections.create_collection": "创建收藏列表", "collections.delete_collection": "删除收藏列表", "collections.description_length_hint": "100字限制", + "collections.detail.accounts_heading": "账号", + "collections.detail.curated_by_author": "由 {author} 精心挑选", + "collections.detail.curated_by_you": "由你精心挑选", + "collections.detail.loading": "正在加载收藏列表…", + "collections.detail.share": "分享此收藏列表", "collections.edit_details": "编辑基本信息", "collections.edit_settings": "编辑设置", "collections.error_loading_collections": "加载你的收藏列表时发生错误。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index b5798703f1..cb9cb725d1 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -263,6 +263,11 @@ "collections.create_collection": "建立收藏名單", "collections.delete_collection": "刪除收藏名單", "collections.description_length_hint": "100 字限制", + "collections.detail.accounts_heading": "帳號", + "collections.detail.curated_by_author": "由 {author} 精選", + "collections.detail.curated_by_you": "由您精選", + "collections.detail.loading": "讀取收藏名單中...", + "collections.detail.share": "分享此收藏名單", "collections.edit_details": "編輯基本資料", "collections.edit_settings": "編輯設定", "collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。", diff --git a/config/locales/da.yml b/config/locales/da.yml index f917fdca53..2e355c23bc 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1499,7 +1499,7 @@ da: copy: Kopier delete: Slet deselect: Afmarkér alle - none: Intet + none: Ingen order_by: Sortér efter save_changes: Gem ændringer select_all_matching_items: @@ -1918,8 +1918,8 @@ da: account_suspension: Kontosuspendering (%{target_name}) domain_block: Serversuspendering (%{target_name}) user_domain_block: "%{target_name} blev blokeret" - lost_followers: Tabte følgere - lost_follows: Mistet følger + lost_followers: Mistet følgere + lost_follows: Mistet fulgte preamble: Du kan miste fulgte og følgere, når du blokerer et domæne, eller når dine moderatorer beslutter at suspendere en fjernserver. Når det sker, kan du downloade lister over afbrudte forhold til inspektion og eventuelt import til en anden server. purged: Oplysninger om denne server er blevet renset af serveradministratoreren. type: Begivenhed diff --git a/config/locales/doorkeeper.es-AR.yml b/config/locales/doorkeeper.es-AR.yml index be075121b0..35f7c22f09 100644 --- a/config/locales/doorkeeper.es-AR.yml +++ b/config/locales/doorkeeper.es-AR.yml @@ -83,6 +83,10 @@ es-AR: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso fallaron debido a que "Doorkeeper.configure.resource_owner_from_credentials" está sin configurar. invalid_client: La autenticación del cliente falló debido a que es un cliente desconocido, o no está incluída la autenticación del cliente, o el método de autenticación no está soportado. + invalid_code_challenge_method: + one: El code_challenge_method debe ser %{challenge_methods}. + other: El code_challenge_method debe ser uno de %{challenge_methods}. + zero: El servidor de autorización no soporta PKCE, ya que no hay valores aceptados de code_challenge_method. invalid_grant: La concesión de autorización ofrecida no es válida, venció, se revocó, no coincide con la dirección web de redireccionamiento usada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La dirección web de redireccionamiento incluida no es válida. invalid_request: diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml index 57b8078e44..2c0a726c29 100644 --- a/config/locales/doorkeeper.es.yml +++ b/config/locales/doorkeeper.es.yml @@ -83,6 +83,10 @@ es: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_from_credentials está sin configurar. invalid_client: La autentificación del cliente falló debido o a que es un cliente desconocido o no está incluída la autentificación del cliente o el método de autentificación no está confirmado. + invalid_code_challenge_method: + one: El code_challenge_method debe ser %{challenge_methods}. + other: El code_challenge_method debe ser uno de %{challenge_methods}. + zero: El servidor de autorización no soporta PKCE, ya que no hay valores aceptados de code_challenge_method. invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La URI de redirección incluida no es válida. invalid_request: diff --git a/config/locales/doorkeeper.fr-CA.yml b/config/locales/doorkeeper.fr-CA.yml index a63e280a19..40575f9a9d 100644 --- a/config/locales/doorkeeper.fr-CA.yml +++ b/config/locales/doorkeeper.fr-CA.yml @@ -83,8 +83,6 @@ fr-CA: access_denied: Le/la propriétaire de la ressource ou le serveur d’autorisation a refusé la requête. credential_flow_not_configured: Le flux des identifiants du mot de passe du/de la propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n’est pas configuré. invalid_client: L’authentification du client a échoué à cause d’un client inconnu, d’aucune authentification de client incluse ou d’une méthode d’authentification non prise en charge. - invalid_code_challenge_method: - one: The code_challenge_method must be %{challenge_methods}. invalid_grant: L’autorisation accordée est invalide, expirée, révoquée, ne concorde pas avec l’URI de redirection utilisée dans la requête d’autorisation, ou a été délivrée à un autre client. invalid_redirect_uri: L’URI de redirection n’est pas valide. invalid_request: diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml index fdc642d9cd..4c7d067a0f 100644 --- a/config/locales/doorkeeper.fr.yml +++ b/config/locales/doorkeeper.fr.yml @@ -83,8 +83,6 @@ fr: access_denied: Le propriétaire de la ressource ou le serveur d’autorisation a refusé la requête. credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n’est pas configuré. invalid_client: L’authentification du client a échoué à cause d’un client inconnu, d’aucune authentification de client incluse ou d’une méthode d’authentification non prise en charge. - invalid_code_challenge_method: - one: The code_challenge_method must be %{challenge_methods}. invalid_grant: L’autorisation accordée est invalide, expirée, annulée, ne concorde pas avec l’URL de redirection utilisée dans la requête d’autorisation, ou a été délivrée à un autre client. invalid_redirect_uri: L’URL de redirection n’est pas valide. invalid_request: diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml index 1d04e50f21..dab7746b4f 100644 --- a/config/locales/doorkeeper.nl.yml +++ b/config/locales/doorkeeper.nl.yml @@ -83,6 +83,10 @@ nl: access_denied: De resource-eigenaar of autorisatie-server weigerde het verzoek. credential_flow_not_configured: De wachtwoordgegevens-flow van de resource-eigenaar is mislukt omdat Doorkeeper.configure.resource_owner_from_credentials niet is ingesteld. invalid_client: Clientverificatie is mislukt door een onbekende client, ontbrekende client-authenticatie of een niet ondersteunde authenticatie-methode. + invalid_code_challenge_method: + one: De code_challenge_method moet %{challenge_methods} zijn. + other: De code_challenge_method moet een van %{challenge_methods} zijn. + zero: De autorisatieserver ondersteunt PKCE niet, aangezien er geen geaccepteerde code_challenge_method waarden zijn. invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect-URI die is opgegeven of werd uitgegeven aan een andere client. invalid_redirect_uri: De opgegeven redirect-URI is ongeldig. invalid_request: diff --git a/config/locales/doorkeeper.tr.yml b/config/locales/doorkeeper.tr.yml index d663f025ac..b51e49f32a 100644 --- a/config/locales/doorkeeper.tr.yml +++ b/config/locales/doorkeeper.tr.yml @@ -83,6 +83,10 @@ tr: access_denied: Kaynak sahibi veya yetkilendirme sunucusu isteği reddetti. credential_flow_not_configured: Kaynak Sahibi Parolası Kimlik Bilgileri akışı Doorkeeper.configure.resource_owner_from_credentials 'ın yapılandırılmamış olması nedeniyle başarısız oldu. invalid_client: İstemcinin kimlik doğrulaması bilinmeyen istemci, istemci kimlik doğrulamasının dahil olmaması veya desteklenmeyen kimlik doğrulama yöntemi nedeniyle başarısız oldu. + invalid_code_challenge_method: + one: code_challenge_method %{challenge_methods} olmalıdır. + other: code_challenge_method %{challenge_methods} seçeneklerinden biri olmalıdır. + zero: Yetkilendirme sunucusu kabul edilen code_challenge_method değerleri olmadığı için PKCE'yi desteklemiyor. invalid_grant: Sağlanan yetkilendirme izni geçersiz, süresi dolmuş, iptal edilmiş, yetkilendirme isteğinde kullanılan yönlendirme URL'siyle eşleşmiyor veya başka bir istemciye verilmiş. invalid_redirect_uri: Dahil edilmiş yönlendirme uri'si geçersiz. invalid_request: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 2a51079520..45cb6c367c 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1302,6 +1302,13 @@ ko: hint_html: "팁: 한 시간 동안 다시 암호를 묻지 않을 것입니다." invalid_password: 잘못된 암호 prompt: 계속하려면 암호를 확인하세요 + color_scheme: + auto: 자동 + dark: 어두움 + light: 밝음 + contrast: + auto: 자동 + high: 높음 crypto: errors: invalid_key: 유효하지 않은 Ed25519 또는 Curve25519 키 diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index fb5228a153..76a7deae73 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -234,6 +234,8 @@ ko: setting_always_send_emails: 항상 이메일 알림 보내기 setting_auto_play_gif: 애니메이션 GIF를 자동 재생 setting_boost_modal: 부스트 공개범위 제어 + setting_color_scheme: 색상 구성 + setting_contrast: 대비 setting_default_language: 게시물 언어 setting_default_privacy: 게시물 공개 범위 setting_default_quote_policy: 인용할 수 있는 사람 diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 97b09c97a9..6d25b184c4 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -224,6 +224,7 @@ nl: email: E-mailadres expires_in: Vervalt na fields: Extra velden + filter_action: Filter-actie header: Omslagfoto honeypot: "%{label} (niet invullen)" inbox_url: Inbox-URL van de relayserver From f48a2990042f31d4dfc615a68b30f7b193373831 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Feb 2026 06:11:46 -0500 Subject: [PATCH 02/17] Use validation matchers for `UrlValidator` spec (#37911) --- spec/validators/url_validator_spec.rb | 64 +++++++++------------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb index 55c0347d18..56459b13fc 100644 --- a/spec/validators/url_validator_spec.rb +++ b/spec/validators/url_validator_spec.rb @@ -3,65 +3,45 @@ require 'rails_helper' RSpec.describe URLValidator do + subject { record_class.new } + let(:record_class) do Class.new do include ActiveModel::Validations + def self.name = 'Record' + attr_accessor :profile validates :profile, url: true end end - let(:record) { record_class.new } - describe '#validate_each' do - context 'with a nil value' do - it 'adds errors' do - record.profile = nil + context 'with a nil value' do + it { is_expected.to_not allow_value(nil).for(:profile).with_message(:invalid) } + end - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + context 'with an invalid url scheme' do + let(:invalid_scheme_url) { 'ftp://example.com/page' } - context 'with an invalid url scheme' do - it 'adds errors' do - record.profile = 'ftp://example.com/page' + it { is_expected.to_not allow_value(invalid_scheme_url).for(:profile).with_message(:invalid) } + end - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + context 'without a hostname' do + let(:no_hostname_url) { 'https:///page' } - context 'without a hostname' do - it 'adds errors' do - record.profile = 'https:///page' + it { is_expected.to_not allow_value(no_hostname_url).for(:profile).with_message(:invalid) } + end - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + context 'with an unparseable value' do + let(:non_numeric_port_url) { 'https://host:port/page' } - context 'with an unparseable value' do - it 'adds errors' do - record.profile = 'https://host:port/page' # non-numeric port string causes invalid uri error + it { is_expected.to_not allow_value(non_numeric_port_url).for(:profile).with_message(:invalid) } + end - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + context 'with a valid url' do + let(:valid_url) { 'https://example.com/page' } - context 'with a valid url' do - it 'does not add errors' do - record.profile = 'https://example.com/page' - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end + it { is_expected.to allow_value(valid_url).for(:profile) } end end From 238d0f8e1d04e14a5b6b4e7dfc5a11bddf73f2db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:12:08 +0100 Subject: [PATCH 03/17] Update dependency devise to v5.0.2 (#37903) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 31e331ea6e..e08cc74ea4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -187,7 +187,7 @@ GEM irb (~> 1.10) reline (>= 0.3.8) debug_inspector (1.2.0) - devise (5.0.1) + devise (5.0.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 7.0) From 6f859364fb3ca57898b200673c4c3bf1cc050ffa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:46:32 +0100 Subject: [PATCH 04/17] Update dependency rack to v3.2.5 (#37895) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From e288bf6516d02f74b30271187a3ecae9994f2391 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 19 Feb 2026 13:46:38 +0100 Subject: [PATCH 05/17] Show reported collections in moderation interface (#37898) --- .../admin/collections_controller.rb | 22 ++++++ app/controllers/admin/reports_controller.rb | 4 +- app/models/collection.rb | 1 + app/views/admin/accounts/_account.html.haml | 3 +- app/views/admin/collections/show.html.haml | 21 ++++++ app/views/admin/reports/index.html.haml | 5 ++ app/views/admin/reports/show.html.haml | 21 +++++- app/views/admin/shared/_collection.html.haml | 22 ++++++ .../shared/_collection_batch_row.html.haml | 5 ++ config/locales/en.yml | 14 +++- config/routes/admin.rb | 2 + spec/requests/admin/collections_spec.rb | 20 ++++++ spec/requests/admin/reports_spec.rb | 68 +++++++++++++++++++ 13 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 app/controllers/admin/collections_controller.rb create mode 100644 app/views/admin/collections/show.html.haml create mode 100644 app/views/admin/shared/_collection.html.haml create mode 100644 app/views/admin/shared/_collection_batch_row.html.haml create mode 100644 spec/requests/admin/collections_spec.rb create mode 100644 spec/requests/admin/reports_spec.rb diff --git a/app/controllers/admin/collections_controller.rb b/app/controllers/admin/collections_controller.rb new file mode 100644 index 0000000000..4701500f9f --- /dev/null +++ b/app/controllers/admin/collections_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Admin + class CollectionsController < BaseController + before_action :set_account + before_action :set_collection, only: :show + + def show + authorize @collection, :show? + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def set_collection + @collection = @account.collections.includes(accepted_collection_items: :account).find(params[:id]) + end + end +end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index aa877f1448..44ee7206bf 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -50,7 +50,7 @@ module Admin private def filtered_reports - ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account) + ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account, :collections) end def filter_params @@ -58,7 +58,7 @@ module Admin end def set_report - @report = Report.find(params[:id]) + @report = Report.includes(collections: :accepted_collection_items).find(params[:id]) end end end diff --git a/app/models/collection.rb b/app/models/collection.rb index e11cb73188..d8386e43b4 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -26,6 +26,7 @@ class Collection < ApplicationRecord belongs_to :tag, optional: true has_many :collection_items, dependent: :delete_all + has_many :accepted_collection_items, -> { accepted }, class_name: 'CollectionItem', inverse_of: :collection # rubocop:disable Rails/HasManyOrHasOneDependent has_many :collection_reports, dependent: :delete_all validates :name, presence: true diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 6b5b5efbdc..74f8494562 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -1,6 +1,7 @@ .batch-table__row{ class: [!account.unavailable? && account.user_pending? && 'batch-table__row--attention', (account.unavailable? || account.user_unconfirmed?) && 'batch-table__row--muted'] } %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox - = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id + - if local_assigns[:f].present? + = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id .batch-table__row__content.batch-table__row__content--unpadded %table.accounts-table %tbody diff --git a/app/views/admin/collections/show.html.haml b/app/views/admin/collections/show.html.haml new file mode 100644 index 0000000000..8f29b26309 --- /dev/null +++ b/app/views/admin/collections/show.html.haml @@ -0,0 +1,21 @@ +- content_for :page_title do + = t('admin.collections.collection_title', name: @account.pretty_acct) + +- content_for :heading_actions do + = link_to t('admin.collections.open'), account_collection_path(@account, @collection), class: 'button', target: '_blank', rel: 'noopener' + +%h3= t('admin.collections.contents') + += render 'admin/shared/collection', collection: @collection + +%hr.spacer/ + +%h3= t('admin.collections.accounts') + +.batch-table + .batch-table__toolbar + .batch-table__body + - if @collection.accepted_collection_items.none? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'admin/accounts/account', collection: @collection.accepted_collection_items.map(&:account) diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index b910e1aab5..1049cf733e 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -67,6 +67,11 @@ = material_symbol('photo_camera') = report.media_attachments_count + - if Mastodon::Feature.collections_enabled? + %span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') } + = material_symbol('groups-fill') + = report.collections.size + - if report.forwarded? · = t('admin.reports.forwarded_to', domain: target_account.domain) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index af92b05768..7ea690dc34 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -32,7 +32,7 @@ %hr.spacer/ %h3 - = t 'admin.reports.statuses' + = t 'admin.reports.reported_content' %small.section-skip-link = link_to '#actions' do = material_symbol 'keyboard_double_arrow_down' @@ -41,6 +41,9 @@ %p = t 'admin.reports.statuses_description_html' +%h4 + = t 'admin.reports.statuses' + = form_with model: @form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id) do |f| .batch-table .batch-table__toolbar @@ -58,6 +61,22 @@ - else = render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f } +- if Mastodon::Feature.collections_enabled? + %h4 + = t 'admin.reports.collections' + + %form + .batch-table + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + -# = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + .batch-table__body + - if @report.collections.empty? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection + - if @report.unresolved? %hr.spacer/ diff --git a/app/views/admin/shared/_collection.html.haml b/app/views/admin/shared/_collection.html.haml new file mode 100644 index 0000000000..e300a986ba --- /dev/null +++ b/app/views/admin/shared/_collection.html.haml @@ -0,0 +1,22 @@ +.status__card + - if collection.tag.present? + .status__prepend + = link_to collection.tag.formatted_name, admin_tag_path(collection.tag_id) + + .status__content + %h6= collection.name + + %p= collection.description + + .detailed-status__meta + = conditional_link_to can?(:show, collection), admin_account_collection_path(collection.account.id, collection), class: 'detailed-status__datetime' do + %time.formatted{ datetime: collection.created_at.iso8601, title: l(collection.created_at) }><= l(collection.created_at) + - if collection.sensitive? +  · + = material_symbol('visibility_off') + = t('stream_entries.sensitive_content') +  · + = t('admin.collections.number_of_accounts', count: collection.accepted_collection_items.size) +  · + = link_to account_collection_path(collection.account, collection), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do + = t('admin.collections.view_publicly') diff --git a/app/views/admin/shared/_collection_batch_row.html.haml b/app/views/admin/shared/_collection_batch_row.html.haml new file mode 100644 index 0000000000..8bf7857e95 --- /dev/null +++ b/app/views/admin/shared/_collection_batch_row.html.haml @@ -0,0 +1,5 @@ +.batch-table__row + %label.batch-table__row__select.batch-checkbox + -# = f.check_box :collection_ids, { multiple: true, include_hidden: false }, collection.id + .batch-table__row__content + = render partial: 'admin/shared/collection', object: collection diff --git a/config/locales/en.yml b/config/locales/en.yml index 2c777e72d3..71c559d738 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -56,6 +56,7 @@ en: label: Change role no_role: No role title: Change role for %{username} + collections: Collections confirm: Confirm confirmed: Confirmed confirming: Confirming @@ -340,6 +341,15 @@ en: unpublish: Unpublish unpublished_msg: Announcement successfully unpublished! updated_msg: Announcement successfully updated! + collections: + accounts: Accounts + collection_title: Collection by %{name} + contents: Contents + number_of_accounts: + one: 1 account + other: "%{count} accounts" + open: Open + view_publicly: View publicly critical_update_pending: Critical update pending custom_emojis: assign_category: Assign category @@ -679,6 +689,7 @@ en: cancel: Cancel category: Category category_description_html: The reason this account and/or content was reported will be cited in communication with the reported account + collections: Collections comment: none: None comment_description_html: 'To provide more information, %{name} wrote:' @@ -708,12 +719,13 @@ en: report: 'Report #%{id}' reported_account: Reported account reported_by: Reported by + reported_content: Reported content reported_with_application: Reported with application resolved: Resolved resolved_msg: Report successfully resolved! skip_to_actions: Skip to actions status: Status - statuses: Reported content + statuses: Posts statuses_description_html: Offending content will be cited in communication with the reported account summary: action_preambles: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 97f84da44e..84beea4611 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -153,6 +153,8 @@ namespace :admin do resource :reset, only: [:create] resource :action, only: [:new, :create], controller: 'account_actions' + resources :collections, only: [:show] + resources :statuses, only: [:index, :show] do collection do post :batch diff --git a/spec/requests/admin/collections_spec.rb b/spec/requests/admin/collections_spec.rb new file mode 100644 index 0000000000..0e87e277ae --- /dev/null +++ b/spec/requests/admin/collections_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Collections' do + describe 'GET /admin/accounts/:account_id/collections/:id' do + let(:collection) { Fabricate(:collection) } + + before do + sign_in Fabricate(:admin_user) + end + + it 'returns success' do + get admin_account_collection_path(collection.account_id, collection) + + expect(response) + .to have_http_status(200) + end + end +end diff --git a/spec/requests/admin/reports_spec.rb b/spec/requests/admin/reports_spec.rb new file mode 100644 index 0000000000..d44db63795 --- /dev/null +++ b/spec/requests/admin/reports_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Reports' do + describe 'GET /admin/reports' do + before do + sign_in Fabricate(:admin_user) + + Fabricate.times(2, :report) + end + + it 'returns success' do + get admin_reports_path + + expect(response) + .to have_http_status(200) + end + end + + describe 'GET /admin/reports/:id' do + let(:report) { Fabricate(:report) } + + before do + sign_in Fabricate(:admin_user) + end + + shared_examples 'successful return' do + it 'returns success' do + get admin_report_path(report) + + expect(response) + .to have_http_status(200) + end + end + + context 'with a simple report' do + it_behaves_like 'successful return' + end + + context 'with a reported status' do + before do + status = Fabricate(:status, account: report.target_account) + report.update(status_ids: [status.id]) + end + + it_behaves_like 'successful return' + end + + context 'with a reported collection', feature: :collections do + before do + report.collections << Fabricate(:collection, account: report.target_account) + end + + it_behaves_like 'successful return' + end + + context 'with both status and collection', feature: :collections do + before do + status = Fabricate(:status, account: report.target_account) + report.update(status_ids: [status.id]) + report.collections << Fabricate(:collection, account: report.target_account) + end + + it_behaves_like 'successful return' + end + end +end From 40f92f3af8058d7bf7b98b344bf6d8b4e2f5ab28 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Feb 2026 07:50:09 -0500 Subject: [PATCH 06/17] Use validation matchers for `UnreservedUsernameValidator` spec (#37910) --- .../unreserved_username_validator_spec.rb | 142 ++++++------------ 1 file changed, 48 insertions(+), 94 deletions(-) diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb index 55dca7db84..aa90ecb0f8 100644 --- a/spec/validators/unreserved_username_validator_spec.rb +++ b/spec/validators/unreserved_username_validator_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe UnreservedUsernameValidator do + subject { record_class.new } + let(:record_class) do Class.new do include ActiveModel::Validations @@ -11,115 +13,67 @@ RSpec.describe UnreservedUsernameValidator do validates_with UnreservedUsernameValidator - def self.name - 'Foo' - end + def self.name = 'Record' end end - let(:record) { record_class.new } + context 'when username is nil' do + it { is_expected.to allow_value(nil).for(:username) } + end - describe '#validate' do - context 'when username is nil' do - it 'does not add errors' do - record.username = nil + context 'when PAM is enabled' do + before do + allow(Devise).to receive(:pam_authentication).and_return(true) + end - expect(record).to be_valid - expect(record.errors).to be_empty + context 'with a pam service available' do + let(:service) { double } + let(:pam_class) do + Class.new do + def self.account(service, username); end + end + end + + before do + stub_const('Rpam2', pam_class) + allow(Devise).to receive(:pam_controlled_service).and_return(service) + end + + context 'when the account exists' do + before do + allow(Rpam2).to receive(:account).with(service, 'username').and_return(true) + end + + it { is_expected.to_not allow_value('username').for(:username).with_message(:reserved) } + end + + context 'when the account does not exist' do + before do + allow(Rpam2).to receive(:account).with(service, 'username').and_return(false) + end + + it { is_expected.to allow_value('username').for(:username) } end end - context 'when PAM is enabled' do + context 'without a pam service' do before do - allow(Devise).to receive(:pam_authentication).and_return(true) + allow(Devise).to receive(:pam_controlled_service).and_return(false) end - context 'with a pam service available' do - let(:service) { double } - let(:pam_class) do - Class.new do - def self.account(service, username); end - end - end - - before do - stub_const('Rpam2', pam_class) - allow(Devise).to receive(:pam_controlled_service).and_return(service) - end - - context 'when the account exists' do - before do - allow(Rpam2).to receive(:account).with(service, 'username').and_return(true) - end - - it 'adds errors to the record' do - record.username = 'username' - - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:username) - expect(record.errors.first.type).to eq(:reserved) - end - end - - context 'when the account does not exist' do - before do - allow(Rpam2).to receive(:account).with(service, 'username').and_return(false) - end - - it 'does not add errors to the record' do - record.username = 'username' - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end + context 'when there are not any reserved usernames' do + it { is_expected.to allow_value('username').for(:username) } end - context 'without a pam service' do - before do - allow(Devise).to receive(:pam_controlled_service).and_return(false) + context 'when there are reserved usernames' do + before { %w(alice bob).each { |username| Fabricate(:username_block, exact: true, username:) } } + + context 'when the username is reserved' do + it { is_expected.to_not allow_values('alice', 'bob').for(:username).with_message(:reserved) } end - context 'when there are not any reserved usernames' do - before do - stub_reserved_usernames(nil) - end - - it 'does not add errors to the record' do - record.username = 'username' - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end - - context 'when there are reserved usernames' do - before do - stub_reserved_usernames(%w(alice bob)) - end - - context 'when the username is reserved' do - it 'adds errors to the record' do - record.username = 'alice' - - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:username) - expect(record.errors.first.type).to eq(:reserved) - end - end - - context 'when the username is not reserved' do - it 'does not add errors to the record' do - record.username = 'chris' - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end - end - - def stub_reserved_usernames(value) - value&.each { |str| Fabricate(:username_block, username: str, exact: true) } + context 'when the username is not reserved' do + it { is_expected.to allow_value('chris').for(:username) } end end end From 157583659a1cc899c0fed212a288927a3ed25597 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Feb 2026 08:14:12 -0500 Subject: [PATCH 07/17] Use validation matchers for `UniqueUsernameValidator` spec (#37909) --- .../unique_username_validator_spec.rb | 89 +++++++++---------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/spec/validators/unique_username_validator_spec.rb b/spec/validators/unique_username_validator_spec.rb index 037ddadb9f..12534714ea 100644 --- a/spec/validators/unique_username_validator_spec.rb +++ b/spec/validators/unique_username_validator_spec.rb @@ -3,72 +3,63 @@ require 'rails_helper' RSpec.describe UniqueUsernameValidator do - describe '#validate' do - context 'when local account' do - it 'does not add errors if username is nil' do - account = instance_double(Account, username: nil, domain: nil, persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) - end + subject { Fabricate.build :account, username: 'abcdef', domain: } - it 'does not add errors when existing one is subject itself' do - account = Fabricate(:account, username: 'abcdef') - expect(account).to be_valid - end + context 'when local account' do + let(:domain) { nil } - it 'adds an error when the username is already used with ignoring cases' do - Fabricate(:account, username: 'ABCdef') - account = instance_double(Account, username: 'abcDEF', domain: nil, persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to have_received(:add) - end + context 'when record is persisted and checking own name' do + before { subject.save } - it 'does not add errors when same username remote account exists' do - Fabricate(:account, username: 'abcdef', domain: 'example.com') - account = instance_double(Account, username: 'abcdef', domain: nil, persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) - end + it { is_expected.to allow_value(subject.username).for(:username) } + end + + context 'when username case insensitive in use already' do + before { Fabricate :account, username: 'ABCdef' } + + it { is_expected.to_not allow_value('abcDEF').for(:username).with_message(:taken) } + end + + context 'when username on remote account is in use' do + before { Fabricate :account, username: 'ABCdef', domain: 'host.example' } + + it { is_expected.to allow_value('abcDEF').for(:username) } end end context 'when remote account' do - it 'does not add errors if username is nil' do - account = instance_double(Account, username: nil, domain: 'example.com', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) + let(:domain) { 'host.example' } + + context 'when record is persisted and checking own name' do + before { subject.save } + + it { is_expected.to allow_value('abcdef').for(:username) } end - it 'does not add errors when existing one is subject itself' do - account = Fabricate(:account, username: 'abcdef', domain: 'example.com') - expect(account).to be_valid + context 'when username case insensitive in use already' do + before { Fabricate :account, username: 'ABCdef', domain: 'host.example' } + + it { is_expected.to_not allow_value('abcDEF').for(:username) } end - it 'adds an error when the username is already used with ignoring cases' do - Fabricate(:account, username: 'ABCdef', domain: 'example.com') - account = instance_double(Account, username: 'abcDEF', domain: 'example.com', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to have_received(:add) + context 'when domain case insensitive in use already' do + before { Fabricate :account, username: 'ABCdef', domain: 'HOST.EXAMPLE' } + + it { is_expected.to_not allow_value('abcDEF').for(:username) } end - it 'adds an error when the domain is already used with ignoring cases' do - Fabricate(:account, username: 'ABCdef', domain: 'example.com') - account = instance_double(Account, username: 'ABCdef', domain: 'EXAMPLE.COM', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to have_received(:add) - end + context 'when same username on other domain is in use already' do + before { Fabricate :account, username: 'abcdef', domain: 'other.example' } - it 'does not add errors when account with the same username and another domain exists' do - Fabricate(:account, username: 'abcdef', domain: 'example.com') - account = instance_double(Account, username: 'abcdef', domain: 'example2.com', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) + it { is_expected.to allow_value('abcdef').for(:username) } end end - private + context 'when account has blank username' do + subject { described_class.new.validate(account) } - def activemodel_errors - instance_double(ActiveModel::Errors, add: nil) + let(:account) { Fabricate.build :account, username: nil } + + it { is_expected.to be_nil } end end From ed4787c1b1de1d7491719be0f45b7a7f16af8c0d Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 19 Feb 2026 14:53:29 +0100 Subject: [PATCH 08/17] Profile editing: Name and bio (#37907) --- .../mastodon/components/account_bio.tsx | 2 +- .../form_fields/text_area_field.stories.tsx | 7 ++ .../form_fields/text_area_field.tsx | 91 ++++++++------ .../account_edit/components/bio_modal.tsx | 94 ++++++++++++++ .../account_edit/components/char_counter.tsx | 27 ++++ .../account_edit/components/emoji_picker.tsx | 27 ++++ .../account_edit/components/name_modal.tsx | 87 +++++++++++++ .../account_edit/components/section.tsx | 62 ++++++++++ .../mastodon/features/account_edit/index.tsx | 117 +++++++++++++++++- .../features/account_edit/styles.module.scss | 106 ++++++++++++++-- .../account_timeline/modals/note_modal.tsx | 2 +- .../mastodon/features/emoji/utils.ts | 18 +++ .../confirmation_modal.tsx | 16 ++- .../components/confirmation_modals/index.ts | 1 + .../quiet_post_quote_info.tsx | 4 +- .../features/ui/components/modal_root.jsx | 2 + app/javascript/mastodon/locales/en.json | 17 +++ 17 files changed, 619 insertions(+), 61 deletions(-) create mode 100644 app/javascript/mastodon/features/account_edit/components/bio_modal.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/char_counter.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/emoji_picker.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/name_modal.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/section.tsx diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index 6d4ab1ddd4..75067530c9 100644 --- a/app/javascript/mastodon/components/account_bio.tsx +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -6,7 +6,7 @@ import { EmojiHTML } from './emoji/html'; import { useElementHandledLink } from './status/handled_link'; interface AccountBioProps { - className: string; + className?: string; accountId: string; showDropdown?: boolean; } diff --git a/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx b/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx index 448af8a28e..190239aee2 100644 --- a/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx +++ b/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx @@ -42,6 +42,13 @@ export const WithError: Story = { }, }; +export const AutoSize: Story = { + args: { + autoSize: true, + defaultValue: 'This textarea will grow as you type more lines.', + }, +}; + export const Plain: Story = { render(args) { return