Merge commit 'f652c54c3334890176331f62150559a96eeb41ae' into glitch-soc/merge-upstream

Conflicts:
- `app/services/backup_service.rb`:
  Upstream refactored activity serialization while glitch-soc passed an extra argument.
  Followed upstream's refactor, keeping our extra argument.
This commit is contained in:
Claire
2026-02-05 12:43:12 +01:00
108 changed files with 1228 additions and 995 deletions

View File

@@ -28,7 +28,7 @@ gem 'bootsnap', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
gem 'devise', '~> 4.9'
gem 'devise'
gem 'devise-two-factor'
group :pam_authentication, optional: true do

View File

@@ -131,7 +131,7 @@ GEM
blurhash (0.1.8)
bootsnap (1.20.1)
msgpack (~> 1.2)
brakeman (8.0.1)
brakeman (8.0.2)
racc
browser (6.2.0)
builder (3.3.0)
@@ -187,10 +187,10 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
debug_inspector (1.2.0)
devise (4.9.4)
devise (5.0.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
railties (>= 7.0)
responders
warden (~> 1.2.3)
devise-two-factor (6.4.0)
@@ -390,7 +390,7 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
kt-paperclip (7.2.2)
kt-paperclip (7.3.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
marcel (~> 1.0.1)
@@ -446,7 +446,7 @@ GEM
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0924)
mime-types-data (3.2026.0127)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (6.0.1)
@@ -862,7 +862,7 @@ GEM
unicode-display_width (>= 1.1.1, < 4)
terrapin (1.1.1)
climate_control
test-prof (1.5.1)
test-prof (1.5.2)
thor (1.5.0)
tilt (2.7.0)
timeout (0.6.0)
@@ -965,7 +965,7 @@ DEPENDENCIES
csv (~> 3.2)
database_cleaner-active_record
debug (~> 1.8)
devise (~> 4.9)
devise
devise-two-factor
devise_pam_authenticatable2 (~> 9.2)
discard (~> 1.2)

View File

@@ -26,7 +26,7 @@ class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::AddSerializer,
serializer: ActivityPub::AddNoteSerializer,
adapter: ActivityPub::Adapter
).as_json
@@ -36,7 +36,7 @@ class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
def distribute_remove_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::RemoveSerializer,
serializer: ActivityPub::RemoveNoteSerializer,
adapter: ActivityPub::Adapter
).as_json

View File

@@ -59,7 +59,7 @@ class Api::V1Alpha::CollectionsController < Api::BaseController
def destroy
authorize @collection, :destroy?
@collection.destroy
DeleteCollectionService.new.call(@collection)
head 200
end

View File

@@ -197,14 +197,14 @@ class Auth::SessionsController < Devise::SessionsController
"2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}"
end
def respond_to_on_destroy
def respond_to_on_destroy(**)
respond_to do |format|
format.json do
render json: {
redirect_to: after_sign_out_path_for(resource_name),
}, status: 200
end
format.all { super }
format.all { super(**) }
end
end
end

View File

@@ -37,7 +37,7 @@ class StatusesController < ApplicationController
def activity
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status), content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
render_with_cache json: @status, content_type: 'application/activity+json', serializer: activity_serializer, adapter: ActivityPub::Adapter
end
def embed
@@ -69,4 +69,8 @@ class StatusesController < ApplicationController
def redirect_to_original
redirect_to(ActivityPub::TagManager.instance.url_for(@status.reblog), allow_other_host: true) if @status.reblog?
end
def activity_serializer
@status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer
end
end

View File

@@ -233,12 +233,10 @@
"column.bookmarks": "Закладкі",
"column.collections": "Мае калекцыі",
"column.community": "Лакальная стужка",
"column.create_collection": "Стварыць калекцыю",
"column.create_list": "Стварыць спіс",
"column.direct": "Прыватныя згадванні",
"column.directory": "Праглядзець профілі",
"column.domain_blocks": "Заблакіраваныя дамены",
"column.edit_collection": "Змяніць калекцыю",
"column.edit_list": "Рэдагаваць спіс",
"column.favourites": "Упадабанае",
"column.firehose": "Стужкі",

View File

