From 73206856c55a981c3b4b9fbf052df7664c5e2e32 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 5 Feb 2026 10:39:27 +0100 Subject: [PATCH 01/11] Refactor activity serialization (#37678) --- .../api/v1/statuses/pins_controller.rb | 4 +- app/controllers/statuses_controller.rb | 6 +- .../activitypub/activity_presenter.rb | 30 ----- .../activitypub/activity_serializer.rb | 20 --- .../add_featured_collection_serializer.rb | 20 +++ .../activitypub/add_hashtag_serializer.rb | 24 ++++ .../activitypub/add_note_serializer.rb | 23 ++++ app/serializers/activitypub/add_serializer.rb | 55 -------- .../activitypub/announce_note_serializer.rb | 53 ++++++++ .../activitypub/create_note_serializer.rb | 31 +++++ ...erializer.rb => delete_note_serializer.rb} | 2 +- .../activitypub/outbox_serializer.rb | 7 +- .../activitypub/remove_hashtag_serializer.rb | 18 +++ .../activitypub/remove_note_serializer.rb | 22 ++++ .../activitypub/remove_serializer.rb | 43 ------- .../activitypub/undo_announce_serializer.rb | 8 +- ...rializer.rb => update_actor_serializer.rb} | 2 +- .../activitypub/update_note_serializer.rb | 37 ++++++ app/services/backup_service.rb | 3 +- app/services/create_collection_service.rb | 2 +- app/services/create_featured_tag_service.rb | 2 +- app/services/reblog_service.rb | 4 - app/services/remove_featured_tag_service.rb | 2 +- app/services/remove_status_service.rb | 2 +- app/services/suspend_account_service.rb | 2 +- app/services/unsuspend_account_service.rb | 2 +- .../activitypub/distribution_worker.rb | 10 +- .../status_update_distribution_worker.rb | 16 +-- .../activitypub/update_distribution_worker.rb | 2 +- .../activitypub/activity_serializer_spec.rb | 102 --------------- ...add_featured_collection_serializer_spec.rb | 26 ++++ .../add_hashtag_serializer_spec.rb | 26 ++++ .../activitypub/add_note_serializer_spec.rb | 24 ++++ .../activitypub/add_serializer_spec.rb | 119 ------------------ .../announce_note_serializer_spec.rb | 79 ++++++++++++ .../create_note_serializer_spec.rb | 26 ++++ ...spec.rb => delete_note_serializer_spec.rb} | 2 +- .../remove_hashtag_serializer_spec.rb | 26 ++++ .../remove_note_serializer_spec.rb | 24 ++++ .../activitypub/remove_serializer_spec.rb | 71 ----------- .../undo_announce_serializer_spec.rb | 13 ++ ...pec.rb => update_actor_serializer_spec.rb} | 2 +- .../update_note_serializer_spec.rb | 26 ++++ .../activitypub/distribution_worker_spec.rb | 41 ++++++ 44 files changed, 582 insertions(+), 477 deletions(-) delete mode 100644 app/presenters/activitypub/activity_presenter.rb delete mode 100644 app/serializers/activitypub/activity_serializer.rb create mode 100644 app/serializers/activitypub/add_featured_collection_serializer.rb create mode 100644 app/serializers/activitypub/add_hashtag_serializer.rb create mode 100644 app/serializers/activitypub/add_note_serializer.rb delete mode 100644 app/serializers/activitypub/add_serializer.rb create mode 100644 app/serializers/activitypub/announce_note_serializer.rb create mode 100644 app/serializers/activitypub/create_note_serializer.rb rename app/serializers/activitypub/{delete_serializer.rb => delete_note_serializer.rb} (91%) create mode 100644 app/serializers/activitypub/remove_hashtag_serializer.rb create mode 100644 app/serializers/activitypub/remove_note_serializer.rb delete mode 100644 app/serializers/activitypub/remove_serializer.rb rename app/serializers/activitypub/{update_serializer.rb => update_actor_serializer.rb} (86%) create mode 100644 app/serializers/activitypub/update_note_serializer.rb delete mode 100644 spec/serializers/activitypub/activity_serializer_spec.rb create mode 100644 spec/serializers/activitypub/add_featured_collection_serializer_spec.rb create mode 100644 spec/serializers/activitypub/add_hashtag_serializer_spec.rb create mode 100644 spec/serializers/activitypub/add_note_serializer_spec.rb delete mode 100644 spec/serializers/activitypub/add_serializer_spec.rb create mode 100644 spec/serializers/activitypub/announce_note_serializer_spec.rb create mode 100644 spec/serializers/activitypub/create_note_serializer_spec.rb rename spec/serializers/activitypub/{delete_serializer_spec.rb => delete_note_serializer_spec.rb} (93%) create mode 100644 spec/serializers/activitypub/remove_hashtag_serializer_spec.rb create mode 100644 spec/serializers/activitypub/remove_note_serializer_spec.rb delete mode 100644 spec/serializers/activitypub/remove_serializer_spec.rb rename spec/serializers/activitypub/{update_serializer_spec.rb => update_actor_serializer_spec.rb} (93%) create mode 100644 spec/serializers/activitypub/update_note_serializer_spec.rb diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb index 7107890af1..32a5f71293 100644 --- a/app/controllers/api/v1/statuses/pins_controller.rb +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -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 diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 65db807d18..be3641589f 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -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 diff --git a/app/presenters/activitypub/activity_presenter.rb b/app/presenters/activitypub/activity_presenter.rb deleted file mode 100644 index 994cbeaf48..0000000000 --- a/app/presenters/activitypub/activity_presenter.rb +++ /dev/null @@ -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 diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb deleted file mode 100644 index 23a5b42bc7..0000000000 --- a/app/serializers/activitypub/activity_serializer.rb +++ /dev/null @@ -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 diff --git a/app/serializers/activitypub/add_featured_collection_serializer.rb b/app/serializers/activitypub/add_featured_collection_serializer.rb new file mode 100644 index 0000000000..022c44a1bc --- /dev/null +++ b/app/serializers/activitypub/add_featured_collection_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/add_hashtag_serializer.rb b/app/serializers/activitypub/add_hashtag_serializer.rb new file mode 100644 index 0000000000..3522cbcc2e --- /dev/null +++ b/app/serializers/activitypub/add_hashtag_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/add_note_serializer.rb b/app/serializers/activitypub/add_note_serializer.rb new file mode 100644 index 0000000000..45e9a8708a --- /dev/null +++ b/app/serializers/activitypub/add_note_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb deleted file mode 100644 index a2a1256ec5..0000000000 --- a/app/serializers/activitypub/add_serializer.rb +++ /dev/null @@ -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 diff --git a/app/serializers/activitypub/announce_note_serializer.rb b/app/serializers/activitypub/announce_note_serializer.rb new file mode 100644 index 0000000000..45dc0b4739 --- /dev/null +++ b/app/serializers/activitypub/announce_note_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/create_note_serializer.rb b/app/serializers/activitypub/create_note_serializer.rb new file mode 100644 index 0000000000..ce753657cb --- /dev/null +++ b/app/serializers/activitypub/create_note_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_note_serializer.rb similarity index 91% rename from app/serializers/activitypub/delete_serializer.rb rename to app/serializers/activitypub/delete_note_serializer.rb index a7d5bd4698..41a218dc3f 100644 --- a/app/serializers/activitypub/delete_serializer.rb +++ b/app/serializers/activitypub/delete_note_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/outbox_serializer.rb b/app/serializers/activitypub/outbox_serializer.rb index 4d3d9706de..942c1bbc4e 100644 --- a/app/serializers/activitypub/outbox_serializer.rb +++ b/app/serializers/activitypub/outbox_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/remove_hashtag_serializer.rb b/app/serializers/activitypub/remove_hashtag_serializer.rb new file mode 100644 index 0000000000..ba7ec00383 --- /dev/null +++ b/app/serializers/activitypub/remove_hashtag_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/remove_note_serializer.rb b/app/serializers/activitypub/remove_note_serializer.rb new file mode 100644 index 0000000000..92a73f05c7 --- /dev/null +++ b/app/serializers/activitypub/remove_note_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb deleted file mode 100644 index 4f78804031..0000000000 --- a/app/serializers/activitypub/remove_serializer.rb +++ /dev/null @@ -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 diff --git a/app/serializers/activitypub/undo_announce_serializer.rb b/app/serializers/activitypub/undo_announce_serializer.rb index a0f09e8ab9..5e6366073d 100644 --- a/app/serializers/activitypub/undo_announce_serializer.rb +++ b/app/serializers/activitypub/undo_announce_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/update_serializer.rb b/app/serializers/activitypub/update_actor_serializer.rb similarity index 86% rename from app/serializers/activitypub/update_serializer.rb rename to app/serializers/activitypub/update_actor_serializer.rb index a5eb857d3f..a10a632eee 100644 --- a/app/serializers/activitypub/update_serializer.rb +++ b/app/serializers/activitypub/update_actor_serializer.rb @@ -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 diff --git a/app/serializers/activitypub/update_note_serializer.rb b/app/serializers/activitypub/update_note_serializer.rb new file mode 100644 index 0000000000..8dac72250b --- /dev/null +++ b/app/serializers/activitypub/update_note_serializer.rb @@ -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 diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 42aa4c93cf..299b5df234 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -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) + serializer = status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer + item = serialize_payload(status, serializer) item.delete(:@context) unless item[:type] == 'Announce' || item[:object][:attachment].blank? diff --git a/app/services/create_collection_service.rb b/app/services/create_collection_service.rb index 008d4b5c09..7e8935e1a4 100644 --- a/app/services/create_collection_service.rb +++ b/app/services/create_collection_service.rb @@ -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 diff --git a/app/services/create_featured_tag_service.rb b/app/services/create_featured_tag_service.rb index 13d6ac0201..298bdb5928 100644 --- a/app/services/create_featured_tag_service.rb +++ b/app/services/create_featured_tag_service.rb @@ -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 diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index cca79eced6..4c292d4b16 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -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 diff --git a/app/services/remove_featured_tag_service.rb b/app/services/remove_featured_tag_service.rb index af8c5a64ee..4fdd43eb6a 100644 --- a/app/services/remove_featured_tag_service.rb +++ b/app/services/remove_featured_tag_service.rb @@ -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 diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index f61cb632b2..caa22b4729 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -103,7 +103,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 diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 7ddf1a553d..666b64cacf 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -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 diff --git a/app/services/unsuspend_account_service.rb b/app/services/unsuspend_account_service.rb index 180b2cd3f6..1a52e80d24 100644 --- a/app/services/unsuspend_account_service.rb +++ b/app/services/unsuspend_account_service.rb @@ -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 diff --git a/app/workers/activitypub/distribution_worker.rb b/app/workers/activitypub/distribution_worker.rb index 125278fa51..63013bdc69 100644 --- a/app/workers/activitypub/distribution_worker.rb +++ b/app/workers/activitypub/distribution_worker.rb @@ -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 diff --git a/app/workers/activitypub/status_update_distribution_worker.rb b/app/workers/activitypub/status_update_distribution_worker.rb index 7f70fcaecc..4ef06dee5a 100644 --- a/app/workers/activitypub/status_update_distribution_worker.rb +++ b/app/workers/activitypub/status_update_distribution_worker.rb @@ -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 diff --git a/app/workers/activitypub/update_distribution_worker.rb b/app/workers/activitypub/update_distribution_worker.rb index 9a418f0f3d..976f516498 100644 --- a/app/workers/activitypub/update_distribution_worker.rb +++ b/app/workers/activitypub/update_distribution_worker.rb @@ -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 diff --git a/spec/serializers/activitypub/activity_serializer_spec.rb b/spec/serializers/activitypub/activity_serializer_spec.rb deleted file mode 100644 index 15e67111e9..0000000000 --- a/spec/serializers/activitypub/activity_serializer_spec.rb +++ /dev/null @@ -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 diff --git a/spec/serializers/activitypub/add_featured_collection_serializer_spec.rb b/spec/serializers/activitypub/add_featured_collection_serializer_spec.rb new file mode 100644 index 0000000000..109aab49fd --- /dev/null +++ b/spec/serializers/activitypub/add_featured_collection_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/add_hashtag_serializer_spec.rb b/spec/serializers/activitypub/add_hashtag_serializer_spec.rb new file mode 100644 index 0000000000..6c3c419e47 --- /dev/null +++ b/spec/serializers/activitypub/add_hashtag_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/add_note_serializer_spec.rb b/spec/serializers/activitypub/add_note_serializer_spec.rb new file mode 100644 index 0000000000..85ef189b95 --- /dev/null +++ b/spec/serializers/activitypub/add_note_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/add_serializer_spec.rb b/spec/serializers/activitypub/add_serializer_spec.rb deleted file mode 100644 index 6ab8a865ad..0000000000 --- a/spec/serializers/activitypub/add_serializer_spec.rb +++ /dev/null @@ -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 diff --git a/spec/serializers/activitypub/announce_note_serializer_spec.rb b/spec/serializers/activitypub/announce_note_serializer_spec.rb new file mode 100644 index 0000000000..d62f93e5ec --- /dev/null +++ b/spec/serializers/activitypub/announce_note_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/create_note_serializer_spec.rb b/spec/serializers/activitypub/create_note_serializer_spec.rb new file mode 100644 index 0000000000..7a2b8ba9f7 --- /dev/null +++ b/spec/serializers/activitypub/create_note_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/delete_serializer_spec.rb b/spec/serializers/activitypub/delete_note_serializer_spec.rb similarity index 93% rename from spec/serializers/activitypub/delete_serializer_spec.rb rename to spec/serializers/activitypub/delete_note_serializer_spec.rb index ba5a4f9cb4..3165c05ce5 100644 --- a/spec/serializers/activitypub/delete_serializer_spec.rb +++ b/spec/serializers/activitypub/delete_note_serializer_spec.rb @@ -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 } diff --git a/spec/serializers/activitypub/remove_hashtag_serializer_spec.rb b/spec/serializers/activitypub/remove_hashtag_serializer_spec.rb new file mode 100644 index 0000000000..cf9ab2c26e --- /dev/null +++ b/spec/serializers/activitypub/remove_hashtag_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/remove_note_serializer_spec.rb b/spec/serializers/activitypub/remove_note_serializer_spec.rb new file mode 100644 index 0000000000..83531de693 --- /dev/null +++ b/spec/serializers/activitypub/remove_note_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/remove_serializer_spec.rb b/spec/serializers/activitypub/remove_serializer_spec.rb deleted file mode 100644 index a79977774d..0000000000 --- a/spec/serializers/activitypub/remove_serializer_spec.rb +++ /dev/null @@ -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 diff --git a/spec/serializers/activitypub/undo_announce_serializer_spec.rb b/spec/serializers/activitypub/undo_announce_serializer_spec.rb index d1ecd3cfc5..19b8106c6f 100644 --- a/spec/serializers/activitypub/undo_announce_serializer_spec.rb +++ b/spec/serializers/activitypub/undo_announce_serializer_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/update_serializer_spec.rb b/spec/serializers/activitypub/update_actor_serializer_spec.rb similarity index 93% rename from spec/serializers/activitypub/update_serializer_spec.rb rename to spec/serializers/activitypub/update_actor_serializer_spec.rb index 7fd034b604..541b4e656d 100644 --- a/spec/serializers/activitypub/update_serializer_spec.rb +++ b/spec/serializers/activitypub/update_actor_serializer_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe ActivityPub::UpdateSerializer do +RSpec.describe ActivityPub::UpdateActorSerializer do subject { serialized_record_json(account, described_class, adapter: ActivityPub::Adapter) } let(:tag_manager) { ActivityPub::TagManager.instance } diff --git a/spec/serializers/activitypub/update_note_serializer_spec.rb b/spec/serializers/activitypub/update_note_serializer_spec.rb new file mode 100644 index 0000000000..4307b29f06 --- /dev/null +++ b/spec/serializers/activitypub/update_note_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::UpdateNoteSerializer do + subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:status) { Fabricate(:status, edited_at: Time.utc(2026, 1, 27, 15, 29, 31)) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => "#{tag_manager.uri_for(status)}#updates/1769527771", + 'type' => 'Update', + '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(%r{/followers$})], + 'object' => a_hash_including( + 'id' => tag_manager.uri_for(status) + ), + }) + + expect(subject).to_not have_key('target') + end +end diff --git a/spec/workers/activitypub/distribution_worker_spec.rb b/spec/workers/activitypub/distribution_worker_spec.rb index 9e5db53185..1551ba63ba 100644 --- a/spec/workers/activitypub/distribution_worker_spec.rb +++ b/spec/workers/activitypub/distribution_worker_spec.rb @@ -51,5 +51,46 @@ RSpec.describe ActivityPub::DistributionWorker do end end end + + context 'with a reblog' do + before do + follower.follow!(reblog.account) + end + + context 'when the reblogged status is not private' do + let(:status) { Fabricate(:status) } + let(:reblog) { Fabricate(:status, reblog: status) } + + it 'delivers an activity without inlining the status' do + expected_json = { + type: 'Announce', + object: ActivityPub::TagManager.instance.uri_for(status), + } + + expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [[match_json_values(expected_json), reblog.account.id, 'http://example.com', anything]]) do + subject.perform(reblog.id) + end + end + end + + context 'when the reblogged status is private' do + let(:status) { Fabricate(:status, visibility: :private) } + let(:reblog) { Fabricate(:status, reblog: status, account: status.account) } + + it 'delivers an activity that inlines the status' do + expected_json = { + type: 'Announce', + object: a_hash_including({ + id: ActivityPub::TagManager.instance.uri_for(status), + type: 'Note', + }), + } + + expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [[match_json_values(expected_json), reblog.account.id, 'http://example.com', anything]]) do + subject.perform(reblog.id) + end + end + end + end end end From 8949ef57aa63dfa7189cc3cd683f1ecada738eff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:56:33 +0100 Subject: [PATCH 02/11] Update dependency kt-paperclip to v7.3.0 (#37689) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 21011f4e8e..5c8483a336 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) From 6f47a7709e5fdcb15e019a90b929dfae9e6060a8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:56:38 +0100 Subject: [PATCH 03/11] Update dependency test-prof to v1.5.2 (#37726) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5c8483a336..1b981595fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) From 8a65965dede1b4c305c25fceb352746c87155ec9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:56:45 +0100 Subject: [PATCH 04/11] Update dependency brakeman to v8.0.2 (#37728) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1b981595fb..a455287945 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) From e82eb2b0376d0d9a35f1aea4a005a67091ca1f0a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 5 Feb 2026 05:04:10 -0500 Subject: [PATCH 05/11] Convert `admin/confirmations` spec controller->request/system (#37738) --- .../admin/confirmations_controller_spec.rb | 64 ------------------- spec/requests/admin/confirmations_spec.rb | 56 ++++++++++++++++ spec/system/admin/confirmations_spec.rb | 54 ++++++++++++++++ 3 files changed, 110 insertions(+), 64 deletions(-) delete mode 100644 spec/controllers/admin/confirmations_controller_spec.rb create mode 100644 spec/requests/admin/confirmations_spec.rb create mode 100644 spec/system/admin/confirmations_spec.rb diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb deleted file mode 100644 index 22035d15e6..0000000000 --- a/spec/controllers/admin/confirmations_controller_spec.rb +++ /dev/null @@ -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 diff --git a/spec/requests/admin/confirmations_spec.rb b/spec/requests/admin/confirmations_spec.rb new file mode 100644 index 0000000000..5f9dbb203f --- /dev/null +++ b/spec/requests/admin/confirmations_spec.rb @@ -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 diff --git a/spec/system/admin/confirmations_spec.rb b/spec/system/admin/confirmations_spec.rb new file mode 100644 index 0000000000..70821af701 --- /dev/null +++ b/spec/system/admin/confirmations_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Confirmations' do + before { sign_in Fabricate(:admin_user) } + + describe 'Confirming a user' do + let!(:user) { Fabricate :user, confirmed_at: nil } + + it 'changes user to confirmed and returns to accounts page' do + # Go to accounts listing page + visit admin_accounts_path + expect(page) + .to have_title(I18n.t('admin.accounts.title')) + + # Go to account page + click_on user.account.username + + # Click to confirm + expect { click_on I18n.t('admin.accounts.confirm') } + .to change(Admin::ActionLog.where(action: 'confirm'), :count).by(1) + expect(page) + .to have_title(I18n.t('admin.accounts.title')) + expect(user.reload) + .to be_confirmed + end + end + + describe 'Resending a confirmation email', :inline_jobs do + let!(:user) { Fabricate(:user, confirmed_at: confirmed_at) } + + context 'when email is not confirmed' do + let(:confirmed_at) { nil } + + it 'resends the confirmation mail' do + visit admin_account_path(id: user.account.id) + + emails = capture_emails { resend_confirmation } + expect(page) + .to have_title(I18n.t('admin.accounts.title')) + .and have_content(I18n.t('admin.accounts.resend_confirmation.success')) + + expect(emails.first) + .to be_present + .and deliver_to(user.email) + end + end + + def resend_confirmation + click_on I18n.t('admin.accounts.resend_confirmation.send') + end + end +end From ed649fbdb719bf0ce1da2206890c358f3cf0354f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:08:56 +0000 Subject: [PATCH 06/11] New Crowdin Translations (automated) (#37740) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 2 - app/javascript/mastodon/locales/ca.json | 2 - app/javascript/mastodon/locales/cy.json | 2 - app/javascript/mastodon/locales/da.json | 19 +++++++- app/javascript/mastodon/locales/de.json | 19 +++++++- app/javascript/mastodon/locales/el.json | 19 +++++++- app/javascript/mastodon/locales/en-GB.json | 2 - app/javascript/mastodon/locales/es-AR.json | 19 +++++++- app/javascript/mastodon/locales/es-MX.json | 19 +++++++- app/javascript/mastodon/locales/es.json | 6 ++- app/javascript/mastodon/locales/et.json | 8 +++- app/javascript/mastodon/locales/fi.json | 19 +++++++- app/javascript/mastodon/locales/fo.json | 2 - app/javascript/mastodon/locales/fr-CA.json | 2 - app/javascript/mastodon/locales/fr.json | 2 - app/javascript/mastodon/locales/ga.json | 2 - app/javascript/mastodon/locales/gl.json | 19 +++++++- app/javascript/mastodon/locales/he.json | 19 +++++++- app/javascript/mastodon/locales/hu.json | 2 - app/javascript/mastodon/locales/is.json | 12 ++++- app/javascript/mastodon/locales/it.json | 2 - app/javascript/mastodon/locales/nan-TW.json | 2 - app/javascript/mastodon/locales/nl.json | 2 - app/javascript/mastodon/locales/pt-BR.json | 2 - app/javascript/mastodon/locales/pt-PT.json | 2 - app/javascript/mastodon/locales/sq.json | 19 +++++++- app/javascript/mastodon/locales/tr.json | 2 - app/javascript/mastodon/locales/vi.json | 19 +++++++- app/javascript/mastodon/locales/zh-CN.json | 52 +++++++++++++++++---- app/javascript/mastodon/locales/zh-TW.json | 19 +++++++- config/locales/de.yml | 3 ++ config/locales/devise.zh-CN.yml | 2 +- config/locales/el.yml | 3 ++ config/locales/es-MX.yml | 3 ++ config/locales/simple_form.zh-CN.yml | 2 +- config/locales/sq.yml | 3 ++ config/locales/vi.yml | 2 + config/locales/zh-CN.yml | 6 ++- 38 files changed, 270 insertions(+), 71 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 5d83ecc251..54d31d3f7d 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -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": "Стужкі", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 993ca4e613..192c659882 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -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", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index be64f50784..5e0efb9e46 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -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", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 0990a736db..3209224d32 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -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", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0a790d7c37..224f634106 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -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", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index d5ce3c9fce..0fb6b06f3e 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -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": "Ζωντανές ροές", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index aa5b970339..7469dc87d4 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -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", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 028233ed22..a863f98714 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -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", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 21362d03fe..5e8e8578e3 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -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", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index f12e9e1108..7a01a770e9 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -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", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index ed9e2c9e5f..85f6a6ae4c 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -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", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index d5f1ce17b2..91070912a7 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -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", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index bf29609785..c923e072c0 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -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", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index fb2244ae45..16a8c62f61 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -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", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 89d66de5b4..9104d1a0aa 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -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", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 63d85c286e..03b3ddcc61 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -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", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index ac09ff300e..b948ad144c 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -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", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index ed41246393..c320f31c84 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -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": "פידים עדכניים", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index d0ebd6a914..bd3e9bb214 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -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", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index d3b2b96eae..10c9f13d49 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -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", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 87ed3f716b..e2ed20eabe 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -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", diff --git a/app/javascript/mastodon/locales/nan-TW.json b/app/javascript/mastodon/locales/nan-TW.json index 1d36c4b3c6..b61be2d745 100644 --- a/app/javascript/mastodon/locales/nan-TW.json +++ b/app/javascript/mastodon/locales/nan-TW.json @@ -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á ê動態", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index d29f970543..fe7a0dbe38 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -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", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index d227787f2a..7d02ab5ae8 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -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", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index ce490ffb40..b71f7750ca 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -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", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index cd4198608d..9467af52a3 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -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”", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index bbae2467d6..7452870778 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -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", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 14bb93720a..89b861d057 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -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", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 6148d2080c..fc203fbde6 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -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": "投票", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 253fbb3c41..ae35edfb6b 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -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": "即時內容", diff --git a/config/locales/de.yml b/config/locales/de.yml index 2b307d7390..9a46e99cc7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -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 diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml index ffffe0cbf4..284dbd9fb0 100644 --- a/config/locales/devise.zh-CN.yml +++ b/config/locales/devise.zh-CN.yml @@ -7,7 +7,7 @@ zh-CN: send_paranoid_instructions: 如果你的邮箱地址存在于我们的数据库中,你将在几分钟内收到一封邮件,内含如何验证邮箱地址的指引。如果你没有收到这封邮件,请检查你的垃圾邮件文件夹。 failure: already_authenticated: 你已登录。 - closed_registrations: 你的注册因为网络政策已被阻止。若您认为这是错误,请联系 %{email}。 + closed_registrations: 你的注册因为网络政策已被阻止。若你认为这是错误,请联系 %{email}。 inactive: 你还没有激活账号。 invalid: "%{authentication_keys} 无效或密码错误。" last_attempt: 你只有最后一次尝试机会,若未通过,账号将被锁定。 diff --git a/config/locales/el.yml b/config/locales/el.yml index 86c1b51959..d097ada0fb 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -12,6 +12,9 @@ el: followers: one: Ακόλουθος other: Ακόλουθοι + following: + one: Ακολουθεί + other: Ακολουθούν instance_actor_flash: Αυτός ο λογαριασμός είναι εικονικός και χρησιμοποιείται για να αντιπροσωπεύει τον ίδιο τον διακομιστή και όχι κάποιον μεμονωμένο χρήστη. Χρησιμοποιείται για σκοπούς ομοσπονδίας και δεν πρέπει να ανασταλεί. last_active: τελευταία ενεργός/ή link_verified_on: Η ιδιοκτησία αυτού του συνδέσμου ελέγχθηκε στις %{date} diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index ce0fbacc77..bc52d92add 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -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} diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index 49dbbb5b0f..576d47189c 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -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: 用户注册时必须确认出生日期 diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 500807cfce..8a5feb6a11 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -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 s’duhet pezulluar. last_active: aktiv së fundi link_verified_on: Pronësia e kësaj lidhjeje qe kontrolluar më %{date} diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 9e0b1d12da..15185484a5 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -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} diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index f422a3abae..8acfce541c 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -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: 或者,你可以只在你的账号资料上设置一个跳转。 - other_data: 其他数据不会自动移动(包括您的嘟文及您关注的账号列表) + other_data: 其他数据不会自动移动(包括你的嘟文及你关注的账号列表) redirect: 在收到一个跳转通知后,你当前的账号资料将会更新,并被排除在搜索范围外 moderation: title: 审核 @@ -1691,7 +1693,7 @@ zh-CN: subject: "%{name} 转嘟了你的嘟文" title: 新的转嘟 severed_relationships: - subject: 由于管理决定,您已失去网络上的关注关系 + subject: 由于管理决定,你已失去网络上的关注关系 status: subject: "%{name} 刚刚发布嘟文" update: From e24a19f8bf478f30dd628c0e328ecb6186d71385 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:23:08 +0100 Subject: [PATCH 07/11] Update dependency jsdom to v28 (#37697) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- streaming/package.json | 2 +- yarn.lock | 134 ++++++++++++++++++++--------------------- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/streaming/package.json b/streaming/package.json index 7a1685749a..cee103bdbd 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -21,7 +21,7 @@ "dotenv": "^17.0.0", "express": "^5.1.0", "ioredis": "^5.3.2", - "jsdom": "^27.0.0", + "jsdom": "^28.0.0", "pg": "^8.5.0", "pg-connection-string": "^2.6.0", "pino": "^10.0.0", diff --git a/yarn.lock b/yarn.lock index 9dc2c09985..7915dbc58f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,10 +12,10 @@ __metadata: languageName: node linkType: hard -"@acemir/cssom@npm:^0.9.28": - version: 0.9.28 - resolution: "@acemir/cssom@npm:0.9.28" - checksum: 10c0/1e192d216c4236171d9930b42b9a965052d4578b23c6ddaa17c7c3d0820ffb872258544a83af163ae2d41b3bdccd6b6c4c14b2d32eb9f8b8b63972d74f46bd83 +"@acemir/cssom@npm:^0.9.31": + version: 0.9.31 + resolution: "@acemir/cssom@npm:0.9.31" + checksum: 10c0/cbfff98812642104ec3b37de1ad3a53f216ddc437e7b9276a23f46f2453844ea3c3f46c200bc4656a2f747fb26567560b3cc5183d549d119a758926551b5f566 languageName: node linkType: hard @@ -46,16 +46,16 @@ __metadata: languageName: node linkType: hard -"@asamuzakjp/css-color@npm:^4.1.0": - version: 4.1.0 - resolution: "@asamuzakjp/css-color@npm:4.1.0" +"@asamuzakjp/css-color@npm:^4.1.1": + version: 4.1.1 + resolution: "@asamuzakjp/css-color@npm:4.1.1" dependencies: "@csstools/css-calc": "npm:^2.1.4" "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - lru-cache: "npm:^11.2.2" - checksum: 10c0/097b9270a5befb765885dda43d6914ccbaa575565525d307e8ba3ba07f98e466062f4a77b9657e65160db9110c41c59cf5a6937479647f73637aeddf5c421579 + lru-cache: "npm:^11.2.4" + checksum: 10c0/2948ae9cd4c2f326ab5470d6ac7d415bb8062150ef254f830d774b6a77d6dccfbdb4b84ed4ef5c86c5643d42c52d77204b8d94d0d90f2e2cea9ec9b6cbb9c336 languageName: node linkType: hard @@ -1336,19 +1336,10 @@ __metadata: languageName: node linkType: hard -"@csstools/css-syntax-patches-for-csstree@npm:1.0.14": - version: 1.0.14 - resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/e431cf5aa4ccd6a40f4a417663ac7178c822c5427b9c8473e466257dc46dd9698e3852d5517ec220b7d1d1ea911e9007ecb429464329ae169a0aa68b56f1c3ac - languageName: node - linkType: hard - -"@csstools/css-syntax-patches-for-csstree@npm:^1.0.25": - version: 1.0.25 - resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.25" - checksum: 10c0/a24229cc44cd64642ba76c73f59e7b0b00cfaffd992bf515d32f266aa68b983d9a945ebf8d45c90671d1e2c775a225d8b6257b01fdfeb6602c0c1f7e3faf77c0 +"@csstools/css-syntax-patches-for-csstree@npm:^1.0.21, @csstools/css-syntax-patches-for-csstree@npm:^1.0.25": + version: 1.0.26 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.26" + checksum: 10c0/65ac2a9e3a6908ce503ae1ba354c59ffdda04fef1f4c6e2b64f083c2896fe24fd775861e693e0eb2f84a1e7e0d333eab0fa26b5873268945833b62d7fae97404 languageName: node linkType: hard @@ -2416,15 +2407,15 @@ __metadata: languageName: node linkType: hard -"@exodus/bytes@npm:^1.6.0": - version: 1.8.0 - resolution: "@exodus/bytes@npm:1.8.0" +"@exodus/bytes@npm:^1.11.0, @exodus/bytes@npm:^1.6.0": + version: 1.11.0 + resolution: "@exodus/bytes@npm:1.11.0" peerDependencies: - "@exodus/crypto": ^1.0.0-rc.4 + "@noble/hashes": ^1.8.0 || ^2.0.0 peerDependenciesMeta: - "@exodus/crypto": + "@noble/hashes": optional: true - checksum: 10c0/1878868519230fa564b80d3d12fa7e2b559dfed143f4e48969eb5f98083caea431bbc70356d6b2f90ba80c8148295fe85af6a5ed8a746dd53baefe4f1ed086e8 + checksum: 10c0/85d0b296cef91ee90f89f17b3a2cd23fa33bda4ae7b96545e9b8e2e68d64c0280eb3cefb77fc3f59f82377379ee7a52a5a5b3a9f99e45fca4166e5e2fa4c0939 languageName: node linkType: hard @@ -3009,7 +3000,7 @@ __metadata: express: "npm:^5.1.0" globals: "npm:^17.0.0" ioredis: "npm:^5.3.2" - jsdom: "npm:^27.0.0" + jsdom: "npm:^28.0.0" pg: "npm:^8.5.0" pg-connection-string: "npm:^2.6.0" pino: "npm:^10.0.0" @@ -6343,14 +6334,15 @@ __metadata: languageName: node linkType: hard -"cssstyle@npm:^5.3.4": - version: 5.3.4 - resolution: "cssstyle@npm:5.3.4" +"cssstyle@npm:^5.3.7": + version: 5.3.7 + resolution: "cssstyle@npm:5.3.7" dependencies: - "@asamuzakjp/css-color": "npm:^4.1.0" - "@csstools/css-syntax-patches-for-csstree": "npm:1.0.14" + "@asamuzakjp/css-color": "npm:^4.1.1" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.21" css-tree: "npm:^3.1.0" - checksum: 10c0/7499ea8cbc2f759ded275428e0811d147baa6a964a44577711cee5edabee2230cf76b6bd20a556603f99ebc6fff80afdcba6c00bcbb1d41ae50cd09cd9fe9a2d + lru-cache: "npm:^11.2.4" + checksum: 10c0/9330f014f4209df06305264b92b8e963dfef636fdc2ae7d13f24ea7da6468aba1dc5eb13082621258bdd22cbd7fb7cb291894e188a3cdf660e8b79cd2c5e5e0e languageName: node linkType: hard @@ -6368,13 +6360,13 @@ __metadata: languageName: node linkType: hard -"data-urls@npm:^6.0.0": - version: 6.0.0 - resolution: "data-urls@npm:6.0.0" +"data-urls@npm:^7.0.0": + version: 7.0.0 + resolution: "data-urls@npm:7.0.0" dependencies: - whatwg-mimetype: "npm:^4.0.0" - whatwg-url: "npm:^15.0.0" - checksum: 10c0/952102a8e6282fea112f7120d79fac482a2f99e20c67f9cb069d661c00627305b042e1f7e3cef8e4bbc795b42c5d481bbc9c6effeff5bb1427f9acaf1722bd35 + whatwg-mimetype: "npm:^5.0.0" + whatwg-url: "npm:^16.0.0" + checksum: 10c0/08d88ef50d8966a070ffdaa703e1e4b29f01bb2da364dfbc1612b1c2a4caa8045802c9532d81347b21781100132addb36a585071c8323b12cce97973961dee9f languageName: node linkType: hard @@ -9116,15 +9108,15 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^27.0.0": - version: 27.4.0 - resolution: "jsdom@npm:27.4.0" +"jsdom@npm:^28.0.0": + version: 28.0.0 + resolution: "jsdom@npm:28.0.0" dependencies: - "@acemir/cssom": "npm:^0.9.28" + "@acemir/cssom": "npm:^0.9.31" "@asamuzakjp/dom-selector": "npm:^6.7.6" - "@exodus/bytes": "npm:^1.6.0" - cssstyle: "npm:^5.3.4" - data-urls: "npm:^6.0.0" + "@exodus/bytes": "npm:^1.11.0" + cssstyle: "npm:^5.3.7" + data-urls: "npm:^7.0.0" decimal.js: "npm:^10.6.0" html-encoding-sniffer: "npm:^6.0.0" http-proxy-agent: "npm:^7.0.2" @@ -9134,18 +9126,18 @@ __metadata: saxes: "npm:^6.0.0" symbol-tree: "npm:^3.2.4" tough-cookie: "npm:^6.0.0" + undici: "npm:^7.20.0" w3c-xmlserializer: "npm:^5.0.0" - webidl-conversions: "npm:^8.0.0" - whatwg-mimetype: "npm:^4.0.0" - whatwg-url: "npm:^15.1.0" - ws: "npm:^8.18.3" + webidl-conversions: "npm:^8.0.1" + whatwg-mimetype: "npm:^5.0.0" + whatwg-url: "npm:^16.0.0" xml-name-validator: "npm:^5.0.0" peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: canvas: optional: true - checksum: 10c0/291bb71a611dbaed81ce516587b71a5ffd9d43337d65bbd0731e7924cd7018f5871cf66614facadfd0dffec2b23a0fc57b2ee36b5a39e20f0f569e2949b3418c + checksum: 10c0/6aa2419506f912f40c5f1c6ca52c6dfdfde5970cfbaf105ebfc55ab975dda2d2492b6f8dc4c62b94e46501c4f77dfd2a60ea229ee67f924d59fe6c51bf653043 languageName: node linkType: hard @@ -9545,7 +9537,7 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4": +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.2.4": version: 11.2.4 resolution: "lru-cache@npm:11.2.4" checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 @@ -13910,6 +13902,13 @@ __metadata: languageName: node linkType: hard +"undici@npm:^7.20.0": + version: 7.20.0 + resolution: "undici@npm:7.20.0" + checksum: 10c0/99054958a07b4105e1461bf5f38550746a15e01d6807e7a2b0849f18e1bc3f481c1ad080ea87b255a39264cec5d80ebf2b3bc82c3e732d81e6a0cc3c920c05c6 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -14414,10 +14413,10 @@ __metadata: languageName: node linkType: hard -"webidl-conversions@npm:^8.0.0": - version: 8.0.0 - resolution: "webidl-conversions@npm:8.0.0" - checksum: 10c0/3244e8a6534163bc3ee5f5f48b507b4bb74e34e7cc7c86a50cd02734753042b88343dae48321f34ad61ddc6b5c90cb1a5b2ee757b8be8e6fadc587a9f3db76cd +"webidl-conversions@npm:^8.0.1": + version: 8.0.1 + resolution: "webidl-conversions@npm:8.0.1" + checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46 languageName: node linkType: hard @@ -14428,20 +14427,21 @@ __metadata: languageName: node linkType: hard -"whatwg-mimetype@npm:^4.0.0": - version: 4.0.0 - resolution: "whatwg-mimetype@npm:4.0.0" - checksum: 10c0/a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df +"whatwg-mimetype@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-mimetype@npm:5.0.0" + checksum: 10c0/eead164fe73a00dd82f817af6fc0bd22e9c273e1d55bf4bc6bdf2da7ad8127fca82ef00ea6a37892f5f5641f8e34128e09508f92126086baba126b9e0d57feb4 languageName: node linkType: hard -"whatwg-url@npm:^15.0.0, whatwg-url@npm:^15.1.0": - version: 15.1.0 - resolution: "whatwg-url@npm:15.1.0" +"whatwg-url@npm:^16.0.0": + version: 16.0.0 + resolution: "whatwg-url@npm:16.0.0" dependencies: + "@exodus/bytes": "npm:^1.11.0" tr46: "npm:^6.0.0" - webidl-conversions: "npm:^8.0.0" - checksum: 10c0/40c49b47044787c87486aaaa5b504da122820661c45ae20ab466c62595ed03c64be7c10c1d180d028949a393cd455db14144966a68359cd37fe6417e3426d128 + webidl-conversions: "npm:^8.0.1" + checksum: 10c0/9b8cb392be244d0e9687ffe543f9ea63b7aa051a98547ea362a38d182d89bfbd96e13e7ed3f40df1f7566bb7c3581f6c081ddea950cf5382532716ce33000ff4 languageName: node linkType: hard From c53c42b650cc4ccbfd6215c350a9d0742803cba9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:27:13 +0000 Subject: [PATCH 08/11] Update dependency @vitejs/plugin-react to v5.1.3 (#37698) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 172 +++++++++++++++++++++++++++--------------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7915dbc58f..07d8b76a47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,57 +79,57 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d languageName: node linkType: hard -"@babel/compat-data@npm:^7.27.2, @babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/compat-data@npm:7.28.0" - checksum: 10c0/c4e527302bcd61052423f757355a71c3bc62362bac13f7f130de16e439716f66091ff5bdecda418e8fa0271d4c725f860f0ee23ab7bf6e769f7a8bb16dfcb531 +"@babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0, @babel/compat-data@npm:^7.28.6": + version: 7.29.0 + resolution: "@babel/compat-data@npm:7.29.0" + checksum: 10c0/08f348554989d23aa801bf1405aa34b15e841c0d52d79da7e524285c77a5f9d298e70e11d91cc578d8e2c9542efc586d50c5f5cf8e1915b254a9dcf786913a94 languageName: node linkType: hard -"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/core@npm:7.28.5" +"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/core@npm:7.29.0" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.28.3" - "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helpers": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 + checksum: 10c0/5127d2e8e842ae409e11bcbb5c2dff9874abf5415e8026925af7308e903f4f43397341467a130490d1a39884f461bc2b67f3063bce0be44340db89687fd852aa languageName: node linkType: hard -"@babel/generator@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/generator@npm:7.28.5" +"@babel/generator@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/generator@npm:7.29.0" dependencies: - "@babel/parser": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + checksum: 10c0/5c3df8f2475bfd5f97ad0211c52171aff630088b148e7b89d056b39d69855179bc9f2d1ee200263c76c2398a49e4fdbb38b9709ebc4f043cc04d9ee09a66668a languageName: node linkType: hard @@ -142,16 +142,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/helper-compilation-targets@npm:7.27.2" +"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2, @babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" dependencies: - "@babel/compat-data": "npm:^7.27.2" + "@babel/compat-data": "npm:^7.28.6" "@babel/helper-validator-option": "npm:^7.27.1" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 languageName: node linkType: hard @@ -217,26 +217,26 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-module-imports@npm:7.27.1" +"@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1, @babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" dependencies: - "@babel/traverse": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/helper-module-transforms@npm:7.28.3" +"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-transforms@npm:7.28.6" dependencies: - "@babel/helper-module-imports": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.3" + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + checksum: 10c0/6f03e14fc30b287ce0b839474b5f271e72837d0cafe6b172d759184d998fbee3903a035e81e07c2c596449e504f453463d58baa65b6f40a37ded5bec74620b2b languageName: node linkType: hard @@ -324,24 +324,24 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/helpers@npm:7.28.4" +"@babel/helpers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helpers@npm:7.28.6" dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" - checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/c4a779c66396bb0cf619402d92f1610601ff3832db2d3b86b9c9dd10983bf79502270e97ac6d5280cea1b1a37de2f06ecbac561bd2271545270407fbe64027cb languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/parser@npm:7.28.5" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/parser@npm:7.29.0" dependencies: - "@babel/types": "npm:^7.28.5" + "@babel/types": "npm:^7.29.0" bin: parser: ./bin/babel-parser.js - checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 languageName: node linkType: hard @@ -1183,39 +1183,39 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/template@npm:7.27.2" +"@babel/template@npm:^7.27.1, @babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/parser": "npm:^7.27.2" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/traverse@npm:7.28.5" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.5" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" debug: "npm:^4.3.1" - checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.4.4": - version: 7.28.5 - resolution: "@babel/types@npm:7.28.5" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.4.4": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f languageName: node linkType: hard @@ -3430,10 +3430,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.53": - version: 1.0.0-beta.53 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.53" - checksum: 10c0/e8b0a7eb76be22f6f103171f28072de821525a4e400454850516da91a7381957932ff0ce495f227bcb168e86815788b0c1d249ca9e34dca366a82c8825b714ce +"@rolldown/pluginutils@npm:1.0.0-rc.2": + version: 1.0.0-rc.2 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.2" + checksum: 10c0/35d3dec35e00ab090d5ff8287e27af98a15da897dc8b034fe0e00d03e0931b9e993603c054be9e8925e2bde040c44c18b48cb8aeea6a261fd1c8f46837038927 languageName: node linkType: hard @@ -4936,18 +4936,18 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^5.0.0": - version: 5.1.2 - resolution: "@vitejs/plugin-react@npm:5.1.2" + version: 5.1.3 + resolution: "@vitejs/plugin-react@npm:5.1.3" dependencies: - "@babel/core": "npm:^7.28.5" + "@babel/core": "npm:^7.29.0" "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" - "@rolldown/pluginutils": "npm:1.0.0-beta.53" + "@rolldown/pluginutils": "npm:1.0.0-rc.2" "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.18.0" peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/d788f269cdf7474425071ba7c4ea7013f174ddaef12b758defe809a551a03ac62a4a80cd858872deb618e7936ccc7cffe178bc12b62e9c836a467e13f15b9390 + checksum: 10c0/814d950f398f13ab5261593952b0a3a10a8b21220923c99817c99456890e1c0df4f1dd4cb2fc538ce4db4f525695e82ba1771a9e13ae1821283b659a7cfedc12 languageName: node linkType: hard From ffb84ea79dbed945827e7b330420d7d410e6c3cc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 5 Feb 2026 05:29:41 -0500 Subject: [PATCH 09/11] Remove `bin/bundle` stub for bundler (#37461) --- bin/brakeman | 11 ---- bin/bundle | 109 ---------------------------------------- bin/bundler-audit | 11 ---- bin/flatware | 11 ---- bin/haml-lint | 11 ---- bin/i18n-tasks | 11 ---- bin/prometheus_exporter | 11 ---- bin/rspec | 11 ---- bin/rubocop | 11 ---- bin/vite | 11 ---- 10 files changed, 208 deletions(-) delete mode 100755 bin/bundle diff --git a/bin/brakeman b/bin/brakeman index b4fe8de266..a2eeb02f0c 100755 --- a/bin/brakeman +++ b/bin/brakeman @@ -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" diff --git a/bin/bundle b/bin/bundle deleted file mode 100755 index 50da5fdf9e..0000000000 --- a/bin/bundle +++ /dev/null @@ -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 diff --git a/bin/bundler-audit b/bin/bundler-audit index 334a73784f..fce0291909 100755 --- a/bin/bundler-audit +++ b/bin/bundler-audit @@ -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" diff --git a/bin/flatware b/bin/flatware index 337ce9277e..3b043dc21f 100755 --- a/bin/flatware +++ b/bin/flatware @@ -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" diff --git a/bin/haml-lint b/bin/haml-lint index bd55739400..7f49382294 100755 --- a/bin/haml-lint +++ b/bin/haml-lint @@ -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" diff --git a/bin/i18n-tasks b/bin/i18n-tasks index 22cbc1f1b1..d592f5eb60 100755 --- a/bin/i18n-tasks +++ b/bin/i18n-tasks @@ -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" diff --git a/bin/prometheus_exporter b/bin/prometheus_exporter index 7e0304de9e..052c9cf292 100755 --- a/bin/prometheus_exporter +++ b/bin/prometheus_exporter @@ -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" diff --git a/bin/rspec b/bin/rspec index cb53ebe5f0..93e191c2ff 100755 --- a/bin/rspec +++ b/bin/rspec @@ -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" diff --git a/bin/rubocop b/bin/rubocop index b3801537d4..02f7736d97 100755 --- a/bin/rubocop +++ b/bin/rubocop @@ -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" diff --git a/bin/vite b/bin/vite index 5da3388e2d..9664d0d987 100755 --- a/bin/vite +++ b/bin/vite @@ -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" From 9cd94168b3a8d982d45b1f978ea1a8cbc8cbfd98 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 5 Feb 2026 11:33:39 +0100 Subject: [PATCH 10/11] Federate `Remove` when collection is deleted (#37741) --- .../api/v1_alpha/collections_controller.rb | 2 +- .../remove_featured_collection_serializer.rb | 24 +++++++++++++++++++ app/services/delete_collection_service.rb | 20 ++++++++++++++++ ...ove_featured_collection_serializer_spec.rb | 24 +++++++++++++++++++ .../delete_collection_service_spec.rb | 21 ++++++++++++++++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 app/serializers/activitypub/remove_featured_collection_serializer.rb create mode 100644 app/services/delete_collection_service.rb create mode 100644 spec/serializers/activitypub/remove_featured_collection_serializer_spec.rb create mode 100644 spec/services/delete_collection_service_spec.rb diff --git a/app/controllers/api/v1_alpha/collections_controller.rb b/app/controllers/api/v1_alpha/collections_controller.rb index 43520154d5..792a072d32 100644 --- a/app/controllers/api/v1_alpha/collections_controller.rb +++ b/app/controllers/api/v1_alpha/collections_controller.rb @@ -59,7 +59,7 @@ class Api::V1Alpha::CollectionsController < Api::BaseController def destroy authorize @collection, :destroy? - @collection.destroy + DeleteCollectionService.new.call(@collection) head 200 end diff --git a/app/serializers/activitypub/remove_featured_collection_serializer.rb b/app/serializers/activitypub/remove_featured_collection_serializer.rb new file mode 100644 index 0000000000..4caf4a6526 --- /dev/null +++ b/app/serializers/activitypub/remove_featured_collection_serializer.rb @@ -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 diff --git a/app/services/delete_collection_service.rb b/app/services/delete_collection_service.rb new file mode 100644 index 0000000000..78b9261fd1 --- /dev/null +++ b/app/services/delete_collection_service.rb @@ -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 diff --git a/spec/serializers/activitypub/remove_featured_collection_serializer_spec.rb b/spec/serializers/activitypub/remove_featured_collection_serializer_spec.rb new file mode 100644 index 0000000000..c96f1e9e9d --- /dev/null +++ b/spec/serializers/activitypub/remove_featured_collection_serializer_spec.rb @@ -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 diff --git a/spec/services/delete_collection_service_spec.rb b/spec/services/delete_collection_service_spec.rb new file mode 100644 index 0000000000..bd4ed5fef6 --- /dev/null +++ b/spec/services/delete_collection_service_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe DeleteCollectionService do + subject { described_class.new } + + let!(:collection) { Fabricate(:collection) } + + describe '#call' do + it 'destroys the collection' do + expect { subject.call(collection) }.to change(Collection, :count).by(-1) + end + + it 'federates a `Remove` activity', feature: :collections_federation do + subject.call(collection) + + expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job + end + end +end From f652c54c3334890176331f62150559a96eeb41ae Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 5 Feb 2026 06:05:32 -0500 Subject: [PATCH 11/11] Update devise to version 5.0 (#37419) --- Gemfile | 2 +- Gemfile.lock | 6 +++--- app/controllers/auth/sessions_controller.rb | 4 ++-- config/initializers/devise.rb | 8 +++----- spec/controllers/auth/sessions_controller_spec.rb | 8 ++++++-- spec/system/log_in_spec.rb | 6 +++--- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 504facf2e9..9a0e1d6094 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index a455287945..5e16507281 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -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) diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 182f242ae5..077f4d9db5 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -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 diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index f69f7519c8..149c1b1af4 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -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, diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 949af2a425..924122d161 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -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 diff --git a/spec/system/log_in_spec.rb b/spec/system/log_in_spec.rb index 10869fd240..af3a99164f 100644 --- a/spec/system/log_in_spec.rb +++ b/spec/system/log_in_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Log in' do it 'A invalid email and password user is not able to log in' do fill_in_auth_details('invalid_email', 'invalid_password') - expect(subject).to have_css('.flash-message', text: failure_message('invalid')) + expect(subject).to have_css('.flash-message', text: /#{failure_message_invalid}/i) end context 'when confirmed at is nil' do @@ -38,8 +38,8 @@ RSpec.describe 'Log in' do end end - def failure_message(message) + def failure_message_invalid keys = User.authentication_keys.map { |key| User.human_attribute_name(key) } - I18n.t("devise.failure.#{message}", authentication_keys: keys.join('support.array.words_connector')) + I18n.t('devise.failure.invalid', authentication_keys: keys.join('support.array.words_connector')) end end