@@ -227,12 +227,10 @@
"column.bookmarks": "Marcadors",
"column.collections": "Les meves coŀleccions",
"column.community": "Línia de temps local",
"column.create_collection": "Crear una coŀlecció",
"column.create_list": "Crea una llista",
"column.direct": "Mencions privades",
"column.directory": "Navega pels perfils",
"column.domain_blocks": "Dominis blocats",
"column.edit_collection": "Editar la col·lecció",
"column.edit_list": "Edita la llista",
"column.favourites": "Favorits",
"column.firehose": "Tuts en directe",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Llyfrnodau",
"column.collections": "Fy nghasgliadau",
"column.community": "Ffrwd lleol",
"column.create_collection": "Creu casgliad",
"column.create_list": "Creu rhestr",
"column.direct": "Crybwylliadau preifat",
"column.directory": "Pori proffiliau",
"column.domain_blocks": "Parthau wedi'u rhwystro",
"column.edit_collection": "Golygu casgliad",
"column.edit_list": "Golygu rhestr",
"column.favourites": "Ffefrynnau",
"column.firehose": "Ffrydiau byw",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Beskrivelse",
"collections.collection_name": "Navn",
"collections.collection_topic": "Emne",
"collections.content_warning": "Indholdsadvarsel",
"collections.continue": "Fortsæt",
"collections.create.accounts_subtitle": "Kun konti, du følger, og som har tilmeldt sig opdagelse, kan tilføjes.",
"collections.create.accounts_title": "Hvem vil du fremhæve i denne samling?",
"collections.create.basic_details_title": "Grundlæggende oplysninger",
"collections.create.settings_title": "Indstillinger",
"collections.create.steps": "Trin {step}/{total}",
"collections.create_a_collection_hint": "Opret en samling for at anbefale eller dele dine yndlingskonti med andre.",
"collections.create_collection": "Opret samling",
"collections.delete_collection": "Slet samling",
"collections.description_length_hint": "Begrænset til 100 tegn",
"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.",
"collections.manage_accounts": "Administrer konti",
"collections.manage_accounts_in_collection": "Administrer konti i denne samling",
"collections.mark_as_sensitive": "Markér som sensitiv",
"collections.mark_as_sensitive_hint": "Skjuler samlingens beskrivelse og konti bag en indholdsadvarsel. Samlingens navn vil stadig være synligt.",
"collections.name_length_hint": "Begrænset til 100 tegn",
"collections.new_collection": "Ny samling",
"collections.no_collections_yet": "Ingen samlinger endnu.",
"collections.topic_hint": "Tilføj et hashtag, der hjælper andre med at forstå det overordnede emne for denne samling.",
"collections.view_collection": "Vis samling",
"collections.visibility_public": "Offentlig",
"collections.visibility_public_hint": "Kan opdages i søgeresultater og andre områder, hvor anbefalinger vises.",
"collections.visibility_title": "Synlighed",
"collections.visibility_unlisted": "Skjul offentligt",
"collections.visibility_unlisted_hint": "Synlig for alle, der har et link. Skjult i søgeresultater og anbefalinger.",
"column.about": "Om",
"column.blocks": "Blokerede brugere",
"column.bookmarks": "Bogmærker",
"column.collections": "Mine samlinger",
"column.community": "Lokal tidslinje",
"column.create_collection": "Opret samling",
"column.create_list": "Opret liste",
"column.direct": "Private omtaler",
"column.directory": "Gennemse profiler",
"column.domain_blocks": "Blokerede domæner",
"column.edit_collection": "Rediger samling",
"column.edit_list": "Redigér liste",
"column.favourites": "Favoritter",
"column.firehose": "Live feeds",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Beschreibung",
"collections.collection_name": "Titel",
"collections.collection_topic": "Thema",
"collections.content_warning": "Inhaltswarnung",
"collections.continue": "Fortfahren",
"collections.create.accounts_subtitle": "Du kannst nur Profile hinzufügen, denen du folgst und das Hinzufügen gestatten.",
"collections.create.accounts_title": "Wen möchtest du in dieser Sammlung präsentieren?",
"collections.create.basic_details_title": "Allgemeine Informationen",
"collections.create.settings_title": "Einstellungen",
"collections.create.steps": "Schritt {step}/{total}",
"collections.create_a_collection_hint": "Erstelle eine Sammlung, um deine Lieblingsprofile anderen zu empfehlen oder sie zu teilen.",
"collections.create_collection": "Sammlung erstellen",
"collections.delete_collection": "Sammlung löschen",
"collections.description_length_hint": "Maximal 100 Zeichen",
"collections.edit_details": "Allgemeine Informationen bearbeiten",
"collections.edit_settings": "Einstellungen bearbeiten",
"collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.",
"collections.manage_accounts": "Profile verwalten",
"collections.manage_accounts_in_collection": "Profile in dieser Sammlung verwalten",
"collections.mark_as_sensitive": "Mit Inhaltswarnung versehen",
"collections.mark_as_sensitive_hint": "Die Beschreibung sowie enthaltenen Profile werden durch eine Inhaltswarnung ausgeblendet. Der Titel bleibt weiterhin sichtbar.",
"collections.name_length_hint": "Maximal 100 Zeichen",
"collections.new_collection": "Neue Sammlung",
"collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.",
"collections.topic_hint": "Ein Hashtag hilft anderen dabei, das zentrale Thema dieser Sammlung besser zu verstehen.",
"collections.view_collection": "Sammlungen anzeigen",
"collections.visibility_public": "Öffentlich",
"collections.visibility_public_hint": "Wird in den Suchergebnissen und anderen Bereichen mit Empfehlungen angezeigt.",
"collections.visibility_title": "Sichtbarkeit",
"collections.visibility_unlisted": "Nicht gelistet",
"collections.visibility_unlisted_hint": "Für alle mit einem Link sichtbar. Wird vor Suchergebnissen und Empfehlungen verborgen.",
"column.about": "Über",
"column.blocks": "Blockierte Profile",
"column.bookmarks": "Lesezeichen",
"column.collections": "Meine Sammlungen",
"column.community": "Lokale Timeline",
"column.create_collection": "Sammlung erstellen",
"column.create_list": "Liste erstellen",
"column.direct": "Private Erwähnungen",
"column.directory": "Profile durchstöbern",
"column.domain_blocks": "Blockierte Domains",
"column.edit_collection": "Sammlung bearbeiten",
"column.edit_list": "Liste bearbeiten",
"column.favourites": "Favoriten",
"column.firehose": "Live-Feeds",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Περιγραφή",
"collections.collection_name": "Όνομα",
"collections.collection_topic": "Θέμα",
"collections.content_warning": "Προειδοποίηση περιεχομένου",
"collections.continue": "Συνέχεια",
"collections.create.accounts_subtitle": "Μόνο οι λογαριασμοί που ακολουθείτε που έχουν επιλέξει ανακάλυψη μπορούν να προστεθούν.",
"collections.create.accounts_title": "Ποιον θα αναδείξετε σε αυτήν τη συλλογή;",
"collections.create.basic_details_title": "Βασικά στοιχεία",
"collections.create.settings_title": "Ρυθμίσεις",
"collections.create.steps": "Βήμα {step}/{total}",
"collections.create_a_collection_hint": "Δημιουργήστε μια συλλογή για να προτείνετε ή να μοιραστείτε τους αγαπημένους σας λογαριασμούς με άλλους.",
"collections.create_collection": "Δημιουργία συλλογής",
"collections.delete_collection": "Διαγραφή συλλογής",
"collections.description_length_hint": "Όριο 100 χαρακτήρων",
"collections.edit_details": "Επεξεργασία βασικών στοιχείων",
"collections.edit_settings": "Επεξεργασία ρυθμίσεων",
"collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.",
"collections.manage_accounts": "Διαχείριση λογαριασμών",
"collections.manage_accounts_in_collection": "Διαχείριση λογαριασμών σε αυτήν τη συλλογή",
"collections.mark_as_sensitive": "Σήμανση ως ευαίσθητο",
"collections.mark_as_sensitive_hint": "Κρύβει την περιγραφή και τους λογαριασμούς της συλλογής πίσω από μια προειδοποίηση περιεχομένου. Το όνομα της συλλογής θα είναι ακόμη ορατό.",
"collections.name_length_hint": "Όριο 100 χαρακτήρων",
"collections.new_collection": "Νέα συλλογή",
"collections.no_collections_yet": "Καμία συλλογή ακόμη.",
"collections.topic_hint": "Προσθέστε μια ετικέτα που βοηθά άλλους να κατανοήσουν το κύριο θέμα αυτής της συλλογής.",
"collections.view_collection": "Προβολή συλλογής",
"collections.visibility_public": "Δημόσια",
"collections.visibility_public_hint": "Ανιχνεύσιμη στα αποτελέσματα αναζήτησης και σε άλλα σημεία όπου εμφανίζονται προτάσεις.",
"collections.visibility_title": "Ορατότητα",
"collections.visibility_unlisted": "Μη καταχωρημένη",
"collections.visibility_unlisted_hint": "Ορατή σε οποιονδήποτε με σύνδεσμο. Κρυμμένη από τα αποτελέσματα αναζήτησης και τις προτάσεις.",
"column.about": "Σχετικά με",
"column.blocks": "Αποκλεισμένοι χρήστες",
"column.bookmarks": "Σελιδοδείκτες",
"column.collections": "Οι συλλογές μου",
"column.community": "Τοπική ροή",
"column.create_collection": "Δημιουργία συλλογής",
"column.create_list": "Δημιουργία λίστας",
"column.direct": "Ιδιωτικές επισημάνσεις",
"column.directory": "Περιήγηση στα προφίλ",
"column.domain_blocks": "Αποκλεισμένοι τομείς",
"column.edit_collection": "Επεξεργασία συλλογής",
"column.edit_list": "Επεξεργασία λίστας",
"column.favourites": "Αγαπημένα",
"column.firehose": "Ζωντανές ροές",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Bookmarks",
"column.collections": "My collections",
"column.community": "Local timeline",
"column.create_collection": "Create collection",
"column.create_list": "Create list",
"column.direct": "Private mentions",
"column.directory": "Browse profiles",
"column.domain_blocks": "Blocked domains",
"column.edit_collection": "Edit collection",
"column.edit_list": "Edit list",
"column.favourites": "Favourites",
"column.firehose": "Live feeds",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Descripción",
"collections.collection_name": "Nombre",
"collections.collection_topic": "Tema",
"collections.content_warning": "Advertencia de contenido",
"collections.continue": "Continuar",
"collections.create.accounts_subtitle": "Solo las cuentas que seguís —las cuales optaron por ser descubiertas— pueden ser agregadas.",
"collections.create.accounts_title": "¿A quién vas a destacar en esta colección?",
"collections.create.basic_details_title": "Detalles básicos",
"collections.create.settings_title": "Configuración",
"collections.create.steps": "Paso {step}/{total}",
"collections.create_a_collection_hint": "Creá una colección para recomendar o compartir tus cuentas favoritas con otras personas.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Límite de 100 caracteres",
"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.",
"collections.manage_accounts": "Administrar cuentas",
"collections.manage_accounts_in_collection": "Administrar cuentas en esta colección",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción de la colección y las cuentas detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
"collections.name_length_hint": "Límite de 100 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "No hay colecciones aún.",
"collections.topic_hint": "Agregá una etiqueta que ayude a otros usuarios a entender el tema principal de esta colección.",
"collections.view_collection": "Abrir colección",
"collections.visibility_public": "Pública",
"collections.visibility_public_hint": "Puede ser descubierta en los resultados de búsqueda y en otras áreas donde aparezcan recomendaciones.",
"collections.visibility_title": "Visibilidad",
"collections.visibility_unlisted": "No listada",
"collections.visibility_unlisted_hint": "Visible para quien tenga el enlace. Oculta de los resultados de búsqueda y recomendaciones.",
"column.about": "Información",
"column.blocks": "Usuarios bloqueados",
"column.bookmarks": "Marcadores",
"column.collections": "Mis colecciones",
"column.community": "Línea temporal local",
"column.create_collection": "Crear colección",
"column.create_list": "Crear lista",
"column.direct": "Menciones privadas",
"column.directory": "Explorar perfiles",
"column.domain_blocks": "Dominios bloqueados",
"column.edit_collection": "Editar colección",
"column.edit_list": "Editar lista",
"column.favourites": "Favoritos",
"column.firehose": "Líneas temporales en vivo",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Descripción",
"collections.collection_name": "Nombre",
"collections.collection_topic": "Tema",
"collections.content_warning": "Advertencia de contenido",
"collections.continue": "Continuar",
"collections.create.accounts_subtitle": "Solo se pueden agregar cuentas que sigas y que hayan optado por aparecer en los resultados de búsqueda.",
"collections.create.accounts_title": "¿A quién incluirás en esta colección?",
"collections.create.basic_details_title": "Detalles básicos",
"collections.create.settings_title": "Configuración",
"collections.create.steps": "Paso {step}/{total}",
"collections.create_a_collection_hint": "Crea una colección para recomendar o compartir tus cuentas favoritas con otras personas.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Limitado a 100 caracteres",
"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.",
"collections.manage_accounts": "Administrar cuentas",
"collections.manage_accounts_in_collection": "Administrar cuentas en esta colección",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción y las cuentas de la colección detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
"collections.name_length_hint": "Limitado a 100 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "No hay colecciones todavía.",
"collections.topic_hint": "Agrega una etiqueta que ayude a los demás a comprender el tema principal de esta colección.",
"collections.view_collection": "Ver colección",
"collections.visibility_public": "Pública",
"collections.visibility_public_hint": "Visible en los resultados de búsqueda y otras áreas donde aparecen recomendaciones.",
"collections.visibility_title": "Visibilidad",
"collections.visibility_unlisted": "No listado",
"collections.visibility_unlisted_hint": "Visible para cualquier persona que tenga el enlace. Oculto en los resultados de búsqueda y las recomendaciones.",
"column.about": "Acerca de",
"column.blocks": "Usuarios bloqueados",
"column.bookmarks": "Marcadores",
"column.collections": "Mis colecciones",
"column.community": "Cronología local",
"column.create_collection": "Crear colección",
"column.create_list": "Crear lista",
"column.direct": "Menciones privadas",
"column.directory": "Buscar perfiles",
"column.domain_blocks": "Dominios ocultados",
"column.edit_collection": "Editar colección",
"column.edit_list": "Editar lista",
"column.favourites": "Favoritos",
"column.firehose": "Feeds en vivo",

View File

@@ -17,8 +17,12 @@
"account.activity": "Actividad",
"account.add_note": "Añadir una nota personal",
"account.add_or_remove_from_list": "Agregar o eliminar de listas",
"account.badges.admin": "Administrador",
"account.badges.blocked": "Bloqueado",
"account.badges.bot": "Automatizada",
"account.badges.domain_blocked": "Dominio bloqueado",
"account.badges.group": "Grupo",
"account.badges.muted": "Silenciado",
"account.block": "Bloquear a @{name}",
"account.block_domain": "Bloquear dominio {domain}",
"account.block_short": "Bloquear",
@@ -229,12 +233,10 @@
"column.bookmarks": "Marcadores",
"column.collections": "Mis colecciones",
"column.community": "Cronología local",
"column.create_collection": "Crear colección",
"column.create_list": "Crear lista",
"column.direct": "Menciones privadas",
"column.directory": "Buscar perfiles",
"column.domain_blocks": "Dominios bloqueados",
"column.edit_collection": "Editar colección",
"column.edit_list": "Editar lista",
"column.favourites": "Favoritos",
"column.firehose": "Cronologías",

View File

@@ -236,6 +236,10 @@
"collections.collection_description": "Kirjeldus",
"collections.collection_name": "Nimi",
"collections.collection_topic": "Teema",
"collections.content_warning": "Sisuhoiatus",
"collections.continue": "Jätka",
"collections.create.settings_title": "Seadistused",
"collections.create.steps": "Samm {step}/{total}",
"collections.create_a_collection_hint": "Soovitamaks oma lemmikuid teistele kasutajatele lisa asjakohane kogumik.",
"collections.create_collection": "Loo kogumik",
"collections.delete_collection": "Kustuta kogumik",
@@ -244,20 +248,20 @@
"collections.mark_as_sensitive": "Märgi delikaatseks",
"collections.mark_as_sensitive_hint": "Peidab kogumiku kirjelduse ja kontod sisuhoiatuse taha. Kogumiku nimi ise on sellele vaatamata nähtav.",
"collections.name_length_hint": "Kuni 100 tähemärki",
"collections.new_collection": "Uus kogumik",
"collections.no_collections_yet": "Kogumikke veel pole.",
"collections.topic_hint": "Lisa teemaviide, mis aitab teistel kasutajatel mõista selle kogumiku põhisisu.",
"collections.view_collection": "Vaata kogumikku",
"collections.visibility_public": "Avalik",
"column.about": "Teave",
"column.blocks": "Blokeeritud kasutajad",
"column.bookmarks": "Järjehoidjad",
"column.collections": "Minu kogumikud",
"column.community": "Kohalik ajajoon",
"column.create_collection": "Loo kogumik",
"column.create_list": "Loo loend",
"column.direct": "Privaatsed mainimised",
"column.directory": "Sirvi profiile",
"column.domain_blocks": "Peidetud domeenid",
"column.edit_collection": "Muuda kogumikku",
"column.edit_list": "Muuda loendit",
"column.favourites": "Lemmikud",
"column.firehose": "Postitused reaalajas",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Kuvaus",
"collections.collection_name": "Nimi",
"collections.collection_topic": "Aihe",
"collections.content_warning": "Sisältövaroitus",
"collections.continue": "Jatka",
"collections.create.accounts_subtitle": "Lisätä voi vain tilejä, joita seuraat ja jotka ovat valinneet tulla löydetyiksi.",
"collections.create.accounts_title": "Keitä esittelet tässä kokoelmassa?",
"collections.create.basic_details_title": "Perustiedot",
"collections.create.settings_title": "Asetukset",
"collections.create.steps": "Vaihe {step}/{total}",
"collections.create_a_collection_hint": "Luomalla kokoelman voit suositella tai jakaa suosikkitilejäsi muiden kanssa.",
"collections.create_collection": "Luo kokoelma",
"collections.delete_collection": "Poista kokoelma",
"collections.description_length_hint": "100 merkin rajoitus",
"collections.edit_details": "Muokkaa perustietoja",
"collections.edit_settings": "Muokkaa asetuksia",
"collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.",
"collections.manage_accounts": "Hallitse tilejä",
"collections.manage_accounts_in_collection": "Hallitse tässä kokoelmassa olevia tilejä",
"collections.mark_as_sensitive": "Merkitse arkaluonteiseksi",
"collections.mark_as_sensitive_hint": "Piilottaa kokoelman kuvauksen ja tilit sisältövaroituksen taakse. Kokoelman nimi jää esiin.",
"collections.name_length_hint": "100 merkin rajoitus",
"collections.new_collection": "Uusi kokoelma",
"collections.no_collections_yet": "Ei vielä kokoelmia.",
"collections.topic_hint": "Lisää aihetunniste, joka auttaa muita ymmärtämään tämän kokoelman pääaiheen.",
"collections.view_collection": "Näytä kokoelma",
"collections.visibility_public": "Julkinen",
"collections.visibility_public_hint": "Löydettävissä hakutuloksista ja muualta, jossa ilmenee suosituksia.",
"collections.visibility_title": "Näkyvyys",
"collections.visibility_unlisted": "Listaamaton",
"collections.visibility_unlisted_hint": "Näkyy kaikille, joilla on linkki. Piilotetaan hakutuloksista ja suosituksista.",
"column.about": "Tietoja",
"column.blocks": "Estetyt käyttäjät",
"column.bookmarks": "Kirjanmerkit",
"column.collections": "Omat kokoelmat",
"column.community": "Paikallinen aikajana",
"column.create_collection": "Luo kokoelma",
"column.create_list": "Luo lista",
"column.direct": "Yksityismaininnat",
"column.directory": "Selaa profiileja",
"column.domain_blocks": "Estetyt verkkotunnukset",
"column.edit_collection": "Muokkaa kokoelmaa",
"column.edit_list": "Muokkaa listaa",
"column.favourites": "Suosikit",
"column.firehose": "Livesyötteet",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Bókamerki",
"column.collections": "Míni søvn",
"column.community": "Lokal tíðarlinja",
"column.create_collection": "Ger savn",
"column.create_list": "Ger lista",
"column.direct": "Privatar umrøður",
"column.directory": "Blaða gjøgnum vangar",
"column.domain_blocks": "Bannað økisnøvn",
"column.edit_collection": "Rætta savn",
"column.edit_list": "Broyt lista",
"column.favourites": "Dámdir postar",
"column.firehose": "Beinleiðis rásir",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Signets",
"column.collections": "Mes collections",
"column.community": "Fil local",
"column.create_collection": "Créer une collection",
"column.create_list": "Créer une liste",
"column.direct": "Mention privée",
"column.directory": "Parcourir les profils",
"column.domain_blocks": "Domaines bloqués",
"column.edit_collection": "Modifier la collection",
"column.edit_list": "Modifier la liste",
"column.favourites": "Favoris",
"column.firehose": "Flux en direct",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Marque-pages",
"column.collections": "Mes collections",
"column.community": "Fil public local",
"column.create_collection": "Créer une collection",
"column.create_list": "Créer une liste",
"column.direct": "Mentions privées",
"column.directory": "Parcourir les profils",
"column.domain_blocks": "Domaines bloqués",
"column.edit_collection": "Modifier la collection",
"column.edit_list": "Modifier la liste",
"column.favourites": "Favoris",
"column.firehose": "Flux en direct",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Leabharmharcanna",
"column.collections": "Mo bhailiúcháin",
"column.community": "Amlíne áitiúil",
"column.create_collection": "Cruthaigh bailiúchán",
"column.create_list": "Cruthaigh liosta",
"column.direct": "Luann príobháideach",
"column.directory": "Brabhsáil próifílí",
"column.domain_blocks": "Fearainn bhactha",
"column.edit_collection": "Cuir bailiúchán in eagar",
"column.edit_list": "Cuir liosta in eagar",
"column.favourites": "Ceanáin",
"column.firehose": "Fothaí beo",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Descrición",
"collections.collection_name": "Nome",
"collections.collection_topic": "Temática",
"collections.content_warning": "Aviso sobre o contido",
"collections.continue": "Continuar",
"collections.create.accounts_subtitle": "Só se poden engadir contas que segues e que optaron por ser incluídas en descubrir.",
"collections.create.accounts_title": "A quen queres incluír nesta colección?",
"collections.create.basic_details_title": "Detalles básicos",
"collections.create.settings_title": "Axustes",
"collections.create.steps": "Paso {step}/{total}",
"collections.create_a_collection_hint": "Crear unha colección para recomendar ou compartir as túas contas favoritas.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Límite de 100 caracteres",
"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.",
"collections.manage_accounts": "Xestionar contas",
"collections.manage_accounts_in_collection": "Xestionar as contas nesta colección",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta a descrición e contas da colección detrás dun aviso sobre o contido. O nome da colección permanece visible.",
"collections.name_length_hint": "Límite de 100 caracteres",
"collections.new_collection": "Nova colección",
"collections.no_collections_yet": "Aínda non tes coleccións.",
"collections.topic_hint": "Engadir un cancelo para que axudar a que outras persoas coñezan a temática desta colección.",
"collections.view_collection": "Ver colección",
"collections.visibility_public": "Pública",
"collections.visibility_public_hint": "Pódese atopar nos resultados das buscas e noutras áreas onde se mostran recomendacións.",
"collections.visibility_title": "Visibilidade",
"collections.visibility_unlisted": "Fóra das listas",
"collections.visibility_unlisted_hint": "Visible para calquera que teña a ligazón. Oculta nos resultados e recomendacións.",
"column.about": "Sobre",
"column.blocks": "Usuarias bloqueadas",
"column.bookmarks": "Marcadores",
"column.collections": "As miñas coleccións",
"column.community": "Cronoloxía local",
"column.create_collection": "Crear colección",
"column.create_list": "Crear lista",
"column.direct": "Mencións privadas",
"column.directory": "Procurar perfís",
"column.domain_blocks": "Dominios agochados",
"column.edit_collection": "Editar colección",
"column.edit_list": "Editar lista",
"column.favourites": "Favoritas",
"column.firehose": "O que acontece",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "תיאור",
"collections.collection_name": "כינוי",
"collections.collection_topic": "נושא",
"collections.content_warning": "אזהרת תוכן",
"collections.continue": "המשך",
"collections.create.accounts_subtitle": "רק חשבונות נעקבים שבחרו להופיע ב\"תגליות\" ניתנים להוספה.",
"collections.create.accounts_title": "את מי תבליטו באוסף זה?",
"collections.create.basic_details_title": "פרטים בסיסיים",
"collections.create.settings_title": "הגדרות",
"collections.create.steps": "צעד {step} מתוך {total}",
"collections.create_a_collection_hint": "יצירת אוסף כדי להמליץ או לשתף את החשבונות החביבים עליך עם אחרים.",
"collections.create_collection": "יצירת אוסף",
"collections.delete_collection": "מחיקת האוסף",
"collections.description_length_hint": "מגבלה של 100 תווים",
"collections.edit_details": "עריכת פרטים בסיסיים",
"collections.edit_settings": "עריכת הגדרות",
"collections.error_loading_collections": "חלה שגיאה בנסיון לטעון את אוספיך.",
"collections.manage_accounts": "ניהול חשבונות",
"collections.manage_accounts_in_collection": "ניהול החשבונות שבאוסף זה",
"collections.mark_as_sensitive": "מסומנים כרגישים",
"collections.mark_as_sensitive_hint": "הסתרת תיאור וחשבונות האוסף מאחורי אזהרת תוכן. שם האוסף עדיין ישאר גלוי.",
"collections.name_length_hint": "מגבלה של 100 תווים",
"collections.new_collection": "אוסף חדש",
"collections.no_collections_yet": "עוד אין אוספים.",
"collections.topic_hint": "הוספת תגית שמסייעת לאחרים להבין את הנושא הראשי של האוסף.",
"collections.view_collection": "צפיה באוסף",
"collections.visibility_public": "פומבי",
"collections.visibility_public_hint": "זמין לגילוי בתוצאות חיפוש ושאר אזורים בהם מופיעות המלצות.",
"collections.visibility_title": "ניראות",
"collections.visibility_unlisted": "מוסתר",
"collections.visibility_unlisted_hint": "זמין לכל מי שקיבל קישור. נסתר מתוצאות חיפוש והמלצות.",
"column.about": "אודות",
"column.blocks": "משתמשים חסומים",
"column.bookmarks": "סימניות",
"column.collections": "האוספים שלי",
"column.community": "פיד שרת מקומי",
"column.create_collection": "יצירת אוסף",
"column.create_list": "יצירת רשימה",
"column.direct": "הודעות פרטיות",
"column.directory": "עיין בפרופילים",
"column.domain_blocks": "קהילות (שמות מתחם) מוסתרות",
"column.edit_collection": "עריכת אוסף",
"column.edit_list": "עריכת רשימה",
"column.favourites": "חיבובים",
"column.firehose": "פידים עדכניים",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Könyvjelzők",
"column.collections": "Saját gyűjtemények",
"column.community": "Helyi idővonal",
"column.create_collection": "Gyűjtemény létrehozása",
"column.create_list": "Lista létrehozása",
"column.direct": "Személyes említések",
"column.directory": "Profilok böngészése",
"column.domain_blocks": "Letiltott domainek",
"column.edit_collection": "Gyűjtemény szerkesztése",
"column.edit_list": "Lista módosítása",
"column.favourites": "Kedvencek",
"column.firehose": "Hírfolyamok",

View File

@@ -236,28 +236,36 @@
"collections.collection_description": "Lýsing",
"collections.collection_name": "Nafn",
"collections.collection_topic": "Umfjöllunarefni",
"collections.content_warning": "Viðvörun vegna efnis",
"collections.continue": "Halda áfram",
"collections.create.settings_title": "Stillingar",
"collections.create.steps": "Skref {step}/{total}",
"collections.create_a_collection_hint": "Búðu til safn með eftirlætisnotendunum þínum til að deila eða mæla með við aðra.",
"collections.create_collection": "Búa til safn",
"collections.delete_collection": "Eyða safni",
"collections.description_length_hint": "100 stafa takmörk",
"collections.edit_settings": "Breyta stillingum",
"collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.",
"collections.manage_accounts": "Sýsla með notandaaðganga",
"collections.mark_as_sensitive": "Merkja sem viðkvæmt",
"collections.mark_as_sensitive_hint": "Felur lýsingu safnsins og notendur á bakvið aðvörun vegna efnis. Nafn safnsins verður áfram sýnilegt.",
"collections.name_length_hint": "100 stafa takmörk",
"collections.new_collection": "Nýtt safn",
"collections.no_collections_yet": "Engin söfn ennþá.",
"collections.topic_hint": "Bættu við myllumerki sem hjálpar öðrum að skilja aðalefni þessa safns.",
"collections.view_collection": "Skoða safn",
"collections.visibility_public": "Opinbert",
"collections.visibility_title": "Sýnileiki",
"collections.visibility_unlisted": "Óskráð",
"column.about": "Um hugbúnaðinn",
"column.blocks": "Útilokaðir notendur",
"column.bookmarks": "Bókamerki",
"column.collections": "Söfnin mín",
"column.community": "Staðvær tímalína",
"column.create_collection": "Búa til safn",
"column.create_list": "Búa til lista",
"column.direct": "Einkaspjall",
"column.directory": "Skoða notendasnið",
"column.domain_blocks": "Útilokuð lén",
"column.edit_collection": "Breyta safni",
"column.edit_list": "Breyta lista",
"column.favourites": "Eftirlæti",
"column.firehose": "Bein streymi",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Segnalibri",
"column.collections": "Le mie collezioni",
"column.community": "Cronologia locale",
"column.create_collection": "Crea la collezione",
"column.create_list": "Crea lista",
"column.direct": "Menzioni private",
"column.directory": "Sfoglia profili",
"column.domain_blocks": "Domini bloccati",
"column.edit_collection": "Modifica la collezione",
"column.edit_list": "Modifica lista",
"column.favourites": "Preferiti",
"column.firehose": "Feed in diretta",

View File

@@ -229,12 +229,10 @@
"column.bookmarks": "冊籤",
"column.collections": "我ê收藏",
"column.community": "本地ê時間線",
"column.create_collection": "建立收藏",
"column.create_list": "建立列單",
"column.direct": "私人ê提起",
"column.directory": "瀏覽個人資料",
"column.domain_blocks": "封鎖ê域名",
"column.edit_collection": "編輯收藏",
"column.edit_list": "編輯列單",
"column.favourites": "Siōng kah意",
"column.firehose": "Tsit-má ê動態",

View File

@@ -229,12 +229,10 @@
"column.bookmarks": "Bladwijzers",
"column.collections": "Mijn verzamelingen",
"column.community": "Lokale tijdlijn",
"column.create_collection": "Verzameling aanmaken",
"column.create_list": "Lijst aanmaken",
"column.direct": "Privéberichten",
"column.directory": "Gebruikersgids",
"column.domain_blocks": "Geblokkeerde servers",
"column.edit_collection": "Verzameling bewerken",
"column.edit_list": "Lijst bewerken",
"column.favourites": "Favorieten",
"column.firehose": "Openbare tijdlijnen",

View File

@@ -228,12 +228,10 @@
"column.bookmarks": "Salvos",
"column.collections": "Minhas coleções",
"column.community": "Linha local",
"column.create_collection": "Criar coleção",
"column.create_list": "Criar lista",
"column.direct": "Menções privadas",
"column.directory": "Explorar perfis",
"column.domain_blocks": "Domínios bloqueados",
"column.edit_collection": "Editar coleção",
"column.edit_list": "Editar lista",
"column.favourites": "Favoritos",
"column.firehose": "Feeds ao vivo",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Marcadores",
"column.collections": "As minhas coleções",
"column.community": "Cronologia local",
"column.create_collection": "Criar coleção",
"column.create_list": "Criar lista",
"column.direct": "Menções privadas",
"column.directory": "Explorar perfis",
"column.domain_blocks": "Domínios bloqueados",
"column.edit_collection": "Editar coleção",
"column.edit_list": "Editar lista",
"column.favourites": "Favoritos",
"column.firehose": "Cronologias em tempo real",

View File

@@ -234,28 +234,43 @@
"collections.collection_description": "Përshkrim",
"collections.collection_name": "Emër",
"collections.collection_topic": "Temë",
"collections.content_warning": "Sinjalizim lënde",
"collections.continue": "Vazhdo",
"collections.create.accounts_subtitle": "Mund të shtohen vetëm llogari që ju ndiqni të cilat kanë zgjedhur të jenë të zbulueshme.",
"collections.create.accounts_title": "Kë do të shfaqni në këtë koleksion?",
"collections.create.basic_details_title": "Hollësi bazë",
"collections.create.settings_title": "Rregullime",
"collections.create.steps": "Hapi {step}/{total}",
"collections.create_a_collection_hint": "Krijoni një koleksion për ta rekomanduar, ose për të ndarë me të tjerët llogaritë tuaja të parapëlqyera.",
"collections.create_collection": "Krijoni koleksion",
"collections.delete_collection": "Fshije koleksionin",
"collections.description_length_hint": "Kufi prej 100 shenjash",
"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.",
"collections.manage_accounts": "Administroni llogari",
"collections.manage_accounts_in_collection": "Administroni llogari në këtë koleksion",
"collections.mark_as_sensitive": "Vëri shenjë si rezervat",
"collections.mark_as_sensitive_hint": "Bën fshehjen e përshkrimit të koleksionit dhe llogarive prapa një sinjalizimi lënde. Emri i koleksionit do të jetë ende i dukshëm.",
"collections.name_length_hint": "Kufi prej 100 shenjash",
"collections.new_collection": "Koleksion i ri",
"collections.no_collections_yet": "Ende pa koleksione.",
"collections.topic_hint": "Shtoni një hashtag që ndihmon të tjerët të kuptojnë temën kryesore të këtij koleksion.",
"collections.view_collection": "Shiheni koleksionin",
"collections.visibility_public": "Publik",
"collections.visibility_public_hint": "I zbulueshëm në përfundime kërkimi dhe fusha të tjera ku shfaqen rekomandime.",
"collections.visibility_title": "Dukshmëri",
"collections.visibility_unlisted": "Jo në listë",
"collections.visibility_unlisted_hint": "I dukshëm për këdo me një lidhje. I fshehur nga përfundime kërkimi dhe rekomandime.",
"column.about": "Mbi",
"column.blocks": "Përdorues të bllokuar",
"column.bookmarks": "Faqerojtës",
"column.collections": "Koleksionet e mi",
"column.community": "Rrjedhë kohore vendore",
"column.create_collection": "Krijoni koleksion",
"column.create_list": "Krijo listë",
"column.direct": "Përmendje private",
"column.directory": "Shfletoni profile",
"column.domain_blocks": "Përkatësi të bllokuara",
"column.edit_collection": "Përpunoni koleksion",
"column.edit_list": "Përpunoni listën",
"column.favourites": "Të parapëlqyer",
"column.firehose": "Prurje “live”",

View File

@@ -252,12 +252,10 @@
"column.bookmarks": "Yer İşaretleri",
"column.collections": "Koleksiyonlarım",
"column.community": "Yerel ağ akışı",
"column.create_collection": "Koleksiyon oluştur",
"column.create_list": "Liste oluştur",
"column.direct": "Özel bahsetmeler",
"column.directory": "Profillere göz at",
"column.domain_blocks": "Engellenen alan adları",
"column.edit_collection": "Koleksiyonu düzenle",
"column.edit_list": "Listeyi düzenle",
"column.favourites": "Gözdelerin",
"column.firehose": "Anlık Akışlar",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "Mô tả",
"collections.collection_name": "Tên",
"collections.collection_topic": "Chủ đề",
"collections.content_warning": "Nội dung ẩn",
"collections.continue": "Tiếp tục",
"collections.create.accounts_subtitle": "Chỉ những tài khoản bạn theo dõi và đã chọn tham gia chương trình khám phá mới có thể được thêm vào.",
"collections.create.accounts_title": "Bạn sẽ chọn ai để giới thiệu trong bộ sưu tập này?",
"collections.create.basic_details_title": "Thông tin cơ bản",
"collections.create.settings_title": "Cài đặt",
"collections.create.steps": "Bước {step}/{total}",
"collections.create_a_collection_hint": "Tạo một collection để giới thiệu hoặc chia sẻ những trạm tút yêu thích của bạn với người khác.",
"collections.create_collection": "Tạo collection",
"collections.delete_collection": "Xóa collection",
"collections.description_length_hint": "Giới hạn 100 ký tự",
"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.",
"collections.manage_accounts": "Quản lý tài khoản",
"collections.manage_accounts_in_collection": "Quản lý tài khoản trong collection này",
"collections.mark_as_sensitive": "Đánh dấu nhạy cảm",
"collections.mark_as_sensitive_hint": "Ẩn phần mô tả và thông tin tài khoản của collection phía sau cảnh báo nội dung. Tên bộ sưu tập vẫn hiển thị.",
"collections.name_length_hint": "Giới hạn 100 ký tự",
"collections.new_collection": "Collection mới",
"collections.no_collections_yet": "Chưa có collection.",
"collections.topic_hint": "Thêm hashtag giúp người khác hiểu chủ đề chính của collection này.",
"collections.view_collection": "Xem collection",
"collections.visibility_public": "Công khai",
"collections.visibility_public_hint": "Có thể tìm thấy trong kết quả tìm kiếm và các khu vực khác nơi xuất hiện đề xuất.",
"collections.visibility_title": "Hiển thị",
"collections.visibility_unlisted": "Hạn chế",
"collections.visibility_unlisted_hint": "Hiển thị cho bất kỳ ai có liên kết. Ẩn khỏi kết quả tìm kiếm và đề xuất.",
"column.about": "Giới thiệu",
"column.blocks": "Trạm tút đã chặn",
"column.bookmarks": "Những tút đã lưu",
"column.collections": "Collection của tôi",
"column.community": "Máy chủ này",
"column.create_collection": "Tạo collection",
"column.create_list": "Tạo danh sách",
"column.direct": "Nhắn riêng",
"column.directory": "Tìm trạm tút",
"column.domain_blocks": "Máy chủ đã chặn",
"column.edit_collection": "Sửa collection",
"column.edit_list": "Sửa danh sách",
"column.favourites": "Những tút đã thích",
"column.firehose": "Bảng tin",

View File

@@ -1,7 +1,7 @@
{
"about.blocks": "被限制的服务器",
"about.contact": "联系方式:",
"about.default_locale": "默认默认",
"about.default_locale": "默认",
"about.disclaimer": "Mastodon 是自由的开源软件,商标由 Mastodon gGmbH 持有。",
"about.domain_blocks.no_reason_available": "原因不可用",
"about.domain_blocks.preamble": "通常来说,在 Mastodon 上,你可以浏览联邦宇宙中任何一台服务器上的内容,并且和上面的用户互动。但其中一些在本服务器上被设置为例外。",
@@ -22,7 +22,7 @@
"account.badges.bot": "机器人",
"account.badges.domain_blocked": "已屏蔽域名",
"account.badges.group": "群组",
"account.badges.muted": "已隐藏",
"account.badges.muted": "已停止提醒",
"account.block": "屏蔽 @{name}",
"account.block_domain": "屏蔽 {domain} 实例",
"account.block_short": "屏蔽",
@@ -46,6 +46,8 @@
"account.featured.hashtags": "话题",
"account.featured_tags.last_status_at": "上次发言于 {date}",
"account.featured_tags.last_status_never": "暂无嘟文",
"account.fields.scroll_next": "显示下一个",
"account.fields.scroll_prev": "显示上一个",
"account.filters.all": "所有活动",
"account.filters.boosts_toggle": "显示转嘟",
"account.filters.posts_boosts": "嘟文与转嘟",
@@ -77,6 +79,23 @@
"account.locked_info": "此账号已锁嘟。账号所有人会手动审核新关注者。",
"account.media": "媒体",
"account.mention": "提及 @{name}",
"account.menu.add_to_list": "添加到列表…",
"account.menu.block": "屏蔽账户",
"account.menu.block_domain": "屏蔽 {domain}",
"account.menu.copied": "已复制账户链接到剪贴板",
"account.menu.copy": "复制链接",
"account.menu.direct": "私下提及",
"account.menu.hide_reblogs": "在时间线中隐藏转嘟",
"account.menu.mention": "提及",
"account.menu.mute": "停止提醒账户",
"account.menu.open_original_page": "在 {domain} 上查看",
"account.menu.remove_follower": "移除关注者",
"account.menu.report": "举报账户",
"account.menu.share": "分享…",
"account.menu.show_reblogs": "在时间线中显示转嘟",
"account.menu.unblock": "取消屏蔽账户",
"account.menu.unblock_domain": "取消屏蔽 {domain}",
"account.menu.unmute": "恢复提醒账户",
"account.moved_to": "{name} 的新账号是:",
"account.mute": "隐藏 @{name}",
"account.mute_notifications_short": "关闭通知",
@@ -85,14 +104,14 @@
"account.muting": "正在静音",
"account.mutual": "你们互相关注",
"account.no_bio": "未提供描述。",
"account.node_modal.callout": "个人备注仅对个人可见。",
"account.node_modal.callout": "个人备注仅对个人可见。",
"account.node_modal.edit_title": "编辑个人备注",
"account.node_modal.error_unknown": "无法保存备注",
"account.node_modal.field_label": "个人备注",
"account.node_modal.save": "保存",
"account.node_modal.title": "添加个人备注",
"account.note.edit_button": "编辑",
"account.note.title": "个人备注(仅对可见)",
"account.note.title": "个人备注(仅对可见)",
"account.open_original_page": "打开原始页面",
"account.posts": "嘟文",
"account.posts_with_replies": "嘟文和回复",
@@ -138,7 +157,7 @@
"annual_report.announcement.action_build": "构建我的 Wrapstodon 年度回顾",
"annual_report.announcement.action_dismiss": "不了,谢谢",
"annual_report.announcement.action_view": "查看我的 Wrapstodon 年度回顾",
"annual_report.announcement.description": "探索更多关于过去一年在 Mastodon 上的互动情况。",
"annual_report.announcement.description": "探索更多关于过去一年在 Mastodon 上的互动情况。",
"annual_report.announcement.title": "Wrapstodon {year} 年度回顾来啦",
"annual_report.nav_item.badge": "新",
"annual_report.shared_page.donate": "捐助",
@@ -217,28 +236,43 @@
"collections.collection_description": "说明",
"collections.collection_name": "名称",
"collections.collection_topic": "话题",
"collections.content_warning": "内容警告",
"collections.continue": "继续",
"collections.create.accounts_subtitle": "只有你关注的且已经主动加入发现功能的账号可以添加。",
"collections.create.accounts_title": "你想在收藏列表中添加哪些人?",
"collections.create.basic_details_title": "基本信息",
"collections.create.settings_title": "设置",
"collections.create.steps": "第 {step} 步(共 {total} 步)",
"collections.create_a_collection_hint": "创建用于向其他人推荐或分享你最喜欢账号的收藏列表。",
"collections.create_collection": "创建收藏列表",
"collections.delete_collection": "删除收藏列表",
"collections.description_length_hint": "100字限制",
"collections.error_loading_collections": "加载您的收藏列表时发生错误。",
"collections.edit_details": "编辑基本信息",
"collections.edit_settings": "编辑设置",
"collections.error_loading_collections": "加载你的收藏列表时发生错误。",
"collections.manage_accounts": "管理账户",
"collections.manage_accounts_in_collection": "管理此收藏列表内的账户",
"collections.mark_as_sensitive": "标记为敏感内容",
"collections.mark_as_sensitive_hint": "将此收藏列表的说明用内容警告隐藏。此收藏列表的名称仍将可见。",
"collections.name_length_hint": "100字限制",
"collections.new_collection": "新建收藏列表",
"collections.no_collections_yet": "尚无收藏列表。",
"collections.topic_hint": "添加话题标签,帮助他人了解此收藏列表的主题。",
"collections.view_collection": "查看收藏列表",
"collections.visibility_public": "公开",
"collections.visibility_public_hint": "可在搜索结果及其他推荐功能可用的区域被发现。",
"collections.visibility_title": "可见性",
"collections.visibility_unlisted": "悄悄公开",
"collections.visibility_unlisted_hint": "对任何知晓链接的人可见。在搜索结果及推荐中隐藏。",
"column.about": "关于",
"column.blocks": "屏蔽的用户",
"column.bookmarks": "书签",
"column.collections": "我的收藏列表",
"column.community": "本站时间线",
"column.create_collection": "创建收藏列表",
"column.create_list": "创建列表",
"column.direct": "私下提及",
"column.directory": "浏览用户资料",
"column.domain_blocks": "已屏蔽的域名",
"column.edit_collection": "编辑收藏列表",
"column.edit_list": "编辑列表",
"column.favourites": "喜欢",
"column.firehose": "实时动态",
@@ -862,7 +896,7 @@
"relative_time.today": "今天",
"remove_quote_hint.button_label": "明白了",
"remove_quote_hint.message": "你可以通过 {icon} 选项菜单进行此操作。",
"remove_quote_hint.title": "是否想要除你的引用嘟文?",
"remove_quote_hint.title": "是否想要除你的引用嘟文?",
"reply_indicator.attachments": "{count, plural, other {# 个附件}}",
"reply_indicator.cancel": "取消",
"reply_indicator.poll": "投票",

View File

@@ -236,28 +236,43 @@
"collections.collection_description": "說明",
"collections.collection_name": "名稱",
"collections.collection_topic": "主題",
"collections.content_warning": "內容警告",
"collections.continue": "繼續",
"collections.create.accounts_subtitle": "僅能加入您跟隨並選擇加入探索功能之帳號。",
"collections.create.accounts_title": "您想於此收藏名單推薦誰?",
"collections.create.basic_details_title": "基本資料",
"collections.create.settings_title": "設定",
"collections.create.steps": "步驟 {step}/{total}",
"collections.create_a_collection_hint": "建立用以向其他人推薦或分享您最喜愛帳號之收藏名單。",
"collections.create_collection": "建立收藏名單",
"collections.delete_collection": "刪除收藏名單",
"collections.description_length_hint": "100 字限制",
"collections.edit_details": "編輯基本資料",
"collections.edit_settings": "編輯設定",
"collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。",
"collections.manage_accounts": "管理帳號",
"collections.manage_accounts_in_collection": "管理此收藏名單之帳號",
"collections.mark_as_sensitive": "標記為敏感內容",
"collections.mark_as_sensitive_hint": "將此收藏名單之說明隱藏於內容警告之後。此收藏名單名稱仍將可見。",
"collections.name_length_hint": "100 字限制",
"collections.new_collection": "新增收藏名單",
"collections.no_collections_yet": "您沒有任何收藏名單。",
"collections.topic_hint": "新增主題標籤以協助其他人瞭解此收藏名單之主題。",
"collections.view_collection": "檢視收藏名單",
"collections.visibility_public": "公開",
"collections.visibility_public_hint": "可於搜尋結果與其他推薦處可見。",
"collections.visibility_title": "可見性",
"collections.visibility_unlisted": "不公開",
"collections.visibility_unlisted_hint": "任何擁有連結的人可見。但隱藏於搜尋結果與推薦中。",
"column.about": "關於",
"column.blocks": "已封鎖使用者",
"column.bookmarks": "書籤",
"column.collections": "我的收藏名單",
"column.community": "本站時間軸",
"column.create_collection": "建立收藏名單",
"column.create_list": "建立列表",
"column.direct": "私訊",
"column.directory": "瀏覽個人檔案",
"column.domain_blocks": "已封鎖網域",
"column.edit_collection": "編輯收藏名單",
"column.edit_list": "編輯列表",
"column.favourites": "最愛",
"column.firehose": "即時內容",

View File

@@ -1,30 +0,0 @@
# frozen_string_literal: true
class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model
attributes :id, :type, :actor, :published, :to, :cc, :virtual_object
class << self
def from_status(status, allow_inlining: true)
new.tap do |presenter|
presenter.id = ActivityPub::TagManager.instance.activity_uri_for(status)
presenter.type = status.reblog? ? 'Announce' : 'Create'
presenter.actor = ActivityPub::TagManager.instance.uri_for(status.account)
presenter.published = status.created_at
presenter.to = ActivityPub::TagManager.instance.to(status)
presenter.cc = ActivityPub::TagManager.instance.cc(status)
presenter.virtual_object = begin
if status.reblog?
if allow_inlining && status.account == status.proper.account && status.proper.private_visibility? && status.local?
status.proper
else
ActivityPub::TagManager.instance.uri_for(status.proper)
end
else
status.proper
end
end
end
end
end
end

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
class ActivityPub::ActivitySerializer < ActivityPub::Serializer
def self.serializer_for(model, options)
case model.class.name
when 'Status'
ActivityPub::NoteSerializer
else
super
end
end
attributes :id, :type, :actor, :published, :to, :cc
has_one :virtual_object, key: :object
def published
object.published.iso8601
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
class ActivityPub::AddFeaturedCollectionSerializer < ActivityPub::Serializer
include RoutingHelper
attributes :type, :actor, :target
has_one :object, serializer: ActivityPub::FeaturedCollectionSerializer
def type
'Add'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def target
ap_account_featured_collections_url(object.account_id)
end
end

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
class ActivityPub::AddHashtagSerializer < ActivityPub::Serializer
attributes :type, :actor, :target
has_one :object, serializer: ActivityPub::HashtagSerializer
def type
'Add'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def target
# Technically this is not correct, as tags have their own collection.
# But sadly we do not store the collection URI for tags anywhere so cannot
# handle `Add` activities to that properly (yet). The receiving code for
# this currently looks at the type of the contained objects to do the
# right thing.
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
end
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
class ActivityPub::AddNoteSerializer < ActivityPub::Serializer
attributes :type, :actor, :target
has_one :proper_object, key: :object
def type
'Add'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def proper_object
ActivityPub::TagManager.instance.uri_for(object)
end
def target
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
end
end

View File

@@ -1,55 +0,0 @@
# frozen_string_literal: true
class ActivityPub::AddSerializer < ActivityPub::Serializer
class UriSerializer < ActiveModel::Serializer
include RoutingHelper
def serializable_hash(*_args)
ActivityPub::TagManager.instance.uri_for(object)
end
end
def self.serializer_for(model, options)
case model
when Status
UriSerializer
when FeaturedTag
ActivityPub::HashtagSerializer
when Collection
ActivityPub::FeaturedCollectionSerializer
else
super
end
end
include RoutingHelper
attributes :type, :actor, :target
has_one :proper_object, key: :object
def type
'Add'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def proper_object
object
end
def target
case object
when Status, FeaturedTag
# Technically this is not correct, as tags have their own collection.
# But sadly we do not store the collection URI for tags anywhere so cannot
# handle `Add` activities to that properly (yet). The receiving code for
# this currently looks at the type of the contained objects to do the
# right thing.
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
when Collection
ap_account_featured_collections_url(object.account_id)
end
end
end

View File

@@ -0,0 +1,53 @@
# frozen_string_literal: true
class ActivityPub::AnnounceNoteSerializer < ActivityPub::Serializer
def self.serializer_for(model, options)
return ActivityPub::NoteSerializer if model.is_a?(Status)
super
end
attributes :id, :type, :actor, :published, :to, :cc
has_one :virtual_object, key: :object
def id
ActivityPub::TagManager.instance.activity_uri_for(object)
end
def type
'Announce'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def to
ActivityPub::TagManager.instance.to(object)
end
def cc
ActivityPub::TagManager.instance.cc(object)
end
def published
object.created_at.iso8601
end
def virtual_object
if allow_inlining? && object.account == object.proper.account && object.proper.private_visibility? && object.local?
object.proper
else
ActivityPub::TagManager.instance.uri_for(object.proper)
end
end
private
def allow_inlining?
return instance_options[:allow_inlining] if instance_options.key?(:allow_inlining)
true
end
end

View File

@@ -0,0 +1,31 @@
# frozen_string_literal: true
class ActivityPub::CreateNoteSerializer < ActivityPub::Serializer
attributes :id, :type, :actor, :published, :to, :cc
has_one :object, serializer: ActivityPub::NoteSerializer
def id
ActivityPub::TagManager.instance.activity_uri_for(object)
end
def type
'Create'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def to
ActivityPub::TagManager.instance.to(object)
end
def cc
ActivityPub::TagManager.instance.cc(object)
end
def published
object.created_at.iso8601
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class ActivityPub::DeleteSerializer < ActivityPub::Serializer
class ActivityPub::DeleteNoteSerializer < ActivityPub::Serializer
class TombstoneSerializer < ActivityPub::Serializer
context_extensions :atom_uri

View File

@@ -2,14 +2,15 @@
class ActivityPub::OutboxSerializer < ActivityPub::CollectionSerializer
def self.serializer_for(model, options)
if model.instance_of?(::ActivityPub::ActivityPresenter)
ActivityPub::ActivitySerializer
case model
when Status
model.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer
else
super
end
end
def items
object.items.map { |status| ActivityPub::ActivityPresenter.from_status(status) }
object.items
end
end

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
class ActivityPub::RemoveFeaturedCollectionSerializer < ActivityPub::Serializer
include RoutingHelper
attributes :type, :actor, :target
has_one :object_uri, key: :object
def type
'Remove'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def target
ap_account_featured_collections_url(object.account_id)
end
def object_uri
ActivityPub::TagManager.instance.uri_for(object)
end
end

View File

@@ -0,0 +1,18 @@
# frozen_string_literal: true
class ActivityPub::RemoveHashtagSerializer < ActivityPub::Serializer
attributes :type, :actor, :target
has_one :object, serializer: ActivityPub::HashtagSerializer
def type
'Remove'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def target
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
class ActivityPub::RemoveNoteSerializer < ActivityPub::Serializer
attributes :type, :actor, :target
has_one :proper_object, key: :object
def type
'Remove'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def proper_object
ActivityPub::TagManager.instance.uri_for(object)
end
def target
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
end
end

View File

@@ -1,43 +0,0 @@
# frozen_string_literal: true
class ActivityPub::RemoveSerializer < ActivityPub::Serializer
class UriSerializer < ActiveModel::Serializer
include RoutingHelper
def serializable_hash(*_args)
ActivityPub::TagManager.instance.uri_for(object)
end
end
def self.serializer_for(model, options)
case model.class.name
when 'Status'
UriSerializer
when 'FeaturedTag'
ActivityPub::HashtagSerializer
else
super
end
end
include RoutingHelper
attributes :type, :actor, :target
has_one :proper_object, key: :object
def type
'Remove'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def proper_object
object
end
def target
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
end
end

View File

@@ -3,7 +3,11 @@
class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer
attributes :id, :type, :actor, :to
has_one :virtual_object, key: :object, serializer: ActivityPub::ActivitySerializer
has_one :virtual_object, key: :object, serializer: ActivityPub::AnnounceNoteSerializer do |serializer|
serializer.send(:instance_options)[:allow_inlining] = false
object
end
def id
[ActivityPub::TagManager.instance.uri_for(object.account), '#announces/', object.id, '/undo'].join
@@ -22,6 +26,6 @@ class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer
end
def virtual_object
ActivityPub::ActivityPresenter.from_status(object, allow_inlining: false)
object
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class ActivityPub::UpdateSerializer < ActivityPub::Serializer
class ActivityPub::UpdateActorSerializer < ActivityPub::Serializer
attributes :id, :type, :actor, :to
has_one :object, serializer: ActivityPub::ActorSerializer

View File

@@ -0,0 +1,37 @@
# frozen_string_literal: true
class ActivityPub::UpdateNoteSerializer < ActivityPub::Serializer
attributes :id, :type, :actor, :published, :to, :cc
has_one :object, serializer: ActivityPub::NoteSerializer
def id
[ActivityPub::TagManager.instance.uri_for(object), '#updates/', edited_at.to_i].join
end
def type
'Update'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def to
ActivityPub::TagManager.instance.to(object)
end
def cc
ActivityPub::TagManager.instance.cc(object)
end
def published
edited_at.iso8601
end
private
def edited_at
instance_options[:updated_at]&.to_datetime || object.edited_at
end
end

View File

@@ -34,7 +34,8 @@ class BackupService < BaseService
add_comma = true
file.write(statuses.map do |status|
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status), ActivityPub::ActivitySerializer, allow_local_only: true)
serializer = status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer
item = serialize_payload(status, serializer, allow_local_only: true)
item.delete(:@context)
unless item[:type] == 'Announce' || item[:object][:attachment].blank?

View File

@@ -32,6 +32,6 @@ class CreateCollectionService
end
def activity_json
ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::AddSerializer, adapter: ActivityPub::Adapter).to_json
ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::AddFeaturedCollectionSerializer, adapter: ActivityPub::Adapter).to_json
end
end

View File

@@ -26,6 +26,6 @@ class CreateFeaturedTagService < BaseService
private
def build_json(featured_tag)
Oj.dump(serialize_payload(featured_tag, ActivityPub::AddSerializer, signer: @account))
Oj.dump(serialize_payload(featured_tag, ActivityPub::AddHashtagSerializer, signer: @account))
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
class DeleteCollectionService
def call(collection)
@collection = collection
@collection.destroy!
distribute_remove_activity if Mastodon::Feature.collections_federation_enabled?
end
private
def distribute_remove_activity
ActivityPub::AccountRawDistributionWorker.perform_async(activity_json, @collection.account.id)
end
def activity_json
ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::RemoveFeaturedCollectionSerializer, adapter: ActivityPub::Adapter).to_json
end
end

View File

@@ -49,8 +49,4 @@ class ReblogService < BaseService
def increment_statistics
ActivityTracker.increment('activity:interactions')
end
def build_json(reblog)
Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_status(reblog), ActivityPub::ActivitySerializer, signer: reblog.account))
end
end

View File

@@ -26,6 +26,6 @@ class RemoveFeaturedTagService < BaseService
private
def build_json(featured_tag)
Oj.dump(serialize_payload(featured_tag, ActivityPub::RemoveSerializer, signer: @account))
Oj.dump(serialize_payload(featured_tag, ActivityPub::RemoveHashtagSerializer, signer: @account))
end
end

View File

@@ -105,7 +105,7 @@ class RemoveStatusService < BaseService
end
def signed_activity_json
@signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account, always_sign: true))
@signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteNoteSerializer, signer: @account, always_sign: true))
end
def remove_reblogs

View File

@@ -72,6 +72,6 @@ class SuspendAccountService < BaseService
end
def signed_activity_json
@signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account))
@signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateActorSerializer, signer: @account))
end
end

View File

@@ -63,6 +63,6 @@ class UnsuspendAccountService < BaseService
end
def signed_activity_json
@signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account))
@signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateActorSerializer, signer: @account))
end
end

View File

@@ -24,11 +24,15 @@ class ActivityPub::DistributionWorker < ActivityPub::RawDistributionWorker
end
def payload
@payload ||= Oj.dump(serialize_payload(activity, ActivityPub::ActivitySerializer, signer: @account))
@payload ||= Oj.dump(serialize_payload(@status, activity_serializer, serializer_options.merge(signer: @account)))
end
def activity
ActivityPub::ActivityPresenter.from_status(@status)
def activity_serializer
@status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer
end
def serializer_options
{}
end
def options

View File

@@ -15,15 +15,11 @@ class ActivityPub::StatusUpdateDistributionWorker < ActivityPub::DistributionWor
protected
def activity
ActivityPub::ActivityPresenter.new(
id: [ActivityPub::TagManager.instance.uri_for(@status), '#updates/', @options[:updated_at]&.to_datetime&.to_i || @status.edited_at.to_i].join,
type: 'Update',
actor: ActivityPub::TagManager.instance.uri_for(@status.account),
published: @options[:updated_at]&.to_datetime || @status.edited_at,
to: ActivityPub::TagManager.instance.to(@status),
cc: ActivityPub::TagManager.instance.cc(@status),
virtual_object: @status
)
def activity_serializer
ActivityPub::UpdateNoteSerializer
end
def serializer_options
super.merge({ updated_at: @options[:updated_at] })
end
end

View File

@@ -23,6 +23,6 @@ class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
end
def payload
@payload ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account, sign_with: @options[:sign_with]))
@payload ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateActorSerializer, signer: @account, sign_with: @options[:sign_with]))
end
end

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "rubygems"
m = Module.new do
module_function
def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__)
end
def env_var_version
ENV["BUNDLER_VERSION"]
end
def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = $1
update_index = i
end
bundler_version
end
def gemfile
gemfile = ENV["BUNDLE_GEMFILE"]
return gemfile if gemfile && !gemfile.empty?
File.expand_path("../Gemfile", __dir__)
end
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
end
def lockfile_version
return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1)
end
def bundler_requirement
@bundler_requirement ||=
env_var_version ||
cli_arg_version ||
bundler_requirement_for(lockfile_version)
end
def bundler_requirement_for(version)
return "#{Gem::Requirement.default}.a" unless version
bundler_gem_version = Gem::Version.new(version)
bundler_gem_version.approximate_recommendation
end
def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile
activate_bundler
end
def activate_bundler
gem_error = activation_error_handling do
gem "bundler", bundler_requirement
end
return if gem_error.nil?
require_error = activation_error_handling do
require "bundler/version"
end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42
end
def activation_error_handling
yield
nil
rescue StandardError, LoadError => e
e
end
end
m.load_bundler!
if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -10,17 +10,6 @@
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"

View File

@@ -105,11 +105,9 @@ Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
# random tokens. Changing this key will render invalid all existing
# confirmation, reset password and unlock tokens in the database.
#
# Set explicitly to Rails default to avoid deprecation warnings.
# https://github.com/heartcombo/devise/pull/5645#issuecomment-1871849856
# Remove when Devise changes `SecretKeyFinder` to not emit deprecations.
config.secret_key = Rails.application.secret_key_base
# Devise will use the `secret_key_base` as its `secret_key`
# by default. You can change it below and use your own secret key.
# config.secret_key = '<%= SecureRandom.hex(64) %>'
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,

View File

@@ -12,6 +12,9 @@ de:
followers:
one: Follower
other: Follower
following:
one: Folge ich
other: Folge ich
instance_actor_flash: Dieses Konto ist ein virtueller Akteur, der den Server selbst repräsentiert, und kein persönliches Profil. Es wird für Föderationszwecke verwendet und sollte daher nicht gesperrt werden.
last_active: zuletzt aktiv
link_verified_on: Das Profil mit dieser E-Mail-Adresse wurde bereits am %{date} bestätigt

View File

@@ -7,7 +7,7 @@ zh-CN:
send_paranoid_instructions: 如果你的邮箱地址存在于我们的数据库中,你将在几分钟内收到一封邮件,内含如何验证邮箱地址的指引。如果你没有收到这封邮件,请检查你的垃圾邮件文件夹。
failure:
already_authenticated: 你已登录。
closed_registrations: 你的注册因为网络政策已被阻止。若认为这是错误,请联系 %{email}。
closed_registrations: 你的注册因为网络政策已被阻止。若认为这是错误,请联系 %{email}。
inactive: 你还没有激活账号。
invalid: "%{authentication_keys} 无效或密码错误。"
last_attempt: 你只有最后一次尝试机会,若未通过,账号将被锁定。

View File

@@ -12,6 +12,9 @@ el:
followers:
one: Ακόλουθος
other: Ακόλουθοι
following:
one: Ακολουθεί
other: Ακολουθούν
instance_actor_flash: Αυτός ο λογαριασμός είναι εικονικός και χρησιμοποιείται για να αντιπροσωπεύει τον ίδιο τον διακομιστή και όχι κάποιον μεμονωμένο χρήστη. Χρησιμοποιείται για σκοπούς ομοσπονδίας και δεν πρέπει να ανασταλεί.
last_active: τελευταία ενεργός/ή
link_verified_on: Η ιδιοκτησία αυτού του συνδέσμου ελέγχθηκε στις %{date}

View File

@@ -12,6 +12,9 @@ es-MX:
followers:
one: Seguidor
other: Seguidores
following:
one: Siguiendo
other: Siguiendo
instance_actor_flash: Esta cuenta es un actor virtual utilizado para representar al servidor en sí mismo y no a ningún usuario individual. Se utiliza para propósitos de la federación y no se debe suspender.
last_active: última conexión
link_verified_on: La propiedad de este vínculo fue verificada el %{date}

View File

@@ -93,7 +93,7 @@ zh-CN:
content_cache_retention_period: 来自其它实例的所有嘟文(包括转嘟与回复)都将在指定天数后被删除,不论本实例用户是否与这些嘟文产生过交互。这包括被本实例用户喜欢和收藏的嘟文。实例间用户的私下提及也将丢失并无法恢复。此设置针对的是特殊用途的实例,用于一般用途时会打破许多用户的期望。
custom_css: 你可以为网页版 Mastodon 应用自定义样式。
favicon: WEBP、PNG、GIF 或 JPG。使用自定义图标覆盖 Mastodon 的默认图标。
landing_page: 选择新访客首次访问的服务器时看到的页面。 如果选择“热门”,则需要在“发现”设置中启用热门趋势。 如果选择“本站动态”,则在“发现”设置中“展示本站嘟文的实时动态访问权限”一项需要设置为“所有人”。
landing_page: 选择新访客首次访问的服务器时看到的页面。 如果选择“热门”,则需要在“发现”设置中启用热门趋势。 如果选择“本站动态”,则在“发现”设置中“展示本站嘟文的实时动态访问权限”一项需要设置为“所有人”。
mascot: 覆盖高级网页界面中的绘图形象。
media_cache_retention_period: 来自外站用户嘟文的媒体文件将被缓存到你的实例上。当该值被设为正值时,缓存的媒体文件将在指定天数后被清除。如果媒体文件在被清除后重新被请求,且源站内容仍然可用,它将被重新下载。由于链接预览卡拉取第三方站点的频率受到限制,建议将此值设置为至少 14 天,如果小于该值,链接预览卡将不会按需更新。
min_age: 用户注册时必须确认出生日期

View File

@@ -12,6 +12,9 @@ sq:
followers:
one: Ndjekës
other: Ndjekës
following:
one: E ndjekur
other: Të ndjekura
instance_actor_flash: Kjo llogari është një aktor virtual, i përdorur për të përfaqësuar vetë serverin dhe jo ndonjë përdorues. Përdoret për qëllime federimi dhe sduhet pezulluar.
last_active: aktiv së fundi
link_verified_on: Pronësia e kësaj lidhjeje qe kontrolluar më %{date}

View File

@@ -11,6 +11,8 @@ vi:
cannot_be_added_to_collections: Tài khoản này không thể thêm vào bộ sưu tập.
followers:
other: Người theo dõi
following:
other: Theo dõi
instance_actor_flash: Tài khoản này được dùng để đại diện cho máy chủ và không phải là người thật. Đừng bao giờ vô hiệu hóa tài khoản này.
last_active: online
link_verified_on: Liên kết này đã được xác minh quyền sở hữu vào %{date}

View File

@@ -11,6 +11,8 @@ zh-CN:
cannot_be_added_to_collections: 此账号无法被添加到收藏列表内。
followers:
other: 关注者
following:
other: 正在关注
instance_actor_flash: 该账号用来代表虚拟角色,并不代表个人用户,仅代表服务器本身。该账号用于联合目的,不应该被停用。
last_active: 上次活跃
link_verified_on: 此链接的所有权已在 %{date} 验证
@@ -1642,7 +1644,7 @@ zh-CN:
disabled_account: 此后,你的当前账号将无法使用。但是,你仍然有权导出数据或者重新激活。
followers: 这步操作将把全部关注者从当前账号移动到新账号
only_redirect_html: 或者,你可以<a href="%{path}">只在你的账号资料上设置一个跳转</a>。
other_data: 其他数据不会自动移动(包括的嘟文及关注的账号列表)
other_data: 其他数据不会自动移动(包括的嘟文及关注的账号列表)
redirect: 在收到一个跳转通知后,你当前的账号资料将会更新,并被排除在搜索范围外
moderation:
title: 审核
@@ -1691,7 +1693,7 @@ zh-CN:
subject: "%{name} 转嘟了你的嘟文"
title: 新的转嘟
severed_relationships:
subject: 由于管理决定,已失去网络上的关注关系
subject: 由于管理决定,已失去网络上的关注关系
status:
subject: "%{name} 刚刚发布嘟文"
update:

View File

@@ -1,64 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::ConfirmationsController do
render_views
before do
sign_in Fabricate(:admin_user), scope: :user
end
describe 'POST #create' do
it 'confirms the user' do
user = Fabricate(:user, confirmed_at: nil)
post :create, params: { account_id: user.account.id }
expect(response).to redirect_to(admin_accounts_path)
expect(user.reload).to be_confirmed
end
it 'raises an error when there is no account' do
post :create, params: { account_id: 'fake' }
expect(response).to have_http_status(404)
end
it 'raises an error when there is no user' do
account = Fabricate(:account, user: nil)
post :create, params: { account_id: account.id }
expect(response).to have_http_status(404)
end
end
describe 'POST #resend' do
subject { post :resend, params: { account_id: user.account.id } }
let!(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
before do
allow(UserMailer).to receive(:confirmation_instructions) { instance_double(ActionMailer::MessageDelivery, deliver_later: nil) }
end
context 'when email is not confirmed' do
let(:confirmed_at) { nil }
it 'resends confirmation mail' do
expect(subject).to redirect_to admin_accounts_path
expect(flash[:notice]).to eq I18n.t('admin.accounts.resend_confirmation.success')
expect(UserMailer).to have_received(:confirmation_instructions).once
end
end
context 'when email is confirmed' do
let(:confirmed_at) { Time.zone.now }
it 'does not resend confirmation mail' do
expect(subject).to redirect_to admin_accounts_path
expect(flash[:error]).to eq I18n.t('admin.accounts.resend_confirmation.already_confirmed')
expect(UserMailer).to_not have_received(:confirmation_instructions)
end
end
end
end

View File

@@ -70,7 +70,7 @@ RSpec.describe Auth::SessionsController do
end
it 'shows a login error and does not log the user in' do
expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: I18n.t('activerecord.attributes.user.email'))
expect(flash[:alert]).to match(/#{failure_message_invalid_email}/i)
expect(controller.current_user).to be_nil
end
@@ -163,7 +163,7 @@ RSpec.describe Auth::SessionsController do
end
it 'shows a login error and does not log the user in' do
expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: I18n.t('activerecord.attributes.user.email'))
expect(flash[:alert]).to match(/#{failure_message_invalid_email}/i)
expect(controller.current_user).to be_nil
end
@@ -420,5 +420,9 @@ RSpec.describe Auth::SessionsController do
end
end
end
def failure_message_invalid_email
I18n.t('devise.failure.invalid', authentication_keys: I18n.t('activerecord.attributes.user.email'))
end
end
end

View File

@@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Admin Confirmations' do
before { sign_in Fabricate(:admin_user) }
describe 'POST /admin/accounts/:account_id/confirmation' do
context 'when account does not exist' do
let(:account_id) { 'fake' }
it 'raises an error' do
post admin_account_confirmation_path(account_id:)
expect(response)
.to have_http_status(404)
end
end
context 'when account does not have a user' do
let(:account) { Fabricate :account, user: nil }
it 'raises an error' do
post admin_account_confirmation_path(account_id: account.id)
expect(response)
.to have_http_status(404)
end
end
end
describe 'POST /admin/accounts/:account_id/confirmation/resend' do
subject { post resend_admin_account_confirmation_path(account_id: user.account.id) }
let(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
context 'when email is confirmed' do
let(:confirmed_at) { Time.zone.now }
it 'does not resend confirmation mail' do
emails = capture_emails { subject }
expect(emails)
.to be_empty
expect(response)
.to redirect_to admin_accounts_path
follow_redirect!
expect(response.body)
.to include(I18n.t('admin.accounts.resend_confirmation.already_confirmed'))
end
end
end
end

View File

@@ -1,102 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::ActivitySerializer do
subject { serialized_record_json(presenter, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:status) { Fabricate(:status, created_at: Time.utc(2026, 0o1, 27, 15, 29, 31)) }
context 'with a new status' do
let(:presenter) { ActivityPub::ActivityPresenter.from_status(status) }
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(status),
'type' => 'Create',
'actor' => tag_manager.uri_for(status.account),
'published' => '2026-01-27T15:29:31Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [a_string_matching(/followers$/)],
'object' => a_hash_including(
'id' => tag_manager.uri_for(status)
),
})
expect(subject).to_not have_key('target')
end
end
context 'with a new reblog' do
let(:reblog) { Fabricate(:status, reblog: status, created_at: Time.utc(2026, 0o1, 27, 16, 21, 44)) }
let(:presenter) { ActivityPub::ActivityPresenter.from_status(reblog) }
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(reblog),
'type' => 'Announce',
'actor' => tag_manager.uri_for(reblog.account),
'published' => '2026-01-27T16:21:44Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)],
'object' => tag_manager.uri_for(status),
})
expect(subject).to_not have_key('target')
end
context 'when inlining of private local status is allowed' do
let(:status) { Fabricate(:status, visibility: :private, created_at: Time.utc(2026, 0o1, 27, 15, 29, 31)) }
let(:reblog) { Fabricate(:status, reblog: status, account: status.account, created_at: Time.utc(2026, 0o1, 27, 16, 21, 44)) }
let(:presenter) { ActivityPub::ActivityPresenter.from_status(reblog, allow_inlining: true) }
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(reblog),
'type' => 'Announce',
'actor' => tag_manager.uri_for(reblog.account),
'published' => '2026-01-27T16:21:44Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)],
'object' => a_hash_including(
'id' => tag_manager.uri_for(status)
),
})
expect(subject).to_not have_key('target')
end
end
end
context 'with a custom presenter for a status `Update`' do
let(:status) { Fabricate(:status, edited_at: Time.utc(2026, 0o1, 27, 15, 29, 31)) }
let(:presenter) do
ActivityPub::ActivityPresenter.new(
id: 'https://localhost/status/1#updates/1769527771',
type: 'Update',
actor: 'https://localhost/actor/1',
published: status.edited_at,
to: ['https://www.w3.org/ns/activitystreams#Public'],
cc: ['https://localhost/actor/1/followers'],
virtual_object: status
)
end
it 'serializes to the expected json' do
expect(subject).to include({
'id' => 'https://localhost/status/1#updates/1769527771',
'type' => 'Update',
'actor' => 'https://localhost/actor/1',
'published' => '2026-01-27T15:29:31Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => ['https://localhost/actor/1/followers'],
'object' => a_hash_including(
'id' => tag_manager.uri_for(status)
),
})
expect(subject).to_not have_key('target')
end
end
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::AddFeaturedCollectionSerializer do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:object) { Fabricate(:collection) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Add',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured_collections$}),
'object' => a_hash_including({
'type' => 'FeaturedCollection',
}),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::AddHashtagSerializer do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:object) { Fabricate(:featured_tag) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Add',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => a_hash_including({
'type' => 'Hashtag',
}),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::AddNoteSerializer do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:object) { Fabricate(:status) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Add',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => tag_manager.uri_for(object),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end

View File

@@ -1,119 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::AddSerializer do
describe '.serializer_for' do
subject { described_class.serializer_for(model, {}) }
context 'with a Status model' do
let(:model) { Status.new }
it { is_expected.to eq(described_class::UriSerializer) }
end
context 'with a FeaturedTag model' do
let(:model) { FeaturedTag.new }
it { is_expected.to eq(ActivityPub::HashtagSerializer) }
end
context 'with a Collection model' do
let(:model) { Collection.new }
it { is_expected.to eq(ActivityPub::FeaturedCollectionSerializer) }
end
context 'with an Array' do
let(:model) { [] }
it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
end
end
describe '#target' do
subject { described_class.new(object).target }
context 'when object is a Status' do
let(:object) { Fabricate(:status) }
it { is_expected.to match(%r{/#{object.account_id}/collections/featured$}) }
end
context 'when object is a FeaturedTag' do
let(:object) { Fabricate(:featured_tag) }
it { is_expected.to match(%r{/#{object.account_id}/collections/featured$}) }
end
context 'when object is a Collection' do
let(:object) { Fabricate(:collection) }
it { is_expected.to match(%r{/#{object.account_id}/featured_collections$}) }
end
end
describe 'Serialization' do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
context 'with a status' do
let(:object) { Fabricate(:status) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Add',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => tag_manager.uri_for(object),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end
context 'with a featured tag' do
let(:object) { Fabricate(:featured_tag) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Add',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => a_hash_including({
'type' => 'Hashtag',
}),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end
context 'with a collection' do
let(:object) { Fabricate(:collection) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Add',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured_collections$}),
'object' => a_hash_including({
'type' => 'FeaturedCollection',
}),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end
end
end

View File

@@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::AnnounceNoteSerializer do
subject { serialized_record_json(reblog, described_class, adapter: ActivityPub::Adapter, options:) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:status) { Fabricate(:status, created_at: Time.utc(2026, 1, 27, 15, 29, 31)) }
let(:reblog) { Fabricate(:status, reblog: status, created_at: Time.utc(2026, 1, 27, 16, 21, 44)) }
let(:options) { {} }
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(reblog),
'type' => 'Announce',
'actor' => tag_manager.uri_for(reblog.account),
'published' => '2026-01-27T16:21:44Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)],
'object' => tag_manager.uri_for(status),
})
expect(subject).to_not have_key('target')
end
context 'when status is local and private' do
let(:status) { Fabricate(:status, visibility: :private, created_at: Time.utc(2026, 1, 27, 15, 29, 31)) }
let(:reblog) { Fabricate(:status, reblog: status, account: status.account, created_at: Time.utc(2026, 1, 27, 16, 21, 44)) }
context 'when inlining of private local status is allowed' do
shared_examples 'serialization with inlining' do
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(reblog),
'type' => 'Announce',
'actor' => tag_manager.uri_for(reblog.account),
'published' => '2026-01-27T16:21:44Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)],
'object' => a_hash_including(
'id' => tag_manager.uri_for(status)
),
})
expect(subject).to_not have_key('target')
end
end
context 'with `allow_inlining` explicitly set to `true`' do
let(:options) { { allow_inlining: true } }
it_behaves_like 'serialization with inlining'
end
context 'with `allow_inlining` unset' do
let(:options) { {} }
it_behaves_like 'serialization with inlining'
end
end
context 'when inlining is not allowed' do
let(:options) { { allow_inlining: false } }
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(reblog),
'type' => 'Announce',
'actor' => tag_manager.uri_for(reblog.account),
'published' => '2026-01-27T16:21:44Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)],
'object' => tag_manager.uri_for(status),
})
end
end
end
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::CreateNoteSerializer do
subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:status) { Fabricate(:status, created_at: Time.utc(2026, 1, 27, 15, 29, 31)) }
it 'serializes to the expected json' do
expect(subject).to include({
'id' => tag_manager.activity_uri_for(status),
'type' => 'Create',
'actor' => tag_manager.uri_for(status.account),
'published' => '2026-01-27T15:29:31Z',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'cc' => [a_string_matching(/followers$/)],
'object' => a_hash_including(
'id' => tag_manager.uri_for(status)
),
})
expect(subject).to_not have_key('target')
end
end

View File

@@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::DeleteSerializer do
RSpec.describe ActivityPub::DeleteNoteSerializer do
subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::RemoveFeaturedCollectionSerializer do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:object) { Fabricate(:collection) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Remove',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured_collections$}),
'object' => tag_manager.uri_for(object),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::RemoveHashtagSerializer do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:object) { Fabricate(:featured_tag) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Remove',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => a_hash_including({
'type' => 'Hashtag',
}),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::RemoveNoteSerializer do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
let(:object) { Fabricate(:status) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Remove',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => tag_manager.uri_for(object),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end

View File

@@ -1,71 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::RemoveSerializer do
describe '.serializer_for' do
subject { described_class.serializer_for(model, {}) }
context 'with a Status model' do
let(:model) { Status.new }
it { is_expected.to eq(described_class::UriSerializer) }
end
context 'with a FeaturedTag model' do
let(:model) { FeaturedTag.new }
it { is_expected.to eq(ActivityPub::HashtagSerializer) }
end
context 'with an Array' do
let(:model) { [] }
it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
end
end
describe 'Serialization' do
subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) }
let(:tag_manager) { ActivityPub::TagManager.instance }
context 'with a status' do
let(:object) { Fabricate(:status) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Remove',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => tag_manager.uri_for(object),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end
context 'with a featured tag' do
let(:object) { Fabricate(:featured_tag) }
it 'serializes to the expected json' do
expect(subject).to include({
'type' => 'Remove',
'actor' => tag_manager.uri_for(object.account),
'target' => a_string_matching(%r{/featured$}),
'object' => a_hash_including({
'type' => 'Hashtag',
}),
})
expect(subject).to_not have_key('id')
expect(subject).to_not have_key('published')
expect(subject).to_not have_key('to')
expect(subject).to_not have_key('cc')
end
end
end
end

View File

@@ -26,4 +26,17 @@ RSpec.describe ActivityPub::UndoAnnounceSerializer do
expect(subject).to_not have_key('cc')
expect(subject).to_not have_key('target')
end
context 'when status is local and private' do
let(:status) { Fabricate(:status, visibility: :private) }
let(:reblog) { Fabricate(:status, reblog: status, account: status.account) }
it 'does not inline the status' do
expect(subject).to include({
'object' => a_hash_including({
'object' => tag_manager.uri_for(status),
}),
})
end
end
end

Some files were not shown because too many files have changed in this diff Show More