diff --git a/.nvmrc b/.nvmrc index 6e77d0a7496300..403f75d03823be 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.19 +22.20 diff --git a/Gemfile b/Gemfile index 126d73f9cab1e3..0d9ab342715c46 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem 'rails', '~> 8.0' gem 'thor', '~> 1.2' gem 'dotenv' -gem 'haml-rails', '~>2.0' +gem 'haml-rails', '~>3.0' gem 'pg', '~> 1.5' gem 'pghero' @@ -160,6 +160,9 @@ group :test do # Stub web requests for specs gem 'webmock', '~> 3.18' + + # Websocket driver for testing integration between rails/sidekiq and streaming + gem 'websocket-driver', '~> 0.8', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 64ef3057d8a664..d67384d8e73c82 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,29 +10,29 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (8.0.2.1) - actionpack (= 8.0.2.1) - activesupport (= 8.0.2.1) + actioncable (8.0.3) + actionpack (= 8.0.3) + activesupport (= 8.0.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.2.1) - actionpack (= 8.0.2.1) - activejob (= 8.0.2.1) - activerecord (= 8.0.2.1) - activestorage (= 8.0.2.1) - activesupport (= 8.0.2.1) + actionmailbox (8.0.3) + actionpack (= 8.0.3) + activejob (= 8.0.3) + activerecord (= 8.0.3) + activestorage (= 8.0.3) + activesupport (= 8.0.3) mail (>= 2.8.0) - actionmailer (8.0.2.1) - actionpack (= 8.0.2.1) - actionview (= 8.0.2.1) - activejob (= 8.0.2.1) - activesupport (= 8.0.2.1) + actionmailer (8.0.3) + actionpack (= 8.0.3) + actionview (= 8.0.3) + activejob (= 8.0.3) + activesupport (= 8.0.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.2.1) - actionview (= 8.0.2.1) - activesupport (= 8.0.2.1) + actionpack (8.0.3) + actionview (= 8.0.3) + activesupport (= 8.0.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -40,15 +40,15 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.2.1) - actionpack (= 8.0.2.1) - activerecord (= 8.0.2.1) - activestorage (= 8.0.2.1) - activesupport (= 8.0.2.1) + actiontext (8.0.3) + actionpack (= 8.0.3) + activerecord (= 8.0.3) + activestorage (= 8.0.3) + activesupport (= 8.0.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.2.1) - activesupport (= 8.0.2.1) + actionview (8.0.3) + activesupport (= 8.0.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -58,22 +58,22 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (8.0.2.1) - activesupport (= 8.0.2.1) + activejob (8.0.3) + activesupport (= 8.0.3) globalid (>= 0.3.6) - activemodel (8.0.2.1) - activesupport (= 8.0.2.1) - activerecord (8.0.2.1) - activemodel (= 8.0.2.1) - activesupport (= 8.0.2.1) + activemodel (8.0.3) + activesupport (= 8.0.3) + activerecord (8.0.3) + activemodel (= 8.0.3) + activesupport (= 8.0.3) timeout (>= 0.4.0) - activestorage (8.0.2.1) - actionpack (= 8.0.2.1) - activejob (= 8.0.2.1) - activerecord (= 8.0.2.1) - activesupport (= 8.0.2.1) + activestorage (8.0.3) + actionpack (= 8.0.3) + activejob (= 8.0.3) + activerecord (= 8.0.3) + activesupport (= 8.0.3) marcel (~> 1.0) - activesupport (8.0.2.1) + activesupport (8.0.3) base64 benchmark (>= 0.3) bigdecimal @@ -150,7 +150,7 @@ GEM playwright-ruby-client (>= 1.16.0) case_transform (0.2) activesupport - cbor (0.5.9.8) + cbor (0.5.10.1) cgi (0.4.2) charlock_holmes (0.7.9) chewy (7.6.0) @@ -282,7 +282,7 @@ GEM temple (>= 0.8.2) thor tilt - haml-rails (2.1.0) + haml-rails (3.0.0) actionpack (>= 5.1) activesupport (>= 5.1) haml (>= 4.0.6) @@ -300,8 +300,8 @@ GEM highline (3.1.2) reline hiredis (0.6.3) - hiredis-client (0.25.3) - redis-client (= 0.25.3) + hiredis-client (0.26.1) + redis-client (= 0.26.1) hkdf (0.3.0) htmlentities (4.3.4) http (5.3.1) @@ -345,7 +345,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.13.2) + json (2.15.0) json-canonicalization (1.0.0) json-jwt (1.16.7) activesupport (>= 4.2) @@ -447,7 +447,7 @@ GEM mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.9) + net-imap (0.5.10) date net-protocol net-ldap (0.20.0) @@ -466,8 +466,9 @@ GEM oj (3.16.11) bigdecimal (>= 3.0) ostruct (>= 0.2) - omniauth (2.1.3) + omniauth (2.1.4) hashie (>= 3.4.6) + logger rack (>= 2.2.3) rack-protection omniauth-cas (3.0.2) @@ -626,10 +627,10 @@ GEM net-smtp premailer (~> 1.7, >= 1.7.9) prettyprint (0.2.0) - prism (1.4.0) + prism (1.5.1) prometheus_exporter (2.3.0) webrick - propshaft (1.2.1) + propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack @@ -637,13 +638,13 @@ GEM date stringio public_suffix (6.0.2) - puma (7.0.3) + puma (7.0.4) nio4r (~> 2.0) - pundit (2.5.1) + pundit (2.5.2) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (3.1.16) + rack (3.2.1) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (3.0.0) @@ -669,20 +670,20 @@ GEM rack (>= 1.3) rackup (2.2.1) rack (>= 3) - rails (8.0.2.1) - actioncable (= 8.0.2.1) - actionmailbox (= 8.0.2.1) - actionmailer (= 8.0.2.1) - actionpack (= 8.0.2.1) - actiontext (= 8.0.2.1) - actionview (= 8.0.2.1) - activejob (= 8.0.2.1) - activemodel (= 8.0.2.1) - activerecord (= 8.0.2.1) - activestorage (= 8.0.2.1) - activesupport (= 8.0.2.1) + rails (8.0.3) + actioncable (= 8.0.3) + actionmailbox (= 8.0.3) + actionmailer (= 8.0.3) + actionpack (= 8.0.3) + actiontext (= 8.0.3) + actionview (= 8.0.3) + activejob (= 8.0.3) + activemodel (= 8.0.3) + activerecord (= 8.0.3) + activestorage (= 8.0.3) + activesupport (= 8.0.3) bundler (>= 1.15.0) - railties (= 8.0.2.1) + railties (= 8.0.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -693,13 +694,14 @@ GEM rails-i18n (8.0.2) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.0.2.1) - actionpack (= 8.0.2.1) - activesupport (= 8.0.2.1) + railties (8.0.3) + actionpack (= 8.0.3) + activesupport (= 8.0.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.0) @@ -719,9 +721,9 @@ GEM reline redcarpet (3.6.1) redis (4.8.1) - redis-client (0.25.3) + redis-client (0.26.1) connection_pool - regexp_parser (2.11.2) + regexp_parser (2.11.3) reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) @@ -765,7 +767,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.4) - rubocop (1.80.2) + rubocop (1.81.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -773,10 +775,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.47.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -789,7 +791,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rails (2.33.3) + rubocop-rails (2.33.4) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -811,11 +813,11 @@ GEM ruby-vips (2.2.5) ffi (~> 1.12) logger - rubyzip (3.1.0) + rubyzip (3.1.1) rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) - safety_net_attestation (0.4.0) - jwt (~> 2.0) + safety_net_attestation (0.5.0) + jwt (>= 2.0, < 4.0) sanitize (7.0.0) crass (~> 1.0.2) nokogiri (>= 1.16.8) @@ -879,6 +881,7 @@ GEM bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) + tsort (0.2.0) tty-color (0.6.0) tty-cursor (0.7.1) tty-prompt (0.23.1) @@ -899,9 +902,9 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.9.1) - unicode-display_width (3.1.5) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) uri (1.0.3) useragent (0.16.11) validate_url (1.0.15) @@ -918,13 +921,13 @@ GEM zeitwerk (~> 2.2) warden (1.2.9) rack (>= 2.0.9) - webauthn (3.4.1) + webauthn (3.4.2) android_key_attestation (~> 0.3.0) bindata (~> 2.4) cbor (~> 0.5.9) cose (~> 1.1) openssl (>= 2.2) - safety_net_attestation (~> 0.4.0) + safety_net_attestation (~> 0.5.0) tpm-key_attestation (~> 0.14.0) webfinger (2.1.3) activesupport @@ -988,7 +991,7 @@ DEPENDENCIES flatware-rspec fog-core (<= 2.6.0) fog-openstack (~> 1.0) - haml-rails (~> 2.0) + haml-rails (~> 3.0) haml_lint hcaptcha (~> 7.1) hiredis (~> 0.6) @@ -1100,6 +1103,7 @@ DEPENDENCIES webauthn (~> 3.0) webmock (~> 3.18) webpush! + websocket-driver (~> 0.8) xorcist (~> 1.1) RUBY VERSION diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 98e68bd8734e09..b02771879260f6 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -75,6 +75,10 @@ def username_param params[:username] end + def account_id_param + params[:id] + end + def skip_temporary_suspension_response? request.format == :json end diff --git a/app/controllers/activitypub/likes_controller.rb b/app/controllers/activitypub/likes_controller.rb index 4aa6a4a771f156..e875517b021ec2 100644 --- a/app/controllers/activitypub/likes_controller.rb +++ b/app/controllers/activitypub/likes_controller.rb @@ -28,7 +28,7 @@ def set_status def likes_collection_presenter ActivityPub::CollectionPresenter.new( - id: account_status_likes_url(@account, @status), + id: ActivityPub::TagManager.instance.likes_uri_for(@status), type: :unordered, size: @status.favourites_count ) diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 171161d4913b62..1a6b99a2fb2b2a 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -73,6 +73,8 @@ def page_params end def set_account - @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative + return super if params[:account_username].present? || params[:account_id].present? + + @account = Account.representative end end diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index 0a19275d38e942..1959f50d676196 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -37,7 +37,7 @@ def set_replies def replies_collection_presenter page = ActivityPub::CollectionPresenter.new( - id: account_status_replies_url(@account, @status, page_params), + id: ActivityPub::TagManager.instance.replies_uri_for(@status, page_params), type: :unordered, part_of: account_status_replies_url(@account, @status), next: next_page, @@ -47,7 +47,7 @@ def replies_collection_presenter return page if page_requested? ActivityPub::CollectionPresenter.new( - id: account_status_replies_url(@account, @status), + id: ActivityPub::TagManager.instance.replies_uri_for(@status), type: :unordered, first: page ) @@ -66,8 +66,7 @@ def next_page # Only consider remote accounts return nil if @replies.size < DESCENDANTS_LIMIT - account_status_replies_url( - @account, + ActivityPub::TagManager.instance.replies_uri_for( @status, page: true, min_id: @replies&.last&.id, @@ -77,8 +76,7 @@ def next_page # For now, we're serving only self-replies, but next page might be other accounts next_only_other_accounts = @replies&.last&.account_id != @account.id || @replies.size < DESCENDANTS_LIMIT - account_status_replies_url( - @account, + ActivityPub::TagManager.instance.replies_uri_for( @status, page: true, min_id: next_only_other_accounts ? nil : @replies&.last&.id, diff --git a/app/controllers/activitypub/shares_controller.rb b/app/controllers/activitypub/shares_controller.rb index 65b4a5b3831326..2d1e389885a935 100644 --- a/app/controllers/activitypub/shares_controller.rb +++ b/app/controllers/activitypub/shares_controller.rb @@ -28,7 +28,7 @@ def set_status def shares_collection_presenter ActivityPub::CollectionPresenter.new( - id: account_status_shares_url(@account, @status), + id: ActivityPub::TagManager.instance.shares_uri_for(@status), type: :unordered, size: @status.reblogs_count ) diff --git a/app/controllers/api/v1/statuses/interaction_policies_controller.rb b/app/controllers/api/v1/statuses/interaction_policies_controller.rb index b8ec4fe140055b..5cfb2d0e8fd185 100644 --- a/app/controllers/api/v1/statuses/interaction_policies_controller.rb +++ b/app/controllers/api/v1/statuses/interaction_policies_controller.rb @@ -4,7 +4,6 @@ class Api::V1::Statuses::InteractionPoliciesController < Api::V1::Statuses::Base include Api::InteractionPoliciesConcern before_action -> { doorkeeper_authorize! :write, :'write:statuses' } - before_action -> { check_feature_enabled } def update authorize @status, :update? @@ -22,12 +21,8 @@ def status_params params.permit(:quote_approval_policy) end - def check_feature_enabled - raise ActionController::RoutingError unless Mastodon::Feature.outgoing_quotes_enabled? - end - def broadcast_updates! - DistributionWorker.perform_async(@status.id, { 'update' => true }) + DistributionWorker.perform_async(@status.id, { 'update' => true, 'skip_notifications' => true }) ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id, { 'updated_at' => Time.now.utc.iso8601 }) end end diff --git a/app/controllers/api/v1/statuses/quotes_controller.rb b/app/controllers/api/v1/statuses/quotes_controller.rb index 962855884ec87c..be3a4edc83d87a 100644 --- a/app/controllers/api/v1/statuses/quotes_controller.rb +++ b/app/controllers/api/v1/statuses/quotes_controller.rb @@ -4,13 +4,13 @@ class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :index before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: :revoke - before_action :check_owner! + before_action :set_statuses, only: :index + before_action :set_quote, only: :revoke after_action :insert_pagination_headers, only: :index def index cache_if_unauthenticated! - @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer end @@ -24,18 +24,26 @@ def revoke private - def check_owner! - authorize @status, :list_quotes? - end - def set_quote @quote = @status.quotes.find_by!(status_id: params[:id]) end - def load_statuses + def set_statuses scope = default_statuses scope = scope.not_excluded_by_account(current_account) unless current_account.nil? - scope.merge(paginated_quotes).to_a + @statuses = scope.merge(paginated_quotes).to_a + + # Store next page info before filtering + @records_continue = @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + @pagination_since_id = @statuses.first.quote.id unless @statuses.empty? + @pagination_max_id = @statuses.last.quote.id if @records_continue + + if current_account&.id != @status.account_id + domains = @statuses.filter_map(&:account_domain).uniq + account_ids = @statuses.map(&:account_id).uniq + relations = current_account&.relations_map(account_ids, domains) || {} + @statuses.reject! { |status| StatusFilter.new(status, current_account, relations).filtered? } + end end def default_statuses @@ -58,15 +66,9 @@ def prev_path api_v1_status_quotes_url pagination_params(since_id: pagination_since_id) unless @statuses.empty? end - def pagination_max_id - @statuses.last.quote.id - end - - def pagination_since_id - @statuses.first.quote.id - end + attr_reader :pagination_max_id, :pagination_since_id def records_continue? - @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + @records_continue end end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 5739d0c7581c4c..daed42c404ecf8 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -171,8 +171,6 @@ def set_thread end def set_quoted_status - return unless Mastodon::Feature.outgoing_quotes_enabled? - @quoted_status = Status.find(status_params[:quoted_status_id]) if status_params[:quoted_status_id].present? authorize(@quoted_status, :quote?) if @quoted_status.present? rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb index 2b132417f7cf33..7b3cd4d3ea607c 100644 --- a/app/controllers/concerns/account_owned_concern.rb +++ b/app/controllers/concerns/account_owned_concern.rb @@ -18,7 +18,11 @@ def account_required? end def set_account - @account = Account.find_local!(username_param) + @account = username_param.present? ? Account.find_local!(username_param) : Account.local.find(account_id_param) + end + + def account_id_param + params[:account_id] end def username_param diff --git a/app/controllers/concerns/api/interaction_policies_concern.rb b/app/controllers/concerns/api/interaction_policies_concern.rb index 5b63705a9bf703..f1e1480c0c0cb9 100644 --- a/app/controllers/concerns/api/interaction_policies_concern.rb +++ b/app/controllers/concerns/api/interaction_policies_concern.rb @@ -4,8 +4,6 @@ module Api::InteractionPoliciesConcern extend ActiveSupport::Concern def quote_approval_policy - return nil unless Mastodon::Feature.outgoing_quotes_enabled? - case status_params[:quote_approval_policy].presence || current_user.setting_default_quote_policy when 'public' Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16 diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 85f6ccc5e41f11..89e4ea38946397 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -60,17 +60,17 @@ def prev_page_url def collection_presenter if page_requested? ActivityPub::CollectionPresenter.new( - id: account_followers_url(@account, page: params.fetch(:page, 1)), + id: page_url(params.fetch(:page, 1)), type: :ordered, size: @account.public_followers_count, items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.account) }, - part_of: account_followers_url(@account), + part_of: ActivityPub::TagManager.instance.followers_uri_for(@account), next: next_page_url, prev: prev_page_url ) else ActivityPub::CollectionPresenter.new( - id: account_followers_url(@account), + id: ActivityPub::TagManager.instance.followers_uri_for(@account), type: :ordered, size: @account.public_followers_count, first: page_url(1) diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 87a702f711ff55..618ec275997c82 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -49,7 +49,7 @@ def page_requested? end def page_url(page) - account_following_index_url(@account, page: page) unless page.nil? + ActivityPub::TagManager.instance.following_uri_for(@account, page: page) unless page.nil? end def next_page_url @@ -63,17 +63,17 @@ def prev_page_url def collection_presenter if page_requested? ActivityPub::CollectionPresenter.new( - id: account_following_index_url(@account, page: params.fetch(:page, 1)), + id: page_url(params.fetch(:page, 1)), type: :ordered, size: @account.public_following_count, items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.target_account) }, - part_of: account_following_index_url(@account), + part_of: ActivityPub::TagManager.instance.following_uri_for(@account), next: next_page_url, prev: prev_page_url ) else ActivityPub::CollectionPresenter.new( - id: account_following_index_url(@account), + id: ActivityPub::TagManager.instance.following_uri_for(@account), type: :ordered, size: @account.public_following_count, first: page_url(1) diff --git a/app/javascript/config/html-tags.json b/app/javascript/config/html-tags.json new file mode 100644 index 00000000000000..c788113487c6c2 --- /dev/null +++ b/app/javascript/config/html-tags.json @@ -0,0 +1,61 @@ +{ + "global": { + "class": "className", + "id": true, + "title": true, + "dir": true, + "lang": true + }, + "tags": { + "p": {}, + "br": { + "children": false + }, + "span": { + "attributes": { + "translate": true + } + }, + "a": { + "attributes": { + "href": true, + "rel": true, + "translate": true, + "target": true + } + }, + "del": {}, + "s": {}, + "pre": {}, + "blockquote": {}, + "code": {}, + "b": {}, + "strong": {}, + "u": {}, + "i": {}, + "img": { + "children": false, + "attributes": { + "src": true, + "alt": true, + "title": true + } + }, + "em": {}, + "ul": {}, + "ol": { + "attributes": { + "start": true, + "reversed": true + } + }, + "li": { + "attributes": { + "value": true + } + }, + "ruby": {}, + "rt": {}, + "rp": {} + } +} diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 9779a9d711d862..7b66e61304aafa 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -205,8 +205,10 @@ export function submitCompose(successCallback) { const statusId = getState().getIn(['compose', 'id'], null); const circleId = getState().getIn(['compose', 'circle_id'], null); const privacy = getState().getIn(['compose', 'privacy']); + const hasQuote = !!getState().getIn(['compose', 'quoted_status_id']); + const spoiler_text = getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : ''; - if ((!status || !status.length) && media.size === 0) { + if (!(status?.length || media.size !== 0 || (hasQuote && spoiler_text?.length))) { return; } @@ -238,11 +240,11 @@ export function submitCompose(successCallback) { method: statusId === null ? 'post' : 'put', data: { status, + spoiler_text, in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: media.map(item => item.get('id')), media_attributes, - sensitive: media.size > 0 ? getState().getIn(['compose', 'spoiler']) : false, - spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '', + sensitive: getState().getIn(['compose', 'spoiler']), markdown: getState().getIn(['compose', 'markdown']), visibility: visibility, searchability: getState().getIn(['compose', 'searchability']), diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index b720b4746d0eba..b5ff686f864ead 100644 --- a/app/javascript/mastodon/components/account_bio.tsx +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -1,11 +1,15 @@ import { useCallback } from 'react'; +import classNames from 'classnames'; + import { useLinks } from 'mastodon/hooks/useLinks'; -import { EmojiHTML } from '../features/emoji/emoji_html'; import { useAppSelector } from '../store'; import { isModernEmojiEnabled } from '../utils/environment'; +import { AnimateEmojiProvider } from './emoji/context'; +import { EmojiHTML } from './emoji/html'; + interface AccountBioProps { className: string; accountId: string; @@ -44,13 +48,13 @@ export const AccountBio: React.FC = ({ } return ( -
-
+ ); }; diff --git a/app/javascript/mastodon/components/alert/alert.stories.tsx b/app/javascript/mastodon/components/alert/alert.stories.tsx index 4d5f8acb65b9d9..f12f06751d7f44 100644 --- a/app/javascript/mastodon/components/alert/alert.stories.tsx +++ b/app/javascript/mastodon/components/alert/alert.stories.tsx @@ -8,6 +8,7 @@ const meta = { component: Alert, args: { isActive: true, + isLoading: false, animateFrom: 'side', title: '', message: '', @@ -20,6 +21,12 @@ const meta = { type: 'boolean', description: 'Animate to the active (displayed) state of the alert', }, + isLoading: { + control: 'boolean', + type: 'boolean', + description: + 'Display a loading indicator in the alert, replacing the dismiss button if present', + }, animateFrom: { control: 'radio', type: 'string', @@ -108,3 +115,11 @@ export const InSizedContainer: Story = { ), }; + +export const WithLoadingIndicator: Story = { + args: { + ...WithDismissButton.args, + isLoading: true, + }, + render: InSizedContainer.render, +}; diff --git a/app/javascript/mastodon/components/alert/index.tsx b/app/javascript/mastodon/components/alert/index.tsx index 1009e77524bdfc..72fee0a4a308f5 100644 --- a/app/javascript/mastodon/components/alert/index.tsx +++ b/app/javascript/mastodon/components/alert/index.tsx @@ -3,6 +3,7 @@ import { useIntl } from 'react-intl'; import classNames from 'classnames'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { IconButton } from '../icon_button'; @@ -10,21 +11,23 @@ import { IconButton } from '../icon_button'; * Snackbar/Toast-style notification component. */ export const Alert: React.FC<{ - isActive?: boolean; - animateFrom?: 'side' | 'below'; title?: string; message: string; action?: string; onActionClick?: () => void; onDismiss?: () => void; + isActive?: boolean; + isLoading?: boolean; + animateFrom?: 'side' | 'below'; }> = ({ - isActive, - animateFrom = 'side', title, message, action, onActionClick, onDismiss, + isActive, + isLoading, + animateFrom = 'side', }) => { const intl = useIntl(); @@ -51,7 +54,13 @@ export const Alert: React.FC<{ )} - {onDismiss && ( + {isLoading && ( + + + + )} + + {onDismiss && !isLoading && ( = ({ try { const pixels = decode(hash, width, height); const ctx = canvas.getContext('2d'); - const imageData = new ImageData(pixels, width, height); + const imageData = ctx?.createImageData(width, height); + imageData?.data.set(pixels); - ctx?.putImageData(imageData, 0, 0); + if (imageData) { + ctx?.putImageData(imageData, 0, 0); + } } catch (err) { console.error('Blurhash decoding failure', { err, hash }); } diff --git a/app/javascript/mastodon/components/display_name/no-domain.tsx b/app/javascript/mastodon/components/display_name/no-domain.tsx index 3a66fe5042cd46..bb5a0936593bdc 100644 --- a/app/javascript/mastodon/components/display_name/no-domain.tsx +++ b/app/javascript/mastodon/components/display_name/no-domain.tsx @@ -2,9 +2,10 @@ import type { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; -import { EmojiHTML } from '@/mastodon/features/emoji/emoji_html'; import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; +import { AnimateEmojiProvider } from '../emoji/context'; +import { EmojiHTML } from '../emoji/html'; import { Skeleton } from '../skeleton'; import type { DisplayNameProps } from './index'; @@ -14,9 +15,10 @@ export const DisplayNameWithoutDomain: FC< ComponentPropsWithoutRef<'span'> > = ({ account, className, children, ...props }) => { return ( - {account ? ( @@ -27,8 +29,8 @@ export const DisplayNameWithoutDomain: FC< ? account.get('display_name') : account.get('display_name_html') } - shallow as='strong' + extraEmojis={account.get('emojis')} /> ) : ( @@ -37,6 +39,6 @@ export const DisplayNameWithoutDomain: FC< )} {children} - + ); }; diff --git a/app/javascript/mastodon/components/display_name/simple.tsx b/app/javascript/mastodon/components/display_name/simple.tsx index 3190c4384b2dcc..375f4932b2e2cb 100644 --- a/app/javascript/mastodon/components/display_name/simple.tsx +++ b/app/javascript/mastodon/components/display_name/simple.tsx @@ -1,8 +1,9 @@ import type { ComponentPropsWithoutRef, FC } from 'react'; -import { EmojiHTML } from '@/mastodon/features/emoji/emoji_html'; import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; +import { EmojiHTML } from '../emoji/html'; + import type { DisplayNameProps } from './index'; export const DisplayNameSimple: FC< @@ -12,12 +13,19 @@ export const DisplayNameSimple: FC< if (!account) { return null; } - const accountName = isModernEmojiEnabled() - ? account.get('display_name') - : account.get('display_name_html'); + return ( - + ); }; diff --git a/app/javascript/mastodon/components/emoji/context.tsx b/app/javascript/mastodon/components/emoji/context.tsx new file mode 100644 index 00000000000000..9fda5714d97d4c --- /dev/null +++ b/app/javascript/mastodon/components/emoji/context.tsx @@ -0,0 +1,108 @@ +import type { MouseEventHandler, PropsWithChildren } from 'react'; +import { + createContext, + useCallback, + useContext, + useMemo, + useState, +} from 'react'; + +import classNames from 'classnames'; + +import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize'; +import { autoPlayGif } from '@/mastodon/initial_state'; +import { polymorphicForwardRef } from '@/types/polymorphic'; +import type { + CustomEmojiMapArg, + ExtraCustomEmojiMap, +} from 'mastodon/features/emoji/types'; + +// Animation context +export const AnimateEmojiContext = createContext(null); + +// Polymorphic provider component +type AnimateEmojiProviderProps = Required & { + className?: string; +}; + +export const AnimateEmojiProvider = polymorphicForwardRef< + 'div', + AnimateEmojiProviderProps +>( + ( + { + children, + as: Wrapper = 'div', + className, + onMouseEnter, + onMouseLeave, + ...props + }, + ref, + ) => { + const [animate, setAnimate] = useState(autoPlayGif ?? false); + + const handleEnter: MouseEventHandler = useCallback( + (event) => { + onMouseEnter?.(event); + if (!autoPlayGif) { + setAnimate(true); + } + }, + [onMouseEnter], + ); + const handleLeave: MouseEventHandler = useCallback( + (event) => { + onMouseLeave?.(event); + if (!autoPlayGif) { + setAnimate(false); + } + }, + [onMouseLeave], + ); + + // If there's a parent context or GIFs autoplay, we don't need handlers. + const parentContext = useContext(AnimateEmojiContext); + if (parentContext !== null || autoPlayGif === true) { + return ( + + {children} + + ); + } + + return ( + + + {children} + + + ); + }, +); +AnimateEmojiProvider.displayName = 'AnimateEmojiProvider'; + +// Handle custom emoji +export const CustomEmojiContext = createContext({}); + +export const CustomEmojiProvider = ({ + children, + emojis: rawEmojis, +}: PropsWithChildren<{ emojis?: CustomEmojiMapArg }>) => { + const emojis = useMemo(() => cleanExtraEmojis(rawEmojis) ?? {}, [rawEmojis]); + return ( + + {children} + + ); +}; diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/components/emoji/html.tsx similarity index 53% rename from app/javascript/mastodon/features/emoji/emoji_html.tsx rename to app/javascript/mastodon/components/emoji/html.tsx index 08d62b2c37aa9a..a6ecc869c1d8e2 100644 --- a/app/javascript/mastodon/features/emoji/emoji_html.tsx +++ b/app/javascript/mastodon/components/emoji/html.tsx @@ -1,11 +1,14 @@ +import { useMemo } from 'react'; import type { ComponentPropsWithoutRef, ElementType } from 'react'; import classNames from 'classnames'; +import type { CustomEmojiMapArg } from '@/mastodon/features/emoji/types'; import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; +import { htmlStringToComponents } from '@/mastodon/utils/html'; -import { useEmojify } from './hooks'; -import type { CustomEmojiMapArg } from './types'; +import { AnimateEmojiProvider, CustomEmojiProvider } from './context'; +import { textToEmojis } from './index'; type EmojiHTMLProps = Omit< ComponentPropsWithoutRef, @@ -14,43 +17,34 @@ type EmojiHTMLProps = Omit< htmlString: string; extraEmojis?: CustomEmojiMapArg; as?: Element; - shallow?: boolean; className?: string; }; export const ModernEmojiHTML = ({ extraEmojis, htmlString, - as: Wrapper = 'div', // Rename for syntax highlighting + as: asProp = 'div', // Rename for syntax highlighting shallow, className = '', ...props }: EmojiHTMLProps) => { - const emojifiedHtml = useEmojify({ - text: htmlString, - extraEmojis, - deep: !shallow, - }); - - if (emojifiedHtml === null) { - return null; - } + const contents = useMemo( + () => htmlStringToComponents(htmlString, { onText: textToEmojis }), + [htmlString], + ); return ( - + + + {contents} + + ); }; -export const EmojiHTML = ( +export const LegacyEmojiHTML = ( props: EmojiHTMLProps, ) => { - if (isModernEmojiEnabled()) { - return ; - } const { as: asElement, htmlString, extraEmojis, className, ...rest } = props; const Wrapper = asElement ?? 'div'; return ( @@ -61,3 +55,7 @@ export const EmojiHTML = ( /> ); }; + +export const EmojiHTML = isModernEmojiEnabled() + ? ModernEmojiHTML + : LegacyEmojiHTML; diff --git a/app/javascript/mastodon/components/emoji/index.tsx b/app/javascript/mastodon/components/emoji/index.tsx new file mode 100644 index 00000000000000..e070eb30dd800e --- /dev/null +++ b/app/javascript/mastodon/components/emoji/index.tsx @@ -0,0 +1,99 @@ +import type { FC } from 'react'; +import { useContext, useEffect, useState } from 'react'; + +import { EMOJI_TYPE_CUSTOM } from '@/mastodon/features/emoji/constants'; +import { useEmojiAppState } from '@/mastodon/features/emoji/hooks'; +import { unicodeHexToUrl } from '@/mastodon/features/emoji/normalize'; +import { + isStateLoaded, + loadEmojiDataToState, + shouldRenderImage, + stringToEmojiState, + tokenizeText, +} from '@/mastodon/features/emoji/render'; + +import { AnimateEmojiContext, CustomEmojiContext } from './context'; + +interface EmojiProps { + code: string; + showFallback?: boolean; + showLoading?: boolean; +} + +export const Emoji: FC = ({ + code, + showFallback = true, + showLoading = true, +}) => { + const customEmoji = useContext(CustomEmojiContext); + + // First, set the emoji state based on the input code. + const [state, setState] = useState(() => + stringToEmojiState(code, customEmoji), + ); + + // If we don't have data, then load emoji data asynchronously. + const appState = useEmojiAppState(); + useEffect(() => { + if (state !== null) { + void loadEmojiDataToState(state, appState.currentLocale).then(setState); + } + }, [appState.currentLocale, state]); + + const animate = useContext(AnimateEmojiContext); + const fallback = showFallback ? code : null; + + // If the code is invalid or we otherwise know it's not valid, show the fallback. + if (!state) { + return fallback; + } + + if (!shouldRenderImage(state, appState.mode)) { + return code; + } + + if (!isStateLoaded(state)) { + if (showLoading) { + return ; + } + return fallback; + } + + if (state.type === EMOJI_TYPE_CUSTOM) { + const shortcode = `:${state.code}:`; + return ( + {shortcode} + ); + } + + const src = unicodeHexToUrl(state.code, appState.darkTheme); + + return ( + {state.data.unicode} + ); +}; + +/** + * Takes a text string and converts it to an array of React nodes. + * @param text The text to be tokenized and converted. + */ +export function textToEmojis(text: string) { + return tokenizeText(text).map((token, index) => { + if (typeof token === 'string') { + return token; + } + return ; + }); +} diff --git a/app/javascript/mastodon/components/exit_animation_wrapper.tsx b/app/javascript/mastodon/components/exit_animation_wrapper.tsx new file mode 100644 index 00000000000000..ab0642b8b2337c --- /dev/null +++ b/app/javascript/mastodon/components/exit_animation_wrapper.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; + +/** + * A helper component for managing the rendering of components that + * need to stay in the DOM a bit longer to finish their CSS exit animation. + * + * In the future, replace this component with plain CSS once that is feasible. + * This will require broader support for `transition-behavior: allow-discrete` + * and https://developer.mozilla.org/en-US/docs/Web/CSS/overlay. + */ +export const ExitAnimationWrapper: React.FC<{ + /** + * Set this to true to indicate that the nested component should be rendered + */ + isActive: boolean; + /** + * How long the component should be rendered after `isActive` was set to `false` + */ + delayMs?: number; + /** + * Set this to true to also delay the entry of the nested component until after + * another one has exited full. + */ + withEntryDelay?: boolean; + /** + * Render prop that provides the nested component with the `delayedIsActive` flag + */ + children: (delayedIsActive: boolean) => React.ReactNode; +}> = ({ isActive = false, delayMs = 500, withEntryDelay, children }) => { + const [delayedIsActive, setDelayedIsActive] = useState(false); + + useEffect(() => { + if (isActive && !withEntryDelay) { + setDelayedIsActive(true); + + return () => ''; + } else { + const timeout = setTimeout(() => { + setDelayedIsActive(isActive); + }, delayMs); + + return () => { + clearTimeout(timeout); + }; + } + }, [isActive, delayMs, withEntryDelay]); + + if (!isActive && !delayedIsActive) { + return null; + } + + return children(isActive && delayedIsActive); +}; diff --git a/app/javascript/mastodon/components/featured_carousel.tsx b/app/javascript/mastodon/components/featured_carousel.tsx index 195331ef9f5d76..df64c43b421836 100644 --- a/app/javascript/mastodon/components/featured_carousel.tsx +++ b/app/javascript/mastodon/components/featured_carousel.tsx @@ -20,7 +20,7 @@ import { useDrag } from '@use-gesture/react'; import { expandAccountFeaturedTimeline } from '@/mastodon/actions/timelines'; import { Icon } from '@/mastodon/components/icon'; import { IconButton } from '@/mastodon/components/icon_button'; -import StatusContainer from '@/mastodon/containers/status_container'; +import { StatusQuoteManager } from '@/mastodon/components/status_quoted'; import { usePrevious } from '@/mastodon/hooks/usePrevious'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; @@ -218,12 +218,7 @@ const FeaturedCarouselItem: React.FC< ref={handleRef} {...props} > - + ); }; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index c64c99b8b78c4f..acf85bec52708d 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -5,24 +5,61 @@ import { useIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; import { useIdentity } from '@/mastodon/identity_context'; -import { fetchRelationships, followAccount } from 'mastodon/actions/accounts'; +import { + fetchRelationships, + followAccount, + unmuteAccount, +} from 'mastodon/actions/accounts'; import { openModal } from 'mastodon/actions/modal'; import { Button } from 'mastodon/components/button'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { me, isShowItem } from 'mastodon/initial_state'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; -const messages = defineMessages({ +import { useBreakpoint } from '../features/ui/hooks/useBreakpoint'; + +const longMessages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, + unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, + followRequest: { + id: 'account.follow_request', + defaultMessage: 'Request to follow', + }, + followRequestCancel: { + id: 'account.follow_request_cancel', + defaultMessage: 'Cancel request', + }, editProfile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, }); +const shortMessages = { + ...longMessages, // Align type signature of shortMessages and longMessages + ...defineMessages({ + followBack: { + id: 'account.follow_back_short', + defaultMessage: 'Follow back', + }, + followRequest: { + id: 'account.follow_request_short', + defaultMessage: 'Request', + }, + followRequestCancel: { + id: 'account.follow_request_cancel_short', + defaultMessage: 'Cancel', + }, + editProfile: { id: 'account.edit_profile_short', defaultMessage: 'Edit' }, + }), +}; + export const FollowButton: React.FC<{ accountId?: string; compact?: boolean; -}> = ({ accountId, compact }) => { + labelLength?: 'auto' | 'short' | 'long'; + className?: string; +}> = ({ accountId, compact, labelLength = 'auto', className }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const { signedIn } = useIdentity(); @@ -57,29 +94,64 @@ export const FollowButton: React.FC<{ if (accountId === me) { return; - } else if (account && (relationship.following || relationship.requested)) { + } else if (relationship.muting) { + dispatch(unmuteAccount(accountId)); + } else if (account && relationship.following) { dispatch( openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), ); + } else if (account && relationship.requested) { + dispatch( + openModal({ + modalType: 'CONFIRM_WITHDRAW_REQUEST', + modalProps: { account }, + }), + ); + } else if (relationship.blocking) { + dispatch( + openModal({ + modalType: 'CONFIRM_UNBLOCK', + modalProps: { account }, + }), + ); } else { dispatch(followAccount(accountId)); } }, [dispatch, accountId, relationship, account, signedIn]); + const isNarrow = useBreakpoint('narrow'); + const useShortLabel = + labelLength === 'short' || (labelLength === 'auto' && isNarrow); + const messages = useShortLabel ? shortMessages : longMessages; + + const followMessage = account?.locked + ? messages.followRequest + : messages.follow; + let label; if (!signedIn) { - label = intl.formatMessage(messages.follow); + label = intl.formatMessage(followMessage); } else if (accountId === me) { label = intl.formatMessage(messages.editProfile); } else if (!relationship) { label = ; - } else if (relationship.following || relationship.requested) { + } else if (relationship.muting) { + label = intl.formatMessage(messages.unmute); + } else if (relationship.following) { label = intl.formatMessage(messages.unfollow); - } else if (relationship.followed_by && isShowItem('relationships')) { + } else if (relationship.blocking) { + label = intl.formatMessage(messages.unblock); + } else if (relationship.requested) { + label = intl.formatMessage(messages.followRequestCancel); + } else if ( + relationship.followed_by && + !account?.locked && + isShowItem('relationships') + ) { label = intl.formatMessage(messages.followBack); } else { - label = intl.formatMessage(messages.follow); + label = intl.formatMessage(followMessage); } if (accountId === me) { @@ -88,7 +160,7 @@ export const FollowButton: React.FC<{ href='/settings/profile' target='_blank' rel='noopener' - className={classNames('button button-secondary', { + className={classNames(className, 'button button-secondary', { 'button--compact': compact, })} > @@ -102,13 +174,12 @@ export const FollowButton: React.FC<{ onClick={handleClick} disabled={ relationship?.blocked_by || - relationship?.blocking || (!(relationship?.following || relationship?.requested) && (account?.suspended || !!account?.moved)) } secondary={following} compact={compact} - className={following ? 'button--destructive' : undefined} + className={classNames(className, { 'button--destructive': following })} > {label} diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 19ffe667c90252..ed9bb998f56637 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -33,7 +33,7 @@ function isNodeLinkHashtag(element: Node): element is HTMLLinkElement { return ( element instanceof HTMLAnchorElement && // it may be a starting with a hashtag - (element.textContent?.[0] === '#' || + (element.textContent.startsWith('#') || // or a # element.previousSibling?.textContent?.[ element.previousSibling.textContent.length - 1 diff --git a/app/javascript/mastodon/components/html_block/html_block.stories.tsx b/app/javascript/mastodon/components/html_block/html_block.stories.tsx new file mode 100644 index 00000000000000..9c104ba45cb68e --- /dev/null +++ b/app/javascript/mastodon/components/html_block/html_block.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { expect } from 'storybook/test'; + +import { HTMLBlock } from './index'; + +const meta = { + title: 'Components/HTMLBlock', + component: HTMLBlock, + args: { + contents: + '

Hello, world!

\n

A link

\n

This should be filtered out:

', + }, + render(args) { + return ( + // Just for visual clarity in Storybook. +
+ +
+ ); + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + async play({ canvas }) { + const link = canvas.queryByRole('link'); + await expect(link).toBeInTheDocument(); + const button = canvas.queryByRole('button'); + await expect(button).not.toBeInTheDocument(); + }, +}; diff --git a/app/javascript/mastodon/components/html_block/index.tsx b/app/javascript/mastodon/components/html_block/index.tsx new file mode 100644 index 00000000000000..51baea614d74b1 --- /dev/null +++ b/app/javascript/mastodon/components/html_block/index.tsx @@ -0,0 +1,50 @@ +import type { FC, ReactNode } from 'react'; +import { useMemo } from 'react'; + +import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize'; +import type { CustomEmojiMapArg } from '@/mastodon/features/emoji/types'; +import { createLimitedCache } from '@/mastodon/utils/cache'; + +import { htmlStringToComponents } from '../../utils/html'; + +// Use a module-level cache to avoid re-rendering the same HTML multiple times. +const cache = createLimitedCache({ maxSize: 1000 }); + +interface HTMLBlockProps { + contents: string; + extraEmojis?: CustomEmojiMapArg; +} + +export const HTMLBlock: FC = ({ + contents: raw, + extraEmojis, +}) => { + const customEmojis = useMemo( + () => cleanExtraEmojis(extraEmojis), + [extraEmojis], + ); + const contents = useMemo(() => { + const key = JSON.stringify({ raw, customEmojis }); + if (cache.has(key)) { + return cache.get(key); + } + + const rendered = htmlStringToComponents(raw, { + onText, + extraArgs: { customEmojis }, + }); + + cache.set(key, rendered); + return rendered; + }, [raw, customEmojis]); + + return contents; +}; + +function onText( + text: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Doesn't do anything, just showing how typing would work. + { customEmojis }: { customEmojis: CustomEmojiMapArg | null }, +) { + return text; +} diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx index 815b4b59abd725..1dc1d45083dfd9 100644 --- a/app/javascript/mastodon/components/router.tsx +++ b/app/javascript/mastodon/components/router.tsx @@ -1,6 +1,7 @@ import type { PropsWithChildren } from 'react'; import type React from 'react'; +import type { useLocation } from 'react-router'; import { Router as OriginalRouter, useHistory } from 'react-router'; import type { @@ -18,7 +19,9 @@ interface MastodonLocationState { mastodonModalKey?: string; } -type LocationState = MastodonLocationState | null | undefined; +export type LocationState = MastodonLocationState | null | undefined; + +export type MastodonLocation = ReturnType>; type HistoryPath = Path | LocationDescriptor; diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx index 93ed201a07f36a..455a8fb968ac72 100644 --- a/app/javascript/mastodon/components/scrollable_list.jsx +++ b/app/javascript/mastodon/components/scrollable_list.jsx @@ -10,7 +10,7 @@ import { connect } from 'react-redux'; import { supportsPassiveEvents } from 'detect-passive-events'; import { throttle } from 'lodash'; -import ScrollContainer from 'mastodon/containers/scroll_container'; +import { ScrollContainer } from 'mastodon/containers/scroll_container'; import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen'; @@ -399,7 +399,7 @@ class ScrollableList extends PureComponent { if (trackScroll) { return ( - + {scrollableArea} ); diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 7e13bb18ff3322..6136c783363c7d 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -126,6 +126,7 @@ class Status extends ImmutablePureComponent { unread: PropTypes.bool, showThread: PropTypes.bool, isQuotedPost: PropTypes.bool, + shouldHighlightOnMount: PropTypes.bool, getScrollPosition: PropTypes.func, updateScrollBottom: PropTypes.func, cacheMediaWidth: PropTypes.func, @@ -604,6 +605,7 @@ class Status extends ImmutablePureComponent { 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted, 'status--is-quote': isQuotedPost, 'status--has-quote': !!status.get('quote'), + 'status--highlighted-entry': this.props.shouldHighlightOnMount, }) } data-id={status.get('id')} diff --git a/app/javascript/mastodon/components/status/boost_button.stories.tsx b/app/javascript/mastodon/components/status/boost_button.stories.tsx index e81d334a938a79..402695a8295b8c 100644 --- a/app/javascript/mastodon/components/status/boost_button.stories.tsx +++ b/app/javascript/mastodon/components/status/boost_button.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import type { StatusVisibility } from '@/mastodon/api_types/statuses'; import { statusFactoryState } from '@/testing/factories'; -import { LegacyReblogButton, StatusBoostButton } from './boost_button'; +import { BoostButton } from './boost_button'; interface StoryProps { visibility: StatusVisibility; @@ -38,10 +38,7 @@ const meta = { }, }, render: (args) => ( - 0} - /> + 0} /> ), } satisfies Meta; @@ -78,12 +75,3 @@ export const Mine: Story = { }, }, }; - -export const Legacy: Story = { - render: (args) => ( - 0} - /> - ), -}; diff --git a/app/javascript/mastodon/components/status/boost_button.tsx b/app/javascript/mastodon/components/status/boost_button.tsx index 7cde4afe954d0b..d3272a29f90212 100644 --- a/app/javascript/mastodon/components/status/boost_button.tsx +++ b/app/javascript/mastodon/components/status/boost_button.tsx @@ -1,5 +1,5 @@ import { useCallback, useMemo } from 'react'; -import type { FC, KeyboardEvent, MouseEvent, MouseEventHandler } from 'react'; +import type { FC, KeyboardEvent, MouseEvent } from 'react'; import { useIntl } from 'react-intl'; @@ -12,7 +12,6 @@ import { openModal } from '@/mastodon/actions/modal'; import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu'; import type { Status } from '@/mastodon/models/status'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; -import { isFeatureEnabled } from '@/mastodon/utils/environment'; import type { SomeRequired } from '@/mastodon/utils/types'; import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu'; @@ -50,10 +49,7 @@ interface ReblogButtonProps { type ActionMenuItemWithIcon = SomeRequired; -export const StatusBoostButton: FC = ({ - status, - counters, -}) => { +export const BoostButton: FC = ({ status, counters }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const statusState = useAppSelector((state) => @@ -75,7 +71,6 @@ export const StatusBoostButton: FC = ({ const statusId = status.get('id') as string; const statusUrl = status.get('url') as string; const wasBoosted = !!status.get('reblogged'); - const isQuoteUiDisabled = !isFeatureEnabled('outgoing_quotes'); const showLoginPrompt = useCallback(() => { dispatch( @@ -110,32 +105,6 @@ export const StatusBoostButton: FC = ({ }; }; - if (isQuoteUiDisabled) { - return [ - generateItem( - boostItem, - () => { - dispatch(toggleReblog(statusId, true, false)); - }, - wasBoosted, - ), - generateItem( - boostWithModalItem, - () => { - dispatch(toggleReblog(statusId, false, true)); - }, - wasBoosted, - ), - generateItem(referenceItem, () => { - dispatch(insertReferenceCompose(0, statusUrl, 'BT')); - }), - ] satisfies [ - ActionMenuItemWithIcon, - ActionMenuItemWithIcon, - ActionMenuItemWithIcon, - ]; - } - return [ generateItem( boostItem, @@ -167,15 +136,7 @@ export const StatusBoostButton: FC = ({ ActionMenuItemWithIcon, ActionMenuItemWithIcon, ]; - }, [ - dispatch, - intl, - statusId, - statusState, - wasBoosted, - statusUrl, - isQuoteUiDisabled, - ]); + }, [dispatch, intl, statusId, statusState, wasBoosted, statusUrl]); const boostIcon = items[0].icon; @@ -255,62 +216,3 @@ const ReblogMenuItem: FC = ({ ); }; - -// Legacy helpers - -// Switch between the legacy and new reblog button based on feature flag. -export const BoostButton: FC = (props) => { - return ; -}; - -export const LegacyReblogButton: FC = ({ - status, - counters, -}) => { - const intl = useIntl(); - const statusState = useAppSelector((state) => - selectStatusState(state, status), - ); - - const { title, meta, iconComponent, disabled } = useMemo( - () => boostItemState(statusState), - [statusState], - ); - - const dispatch = useAppDispatch(); - const handleClick: MouseEventHandler = useCallback( - (event) => { - if (statusState.isLoggedIn) { - dispatch(toggleReblog(status.get('id') as string, event.shiftKey)); - } else { - dispatch( - openModal({ - modalType: 'INTERACTION', - modalProps: { - accountId: status.getIn(['account', 'id']), - url: status.get('uri'), - }, - }), - ); - } - }, - [dispatch, status, statusState.isLoggedIn], - ); - - return ( - - ); -}; diff --git a/app/javascript/mastodon/components/status_action_bar/index.jsx b/app/javascript/mastodon/components/status_action_bar/index.jsx index b4de5977e586dd..ab33502873e335 100644 --- a/app/javascript/mastodon/components/status_action_bar/index.jsx +++ b/app/javascript/mastodon/components/status_action_bar/index.jsx @@ -24,7 +24,6 @@ import { Dropdown } from 'mastodon/components/dropdown_menu'; import { enableEmojiReaction , bookmarkCategoryNeeded, simpleTimelineMenu, me, isHideItem } from '../../initial_state'; import { IconButton } from '../icon_button'; -import { isFeatureEnabled } from '../../utils/environment'; import { BoostButton } from '../status/boost_button'; import { RemoveQuoteHint } from './remove_quote_hint'; @@ -337,7 +336,7 @@ class StatusActionBar extends ImmutablePureComponent { if (writtenByMe || withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); - if (writtenByMe && isFeatureEnabled('outgoing_quotes') && !['private', 'direct'].includes(status.get('visibility'))) { + if (writtenByMe && !['private', 'direct'].includes(status.get('visibility'))) { menu.push({ text: intl.formatMessage(messages.quotePolicyChange), action: this.handleQuotePolicyChange }); } menu.push(null); diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 719b33960bd7af..76c81703e8572d 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -13,10 +13,12 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react' import { Icon } from 'mastodon/components/icon'; import { Poll } from 'mastodon/components/poll'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; -import { EmojiHTML } from '../features/emoji/emoji_html'; +import { languages as preloadedLanguages } from 'mastodon/initial_state'; + import { isModernEmojiEnabled } from '../utils/environment'; +import { EmojiHTML } from './emoji/html'; + const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) /** diff --git a/app/javascript/mastodon/containers/compose_container.jsx b/app/javascript/mastodon/containers/compose_container.jsx index 4c43adbd172e00..468c78828e588a 100644 --- a/app/javascript/mastodon/containers/compose_container.jsx +++ b/app/javascript/mastodon/containers/compose_container.jsx @@ -6,7 +6,7 @@ import { fetchServer } from 'mastodon/actions/server'; import { hydrateStore } from 'mastodon/actions/store'; import { Router } from 'mastodon/components/router'; import Compose from 'mastodon/features/standalone/compose'; -import initialState from 'mastodon/initial_state'; +import { initialState } from 'mastodon/initial_state'; import { IntlProvider } from 'mastodon/locales'; import { store } from 'mastodon/store'; diff --git a/app/javascript/mastodon/containers/mastodon.jsx b/app/javascript/mastodon/containers/mastodon.jsx index 862d07ea2f2e14..d045eada013aab 100644 --- a/app/javascript/mastodon/containers/mastodon.jsx +++ b/app/javascript/mastodon/containers/mastodon.jsx @@ -5,7 +5,6 @@ import { Route } from 'react-router-dom'; import { Provider as ReduxProvider } from 'react-redux'; -import { ScrollContext } from 'react-router-scroll-4'; import { fetchCircles } from 'mastodon/actions/circles'; import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis'; @@ -16,12 +15,14 @@ import ErrorBoundary from 'mastodon/components/error_boundary'; import { Router } from 'mastodon/components/router'; import UI from 'mastodon/features/ui'; import { IdentityContext, createIdentityContext } from 'mastodon/identity_context'; -import initialState, { title as siteTitle } from 'mastodon/initial_state'; +import { initialState, title as siteTitle } from 'mastodon/initial_state'; import { IntlProvider } from 'mastodon/locales'; import { store } from 'mastodon/store'; import { isProduction } from 'mastodon/utils/environment'; import { BodyScrollLock } from 'mastodon/features/ui/components/body_scroll_lock'; +import { ScrollContext } from './scroll_container/scroll_context'; + const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`; const hydrateAction = hydrateStore(initialState); @@ -49,10 +50,6 @@ export default class Mastodon extends PureComponent { } } - shouldUpdateScroll (prevRouterProps, { location }) { - return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey); - } - render () { return ( @@ -60,7 +57,7 @@ export default class Mastodon extends PureComponent { - + diff --git a/app/javascript/mastodon/containers/scroll_container.js b/app/javascript/mastodon/containers/scroll_container.js deleted file mode 100644 index d21ff63687dbfd..00000000000000 --- a/app/javascript/mastodon/containers/scroll_container.js +++ /dev/null @@ -1,18 +0,0 @@ -import { ScrollContainer as OriginalScrollContainer } from 'react-router-scroll-4'; - -// ScrollContainer is used to automatically scroll to the top when pushing a -// new history state and remembering the scroll position when going back. -// There are a few things we need to do differently, though. -const defaultShouldUpdateScroll = (prevRouterProps, { location }) => { - // If the change is caused by opening a modal, do not scroll to top - return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey); -}; - -export default -class ScrollContainer extends OriginalScrollContainer { - - static defaultProps = { - shouldUpdateScroll: defaultShouldUpdateScroll, - }; - -} diff --git a/app/javascript/mastodon/containers/scroll_container/default_should_update_scroll.tsx b/app/javascript/mastodon/containers/scroll_container/default_should_update_scroll.tsx new file mode 100644 index 00000000000000..b8726a1a75170b --- /dev/null +++ b/app/javascript/mastodon/containers/scroll_container/default_should_update_scroll.tsx @@ -0,0 +1,25 @@ +import type { MastodonLocation } from 'mastodon/components/router'; + +export type ShouldUpdateScrollFn = ( + prevLocationContext: MastodonLocation | null, + locationContext: MastodonLocation, +) => boolean; + +/** + * ScrollBehavior will automatically scroll to the top on navigations + * or restore saved scroll positions, but on some location changes we + * need to prevent this. + */ + +export const defaultShouldUpdateScroll: ShouldUpdateScrollFn = ( + prevLocation, + location, +) => { + // If the change is caused by opening a modal, do not scroll to top + const shouldUpdateScroll = !( + location.state?.mastodonModalKey && + location.state.mastodonModalKey !== prevLocation?.state?.mastodonModalKey + ); + + return shouldUpdateScroll; +}; diff --git a/app/javascript/mastodon/containers/scroll_container/index.tsx b/app/javascript/mastodon/containers/scroll_container/index.tsx new file mode 100644 index 00000000000000..0d0ab364dc0b9c --- /dev/null +++ b/app/javascript/mastodon/containers/scroll_container/index.tsx @@ -0,0 +1,76 @@ +import React, { + useContext, + useEffect, + useImperativeHandle, + useRef, +} from 'react'; + +import { defaultShouldUpdateScroll } from './default_should_update_scroll'; +import type { ShouldUpdateScrollFn } from './default_should_update_scroll'; +import { ScrollBehaviorContext } from './scroll_context'; + +interface ScrollContainerProps { + /** + * This key must be static for the element & not change + * while the component is mounted. + */ + scrollKey: string; + shouldUpdateScroll?: ShouldUpdateScrollFn; + childRef?: React.ForwardedRef; + children: React.ReactElement; +} + +/** + * `ScrollContainer` is used to manage the scroll position of elements on the page + * that can be scrolled independently of the page body. + * This component is a port of the unmaintained https://github.com/ytase/react-router-scroll/ + */ + +export const ScrollContainer: React.FC = ({ + children, + scrollKey, + childRef, + shouldUpdateScroll = defaultShouldUpdateScroll, +}) => { + const scrollBehaviorContext = useContext(ScrollBehaviorContext); + + const containerRef = useRef(); + + /** + * If a childRef is passed, sync it with the containerRef. This + * is necessary because in this component's return statement, + * we're overwriting the immediate child component's ref prop. + */ + useImperativeHandle(childRef, () => containerRef.current, []); + + /** + * Register/unregister scrollable element with ScrollBehavior + */ + useEffect(() => { + if (!scrollBehaviorContext || !containerRef.current) { + return; + } + + scrollBehaviorContext.registerElement( + scrollKey, + containerRef.current, + (prevLocation, location) => { + // Hack to allow accessing scrollBehavior._stateStorage + return shouldUpdateScroll.call( + scrollBehaviorContext.scrollBehavior, + prevLocation, + location, + ); + }, + ); + + return () => { + scrollBehaviorContext.unregisterElement(scrollKey); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return React.Children.only( + React.cloneElement(children, { ref: containerRef }), + ); +}; diff --git a/app/javascript/mastodon/containers/scroll_container/scroll_context.tsx b/app/javascript/mastodon/containers/scroll_container/scroll_context.tsx new file mode 100644 index 00000000000000..a7eb7808003797 --- /dev/null +++ b/app/javascript/mastodon/containers/scroll_container/scroll_context.tsx @@ -0,0 +1,141 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; + +import { useLocation, useHistory } from 'react-router-dom'; + +import type { LocationBase } from 'scroll-behavior'; +import ScrollBehavior from 'scroll-behavior'; + +import type { + LocationState, + MastodonLocation, +} from 'mastodon/components/router'; +import { usePrevious } from 'mastodon/hooks/usePrevious'; + +import { defaultShouldUpdateScroll } from './default_should_update_scroll'; +import type { ShouldUpdateScrollFn } from './default_should_update_scroll'; +import { SessionStorage } from './state_storage'; + +type ScrollBehaviorInstance = InstanceType< + typeof ScrollBehavior +>; + +export interface ScrollBehaviorContextType { + registerElement: ( + key: string, + element: HTMLElement, + shouldUpdateScroll: ( + prevLocationContext: MastodonLocation | null, + locationContext: MastodonLocation, + ) => boolean, + ) => void; + unregisterElement: (key: string) => void; + scrollBehavior?: ScrollBehaviorInstance; +} + +export const ScrollBehaviorContext = + React.createContext(null); + +interface ScrollContextProps { + shouldUpdateScroll?: ShouldUpdateScrollFn; + children: React.ReactElement; +} + +/** + * A top-level wrapper that provides the app with an instance of the + * ScrollBehavior object. scroll-behavior is a library for managing the + * scroll position of a single-page app in the same way the browser would + * normally do for a multi-page app. This means it'll scroll back to top + * when navigating to a new page, and will restore the scroll position + * when navigating e.g. using `history.back`. + * The library keeps a record of scroll positions in session storage. + * + * This component is a port of the unmaintained https://github.com/ytase/react-router-scroll/ + */ + +export const ScrollContext: React.FC = ({ + children, + shouldUpdateScroll = defaultShouldUpdateScroll, +}) => { + const location = useLocation(); + const history = useHistory(); + + /** + * Keep the current location in a mutable ref so that ScrollBehavior's + * `getCurrentLocation` can access it without having to recreate the + * whole ScrollBehavior object + */ + const currentLocationRef = useRef(location); + useEffect(() => { + currentLocationRef.current = location; + }, [location]); + + /** + * Initialise ScrollBehavior object once – using state rather + * than a ref to simplify the types and ensure it's defined immediately. + */ + const [scrollBehavior] = useState( + (): ScrollBehaviorInstance => + new ScrollBehavior({ + addNavigationListener: history.listen.bind(history), + stateStorage: new SessionStorage(), + getCurrentLocation: () => + currentLocationRef.current as unknown as LocationBase, + shouldUpdateScroll: ( + prevLocationContext: MastodonLocation | null, + locationContext: MastodonLocation, + ) => + // Hack to allow accessing scrollBehavior._stateStorage + shouldUpdateScroll.call( + scrollBehavior, + prevLocationContext, + locationContext, + ), + }), + ); + + /** + * Handle scroll update when location changes + */ + const prevLocation = usePrevious(location) ?? null; + useEffect(() => { + scrollBehavior.updateScroll(prevLocation, location); + }, [location, prevLocation, scrollBehavior]); + + /** + * Stop Scrollbehavior on unmount + */ + useEffect(() => { + return () => { + scrollBehavior.stop(); + }; + }, [scrollBehavior]); + + /** + * Provide the app with a way to register separately scrollable + * elements to also be tracked by ScrollBehavior. (By default + * ScrollBehavior only handles scrolling on the main document body.) + */ + const contextValue = useMemo( + () => ({ + registerElement: (key, element, shouldUpdateScroll) => { + scrollBehavior.registerElement( + key, + element, + shouldUpdateScroll, + location, + ); + }, + unregisterElement: (key) => { + scrollBehavior.unregisterElement(key); + }, + scrollBehavior, + }), + [location, scrollBehavior], + ); + + return ( + + {React.Children.only(children)} + + ); +}; diff --git a/app/javascript/mastodon/containers/scroll_container/state_storage.ts b/app/javascript/mastodon/containers/scroll_container/state_storage.ts new file mode 100644 index 00000000000000..fe8a208aae4390 --- /dev/null +++ b/app/javascript/mastodon/containers/scroll_container/state_storage.ts @@ -0,0 +1,46 @@ +import type { LocationBase, ScrollPosition } from 'scroll-behavior'; + +const STATE_KEY_PREFIX = '@@scroll|'; + +interface LocationBaseWithKey extends LocationBase { + key?: string; +} + +/** + * This module is part of our port of https://github.com/ytase/react-router-scroll/ + * and handles storing scroll positions in SessionStorage. + * Stored positions (`[x, y]`) are keyed by the location key and an optional + * `scrollKey` that's used for to track separately scrollable elements other + * than the document body. + */ + +export class SessionStorage { + read( + location: LocationBaseWithKey, + key: string | null, + ): ScrollPosition | null { + const stateKey = this.getStateKey(location, key); + + try { + const value = sessionStorage.getItem(stateKey); + return value ? (JSON.parse(value) as ScrollPosition) : null; + } catch { + return null; + } + } + + save(location: LocationBaseWithKey, key: string | null, value: unknown) { + const stateKey = this.getStateKey(location, key); + const storedValue = JSON.stringify(value); + + try { + sessionStorage.setItem(stateKey, storedValue); + } catch {} + } + + getStateKey(location: LocationBaseWithKey, key: string | null) { + const locationKey = location.key; + const stateKeyBase = `${STATE_KEY_PREFIX}${locationKey}`; + return key == null ? stateKeyBase : `${stateKeyBase}|${key}`; + } +} diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index 76af31cb59c594..14045d48f11005 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -50,8 +50,6 @@ import Status from '../components/status'; import { deleteModal } from '../initial_state'; import { makeGetStatus, makeGetPictureInPicture } from '../selectors'; -import { isFeatureEnabled } from 'mastodon/utils/environment'; - const makeMapStateToProps = () => { const getStatus = makeGetStatus(); const getPictureInPicture = makeGetPictureInPicture(); @@ -90,9 +88,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({ }, onQuote (status) { - if (isFeatureEnabled('outgoing_quotes')) { - dispatch(quoteComposeById(status.get('id'))); - } + dispatch(quoteComposeById(status.get('id'))); }, onFavourite (status) { diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 4f9cf8e412ad0e..330ca61853aabd 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -8,6 +8,7 @@ import { NavLink } from 'react-router-dom'; import { AccountBio } from '@/mastodon/components/account_bio'; import { DisplayName } from '@/mastodon/components/display_name'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; @@ -33,7 +34,6 @@ import { initMuteModal } from 'mastodon/actions/mutes'; import { initReport } from 'mastodon/actions/reports'; import { Avatar } from 'mastodon/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; -import { Button } from 'mastodon/components/button'; import { CopyIconButton } from 'mastodon/components/copy_icon_button'; import { FollowersCounter, @@ -450,7 +450,7 @@ export const AccountHeader: React.FC<{ const isRemote = account?.acct !== account?.username; const remoteDomain = isRemote ? account?.acct.split('@')[1] : null; - const menu = useMemo(() => { + const menuItems = useMemo(() => { const arr: MenuItem[] = []; if (!account) { @@ -690,6 +690,15 @@ export const AccountHeader: React.FC<{ isHideRelationships, ]); + const menu = accountId !== me && ( + + ); + if (!account) { return null; } @@ -804,21 +813,16 @@ export const AccountHeader: React.FC<{ ); } - if (relationship?.blocking) { + const isMovedAndUnfollowedAccount = account.moved && !relationship?.following; + + if (!isMovedAndUnfollowedAccount) { actionBtn = ( - - - - {({ props, placement }) => ( -
-
- -
-
- )} -
- - ); - } - -} - -export default injectIntl(SearchabilityDropdown); diff --git a/app/javascript/mastodon/features/compose/components/visibility_button.tsx b/app/javascript/mastodon/features/compose/components/visibility_button.tsx index cc6e667f079872..2a269ce598c5af 100644 --- a/app/javascript/mastodon/features/compose/components/visibility_button.tsx +++ b/app/javascript/mastodon/features/compose/components/visibility_button.tsx @@ -19,7 +19,6 @@ import type { } from '@/mastodon/api_types/statuses'; import { Icon } from '@/mastodon/components/icon'; import { useAppSelector, useAppDispatch } from '@/mastodon/store'; -import { isFeatureEnabled } from '@/mastodon/utils/environment'; import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import BlockIcon from '@/material-icons/400-24px/block.svg?react'; @@ -34,10 +33,52 @@ import LimitedIcon from '@/material-icons/400-24px/shield.svg?react'; import PersonalIcon from '@/material-icons/400-24px/sticky_note.svg?react'; import type { VisibilityModalCallback } from '../../ui/components/visibility_modal'; -import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import { messages as privacyMessages } from './privacy_dropdown'; -import { messages as searchabilityMessages } from './searchability_dropdown'; + +const searchabilityMessages = defineMessages({ + public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, + public_long: { + id: 'searchability.public.long', + defaultMessage: 'Anyone can find', + }, + public_unlisted_short: { + id: 'searchability.public_unlisted.short', + defaultMessage: 'Local public', + }, + public_unlisted_long: { + id: 'searchability.public_unlisted.long', + defaultMessage: 'Local users and followers can find', + }, + private_short: { + id: 'searchability.unlisted.short', + defaultMessage: 'Followers', + }, + private_long: { + id: 'searchability.unlisted.long', + defaultMessage: 'Your followers can find', + }, + direct_short: { + id: 'searchability.private.short', + defaultMessage: 'Reactionners', + }, + direct_long: { + id: 'searchability.private.long', + defaultMessage: 'Reacter of this post can find', + }, + limited_short: { + id: 'searchability.direct.short', + defaultMessage: 'Self only', + }, + limited_long: { + id: 'searchability.direct.long', + defaultMessage: 'Nobody can find, but you can', + }, + change_searchability: { + id: 'searchability.change', + defaultMessage: 'Set status searchability', + }, +}); const messages = defineMessages({ anyone_quote: { @@ -59,9 +100,6 @@ interface PrivacyDropdownProps { } export const VisibilityButton: FC = (props) => { - if (!isFeatureEnabled('outgoing_quotes')) { - return ; - } return ; }; diff --git a/app/javascript/mastodon/features/compose/containers/searchability_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/searchability_dropdown_container.js deleted file mode 100644 index 09c666cea087da..00000000000000 --- a/app/javascript/mastodon/features/compose/containers/searchability_dropdown_container.js +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux'; - -import { changeComposeSearchability } from '../../../actions/compose'; -import { openModal, closeModal } from '../../../actions/modal'; -import { isUserTouching } from '../../../is_mobile'; -import SearchabilityDropdown from '../components/searchability_dropdown'; - -const mapStateToProps = state => ({ - value: state.getIn(['compose', 'searchability']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeComposeSearchability(value)); - }, - - isUserTouching, - onModalOpen: props => dispatch(openModal({ - modalType: 'ACTIONS', - modalProps: props, - })), - onModalClose: () => dispatch(closeModal({ - modalType: undefined, - ignoreFocus: false, - })), - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(SearchabilityDropdown); diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index dc1461e5b41491..fbe37f58a29888 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -25,6 +25,7 @@ import StatusContent from 'mastodon/components/status_content'; import { Dropdown } from 'mastodon/components/dropdown_menu'; import { makeGetStatus } from 'mastodon/selectors'; import { LinkedDisplayName } from '@/mastodon/components/display_name'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, @@ -136,9 +137,9 @@ export const Conversation = ({ conversation, scrollKey }) => { {unread && } -
+ {names} }} /> -
+ = ({ accountId }) => { - const intl = useIntl(); const account = useAppSelector((s) => getAccount(s, accountId)); - const dispatch = useAppDispatch(); - - const handleFollow = useCallback(() => { - if (!account) return; - - if ( - account.getIn(['relationship', 'following']) || - account.getIn(['relationship', 'requested']) - ) { - dispatch( - openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), - ); - } else { - dispatch(followAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleBlock = useCallback(() => { - if (account?.relationship?.blocking) { - dispatch(unblockAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleMute = useCallback(() => { - if (account?.relationship?.muting) { - dispatch(unmuteAccount(account.get('id'))); - } - }, [account, dispatch]); - - const handleEditProfile = useCallback(() => { - window.open('/settings/profile', '_blank'); - }, []); if (!account) return null; - let actionBtn; - - if (me !== account.get('id')) { - if (!account.get('relationship')) { - // Wait until the relationship is loaded - actionBtn = ''; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = ( -
+
+ +
); } - if (!refresh && !loading) { - return null; - } - return ( -
- +
+ + + +
); }; diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 4e9d330933a072..ecd0a31fafbe4d 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -5,6 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; import { withRouter } from 'react-router-dom'; +import { difference } from 'lodash'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -15,7 +16,7 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac import { Hotkeys } from 'mastodon/components/hotkeys'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import ScrollContainer from 'mastodon/containers/scroll_container'; +import { ScrollContainer } from 'mastodon/containers/scroll_container'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -157,6 +158,11 @@ class Status extends ImmutablePureComponent { fullscreen: false, showMedia: defaultMediaVisibility(this.props.status), loadedStatusId: undefined, + /** + * Holds the ids of newly added replies, excluding the initial load. + * Used to highlight newly added replies in the UI + */ + newRepliesIds: [], }; UNSAFE_componentWillMount () { @@ -518,6 +524,7 @@ class Status extends ImmutablePureComponent { previousId={i > 0 ? list[i - 1] : undefined} nextId={list[i + 1] || (ancestors && statusId)} rootId={statusId} + shouldHighlightOnMount={this.state.newRepliesIds.includes(id)} /> )); } @@ -551,11 +558,20 @@ class Status extends ImmutablePureComponent { } componentDidUpdate (prevProps) { - const { status, ancestorsIds, referencesIds } = this.props; + const { status, ancestorsIds, descendantsIds, referencesIds } = this.props; if (status && (ancestorsIds.length + referencesIds.length > prevProps.ancestorsIds.length + prevProps.referencesIds.length || prevProps.status?.get('id') !== status.get('id'))) { this._scrollStatusIntoView(); } + + // Only highlight replies after the initial load + if (prevProps.descendantsIds.length) { + const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds); + + if (newRepliesIds.length) { + this.setState({newRepliesIds}); + } + } } componentWillUnmount () { @@ -566,9 +582,9 @@ class Status extends ImmutablePureComponent { this.setState({ fullscreen: isFullscreen() }); }; - shouldUpdateScroll = (prevRouterProps, { location }) => { + shouldUpdateScroll = (prevLocation, location) => { // Do not change scroll when opening a modal - if (location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey) { + if (location.state?.mastodonModalKey !== prevLocation?.state?.mastodonModalKey) { return false; } @@ -646,7 +662,7 @@ class Status extends ImmutablePureComponent { )} /> - +
{references} {ancestors} @@ -700,8 +716,8 @@ class Status extends ImmutablePureComponent {
- {remoteHint} {descendants} + {remoteHint}
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx index 19ffe2bae52f1d..47f9fca890441f 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -11,7 +11,7 @@ export interface BaseConfirmationModalProps { export const ConfirmationModal: React.FC< { title: React.ReactNode; - message: React.ReactNode; + message?: React.ReactNode; confirm: React.ReactNode; cancel?: React.ReactNode; secondary?: React.ReactNode; @@ -48,7 +48,7 @@ export const ConfirmationModal: React.FC<

{title}

-

{message}

+ {message &&

{message}

}
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts index 62011cebab472a..21955f075e9bc0 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts @@ -8,7 +8,9 @@ export { ConfirmReplyModal, ConfirmEditStatusModal, } from './discard_draft_confirmation'; +export { ConfirmWithdrawRequestModal } from './withdraw_follow_request'; export { ConfirmUnfollowModal } from './unfollow'; +export { ConfirmUnblockModal } from './unblock'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/unblock.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/unblock.tsx new file mode 100644 index 00000000000000..e2154ef48df009 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/unblock.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unblockAccount } from 'mastodon/actions/accounts'; +import type { Account } from 'mastodon/models/account'; +import { useAppDispatch } from 'mastodon/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + unblockConfirm: { + id: 'confirmations.unblock.confirm', + defaultMessage: 'Unblock', + }, +}); + +export const ConfirmUnblockModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unblockAccount(account.id)); + }, [dispatch, account.id]); + + return ( + + } + confirm={intl.formatMessage(messages.unblockConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx index 58e39da07bf95a..45b8d458b10597 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/unfollow.tsx @@ -10,10 +10,6 @@ import type { BaseConfirmationModalProps } from './confirmation_modal'; import { ConfirmationModal } from './confirmation_modal'; const messages = defineMessages({ - unfollowTitle: { - id: 'confirmations.unfollow.title', - defaultMessage: 'Unfollow user?', - }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow', @@ -34,12 +30,11 @@ export const ConfirmUnfollowModal: React.FC< return ( @{account.acct} }} + id='confirmations.unfollow.title' + defaultMessage='Unfollow {name}?' + values={{ name: `@${account.acct}` }} /> } confirm={intl.formatMessage(messages.unfollowConfirm)} diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/withdraw_follow_request.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/withdraw_follow_request.tsx new file mode 100644 index 00000000000000..a0bd236637439d --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/withdraw_follow_request.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unfollowAccount } from 'mastodon/actions/accounts'; +import type { Account } from 'mastodon/models/account'; +import { useAppDispatch } from 'mastodon/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + withdrawConfirm: { + id: 'confirmations.withdraw_request.confirm', + defaultMessage: 'Withdraw request', + }, +}); + +export const ConfirmWithdrawRequestModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unfollowAccount(account.id)); + }, [dispatch, account.id]); + + return ( + + } + confirm={intl.formatMessage(messages.withdrawConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.tsx b/app/javascript/mastodon/features/ui/components/embed_modal.tsx index b78d5b64c4fd72..0290b01d2f548d 100644 --- a/app/javascript/mastodon/features/ui/components/embed_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/embed_modal.tsx @@ -36,6 +36,7 @@ const EmbedModal: React.FC<{ } iframeDocument.open(); + // eslint-disable-next-line @typescript-eslint/no-deprecated iframeDocument.write(data.html); iframeDocument.close(); diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 64295f02a91fbc..edbddf1c0c6d53 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -38,7 +38,9 @@ import { ConfirmDeleteBookmarkCategoryModal, ConfirmReplyModal, ConfirmEditStatusModal, + ConfirmUnblockModal, ConfirmUnfollowModal, + ConfirmWithdrawRequestModal, ConfirmClearNotificationsModal, ConfirmLogOutModal, ConfirmFollowToListModal, @@ -66,7 +68,9 @@ export const MODAL_COMPONENTS = { 'CONFIRM_DELETE_BOOKMARK_CATEGORY': () => Promise.resolve({ default: ConfirmDeleteBookmarkCategoryModal }), 'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }), 'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }), + 'CONFIRM_UNBLOCK': () => Promise.resolve({ default: ConfirmUnblockModal }), 'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }), + 'CONFIRM_WITHDRAW_REQUEST': () => Promise.resolve({ default: ConfirmWithdrawRequestModal }), 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), 'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), diff --git a/app/javascript/mastodon/features/ui/components/visibility_modal.tsx b/app/javascript/mastodon/features/ui/components/visibility_modal.tsx index 41f1dc93797bb1..167325134fd1aa 100644 --- a/app/javascript/mastodon/features/ui/components/visibility_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/visibility_modal.tsx @@ -20,7 +20,6 @@ import { Dropdown } from '@/mastodon/components/dropdown'; import type { SelectItem } from '@/mastodon/components/dropdown_selector'; import { IconButton } from '@/mastodon/components/icon_button'; import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown'; -import { messages as searchabilityMessages } from '@/mastodon/features/compose/components/searchability_dropdown'; import { enabledVisibilites } from '@/mastodon/initial_state'; import { createAppSelector, useAppSelector } from '@/mastodon/store'; import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; @@ -38,6 +37,50 @@ import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal'; +const searchabilityMessages = defineMessages({ + public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, + public_long: { + id: 'searchability.public.long', + defaultMessage: 'Anyone can find', + }, + public_unlisted_short: { + id: 'searchability.public_unlisted.short', + defaultMessage: 'Local public', + }, + public_unlisted_long: { + id: 'searchability.public_unlisted.long', + defaultMessage: 'Local users and followers can find', + }, + private_short: { + id: 'searchability.unlisted.short', + defaultMessage: 'Followers', + }, + private_long: { + id: 'searchability.unlisted.long', + defaultMessage: 'Your followers can find', + }, + direct_short: { + id: 'searchability.private.short', + defaultMessage: 'Reactionners', + }, + direct_long: { + id: 'searchability.private.long', + defaultMessage: 'Reacter of this post can find', + }, + limited_short: { + id: 'searchability.direct.short', + defaultMessage: 'Self only', + }, + limited_long: { + id: 'searchability.direct.long', + defaultMessage: 'Nobody can find, but you can', + }, + change_searchability: { + id: 'searchability.change', + defaultMessage: 'Set status searchability', + }, +}); + const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, buttonTitle: { diff --git a/app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx b/app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx index af96ab3766345c..cb7b3551f222f0 100644 --- a/app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx +++ b/app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx @@ -1,11 +1,12 @@ import { useState, useEffect } from 'react'; const breakpoints = { + narrow: 479, // Device width under which horizontal space is constrained openable: 759, // Device width at which the sidebar becomes an openable hamburger menu full: 1174, // Device width at which all 3 columns can be displayed }; -type Breakpoint = 'openable' | 'full'; +type Breakpoint = keyof typeof breakpoints; export const useBreakpoint = (breakpoint: Breakpoint) => { const [isMatching, setIsMatching] = useState(false); diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 29363bab903fee..2162610ff43f47 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -27,7 +27,7 @@ import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../act import { clearHeight } from '../../actions/height_cache'; import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server'; import { expandHomeTimeline } from '../../actions/timelines'; -import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards, autoPlayGif } from '../../initial_state'; +import { initialState, me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards, autoPlayGif } from '../../initial_state'; import BundleColumnError from './components/bundle_column_error'; import { NavigationBar } from './components/navigation_bar'; diff --git a/app/javascript/mastodon/features/ui/util/focusUtils.ts b/app/javascript/mastodon/features/ui/util/focusUtils.ts index a19852e0d26f8c..e46ede3553f8c4 100644 --- a/app/javascript/mastodon/features/ui/util/focusUtils.ts +++ b/app/javascript/mastodon/features/ui/util/focusUtils.ts @@ -1,4 +1,4 @@ -import initialState from '@/mastodon/initial_state'; +import { initialState } from '@/mastodon/initial_state'; interface FocusColumnOptions { index?: number; @@ -60,23 +60,13 @@ export function focusColumn({ * Get the index of the currently focused item in one of our item lists */ export function getFocusedItemIndex() { - const focusedElement = document.activeElement; - const itemList = focusedElement?.closest('.item-list'); - - if (!focusedElement || !itemList) { - return -1; - } - - let focusedItem: HTMLElement | null = null; - if (focusedElement.parentElement === itemList) { - focusedItem = focusedElement as HTMLElement; - } else { - focusedItem = focusedElement.closest('.item-list > *'); - } - + const focusedItem = document.activeElement?.closest('.item-list > *'); if (!focusedItem) return -1; - const items = Array.from(itemList.children); + const { parentElement } = focusedItem; + if (!parentElement) return -1; + + const items = Array.from(parentElement.children); return items.indexOf(focusedItem); } diff --git a/app/javascript/mastodon/hooks/useLinks.ts b/app/javascript/mastodon/hooks/useLinks.ts index 160fe18503f36a..00e1dd9bb44253 100644 --- a/app/javascript/mastodon/hooks/useLinks.ts +++ b/app/javascript/mastodon/hooks/useLinks.ts @@ -12,7 +12,7 @@ const isMentionClick = (element: HTMLAnchorElement) => !element.classList.contains('hashtag'); const isHashtagClick = (element: HTMLAnchorElement) => - element.textContent?.[0] === '#' || + element.textContent.startsWith('#') || element.previousSibling?.textContent?.endsWith('#'); export const useLinks = (skipHashtags?: boolean) => { diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js deleted file mode 100644 index 1b076e15cd1d73..00000000000000 --- a/app/javascript/mastodon/initial_state.js +++ /dev/null @@ -1,192 +0,0 @@ -// @ts-check - -/** - * @typedef { 'emoji_reaction_on_timeline' - * | 'emoji_reaction_unavailable_server' - * | 'emoji_reaction_count' - * | 'favourite_menu' - * | 'recent_emojis' - * | 'relationships' - * | 'status_reference_unavailable_server' - * | 'avatar_on_filter' - * } HideItemsDefinition - */ - -/** - * @typedef {[code: string, name: string, localName: string]} InitialStateLanguage - */ - -/** - * @typedef InitialStateMeta - * @property {string} access_token - * @property {boolean=} advanced_layout - * @property {boolean} auto_play_gif - * @property {boolean} activity_api_enabled - * @property {string} admin - * @property {boolean} bookmark_category_needed - * @property {boolean=} boost_modal - * @property {boolean} community_timeline_instead_of_search_menu - * @property {boolean=} delete_modal - * @property {boolean=} missing_alt_text_modal - * @property {boolean=} disable_swiping - * @property {boolean=} disable_hover_cards - * @property {string=} disabled_account_id - * @property {string[]} enabled_visibilities - * @property {string} display_media - * @property {string} domain - * @property {string} dtl_tag - * @property {boolean} enable_emoji_reaction - * @property {boolean} enable_local_timeline - * @property {boolean} enable_dtl_menu - * @property {boolean=} expand_spoilers - * @property {string[]} featured_tags - * @property {HideItemsDefinition[]} hide_items - * @property {boolean} limited_federation_mode - * @property {string} locale - * @property {string | null} mascot - * @property {string=} me - * @property {string=} moved_to_account_id - * @property {string=} owner - * @property {boolean} profile_directory - * @property {boolean} registrations_open - * @property {boolean} registrations_reach_limit - * @property {boolean} reduce_motion - * @property {string} repository - * @property {boolean} search_enabled - * @property {boolean} trends_enabled - * @property {boolean} simple_timeline_menu - * @property {boolean} single_user_mode - * @property {string} source_url - * @property {string} streaming_api_base_url - * @property {boolean} timeline_preview - * @property {string} title - * @property {boolean} show_trends - * @property {boolean} trends_as_landing_page - * @property {boolean} use_blurhash - * @property {boolean=} use_pending_items - * @property {string} version - * @property {string} sso_redirect - * @property {string} status_page_url - * @property {boolean} terms_of_service_enabled - * @property {string?} emoji_style - */ - -/** - * @typedef Role - * @property {string} id - * @property {string} name - * @property {string} permissions - * @property {string} color - * @property {boolean} highlighted - */ - -/** - * @typedef InitialState - * @property {Record} accounts - * @property {InitialStateLanguage[]} languages - * @property {boolean=} critical_updates_pending - * @property {InitialStateMeta} meta - * @property {Role?} role - * @property {string[]} features - */ - -const element = document.getElementById('initial-state'); -/** @type {InitialState | undefined} */ -const initialState = element?.textContent && JSON.parse(element.textContent); - -/** @type {string} */ -const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? ''; -/** @type {boolean} */ -export const hasMultiColumnPath = initialPath === '/' - || initialPath === '/getting-started' - || initialPath === '/home' - || initialPath.startsWith('/deck'); - -/** - * @template {keyof InitialStateMeta} K - * @param {K} prop - * @returns {InitialStateMeta[K] | undefined} - */ -const getMeta = (prop) => initialState?.meta && initialState.meta[prop]; - -const hideItems = getMeta('hide_items'); - -/** - * @param {HideItemsDefinition} key - * @returns {boolean} - */ -export const isHideItem = (key) => (hideItems && hideItems.includes(key)) || false; - -/** - * @param {HideItemsDefinition} key - * @returns {boolean} - */ -export const isShowItem = (key) => !isHideItem(key); - -export const activityApiEnabled = getMeta('activity_api_enabled'); -export const autoPlayGif = getMeta('auto_play_gif'); -export const bookmarkCategoryNeeded = getMeta('bookmark_category_needed'); -export const boostModal = getMeta('boost_modal'); -export const deleteModal = getMeta('delete_modal'); -export const missingAltTextModal = getMeta('missing_alt_text_modal'); -export const disableSwiping = getMeta('disable_swiping'); -export const disableHoverCards = getMeta('disable_hover_cards'); -export const disabledAccountId = getMeta('disabled_account_id'); -export const enabledVisibilites = getMeta('enabled_visibilities'); -export const displayMedia = getMeta('display_media'); -export const domain = getMeta('domain'); -export const dtlTag = getMeta('dtl_tag'); -export const enableEmojiReaction = getMeta('enable_emoji_reaction'); -export const enableLocalTimeline = getMeta('enable_local_timeline'); -export const enableDtlMenu = getMeta('enable_dtl_menu'); -export const emojiStyle = getMeta('emoji_style') || 'auto'; -export const expandSpoilers = getMeta('expand_spoilers'); -export const featuredTags = getMeta('featured_tags') || []; -export const forceSingleColumn = !getMeta('advanced_layout'); -export const limitedFederationMode = getMeta('limited_federation_mode'); -export const mascot = getMeta('mascot'); -export const me = getMeta('me'); -export const movedToAccountId = getMeta('moved_to_account_id'); -export const owner = getMeta('owner'); -export const profile_directory = getMeta('profile_directory'); -export const reduceMotion = getMeta('reduce_motion'); -export const registrationsOpen = getMeta('registrations_open'); -export const registrationsReachLimit = getMeta('registrations_reach_limit'); -export const repository = getMeta('repository'); -export const searchEnabled = getMeta('search_enabled'); -export const trendsEnabled = getMeta('trends_enabled'); -export const showTrends = getMeta('show_trends'); -export const simpleTimelineMenu = getMeta('simple_timeline_menu'); -export const communityTimelineInsteadOfSearchMenu = getMeta('community_timeline_instead_of_search_menu'); -export const singleUserMode = getMeta('single_user_mode'); -export const source_url = getMeta('source_url'); -export const timelinePreview = getMeta('timeline_preview'); -export const title = getMeta('title'); -export const trendsAsLanding = getMeta('trends_as_landing_page'); -export const useBlurhash = getMeta('use_blurhash'); -export const usePendingItems = getMeta('use_pending_items'); -export const version = getMeta('version'); -export const criticalUpdatesPending = initialState?.critical_updates_pending; -export const statusPageUrl = getMeta('status_page_url'); -export const sso_redirect = getMeta('sso_redirect'); -export const termsOfServiceEnabled = getMeta('terms_of_service_enabled'); - -const displayNames = Intl.DisplayNames && new Intl.DisplayNames(getMeta('locale'), { - type: 'language', - fallback: 'none', - languageDisplay: 'standard', -}); - -export const languages = initialState?.languages?.map(lang => { - // zh-YUE is not a valid CLDR unicode_language_id - return [lang[0], displayNames?.of(lang[0].replace('zh-YUE', 'yue')) || lang[1], lang[2]]; -}); - -/** - * @returns {string | undefined} - */ -export function getAccessToken() { - return getMeta('access_token'); -} - -export default initialState; diff --git a/app/javascript/mastodon/initial_state.ts b/app/javascript/mastodon/initial_state.ts new file mode 100644 index 00000000000000..00e216437c5509 --- /dev/null +++ b/app/javascript/mastodon/initial_state.ts @@ -0,0 +1,180 @@ +import type { ApiAccountJSON } from './api_types/accounts'; + +type HideItemsDefinition = + | 'emoji_reaction_on_timeline' + | 'emoji_reaction_unavailable_server' + | 'emoji_reaction_count' + | 'favourite_menu' + | 'recent_emojis' + | 'relationships' + | 'status_reference_unavailable_server' + | 'avatar_on_filter'; + +type InitialStateLanguage = [code: string, name: string, localName: string]; + +interface InitialStateMeta { + access_token: string; + advanced_layout?: boolean; + auto_play_gif: boolean; + activity_api_enabled: boolean; + admin: string; + boost_modal?: boolean; + delete_modal?: boolean; + missing_alt_text_modal?: boolean; + disable_swiping?: boolean; + disable_hover_cards?: boolean; + disabled_account_id?: string; + display_media: string; + domain: string; + expand_spoilers?: boolean; + limited_federation_mode: boolean; + locale: string; + mascot: string | null; + me?: string; + moved_to_account_id?: string; + owner?: string; + profile_directory: boolean; + registrations_open: boolean; + reduce_motion: boolean; + repository: string; + search_enabled: boolean; + trends_enabled: boolean; + single_user_mode: boolean; + source_url: string; + streaming_api_base_url: string; + timeline_preview: boolean; + title: string; + show_trends: boolean; + trends_as_landing_page: boolean; + use_blurhash: boolean; + use_pending_items?: boolean; + version: string; + sso_redirect: string; + status_page_url: string; + terms_of_service_enabled: boolean; + emoji_style?: string; + bookmark_category_needed: boolean; + community_timeline_instead_of_search_menu: boolean; + enabled_visibilities: string[]; + dtl_tag: string; + enable_emoji_reaction: boolean; + enable_local_timeline: boolean; + enable_dtl_menu: boolean; + featured_tags: string[]; + hide_items: HideItemsDefinition[]; + registrations_reach_limit: boolean; + simple_timeline_menu: boolean; +} + +interface Role { + id: string; + name: string; + permissions: string; + color: string; + highlighted: boolean; +} + +export interface InitialState { + accounts: Record; + languages: InitialStateLanguage[]; + critical_updates_pending?: boolean; + meta: InitialStateMeta; + role?: Role; + features: string[]; +} + +const element = document.getElementById('initial-state'); +export const initialState: InitialState | undefined = element?.textContent + ? (JSON.parse(element.textContent) as InitialState) + : undefined; + +const initialPath: string = + document + .querySelector('head meta[name=initialPath]') + ?.getAttribute('content') ?? ''; +export const hasMultiColumnPath: boolean = + initialPath === '/' || + initialPath === '/getting-started' || + initialPath === '/home' || + initialPath.startsWith('/deck'); + +function getMeta( + prop: K, +): InitialStateMeta[K] | undefined { + return initialState?.meta[prop]; +} + +const hideItems = getMeta('hide_items'); +export const isHideItem = (key: HideItemsDefinition): boolean => + hideItems?.includes(key) || false; +export const isShowItem = (key: HideItemsDefinition): boolean => + !isHideItem(key); + +export const activityApiEnabled = getMeta('activity_api_enabled'); +export const autoPlayGif = getMeta('auto_play_gif'); +export const boostModal = getMeta('boost_modal'); +export const deleteModal = getMeta('delete_modal'); +export const missingAltTextModal = getMeta('missing_alt_text_modal'); +export const disableSwiping = getMeta('disable_swiping'); +export const disableHoverCards = getMeta('disable_hover_cards'); +export const disabledAccountId = getMeta('disabled_account_id'); +export const displayMedia = getMeta('display_media'); +export const domain = getMeta('domain'); +export const emojiStyle = getMeta('emoji_style') ?? 'auto'; +export const expandSpoilers = getMeta('expand_spoilers'); +export const forceSingleColumn = !getMeta('advanced_layout'); +export const limitedFederationMode = getMeta('limited_federation_mode'); +export const mascot = getMeta('mascot'); +export const me = getMeta('me'); +export const movedToAccountId = getMeta('moved_to_account_id'); +export const owner = getMeta('owner'); +export const profile_directory = getMeta('profile_directory'); +export const reduceMotion = getMeta('reduce_motion'); +export const registrationsOpen = getMeta('registrations_open'); +export const repository = getMeta('repository'); +export const searchEnabled = getMeta('search_enabled'); +export const trendsEnabled = getMeta('trends_enabled'); +export const showTrends = getMeta('show_trends'); +export const singleUserMode = getMeta('single_user_mode'); +export const source_url = getMeta('source_url'); +export const timelinePreview = getMeta('timeline_preview'); +export const title = getMeta('title'); +export const trendsAsLanding = getMeta('trends_as_landing_page'); +export const useBlurhash = getMeta('use_blurhash'); +export const usePendingItems = getMeta('use_pending_items'); +export const version = getMeta('version'); +export const criticalUpdatesPending = initialState?.critical_updates_pending; +export const statusPageUrl = getMeta('status_page_url'); +export const sso_redirect = getMeta('sso_redirect'); +export const termsOfServiceEnabled = getMeta('terms_of_service_enabled'); +export const bookmarkCategoryNeeded = getMeta('bookmark_category_needed'); +export const enabledVisibilites = getMeta('enabled_visibilities'); +export const dtlTag = getMeta('dtl_tag'); +export const enableEmojiReaction = getMeta('enable_emoji_reaction'); +export const enableLocalTimeline = getMeta('enable_local_timeline'); +export const enableDtlMenu = getMeta('enable_dtl_menu'); +export const featuredTags = getMeta('featured_tags') ?? []; +export const registrationsReachLimit = getMeta('registrations_reach_limit'); +export const simpleTimelineMenu = getMeta('simple_timeline_menu'); +export const communityTimelineInsteadOfSearchMenu = getMeta( + 'community_timeline_instead_of_search_menu', +); + +const displayNames = new Intl.DisplayNames(getMeta('locale'), { + type: 'language', + fallback: 'none', + languageDisplay: 'standard', +}); + +export const languages = initialState?.languages.map((lang) => { + // zh-YUE is not a valid CLDR unicode_language_id + return [ + lang[0], + displayNames.of(lang[0].replace('zh-YUE', 'yue')) ?? lang[1], + lang[2], + ]; +}); + +export function getAccessToken(): string | undefined { + return getMeta('access_token'); +} diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json index 6df4608528097a..7ab1d793ed4aaa 100644 --- a/app/javascript/mastodon/locales/af.json +++ b/app/javascript/mastodon/locales/af.json @@ -44,7 +44,6 @@ "account.posts": "Plasings", "account.posts_with_replies": "Plasings en antwoorde", "account.report": "Rapporteer @{name}", - "account.requested": "Wag op goedkeuring. Klik om volgversoek te kanselleer", "account.requested_follow": "{name} het versoek om jou te volg", "account.share": "Deel @{name} se profiel", "account.show_reblogs": "Wys aangestuurde plasings van @{name}", diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json index 661a1bebdb3fa4..6733b1b929bc22 100644 --- a/app/javascript/mastodon/locales/an.json +++ b/app/javascript/mastodon/locales/an.json @@ -44,7 +44,6 @@ "account.posts": "Publicacions", "account.posts_with_replies": "Publicacions y respuestas", "account.report": "Denunciar a @{name}", - "account.requested": "Esperando l'aprebación", "account.requested_follow": "{name} ha demandau seguir-te", "account.share": "Compartir lo perfil de @{name}", "account.show_reblogs": "Amostrar retutz de @{name}", @@ -134,7 +133,6 @@ "confirmations.mute.confirm": "Silenciar", "confirmations.redraft.confirm": "Borrar y tornar ta borrador", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "Yes seguro que quiers deixar de seguir a {name}?", "conversation.delete": "Borrar conversación", "conversation.mark_as_read": "Marcar como leyiu", "conversation.open": "Veyer conversación", diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 3af126d5e71b0b..514a8d7f28954b 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "المنشورات والرُدود", "account.remove_from_followers": "إزالة {name} من المتابعين", "account.report": "الإبلاغ عن @{name}", - "account.requested": "في انتظار القبول. اضغط لإلغاء طلب المُتابعة", "account.requested_follow": "لقد طلب {name} متابعتك", "account.requests_to_follow_you": "طلبات المتابعة", "account.share": "شارِك الملف التعريفي لـ @{name}", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "لا يمكن التراجع عن هذا الإجراء.", "confirmations.revoke_quote.title": "أتريد إزالة المنشور؟", "confirmations.unfollow.confirm": "إلغاء المتابعة", - "confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟", - "confirmations.unfollow.title": "إلغاء متابعة المستخدم؟", "content_warning.hide": "إخفاء المنشور", "content_warning.show": "إظهار على أي حال", "content_warning.show_more": "إظهار المزيد", @@ -861,8 +858,6 @@ "status.cancel_reblog_private": "إلغاء إعادة النشر", "status.cannot_quote": "غير مصرح لك باقتباس هذا المنشور", "status.cannot_reblog": "لا يمكن إعادة نشر هذا المنشور", - "status.context.load_new_replies": "الردود الجديدة المتاحة", - "status.context.loading": "التحقق من المزيد من الردود", "status.continued_thread": "تكملة للخيط", "status.copy": "انسخ رابط الرسالة", "status.delete": "احذف", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index 892a5f7ae81956..50b0122297d525 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -155,8 +155,6 @@ "confirmations.redraft.confirm": "Desaniciar y reeditar", "confirmations.redraft.title": "¿Desaniciar y reeditar la publicación?", "confirmations.unfollow.confirm": "Dexar de siguir", - "confirmations.unfollow.message": "¿De xuru que quies dexar de siguir a {name}?", - "confirmations.unfollow.title": "¿Dexar de siguir al usuariu?", "content_warning.hide": "Esconder la publicación", "content_warning.show": "Amosar de toes toes", "content_warning.show_more": "Amosar más", diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index be7541011e3fb4..720719d84df168 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Paylaşım və cavablar", "account.remove_from_followers": "{name} - izləyicilərdən çıxart", "account.report": "@{name} istifadəçisini şikayət et", - "account.requested": "Təsdiq edilməsi gözlənilir. İzləmə sorğusunu ləğv etmək üçün kliklə", "account.requested_follow": "{name} sizi izləmək sorğusu göndərib", "account.requests_to_follow_you": "Sizi izləmək istəyir", "account.share": "@{name} profilini paylaş", @@ -249,8 +248,6 @@ "confirmations.revoke_quote.message": "Bu əməliyyatın geri dönüşü yoxdur.", "confirmations.revoke_quote.title": "Göndəriş silinsin?", "confirmations.unfollow.confirm": "İzləmədən çıxar", - "confirmations.unfollow.message": "{name} izləmədən çıxmaq istədiyinizə əminsiniz?", - "confirmations.unfollow.title": "İstifadəçi izləmədən çıxarılsın?", "content_warning.hide": "Paylaşımı gizlət", "content_warning.show": "Yenə də göstər", "content_warning.show_more": "Daha çox göstər", @@ -829,8 +826,6 @@ "status.bookmark": "Əlfəcin", "status.cancel_reblog_private": "Təkrar paylaşımı geri al", "status.cannot_reblog": "Bu göndəriş təkrar paylaşıla bilməz", - "status.context.load_new_replies": "Yeni cavablar mövcuddur", - "status.context.loading": "Daha çox cavab yoxlanılır", "status.continued_thread": "Davam edən mövzu", "status.copy": "Göndəriş keçidini kopyala", "status.delete": "Sil", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index bf4f493831edb9..e51b5a56b1d798 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}", "account.domain_blocking": "Блакіраванне дамена", "account.edit_profile": "Рэдагаваць профіль", + "account.edit_profile_short": "Рэдагаваць", "account.enable_notifications": "Апавяшчаць мяне пра допісы @{name}", "account.endorse": "Паказваць у профілі", "account.familiar_followers_many": "Мае сярод падпісчыкаў {name1}, {name2}, і {othersCount, plural, one {яшчэ # чалавека, знаёмага вам} few {яшчэ # чалавекі, знаёмыя вам} many {яшчэ # чалавек, знаёмых вам} other {яшчэ # чалавекі, знаёмыя вам}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Няма допісаў", "account.follow": "Падпісацца", "account.follow_back": "Падпісацца ў адказ", + "account.follow_back_short": "Падпісацца ў адказ", + "account.follow_request": "Даслаць запыт на падпіску", + "account.follow_request_cancel": "Скасаваць запыт", + "account.follow_request_cancel_short": "Скасаваць", + "account.follow_request_short": "Запыт", "account.followers": "Падпісчыкі", "account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.", "account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Допісы і адказы", "account.remove_from_followers": "Выдаліць {name} з падпісчыкаў", "account.report": "Паскардзіцца на @{name}", - "account.requested": "Чакаецца ўхваленне. Націсніце, каб скасаваць запыт на падпіску", "account.requested_follow": "{name} адправіў(-ла) запыт на падпіску", "account.requests_to_follow_you": "Хоча падпісацца на вас", "account.share": "Абагуліць профіль @{name}", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Гэтае дзеянне немагчыма адмяніць.", "confirmations.revoke_quote.title": "Выдаліць допіс?", "confirmations.unfollow.confirm": "Адпісацца", - "confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?", - "confirmations.unfollow.title": "Адпісацца ад карыстальніка?", "content_warning.hide": "Схаваць допіс", "content_warning.show": "Усё адно паказаць", "content_warning.show_more": "Паказаць усё роўна", @@ -864,8 +867,14 @@ "status.cancel_reblog_private": "Прыбраць", "status.cannot_quote": "Вы не маеце дазвол цытаваць гэты допіс", "status.cannot_reblog": "Гэты допіс нельга пашырыць", - "status.context.load_new_replies": "Даступныя новыя адказы", - "status.context.loading": "Правяраюцца новыя адказы", + "status.contains_quote": "Утрымлівае цытату", + "status.context.loading": "Загружаюцца іншыя адказы", + "status.context.loading_error": "Немагчыма загрузіць новыя адказы", + "status.context.loading_more": "Загружаюцца іншыя адказы", + "status.context.loading_success": "Усе адказы загружаныя", + "status.context.more_replies_found": "Знойдзеныя іншыя адказы", + "status.context.retry": "Паспрабаваць зноў", + "status.context.show": "Паказаць", "status.continued_thread": "Працяг ланцужка", "status.copy": "Скапіраваць спасылку на допіс", "status.delete": "Выдаліць", @@ -903,6 +912,7 @@ "status.quote_error.revoked": "Аўтар выдаліў допіс", "status.quote_followers_only": "Толькі падпісчыкі могуць цытаваць гэты допіс", "status.quote_manual_review": "Аўтар зробіць агляд уручную", + "status.quote_noun": "Цытаваць", "status.quote_policy_change": "Змяніць, хто можа цытаваць", "status.quote_post_author": "Цытаваў допіс @{name}", "status.quote_private": "Прыватныя допісы нельга цытаваць", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 618ce28c61b43f..60256398848115 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Публ. и отговори", "account.remove_from_followers": "Премахване на {name} от последователи", "account.report": "Докладване на @{name}", - "account.requested": "Чака се одобрение. Щракнете за отмяна на заявката за последване", "account.requested_follow": "{name} поиска да ви последва", "account.requests_to_follow_you": "Заявки да ви последват", "account.share": "Споделяне на профила на @{name}", @@ -249,8 +248,6 @@ "confirmations.revoke_quote.message": "Действието е неотменимо.", "confirmations.revoke_quote.title": "Премахвате ли публикацията?", "confirmations.unfollow.confirm": "Без следване", - "confirmations.unfollow.message": "Наистина ли искате вече да не следвате {name}?", - "confirmations.unfollow.title": "Спирате ли да следвате потребителя?", "content_warning.hide": "Скриване на публ.", "content_warning.show": "Нека се покаже", "content_warning.show_more": "Показване на още", @@ -832,8 +829,6 @@ "status.bookmark": "Отмятане", "status.cancel_reblog_private": "Край на подсилването", "status.cannot_reblog": "Публикацията не може да се подсилва", - "status.context.load_new_replies": "Има нови отговори", - "status.context.loading": "Проверка за още отговори", "status.continued_thread": "Продължена нишка", "status.copy": "Копиране на връзката към публикация", "status.delete": "Изтриване", diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json index 4caa8bc3953a74..526248e76c61fa 100644 --- a/app/javascript/mastodon/locales/bn.json +++ b/app/javascript/mastodon/locales/bn.json @@ -53,7 +53,6 @@ "account.posts": "পোষ্টসমূহ", "account.posts_with_replies": "টুট এবং মতামত", "account.report": "@{name} কে রিপোর্ট করুন", - "account.requested": "অনুমতির অপেক্ষা। অনুসরণ করার অনুরোধ বাতিল করতে এখানে ক্লিক করুন", "account.requested_follow": "{name} আপনাকে অনুসরণ করার জন্য অনুরোধ করেছে", "account.share": "@{name} র প্রোফাইল অন্যদের দেখান", "account.show_reblogs": "@{name} র সমর্থনগুলো দেখান", @@ -153,7 +152,6 @@ "confirmations.mute.confirm": "সরিয়ে ফেলুন", "confirmations.redraft.confirm": "মুছে ফেলুন এবং আবার সম্পাদন করুন", "confirmations.unfollow.confirm": "অনুসরণ বন্ধ করো", - "confirmations.unfollow.message": "তুমি কি নিশ্চিত {name} কে আর অনুসরণ করতে চাও না?", "conversation.delete": "কথোপকথন মুছে ফেলুন", "conversation.mark_as_read": "পঠিত হিসেবে চিহ্নিত করুন", "conversation.open": "কথপোকথন দেখান", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index 0f2f63c9ce75e6..c33d64a54e5bd5 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -68,7 +68,6 @@ "account.posts_with_replies": "Embannadurioù ha respontoù", "account.remove_from_followers": "Dilemel {name} eus an heulierien·ezed", "account.report": "Disklêriañ @{name}", - "account.requested": "O c'hortoz an asant. Klikit evit nullañ ar goulenn heuliañ", "account.requested_follow": "Gant {name} eo bet goulennet ho heuliañ", "account.requests_to_follow_you": "Rekedoù d'ho heuliañ", "account.share": "Skignañ profil @{name}", @@ -218,8 +217,6 @@ "confirmations.revoke_quote.confirm": "Dilemel an embannadur", "confirmations.revoke_quote.title": "Dilemel an embannadur?", "confirmations.unfollow.confirm": "Diheuliañ", - "confirmations.unfollow.message": "Ha sur oc'h e fell deoc'h paouez da heuliañ {name} ?", - "confirmations.unfollow.title": "Paouez da heuliañ an implijer·ez?", "content_warning.hide": "Kuzhat an embannadur", "content_warning.show": "Diskwel memes tra", "content_warning.show_more": "Diskouez muioc'h", @@ -653,8 +650,6 @@ "status.bookmark": "Ouzhpennañ d'ar sinedoù", "status.cancel_reblog_private": "Nac'hañ ar skignadenn", "status.cannot_reblog": "Ar c'hannad-se na c'hall ket bezañ skignet", - "status.context.load_new_replies": "Respontoù nevez zo", - "status.context.loading": "O kerc'hat muioc'h a respontoù", "status.copy": "Eilañ liamm ar c'hannad", "status.delete": "Dilemel", "status.delete.success": "Embannadur dilamet", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 5f3d6913ab2b62..4a1acfd695c709 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Deixa de notificar-me els tuts de @{name}", "account.domain_blocking": "Bloquem el domini", "account.edit_profile": "Edita el perfil", + "account.edit_profile_short": "Edita", "account.enable_notifications": "Notifica'm els tuts de @{name}", "account.endorse": "Recomana en el perfil", "account.familiar_followers_many": "Seguit per {name1}, {name2} i {othersCount, plural, one {# altre compte} other {# altres comptes}} que coneixeu", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "No hi ha tuts", "account.follow": "Segueix", "account.follow_back": "Segueix tu també", + "account.follow_back_short": "Segueix tu també", + "account.follow_request": "Sol·licita seguir", + "account.follow_request_cancel": "Cancel·la la petició", + "account.follow_request_cancel_short": "Cancel·la", + "account.follow_request_short": "Petició", "account.followers": "Seguidors", "account.followers.empty": "A aquest usuari encara no el segueix ningú.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidors}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Tuts i respostes", "account.remove_from_followers": "Elimina {name} dels seguidors", "account.report": "Informa sobre @{name}", - "account.requested": "S'espera l'aprovació. Clica per a cancel·lar la petició de seguiment", "account.requested_follow": "{name} ha demanat de seguir-te", "account.requests_to_follow_you": "Peticions de seguir-vos", "account.share": "Comparteix el perfil de @{name}", @@ -239,6 +244,8 @@ "confirmations.missing_alt_text.secondary": "Publica-la igualment", "confirmations.missing_alt_text.title": "Hi voleu afegir text alternatiu?", "confirmations.mute.confirm": "Silencia", + "confirmations.quiet_post_quote_info.dismiss": "No m'ho tornis a recordar", + "confirmations.quiet_post_quote_info.got_it": "Entesos", "confirmations.redraft.confirm": "Esborra i reescriu", "confirmations.redraft.message": "Segur que vols eliminar aquest tut i tornar a escriure'l? Es perdran tots els impulsos i els favorits, i les respostes al tut original quedaran aïllades.", "confirmations.redraft.title": "Esborrar i reescriure la publicació?", @@ -248,9 +255,12 @@ "confirmations.revoke_quote.confirm": "Eliminar la publicació", "confirmations.revoke_quote.message": "Aquesta acció no es pot desfer.", "confirmations.revoke_quote.title": "Eliminar la publicació?", + "confirmations.unblock.confirm": "Desbloca", + "confirmations.unblock.title": "Desblocar {name}?", "confirmations.unfollow.confirm": "Deixa de seguir", - "confirmations.unfollow.message": "Segur que vols deixar de seguir {name}?", - "confirmations.unfollow.title": "Deixar de seguir l'usuari?", + "confirmations.unfollow.title": "Deixar de seguir {name}?", + "confirmations.withdraw_request.confirm": "Retirar la sol·licitud", + "confirmations.withdraw_request.title": "Retirar la sol·licitud de seguir {name}?", "content_warning.hide": "Amaga la publicació", "content_warning.show": "Mostra-la igualment", "content_warning.show_more": "Mostra'n més", @@ -448,10 +458,12 @@ "ignore_notifications_modal.not_following_title": "Voleu ignorar les notificacions de qui no seguiu?", "ignore_notifications_modal.private_mentions_title": "Voleu ignorar les notificacions de mencions privades no sol·licitades?", "info_button.label": "Ajuda", + "interaction_modal.action": "Per a interactuar amb la publicació de {name} cal que inicieu la sessió en el servidor que feu servir.", "interaction_modal.go": "Endavant", "interaction_modal.no_account_yet": "Encara no teniu cap compte?", "interaction_modal.on_another_server": "A un altre servidor", "interaction_modal.on_this_server": "En aquest servidor", + "interaction_modal.title": "Inicieu la sessió per a continuar", "interaction_modal.username_prompt": "P. ex. {example}", "intervals.full.days": "{number, plural, one {# dia} other {# dies}}", "intervals.full.hours": "{number, plural, one {# hora} other {# hores}}", @@ -729,10 +741,18 @@ "privacy.private.short": "Seguidors", "privacy.public.long": "Tothom dins o fora Mastodon", "privacy.public.short": "Públic", + "privacy.quote.anyone": "{visibility}, qualsevol pot citar", + "privacy.quote.disabled": "{visibility}, cites desactivades", + "privacy.quote.limited": "{visibility}, cites limitades", "privacy.unlisted.additional": "Es comporta igual que públic, excepte que la publicació no apareixerà als canals en directe o etiquetes, l'explora o a la cerca de Mastodon, fins i tot si ho heu activat a nivell de compte.", + "privacy.unlisted.long": "Amagat dels resultats de cerca de Mastodon, de les tendències i de les línies temporals", "privacy.unlisted.short": "Públic silenciós", "privacy_policy.last_updated": "Darrera actualització {date}", "privacy_policy.title": "Política de Privacitat", + "quote_error.poll": "Amb les enquestes no es permeten cites.", + "quote_error.quote": "Només es permet una cita alhora.", + "quote_error.unauthorized": "No se us permet de citar aquesta publicació.", + "quote_error.upload": "Amb media adjunts no es permeten cites.", "recommended": "Recomanat", "refresh": "Actualitza", "regeneration_indicator.please_stand_by": "Espereu.", @@ -748,6 +768,9 @@ "relative_time.minutes": "{number}min", "relative_time.seconds": "{number}s", "relative_time.today": "avui", + "remove_quote_hint.button_label": "Entesos", + "remove_quote_hint.message": "Ho podeu fer des de {icon} al menú d'opcions.", + "remove_quote_hint.title": "Voleu eliminar la vostra publicació citada?", "reply_indicator.attachments": "{count, plural, one {# adjunt} other {# adjunts}}", "reply_indicator.cancel": "Cancel·la", "reply_indicator.poll": "Enquesta", @@ -843,9 +866,16 @@ "status.block": "Bloca @{name}", "status.bookmark": "Marca", "status.cancel_reblog_private": "Desfés l'impuls", + "status.cannot_quote": "No se't permet de citar aquesta publicació", "status.cannot_reblog": "No es pot impulsar aquest tut", - "status.context.load_new_replies": "Hi ha respostes noves", - "status.context.loading": "Comprovació de més respostes", + "status.contains_quote": "Conté una cita", + "status.context.loading": "Es carreguen més respostes", + "status.context.loading_error": "No s'han pogut carregar respostes noves", + "status.context.loading_more": "Es carreguen més respostes", + "status.context.loading_success": "S'han carregat totes les respostes", + "status.context.more_replies_found": "S'han trobat més respostes", + "status.context.retry": "Torna-ho a provar", + "status.context.show": "Mostra", "status.continued_thread": "Continuació del fil", "status.copy": "Copia l'enllaç al tut", "status.delete": "Elimina", @@ -875,24 +905,33 @@ "status.quote": "Cita", "status.quote.cancel": "Canceŀlar la citació", "status.quote_error.filtered": "No es mostra a causa d'un dels vostres filtres", + "status.quote_error.limited_account_hint.action": "Mostra-la igualment", + "status.quote_error.limited_account_hint.title": "Aquest perfil l'han amagat els moderadors de {domain}.", "status.quote_error.not_available": "Publicació no disponible", "status.quote_error.pending_approval": "Publicació pendent", + "status.quote_error.pending_approval_popout.body": "A Mastodon pots controlar si algú et pot citar. Aquesta publicació està pendent mentre esperem l'aprovació de l'autor original.", + "status.quote_error.revoked": "Publicació eliminada per l'autor", "status.quote_followers_only": "Només els seguidors poden citar aquesta publicació", "status.quote_manual_review": "L'autor ho revisarà manualment", + "status.quote_noun": "Cita", "status.quote_policy_change": "Canvieu qui us pot citar", "status.quote_post_author": "S'ha citat una publicació de @{name}", "status.quote_private": "No es poden citar les publicacions privades", "status.quotes": "{count, plural, one {cita} other {cites}}", "status.quotes.empty": "Encara no ha citat aquesta publicació ningú. Quan ho faci algú apareixerà aquí.", + "status.quotes.local_other_disclaimer": "No es mostraran les cites rebutjades per l'autor.", + "status.quotes.remote_other_disclaimer": "Només es garanteix que es mostraran aquí les cites de {domain}. No es mostraran les rebutjades per l'autor.", "status.read_more": "Més informació", "status.reblog": "Impulsa", "status.reblog_or_quote": "Impuls or cita", + "status.reblog_private": "Compartiu de nou amb els vostres seguidors", "status.reblogged_by": "impulsat per {name}", "status.reblogs": "{count, plural, one {impuls} other {impulsos}}", "status.reblogs.empty": "Encara no ha impulsat ningú aquest tut. Quan algú ho faci, apareixerà aquí.", "status.redraft": "Esborra i reescriu", "status.remove_bookmark": "Elimina el marcador", "status.remove_favourite": "Elimina dels preferits", + "status.remove_quote": "Elimina", "status.replied_in_thread": "Respost al fil", "status.replied_to": "En resposta a {name}", "status.reply": "Respon", @@ -937,6 +976,7 @@ "upload_button.label": "Afegeix imatges, un vídeo o un fitxer d'àudio", "upload_error.limit": "S'ha superat el límit de càrrega d'arxius.", "upload_error.poll": "No es permet carregar fitxers a les enquestes.", + "upload_error.quote": "No es permet de carregat fitxer amb cites.", "upload_form.drag_and_drop.instructions": "Per a agafar un fitxer multimèdia adjunt, premeu l'espai o la tecla Enter. Mentre l'arrossegueu, utilitzeu les fletxes per a moure l'adjunt en qualsevol direcció. Premeu espai o Enter un altre cop per a deixar-lo anar a la seva nova posició, o premeu la tecla d'escapament per cancel·lar.", "upload_form.drag_and_drop.on_drag_cancel": "S'ha cancel·lat l'arrossegament. S'ha deixat anar l'adjunt multimèdia {item}.", "upload_form.drag_and_drop.on_drag_end": "S'ha deixat anar l'adjunt multimèdia {item}.", @@ -965,7 +1005,9 @@ "visibility_modal.helper.direct_quoting": "No es poden citar mencions privades fetes a Mastondon.", "visibility_modal.helper.private_quoting": "No es poden citar publicacions fetes a Mastodon només per a seguidors.", "visibility_modal.helper.unlisted_quoting": "Quan la gent et citi les seves publicacions estaran amagades de les línies de temps de tendències.", + "visibility_modal.privacy_label": "Visibilitat", "visibility_modal.quote_followers": "Només seguidors", + "visibility_modal.quote_label": "Qui pot citar", "visibility_modal.quote_nobody": "Només jo", "visibility_modal.quote_public": "Qualsevol", "visibility_modal.save": "Desa" diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index 0188b67e2742b4..ea27df849219db 100644 --- a/app/javascript/mastodon/locales/ckb.json +++ b/app/javascript/mastodon/locales/ckb.json @@ -52,7 +52,6 @@ "account.posts": "نووسراوەکان", "account.posts_with_replies": "توتس و وەڵامەکان", "account.report": "گوزارشت @{name}", - "account.requested": "چاوەڕێی ڕەزامەندین. کرتە بکە بۆ هەڵوەشاندنەوەی داواکاری شوێنکەوتن", "account.requested_follow": "{name} داوای کردووە شوێنت بکەوێت", "account.share": "پرۆفایلی @{name} هاوبەش بکە", "account.show_reblogs": "پیشاندانی بەرزکردنەوەکان لە @{name}", @@ -161,7 +160,6 @@ "confirmations.redraft.confirm": "سڕینەوە & دووبارە ڕەشکردنەوە", "confirmations.redraft.message": "دڵنیای دەتەوێت ئەم پۆستە بسڕیتەوە و دووبارە دایبڕێژیتەوە؟ فەڤۆریت و بووستەکان لەدەست دەچن، وەڵامەکانی پۆستە ئەسڵیەکەش هەتیو دەبن.", "confirmations.unfollow.confirm": "بەدوادانەچو", - "confirmations.unfollow.message": "ئایا دڵنیایت لەوەی دەتەوێت پەیڕەوی {name}?", "conversation.delete": "سڕینەوەی گفتوگۆ", "conversation.mark_as_read": "نیشانەکردن وەک خوێندراوە", "conversation.open": "نیشاندان گفتوگۆ", diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json index 7a0306bc31c64c..ce58faf5e008e9 100644 --- a/app/javascript/mastodon/locales/co.json +++ b/app/javascript/mastodon/locales/co.json @@ -24,7 +24,6 @@ "account.posts": "Statuti", "account.posts_with_replies": "Statuti è risposte", "account.report": "Palisà @{name}", - "account.requested": "In attesa d'apprubazione. Cliccate per annullà a dumanda", "account.share": "Sparte u prufile di @{name}", "account.show_reblogs": "Vede spartere da @{name}", "account.unblock": "Sbluccà @{name}", @@ -88,7 +87,6 @@ "confirmations.mute.confirm": "Piattà", "confirmations.redraft.confirm": "Sguassà è riscrive", "confirmations.unfollow.confirm": "Disabbunassi", - "confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?", "conversation.delete": "Sguassà a cunversazione", "conversation.mark_as_read": "Marcà cum'è lettu", "conversation.open": "Vede a cunversazione", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index d1aaffe2f2a348..7b69cdc6542511 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek", "account.domain_blocking": "Blokované domény", "account.edit_profile": "Upravit profil", + "account.edit_profile_short": "Upravit", "account.enable_notifications": "Oznamovat mi příspěvky @{name}", "account.endorse": "Zvýraznit na profilu", "account.familiar_followers_many": "Sleduje je {name1}, {name2} a {othersCount, plural, one {jeden další, které znáte} few {# další, které znáte} many {# dalších, které znáte} other {# dalších, které znáte}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Žádné příspěvky", "account.follow": "Sledovat", "account.follow_back": "Také sledovat", + "account.follow_back_short": "Také sledovat", + "account.follow_request": "Požádat o sledování", + "account.follow_request_cancel": "Zrušit požadavek", + "account.follow_request_cancel_short": "Zrušit", + "account.follow_request_short": "Požádat", "account.followers": "Sledující", "account.followers.empty": "Tohoto uživatele zatím nikdo nesleduje.", "account.followers_counter": "{count, plural, one {{counter} sledující} few {{counter} sledující} many {{counter} sledujících} other {{counter} sledujících}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Příspěvky a odpovědi", "account.remove_from_followers": "Odebrat {name} ze sledujících", "account.report": "Nahlásit @{name}", - "account.requested": "Čeká na schválení. Kliknutím žádost o sledování zrušíte", "account.requested_follow": "{name} tě požádal o sledování", "account.requests_to_follow_you": "Žádosti o sledování", "account.share": "Sdílet profil @{name}", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Tuto akci nelze vrátit zpět.", "confirmations.revoke_quote.title": "Odstranit příspěvek?", "confirmations.unfollow.confirm": "Přestat sledovat", - "confirmations.unfollow.message": "Opravdu chcete {name} přestat sledovat?", - "confirmations.unfollow.title": "Přestat sledovat uživatele?", "content_warning.hide": "Skrýt příspěvek", "content_warning.show": "Přesto zobrazit", "content_warning.show_more": "Zobrazit více", @@ -865,8 +868,13 @@ "status.cannot_quote": "Nemáte oprávnění citovat tento příspěvek", "status.cannot_reblog": "Tento příspěvek nemůže být boostnutý", "status.contains_quote": "Obsahuje citaci", - "status.context.load_new_replies": "K dispozici jsou nové odpovědi", - "status.context.loading": "Hledání dalších odpovědí", + "status.context.loading": "Načítání dalších odpovědí", + "status.context.loading_error": "Nelze načíst nové odpovědi", + "status.context.loading_more": "Načítání dalších odpovědí", + "status.context.loading_success": "Všechny odpovědi načteny", + "status.context.more_replies_found": "Nalezeny další odpovědi", + "status.context.retry": "Zkusit znovu", + "status.context.show": "Zobrazit", "status.continued_thread": "Pokračuje ve vlákně", "status.copy": "Zkopírovat odkaz na příspěvek", "status.delete": "Smazat", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 4c24eaa387703d..843e2ae18798b4 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Stopiwch fy hysbysu pan fydd @{name} yn postio", "account.domain_blocking": "Parthau'n cael eu rhwystro", "account.edit_profile": "Golygu'r proffil", + "account.edit_profile_short": "Golygu", "account.enable_notifications": "Rhowch wybod i fi pan fydd @{name} yn postio", "account.endorse": "Dangos ar fy mhroffil", "account.familiar_followers_many": "Yn cael ei ddilyn gan {name1},{name2}, a {othersCount, plural, one {one other you know} other{# others you know}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Dim postiadau", "account.follow": "Dilyn", "account.follow_back": "Dilyn nôl", + "account.follow_back_short": "Dilyn nôl", + "account.follow_request": "Cais i ddilyn", + "account.follow_request_cancel": "Diddymu cais", + "account.follow_request_cancel_short": "Diddymu", + "account.follow_request_short": "Gofyn", "account.followers": "Dilynwyr", "account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.", "account.followers_counter": "{count, plural, one {{counter} dilynwr} two {{counter} ddilynwr} other {{counter} dilynwr}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Postiadau ac ymatebion", "account.remove_from_followers": "Tynnu {name} o'ch dilynwyr", "account.report": "Adrodd @{name}", - "account.requested": "Aros am gymeradwyaeth. Cliciwch er mwyn canslo cais dilyn", "account.requested_follow": "Mae {name} wedi gwneud cais i'ch dilyn", "account.requests_to_follow_you": "Ceisiadau i'ch dilyn", "account.share": "Rhannu proffil @{name}", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Does dim modd dadwneud y weithred hon.", "confirmations.revoke_quote.title": "Dileu'r postiad?", "confirmations.unfollow.confirm": "Dad-ddilyn", - "confirmations.unfollow.message": "Ydych chi'n siŵr eich bod am ddad-ddilyn {name}?", - "confirmations.unfollow.title": "Dad-ddilyn defnyddiwr?", "content_warning.hide": "Cuddio'r postiad", "content_warning.show": "Dangos beth bynnag", "content_warning.show_more": "Dangos rhagor", @@ -864,8 +867,14 @@ "status.cancel_reblog_private": "Dadhybu", "status.cannot_quote": "Does dim caniatâd i chi ddyfynnu'r postiad hwn", "status.cannot_reblog": "Does dim modd hybu'r postiad hwn", - "status.context.load_new_replies": "Mae atebion newydd ar gael", - "status.context.loading": "Yn chwilio am fwy o atebion", + "status.contains_quote": "Yn cynnwys dyfyniad", + "status.context.loading": "Yn llwytho mwy o atebion", + "status.context.loading_error": "Wedi methu llwytho atebion newydd", + "status.context.loading_more": "Yn llwytho mwy o atebion", + "status.context.loading_success": "Wedi llwytho'r holl atebion", + "status.context.more_replies_found": "Mwy o atebion wedi'u canfod", + "status.context.retry": "Ceisio eto", + "status.context.show": "Dangos", "status.continued_thread": "Edefyn parhaus", "status.copy": "Copïo dolen i'r post", "status.delete": "Dileu", @@ -903,6 +912,7 @@ "status.quote_error.revoked": "Postiad wedi'i ddileu gan yr awdur", "status.quote_followers_only": "Dim ond dilynwyr all ddyfynnu'r postiad hwn", "status.quote_manual_review": "Bydd yr awdur yn ei adolygu ei hyn", + "status.quote_noun": "Dyfynnu", "status.quote_policy_change": "Newid pwy all ddyfynnu", "status.quote_post_author": "Wedi dyfynnu postiad gan @{name}", "status.quote_private": "Does dim modd dyfynnu postiadau preifat", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 9521401bca01fe..5c4dc9ff1d4fe5 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Giv mig ikke længere en notifikation, når @{name} laver indlæg", "account.domain_blocking": "Blokerer domæne", "account.edit_profile": "Redigér profil", + "account.edit_profile_short": "Redigér", "account.enable_notifications": "Giv mig besked, når @{name} laver indlæg", "account.endorse": "Fremhæv på profil", "account.familiar_followers_many": "Følges af {name1}, {name2} og {othersCount, plural, one {# mere, man kender} other {# mere, du kender}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Ingen indlæg", "account.follow": "Følg", "account.follow_back": "Følg tilbage", + "account.follow_back_short": "Følg tilbage", + "account.follow_request": "Anmod om at følge", + "account.follow_request_cancel": "Annuller anmodning", + "account.follow_request_cancel_short": "Annullér", + "account.follow_request_short": "Anmod", "account.followers": "Følgere", "account.followers.empty": "Ingen følger denne bruger endnu.", "account.followers_counter": "{count, plural, one {{counter} følger} other {{counter} følgere}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Indlæg og svar", "account.remove_from_followers": "Fjern {name} fra følgere", "account.report": "Anmeld @{name}", - "account.requested": "Afventer godkendelse. Tryk for at annullere følgeanmodning", "account.requested_follow": "{name} har anmodet om at følge dig", "account.requests_to_follow_you": "Anmodninger om at følge dig", "account.share": "Del @{name}s profil", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Fjern indlæg", "confirmations.revoke_quote.message": "Denne handling kan ikke fortrydes.", "confirmations.revoke_quote.title": "Fjern indlæg?", + "confirmations.unblock.confirm": "Fjern blokering", + "confirmations.unblock.title": "Fjern blokering af {name}?", "confirmations.unfollow.confirm": "Følg ikke længere", - "confirmations.unfollow.message": "Er du sikker på, at du ikke længere vil følge {name}?", - "confirmations.unfollow.title": "Følg ikke længere bruger?", + "confirmations.unfollow.title": "Følg ikke længere {name}?", + "confirmations.withdraw_request.confirm": "Annullér anmodning", + "confirmations.withdraw_request.title": "Annullér anmodning om at følge {name}?", "content_warning.hide": "Skjul indlæg", "content_warning.show": "Vis alligevel", "content_warning.show_more": "Vis flere", @@ -865,8 +873,13 @@ "status.cannot_quote": "Du har ikke tilladelse til at citere dette indlæg", "status.cannot_reblog": "Dette indlæg kan ikke fremhæves", "status.contains_quote": "Indeholder citat", - "status.context.load_new_replies": "Nye svar tilgængelige", - "status.context.loading": "Tjekker for flere svar", + "status.context.loading": "Indlæser flere svar", + "status.context.loading_error": "Kunne ikke indlæse nye svar", + "status.context.loading_more": "Indlæser flere svar", + "status.context.loading_success": "Alle svar indlæst", + "status.context.more_replies_found": "Flere svar fundet", + "status.context.retry": "Prøv igen", + "status.context.show": "Vis", "status.continued_thread": "Fortsat tråd", "status.copy": "Kopiér link til indlæg", "status.delete": "Slet", @@ -910,6 +923,8 @@ "status.quote_private": "Private indlæg kan ikke citeres", "status.quotes": "{count, plural, one {citat} other {citater}}", "status.quotes.empty": "Ingen har citeret dette indlæg endnu. Når det sker, vil det fremgå her.", + "status.quotes.local_other_disclaimer": "Citater afvist af forfatteren vil ikke blive vist.", + "status.quotes.remote_other_disclaimer": "Kun citater fra {domain} vises med garanti her. Citater afvist af forfatteren vil ikke blive vist.", "status.read_more": "Læs mere", "status.reblog": "Fremhæv", "status.reblog_or_quote": "Fremhæv eller citér", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0e18f9a79a3a2e..2ee15010ccf2ab 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Höre auf mich zu benachrichtigen wenn @{name} etwas postet", "account.domain_blocking": "Domain blockiert", "account.edit_profile": "Profil bearbeiten", + "account.edit_profile_short": "Bearbeiten", "account.enable_notifications": "Benachrichtige mich wenn @{name} etwas postet", "account.endorse": "Im Profil vorstellen", "account.familiar_followers_many": "Gefolgt von {name1}, {name2} und {othersCount, plural, one {einem weiteren Profil, das dir bekannt ist} other {# weiteren Profilen, die dir bekannt sind}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Keine Beiträge", "account.follow": "Folgen", "account.follow_back": "Ebenfalls folgen", + "account.follow_back_short": "Ebenfalls folgen", + "account.follow_request": "Anfrage zum Folgen", + "account.follow_request_cancel": "Anfrage zurückziehen", + "account.follow_request_cancel_short": "Abbrechen", + "account.follow_request_short": "Anfragen", "account.followers": "Follower", "account.followers.empty": "Diesem Profil folgt noch niemand.", "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Beiträge und Antworten", "account.remove_from_followers": "{name} als Follower entfernen", "account.report": "@{name} melden", - "account.requested": "Die Genehmigung steht noch aus. Klicke hier, um die Follower-Anfrage zurückzuziehen", "account.requested_follow": "{name} möchte dir folgen", "account.requests_to_follow_you": "Möchte dir folgen", "account.share": "Profil von @{name} teilen", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Beitrag entfernen", "confirmations.revoke_quote.message": "Diese Aktion kann nicht rückgängig gemacht werden.", "confirmations.revoke_quote.title": "Beitrag entfernen?", + "confirmations.unblock.confirm": "Nicht mehr blockieren", + "confirmations.unblock.title": "{name} nicht mehr blockieren?", "confirmations.unfollow.confirm": "Entfolgen", - "confirmations.unfollow.message": "Möchtest du {name} wirklich entfolgen?", - "confirmations.unfollow.title": "Profil entfolgen?", + "confirmations.unfollow.title": "{name} entfolgen?", + "confirmations.withdraw_request.confirm": "Anfrage zurückziehen", + "confirmations.withdraw_request.title": "Anfrage zum Folgen von {name} zurückziehen?", "content_warning.hide": "Beitrag ausblenden", "content_warning.show": "Trotzdem anzeigen", "content_warning.show_more": "Beitrag anzeigen", @@ -864,9 +872,14 @@ "status.cancel_reblog_private": "Beitrag nicht mehr teilen", "status.cannot_quote": "Dir ist es nicht gestattet, diesen Beitrag zu zitieren", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", - "status.contains_quote": "Beinhaltet Zitat", - "status.context.load_new_replies": "Neue Antworten verfügbar", - "status.context.loading": "Weitere Antworten werden abgerufen", + "status.contains_quote": "Enthält Zitat", + "status.context.loading": "Weitere Antworten laden", + "status.context.loading_error": "Weitere Antworten konnten nicht geladen werden", + "status.context.loading_more": "Weitere Antworten laden", + "status.context.loading_success": "Alle weiteren Antworten geladen", + "status.context.more_replies_found": "Weitere Antworten verfügbar", + "status.context.retry": "Erneut versuchen", + "status.context.show": "Anzeigen", "status.continued_thread": "Fortgeführter Thread", "status.copy": "Link zum Beitrag kopieren", "status.delete": "Beitrag löschen", @@ -910,6 +923,8 @@ "status.quote_private": "Private Beiträge können nicht zitiert werden", "status.quotes": "{count, plural, one {Mal zitiert} other {Mal zitiert}}", "status.quotes.empty": "Diesen Beitrag hat bisher noch niemand zitiert. Sobald es jemand tut, wird das Profil hier erscheinen.", + "status.quotes.local_other_disclaimer": "Durch Autor*in abgelehnte Zitate werden nicht angezeigt.", + "status.quotes.remote_other_disclaimer": "Nur Zitate von {domain} werden hier garantiert angezeigt. Durch Autor*in abgelehnte Zitate werden nicht angezeigt.", "status.read_more": "Gesamten Beitrag anschauen", "status.reblog": "Teilen", "status.reblog_or_quote": "Teilen oder zitieren", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index feefaabeea6fa7..6df9c3c1e2ed1b 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Σταμάτα να με ειδοποιείς όταν δημοσιεύει ο @{name}", "account.domain_blocking": "Αποκλείεται ο τομέας", "account.edit_profile": "Επεξεργασία προφίλ", + "account.edit_profile_short": "Επεξεργασία", "account.enable_notifications": "Ειδοποίησέ με όταν δημοσιεύει ο @{name}", "account.endorse": "Προβολή στο προφίλ", "account.familiar_followers_many": "Ακολουθείται από {name1}, {name2}, και {othersCount, plural, one {ένας ακόμα που ξέρεις} other {# ακόμα που ξέρεις}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Καμία ανάρτηση", "account.follow": "Ακολούθησε", "account.follow_back": "Ακολούθησε και εσύ", + "account.follow_back_short": "Ακολούθησε και εσύ", + "account.follow_request": "Αίτημα για ακολούθηση", + "account.follow_request_cancel": "Ακύρωση αιτήματος", + "account.follow_request_cancel_short": "Ακύρωση", + "account.follow_request_short": "Αίτημα", "account.followers": "Ακόλουθοι", "account.followers.empty": "Κανείς δεν ακολουθεί αυτόν τον χρήστη ακόμα.", "account.followers_counter": "{count, plural, one {{counter} ακόλουθος} other {{counter} ακόλουθοι}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Τουτ και απαντήσεις", "account.remove_from_followers": "Κατάργηση {name} από τους ακόλουθους", "account.report": "Κατάγγειλε @{name}", - "account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα παρακολούθησης", "account.requested_follow": "Ο/Η {name} αιτήθηκε να σε ακολουθήσει", "account.requests_to_follow_you": "Αιτήματα για να σε ακολουθήσουν", "account.share": "Κοινοποίηση του προφίλ @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Αφαίρεση ανάρτησης", "confirmations.revoke_quote.message": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmations.revoke_quote.title": "Αφαίρεση ανάρτησης;", + "confirmations.unblock.confirm": "Άρση αποκλεισμού", + "confirmations.unblock.title": "Άρση αποκλεισμού {name};", "confirmations.unfollow.confirm": "Άρση ακολούθησης", - "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};", - "confirmations.unfollow.title": "Άρση ακολούθησης;", + "confirmations.unfollow.title": "Κατάργηση ακολούθησης του/της {name};", + "confirmations.withdraw_request.confirm": "Απόσυρση αιτήματος", + "confirmations.withdraw_request.title": "Απόσυρση αιτήματος για να ακολουθήσετε τον/την {name};", "content_warning.hide": "Απόκρυψη ανάρτησης", "content_warning.show": "Εμφάνιση ούτως ή άλλως", "content_warning.show_more": "Εμφάνιση περισσότερων", @@ -865,8 +873,13 @@ "status.cannot_quote": "Δε σας επιτρέπετε να παραθέσετε αυτή την ανάρτηση", "status.cannot_reblog": "Αυτή η ανάρτηση δεν μπορεί να ενισχυθεί", "status.contains_quote": "Περιέχει παράθεση", - "status.context.load_new_replies": "Νέες απαντήσεις διαθέσιμες", - "status.context.loading": "Γίνεται έλεγχος για περισσότερες απαντήσεις", + "status.context.loading": "Φόρτωση περισσότερων απαντήσεων", + "status.context.loading_error": "Αδυναμία φόρτωσης νέων απαντήσεων", + "status.context.loading_more": "Φόρτωση περισσότερων απαντήσεων", + "status.context.loading_success": "Όλες οι απαντήσεις φορτώθηκαν", + "status.context.more_replies_found": "Βρέθηκαν περισσότερες απαντήσεις", + "status.context.retry": "Επανάληψη", + "status.context.show": "Εμφάνιση", "status.continued_thread": "Συνεχιζόμενο νήματος", "status.copy": "Αντιγραφή συνδέσμου ανάρτησης", "status.delete": "Διαγραφή", @@ -910,6 +923,8 @@ "status.quote_private": "Ιδιωτικές αναρτήσεις δεν μπορούν να παρατεθούν", "status.quotes": "{count, plural, one {# παράθεση} other {# παραθέσεις}}", "status.quotes.empty": "Κανείς δεν έχει παραθέσει αυτή την ανάρτηση ακόμα. Μόλις το κάνει, θα εμφανιστεί εδώ.", + "status.quotes.local_other_disclaimer": "Οι παραθέσεις που απορρίφθηκαν από τον συντάκτη δε θα εμφανιστούν.", + "status.quotes.remote_other_disclaimer": "Μόνο παραθέσεις από το {domain} είναι εγγυημένες ότι θα εμφανιστούν εδώ. Παραθέσεις που απορρίφθηκαν από τον συντάκτη δε θα εμφανιστούν.", "status.read_more": "Διάβασε περισότερα", "status.reblog": "Ενίσχυση", "status.reblog_or_quote": "Ενίσχυση ή παράθεση", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 60f4e0d8f9d82d..908c0509efca62 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Posts and replies", "account.remove_from_followers": "Remove {name} from followers", "account.report": "Report @{name}", - "account.requested": "Awaiting approval. Click to cancel follow request", "account.requested_follow": "{name} has requested to follow you", "account.requests_to_follow_you": "Requests to follow you", "account.share": "Share @{name}'s profile", @@ -246,8 +245,6 @@ "confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?", "confirmations.remove_from_followers.title": "Remove follower?", "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "confirmations.unfollow.title": "Unfollow user?", "content_warning.hide": "Hide post", "content_warning.show": "Show anyway", "content_warning.show_more": "Show more", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 673dffebb5d17d..cef70799095749 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -40,6 +40,7 @@ "account.disable_notifications": "Stop notifying me when @{name} posts", "account.domain_blocking": "Blocking domain", "account.edit_profile": "Edit profile", + "account.edit_profile_short": "Edit", "account.enable_notifications": "Notify me when @{name} posts", "account.endorse": "Feature on profile", "account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}", @@ -52,6 +53,11 @@ "account.featured_tags.last_status_never": "No posts", "account.follow": "Follow", "account.follow_back": "Follow back", + "account.follow_back_short": "Follow back", + "account.follow_request": "Request to follow", + "account.follow_request_cancel": "Cancel request", + "account.follow_request_cancel_short": "Cancel", + "account.follow_request_short": "Request", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", "account.followers.hidden_from_me": "This information is hidden by your setting.", @@ -83,7 +89,6 @@ "account.posts_with_replies": "Posts and replies", "account.remove_from_followers": "Remove {name} from followers", "account.report": "Report @{name}", - "account.requested": "Awaiting approval. Click to cancel follow request", "account.requested_follow": "{name} has requested to follow you", "account.requests_to_follow_you": "Requests to follow you", "account.share": "Share @{name}'s profile", @@ -392,9 +397,12 @@ "confirmations.revoke_quote.confirm": "Remove post", "confirmations.revoke_quote.message": "This action cannot be undone.", "confirmations.revoke_quote.title": "Remove post?", + "confirmations.unblock.confirm": "Unblock", + "confirmations.unblock.title": "Unblock {name}?", "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "confirmations.unfollow.title": "Unfollow user?", + "confirmations.unfollow.title": "Unfollow {name}?", + "confirmations.withdraw_request.confirm": "Withdraw request", + "confirmations.withdraw_request.title": "Withdraw request to follow {name}?", "content_warning.hide": "Hide post", "content_warning.show": "Show anyway", "content_warning.show_more": "Show more", @@ -1062,8 +1070,13 @@ "status.cannot_reblog": "This post cannot be boosted", "status.cannot_reference": "This server cannot receive link", "status.contains_quote": "Contains quote", - "status.context.load_new_replies": "New replies available", - "status.context.loading": "Checking for more replies", + "status.context.loading": "Loading more replies", + "status.context.loading_error": "Couldn't load new replies", + "status.context.loading_more": "Loading more replies", + "status.context.loading_success": "All replies loaded", + "status.context.more_replies_found": "More replies found", + "status.context.retry": "Retry", + "status.context.show": "Show", "status.continued_thread": "Continued thread", "status.copy": "Copy link to post", "status.delete": "Delete", @@ -1120,6 +1133,8 @@ "status.quote_private": "Private posts cannot be quoted", "status.quotes": "{count, plural, one {quote} other {quotes}}", "status.quotes.empty": "No one has quoted this post yet. When someone does, it will show up here.", + "status.quotes.local_other_disclaimer": "Quotes rejected by the author will not be shown.", + "status.quotes.remote_other_disclaimer": "Only quotes from {domain} are guaranteed to be shown here. Quotes rejected by the author will not be shown.", "status.read_more": "Read more", "status.reblog": "Boost", "status.reblog_or_quote": "Boost or quote", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 34967d20b09b5a..6ce220a5384a9d 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -69,7 +69,6 @@ "account.posts_with_replies": "Afiŝoj kaj respondoj", "account.remove_from_followers": "Forigi {name}-n de sekvantoj", "account.report": "Raporti @{name}", - "account.requested": "Atendo de aprobo. Klaku por nuligi la peton por sekvado", "account.requested_follow": "{name} petis sekvi vin", "account.requests_to_follow_you": "Petoj sekvi vin", "account.share": "Diskonigi la profilon de @{name}", @@ -249,8 +248,6 @@ "confirmations.revoke_quote.message": "Ĉi tiu ago ne povas esti malfarita.", "confirmations.revoke_quote.title": "Ĉu forigi afiŝon?", "confirmations.unfollow.confirm": "Ne plu sekvi", - "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?", - "confirmations.unfollow.title": "Ĉu ĉesi sekvi uzanton?", "content_warning.hide": "Kaŝi afiŝon", "content_warning.show": "Montri ĉiukaze", "content_warning.show_more": "Montri pli", @@ -849,8 +846,6 @@ "status.cancel_reblog_private": "Ne plu diskonigi", "status.cannot_quote": "Vi ne rajtas citi ĉi tiun afiŝon", "status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi", - "status.context.load_new_replies": "Disponeblaj novaj respondoj", - "status.context.loading": "Serĉante pliajn respondojn", "status.continued_thread": "Daŭrigis fadenon", "status.copy": "Kopii la ligilon al la afiŝo", "status.delete": "Forigi", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 3f582452d51bda..485ac9036ad6ad 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Dejar de notificarme cuando @{name} envíe mensajes", "account.domain_blocking": "Dominio bloqueado", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificarme cuando @{name} envíe mensajes", "account.endorse": "Destacar en el perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural, one {# cuenta más que conocés} other {# cuentas más que conocés}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sin mensajes", "account.follow": "Seguir", "account.follow_back": "Seguir", + "account.follow_back_short": "Seguir", + "account.follow_request": "Solicitud para seguir", + "account.follow_request_cancel": "Cancelar solicitud", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Todavía nadie sigue a este usuario.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Mnsjs y resp. públicas", "account.remove_from_followers": "Quitar a {name} de tus seguidores", "account.report": "Denunciar a @{name}", - "account.requested": "Esperando aprobación. Hacé clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} solicitó seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir el perfil de @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Eliminar mensaje", "confirmations.revoke_quote.message": "Esta acción no se puede deshacer.", "confirmations.revoke_quote.title": "¿Eliminar mensaje?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "¿Desbloquear a {name}?", "confirmations.unfollow.confirm": "Dejar de seguir", - "confirmations.unfollow.message": "¿Estás seguro que querés dejar de seguir a {name}?", - "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", + "confirmations.unfollow.title": "¿Dejar de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitud", + "confirmations.withdraw_request.title": "¿Retirar solicitud de seguimiento a {name}?", "content_warning.hide": "Ocultar mensaje", "content_warning.show": "Mostrar de todos modos", "content_warning.show_more": "Mostrar más", @@ -865,8 +873,13 @@ "status.cannot_quote": "No te es permitido citar este mensaje", "status.cannot_reblog": "No se puede adherir a este mensaje", "status.contains_quote": "Contiene cita", - "status.context.load_new_replies": "Hay nuevas respuestas", - "status.context.loading": "Buscando más respuestas", + "status.context.loading": "Cargando más respuestas", + "status.context.loading_error": "No se pudieron cargar nuevas respuestas", + "status.context.loading_more": "Cargando más respuestas", + "status.context.loading_success": "Se cargaron todas las respuestas", + "status.context.more_replies_found": "Se encontraron más respuestas", + "status.context.retry": "Reintentar", + "status.context.show": "Mostrar", "status.continued_thread": "Continuación de hilo", "status.copy": "Copiar enlace al mensaje", "status.delete": "Eliminar", @@ -910,6 +923,8 @@ "status.quote_private": "No se pueden citar los mensajes privados", "status.quotes": "{count, plural, one {# voto} other {# votos}}", "status.quotes.empty": "Todavía nadie citó este mensaje. Cuando alguien lo haga, se mostrará acá.", + "status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no se mostrarán.", + "status.quotes.remote_other_disclaimer": "Solo se muestran las citas de {domain}. Las citas rechazadas por el autor no se mostrarán.", "status.read_more": "Leé más", "status.reblog": "Adherir", "status.reblog_or_quote": "Adherir o citar", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 6b48c21d36127b..3875b0187d02d8 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo", "account.domain_blocking": "Bloqueando dominio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en mi perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {# más que conoces}other {# más que conoces}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sin publicaciones", "account.follow": "Seguir", "account.follow_back": "Seguir también", + "account.follow_back_short": "Seguir también", + "account.follow_request": "Solicitud de seguimiento", + "account.follow_request_cancel": "Cancelar solicitud", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Nadie sigue a este usuario todavía.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicaciones y respuestas", "account.remove_from_followers": "Eliminar {name} de tus seguidores", "account.report": "Denunciar a @{name}", - "account.requested": "Esperando aprobación. Haz clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} ha solicitado seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir el perfil de @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Eliminar publicación", "confirmations.revoke_quote.message": "Esta acción no se puede deshacer.", "confirmations.revoke_quote.title": "¿Deseas eliminar la publicación?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "¿Desbloquear a {name}?", "confirmations.unfollow.confirm": "Dejar de seguir", - "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?", - "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", + "confirmations.unfollow.title": "¿Dejar de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitud", + "confirmations.withdraw_request.title": "¿Retirar solicitud de seguimiento a {name}?", "content_warning.hide": "Ocultar publicación", "content_warning.show": "Mostrar de todos modos", "content_warning.show_more": "Mostrar más", @@ -865,8 +873,13 @@ "status.cannot_quote": "No está permitido citar esta publicación", "status.cannot_reblog": "Esta publicación no puede ser impulsada", "status.contains_quote": "Contiene cita", - "status.context.load_new_replies": "Nuevas respuestas disponibles", - "status.context.loading": "Comprobando si hay más respuestas", + "status.context.loading": "Cargando más respuestas", + "status.context.loading_error": "No se pudieron cargar nuevas respuestas", + "status.context.loading_more": "Cargando más respuestas", + "status.context.loading_success": "Todas las respuestas cargadas", + "status.context.more_replies_found": "Se han encontrado más respuestas", + "status.context.retry": "Reintentar", + "status.context.show": "Mostrar", "status.continued_thread": "Hilo continuado", "status.copy": "Copiar enlace al estado", "status.delete": "Borrar", @@ -910,6 +923,8 @@ "status.quote_private": "Las publicaciones privadas no pueden citarse", "status.quotes": "{count, plural,one {cita} other {citas}}", "status.quotes.empty": "Nadie ha citado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no se mostrarán.", + "status.quotes.remote_other_disclaimer": "Solo se muestran las citas de {domain}. Las citas rechazadas por el autor no se mostrarán.", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_or_quote": "Impulsar o citar", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index f84cb4151145fb..3123f55b85fe13 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo", "account.domain_blocking": "Bloqueando dominio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en el perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {# más que conoces}other {# más que conoces}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sin publicaciones", "account.follow": "Seguir", "account.follow_back": "Seguir también", + "account.follow_back_short": "Seguir también", + "account.follow_request": "Solicitud de seguimiento", + "account.follow_request_cancel": "Cancelar solicitud", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidores", "account.followers.empty": "Todavía nadie sigue a este usuario.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicaciones y respuestas", "account.remove_from_followers": "Eliminar {name} de tus seguidores", "account.report": "Reportar a @{name}", - "account.requested": "Esperando aprobación. Haz clic para cancelar la solicitud de seguimiento", "account.requested_follow": "{name} ha solicitado seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir el perfil de @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Eliminar publicación", "confirmations.revoke_quote.message": "Esta acción no tiene vuelta atrás.", "confirmations.revoke_quote.title": "¿Eliminar la publicación?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "¿Desbloquear a {name}?", "confirmations.unfollow.confirm": "Dejar de seguir", - "confirmations.unfollow.message": "¿Seguro que quieres dejar de seguir a {name}?", - "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", + "confirmations.unfollow.title": "¿Dejar de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitud", + "confirmations.withdraw_request.title": "¿Retirar solicitud de seguimiento a {name}?", "content_warning.hide": "Ocultar publicación", "content_warning.show": "Mostrar de todos modos", "content_warning.show_more": "Mostrar más", @@ -865,8 +873,13 @@ "status.cannot_quote": "No tienes permiso para citar esta publicación", "status.cannot_reblog": "Esta publicación no se puede impulsar", "status.contains_quote": "Contiene cita", - "status.context.load_new_replies": "Hay nuevas respuestas", - "status.context.loading": "Buscando más respuestas", + "status.context.loading": "Cargando más respuestas", + "status.context.loading_error": "No se pudieron cargar nuevas respuestas", + "status.context.loading_more": "Cargando más respuestas", + "status.context.loading_success": "Se cargaron todas las respuestas", + "status.context.more_replies_found": "Se encontraron más respuestas", + "status.context.retry": "Reintentar", + "status.context.show": "Mostrar", "status.continued_thread": "Continuó el hilo", "status.copy": "Copiar enlace a la publicación", "status.delete": "Borrar", @@ -910,6 +923,8 @@ "status.quote_private": "Las publicaciones privadas no pueden ser citadas", "status.quotes": "{count, plural,one {cita} other {citas}}", "status.quotes.empty": "Nadie ha citado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", + "status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no se mostrarán.", + "status.quotes.remote_other_disclaimer": "Solo se muestran las citas de {domain}. Las citas rechazadas por el autor no se mostrarán.", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_or_quote": "Impulsar o citar", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 791736f1310148..9175a6cc29ed45 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Peata teavitused @{name} postitustest", "account.domain_blocking": "Blokeeritud domeen", "account.edit_profile": "Muuda profiili", + "account.edit_profile_short": "Muuda", "account.enable_notifications": "Teavita mind @{name} postitustest", "account.endorse": "Too profiilil esile", "account.familiar_followers_many": "Jälgijateks {name1}, {name2} ja veel {othersCount, plural, one {üks kasutaja, keda tead} other {# kasutajat, keda tead}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Postitusi pole", "account.follow": "Jälgi", "account.follow_back": "Jälgi vastu", + "account.follow_back_short": "Jälgi vastu", + "account.follow_request": "Jälgimispäring", + "account.follow_request_cancel": "Tühista päring", + "account.follow_request_cancel_short": "Katkesta", + "account.follow_request_short": "Koosta päring", "account.followers": "Jälgijad", "account.followers.empty": "Keegi ei jälgi veel seda kasutajat.", "account.followers_counter": "{count, plural, one {{counter} jälgija} other {{counter} jälgijat}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Postitused ja vastused", "account.remove_from_followers": "Eemalda {name} jälgijate seast", "account.report": "Raporteeri @{name}", - "account.requested": "Ootab kinnitust. Klõpsa jälgimise soovi tühistamiseks", "account.requested_follow": "{name} on taodelnud sinu jälgimist", "account.requests_to_follow_you": "soovib sind jälgida", "account.share": "Jaga @{name} profiili", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Seda tegevust ei saa tagasi pöörata.", "confirmations.revoke_quote.title": "Kas eemaldame postituse?", "confirmations.unfollow.confirm": "Ära jälgi", - "confirmations.unfollow.message": "Oled kindel, et ei soovi rohkem jälgida kasutajat {name}?", - "confirmations.unfollow.title": "Ei jälgi enam kasutajat?", "content_warning.hide": "Peida postitus", "content_warning.show": "Näita ikkagi", "content_warning.show_more": "Näita rohkem", @@ -864,8 +867,14 @@ "status.cancel_reblog_private": "Lõpeta jagamine", "status.cannot_quote": "Sul pole õigust seda postitust tsiteerida", "status.cannot_reblog": "Seda postitust ei saa jagada", - "status.context.load_new_replies": "Leidub uusi vastuseid", - "status.context.loading": "Kontrollin täiendavate vastuste olemasolu", + "status.contains_quote": "Sisaldab tsitaati", + "status.context.loading": "Laadin veel vastuseid", + "status.context.loading_error": "Uute vastuste laadimine ei õnnestunud", + "status.context.loading_more": "Laadin veel vastuseid", + "status.context.loading_success": "Kõik vastused on laaditud", + "status.context.more_replies_found": "Leidub veel vastuseid", + "status.context.retry": "Proovi uuesti", + "status.context.show": "Näita", "status.continued_thread": "Jätkatud lõim", "status.copy": "Kopeeri postituse link", "status.delete": "Kustuta", @@ -903,6 +912,7 @@ "status.quote_error.revoked": "Autor on postituse eemaldanud", "status.quote_followers_only": "Vaid jälgijad saavad seda postitust tsiteerida", "status.quote_manual_review": "Autor vaatab selle üle", + "status.quote_noun": "Tsitaat", "status.quote_policy_change": "Muuda neid, kes võivad tsiteerida", "status.quote_post_author": "Tsiteeris kasutaja @{name} postitust", "status.quote_private": "Otsepostituste tsiteerimine pole võimalik", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 579701a525ec10..945ae1cd278a68 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Bidalketak eta erantzunak", "account.remove_from_followers": "Kendu {name} zure jarraitzaileengandik", "account.report": "Salatu @{name}", - "account.requested": "Onarpenaren zain. Egin klik jarraipen-eskaera ezeztatzeko", "account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du", "account.requests_to_follow_you": "Zu jarraitzeko eskaera egin du", "account.share": "Partekatu @{name} erabiltzailearen profila", @@ -241,8 +240,6 @@ "confirmations.remove_from_followers.message": "{name}-k zu jarraitzeari utziko dio. Seguru zaude jarraitu nahi duzula?", "confirmations.remove_from_followers.title": "Jarraitzailea kendu nahi duzu?", "confirmations.unfollow.confirm": "Utzi jarraitzeari", - "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?", - "confirmations.unfollow.title": "Erabiltzailea jarraitzeari utzi?", "content_warning.hide": "Tuta ezkutatu", "content_warning.show": "Erakutsi hala ere", "content_warning.show_more": "Erakutsi gehiago", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index b2b0a6c2de372c..48d686a4d6bf30 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "فرسته‌ها و پاسخ‌ها", "account.remove_from_followers": "برداشتن {name} از پی‌گیران", "account.report": "گزارش ‎@{name}", - "account.requested": "منتظر پذیرش است. برای لغو درخواست پی‌گیری کلیک کنید", "account.requested_follow": "{name} درخواست پی‌گیریتان را داد", "account.requests_to_follow_you": "درخواست پی‌گیریتان را دارد", "account.share": "هم‌رسانی نمایهٔ ‎@{name}", @@ -249,8 +248,6 @@ "confirmations.revoke_quote.message": "این اقدام قابل بازگشت نیست.", "confirmations.revoke_quote.title": "آیا فرسته را حذف کنم؟", "confirmations.unfollow.confirm": "پی‌نگرفتن", - "confirmations.unfollow.message": "مطمئنید که می‌خواهید به پی‌گیری از {name} پایان دهید؟", - "confirmations.unfollow.title": "ناپی‌گیری کاربر؟", "content_warning.hide": "نهفتن فرسته", "content_warning.show": "در هر صورت نشان داده شود", "content_warning.show_more": "نمایش بیش‌تر", @@ -842,8 +839,6 @@ "status.bookmark": "نشانک", "status.cancel_reblog_private": "ناتقویت", "status.cannot_reblog": "این فرسته قابل تقویت نیست", - "status.context.load_new_replies": "پاسخ‌های جدیدی موجودند", - "status.context.loading": "بررسی کردن برای پاسخ‌های بیش‌تر", "status.continued_thread": "رشتهٔ دنباله دار", "status.copy": "رونوشت از پیوند فرسته", "status.delete": "حذف", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 8bf69ab31d873b..27b5c209e6de17 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Lopeta ilmoittamasta minulle, kun @{name} julkaisee", "account.domain_blocking": "Verkkotunnus estetty", "account.edit_profile": "Muokkaa profiilia", + "account.edit_profile_short": "Muokkaa", "account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee", "account.endorse": "Suosittele profiilissa", "account.familiar_followers_many": "Seuraajina {name1}, {name2} ja {othersCount, plural, one {1 muu, jonka tunnet} other {# muuta, jotka tunnet}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Ei julkaisuja", "account.follow": "Seuraa", "account.follow_back": "Seuraa takaisin", + "account.follow_back_short": "Seuraa takaisin", + "account.follow_request": "Pyydä lupaa seurata", + "account.follow_request_cancel": "Peruuta pyyntö", + "account.follow_request_cancel_short": "Peruuta", + "account.follow_request_short": "Pyyntö", "account.followers": "Seuraajat", "account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.", "account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Julkaisut ja vastaukset", "account.remove_from_followers": "Poista {name} seuraajista", "account.report": "Raportoi @{name}", - "account.requested": "Odottaa hyväksyntää. Peruuta seurantapyyntö napsauttamalla", "account.requested_follow": "{name} on pyytänyt lupaa seurata sinua", "account.requests_to_follow_you": "Pyynnöt seurata sinua", "account.share": "Jaa käyttäjän @{name} profiili", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Poista julkaisu", "confirmations.revoke_quote.message": "Tätä toimea ei voi peruuttaa.", "confirmations.revoke_quote.title": "Poistetaanko julkaisu?", + "confirmations.unblock.confirm": "Kumoa esto", + "confirmations.unblock.title": "Kumotaanko käyttäjän {name} esto?", "confirmations.unfollow.confirm": "Lopeta seuraaminen", - "confirmations.unfollow.message": "Haluatko varmasti lopettaa profiilin {name} seuraamisen?", - "confirmations.unfollow.title": "Lopetetaanko käyttäjän seuraaminen?", + "confirmations.unfollow.title": "Lopetetaanko käyttäjän {name} seuraaminen?", + "confirmations.withdraw_request.confirm": "Peruuta pyyntö", + "confirmations.withdraw_request.title": "Peruutetaanko pyyntö seurata käyttäjää {name}?", "content_warning.hide": "Piilota julkaisu", "content_warning.show": "Näytä kuitenkin", "content_warning.show_more": "Näytä lisää", @@ -865,8 +873,13 @@ "status.cannot_quote": "Sinulla ei ole oikeutta lainata tätä julkaisua", "status.cannot_reblog": "Tätä julkaisua ei voi tehostaa", "status.contains_quote": "Sisältää lainauksen", - "status.context.load_new_replies": "Uusia vastauksia saatavilla", - "status.context.loading": "Tarkistetaan lisävastauksia", + "status.context.loading": "Ladataan lisää vastauksia", + "status.context.loading_error": "Ei voitu ladata lisää vastauksia", + "status.context.loading_more": "Ladataan lisää vastauksia", + "status.context.loading_success": "Kaikki vastaukset ladattu", + "status.context.more_replies_found": "Löytyi lisää vastauksia", + "status.context.retry": "Yritä uudelleen", + "status.context.show": "Näytä", "status.continued_thread": "Jatkoi ketjua", "status.copy": "Kopioi linkki julkaisuun", "status.delete": "Poista", @@ -910,6 +923,8 @@ "status.quote_private": "Yksityisiä julkaisuja ei voi lainata", "status.quotes": "{count, plural, one {lainaus} other {lainausta}}", "status.quotes.empty": "Kukaan ei ole vielä lainannut tätä julkaisua. Kun joku tekee niin, se tulee tähän näkyviin.", + "status.quotes.local_other_disclaimer": "Tekijän hylkäämiä lainauksia ei näytetä.", + "status.quotes.remote_other_disclaimer": "Vain palvelimen {domain} lainaukset näkyvät taatusti tässä. Tekijän hylkäämiä lainauksia ei näytetä.", "status.read_more": "Näytä enemmän", "status.reblog": "Tehosta", "status.reblog_or_quote": "Tehosta tai lainaa", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index f8425dd8efd185..37db5a1b1596cb 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -60,7 +60,6 @@ "account.open_original_page": "Buksan ang pinagmulang pahina", "account.posts": "Mga post", "account.report": "I-ulat si/ang @{name}", - "account.requested": "Naghihintay ng pag-apruba. I-click upang ikansela ang hiling sa pagsunod", "account.requested_follow": "Hinihiling ni {name} na sundan ka", "account.share": "Ibahagi ang profile ni @{name}", "account.show_reblogs": "Ipakita ang mga pagpapalakas mula sa/kay {name}", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index a8db9baeca0078..0863717ab39822 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -19,7 +19,7 @@ "account.badges.group": "Bólkur", "account.block": "Banna @{name}", "account.block_domain": "Banna økisnavnið {domain}", - "account.block_short": "Blokera", + "account.block_short": "Banna", "account.blocked": "Bannað/ur", "account.blocking": "Banni", "account.cancel_follow_request": "Strika fylgjaraumbøn", @@ -28,6 +28,7 @@ "account.disable_notifications": "Ikki boða mær frá, tá @{name} skrivar", "account.domain_blocking": "Banni økisnavn", "account.edit_profile": "Broyt vanga", + "account.edit_profile_short": "Rætta", "account.enable_notifications": "Boða mær frá, tá @{name} skrivar", "account.endorse": "Víst á vangamyndini", "account.familiar_followers_many": "{name1}, {name2} og {othersCount, plural, one {ein annar/onnur tú kennir} other {# onnur tú kennir}} fylgja", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Einki uppslag", "account.follow": "Fylg", "account.follow_back": "Fylg aftur", + "account.follow_back_short": "Fylg aftur", + "account.follow_request": "Umbønir um at fylgja tær", + "account.follow_request_cancel": "Strika víðaribeining", + "account.follow_request_cancel_short": "Ógilda", + "account.follow_request_short": "Áheitan", "account.followers": "Fylgjarar", "account.followers.empty": "Ongar fylgjarar enn.", "account.followers_counter": "{count, plural, one {{counter} fylgjari} other {{counter} fylgjarar}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Uppsløg og svar", "account.remove_from_followers": "Strika {name} av fylgjaralista", "account.report": "Melda @{name}", - "account.requested": "Bíðar eftir góðkenning. Trýst fyri at angra umbønina", "account.requested_follow": "{name} hevur biðið um at fylgja tær", "account.requests_to_follow_you": "Umbønir um at fylgja tær", "account.share": "Deil vanga @{name}'s", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Strika post", "confirmations.revoke_quote.message": "Hendan atgerðin kann ikki angrast.", "confirmations.revoke_quote.title": "Strika post?", + "confirmations.unblock.confirm": "Banna ikki", + "confirmations.unblock.title": "Banna ikki {name}?", "confirmations.unfollow.confirm": "Fylg ikki", - "confirmations.unfollow.message": "Ert tú vís/ur í, at tú vil steðga við at fylgja {name}?", - "confirmations.unfollow.title": "Gevst at fylgja brúkara?", + "confirmations.unfollow.title": "Gevst at fylgja {name}?", + "confirmations.withdraw_request.confirm": "Tak umbønina aftur", + "confirmations.withdraw_request.title": "Tak umbønina um at fylgja {name} aftur?", "content_warning.hide": "Fjal post", "content_warning.show": "Vís kortini", "content_warning.show_more": "Vís meiri", @@ -865,8 +873,13 @@ "status.cannot_quote": "Tú hevur ikki loyvi at sitera hendan postin", "status.cannot_reblog": "Tað ber ikki til at stimbra hendan postin", "status.contains_quote": "Inniheldur sitat", - "status.context.load_new_replies": "Nýggj svar tøk", - "status.context.loading": "Kanni um tað eru fleiri svar", + "status.context.loading": "Tekur fleiri svar niður", + "status.context.loading_error": "Fekk ikki tikið nýggj svar niður", + "status.context.loading_more": "Tekur fleiri svar niður", + "status.context.loading_success": "Øll svar tikin niður", + "status.context.more_replies_found": "Fleiri svar funnin", + "status.context.retry": "Royn aftur", + "status.context.show": "Vís", "status.continued_thread": "Framhaldandi tráður", "status.copy": "Kopiera leinki til postin", "status.delete": "Strika", @@ -910,6 +923,8 @@ "status.quote_private": "Privatir postar kunnu ikki siterast", "status.quotes": "{count, plural, one {sitat} other {sitat}}", "status.quotes.empty": "Eingin hevur siterað hendan postin enn. Tá onkur siterar postin, verður hann sjónligur her.", + "status.quotes.local_other_disclaimer": "Sitatir, sum eru avvíst av høvundanum, verða ikki víst.", + "status.quotes.remote_other_disclaimer": "Einans sitatir frá {domain} vera garanterað víst her. Sitatir, sum eru avvíst av høvundanum, verða ikki víst.", "status.read_more": "Les meira", "status.reblog": "Stimbra", "status.reblog_or_quote": "Stimbra ella sitera", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 2bc6195c55cec2..63e8fdf2ab7365 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Publications et réponses", "account.remove_from_followers": "Retirer {name} des suiveurs", "account.report": "Signaler @{name}", - "account.requested": "En attente d’approbation. Cliquez pour annuler la demande", "account.requested_follow": "{name} a demandé à vous suivre", "account.requests_to_follow_you": "Demande a vous suivre", "account.share": "Partager le profil de @{name}", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Cette action ne peut pas être annulée.", "confirmations.revoke_quote.title": "Retirer la publication ?", "confirmations.unfollow.confirm": "Ne plus suivre", - "confirmations.unfollow.message": "Voulez-vous vraiment arrêter de suivre {name}?", - "confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?", "content_warning.hide": "Masquer le message", "content_warning.show": "Montrer quand même", "content_warning.show_more": "Montrer plus", @@ -864,8 +861,6 @@ "status.cancel_reblog_private": "Débooster", "status.cannot_quote": "Vous n'êtes pas autorisé à citer ce message", "status.cannot_reblog": "Cette publication ne peut pas être boostée", - "status.context.load_new_replies": "Nouvelles réponses disponibles", - "status.context.loading": "Vérification de plus de réponses", "status.continued_thread": "Suite du fil", "status.copy": "Copier un lien vers cette publication", "status.delete": "Supprimer", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index e579dbe09b904f..e16e177ca49403 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Messages et réponses", "account.remove_from_followers": "Retirer {name} des suiveurs", "account.report": "Signaler @{name}", - "account.requested": "En attente d’approbation. Cliquez pour annuler la demande", "account.requested_follow": "{name} a demandé à vous suivre", "account.requests_to_follow_you": "Demande a vous suivre", "account.share": "Partager le profil de @{name}", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Cette action ne peut pas être annulée.", "confirmations.revoke_quote.title": "Retirer la publication ?", "confirmations.unfollow.confirm": "Ne plus suivre", - "confirmations.unfollow.message": "Voulez-vous vraiment vous désabonner de {name} ?", - "confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?", "content_warning.hide": "Masquer le message", "content_warning.show": "Montrer quand même", "content_warning.show_more": "Montrer plus", @@ -864,8 +861,6 @@ "status.cancel_reblog_private": "Annuler le partage", "status.cannot_quote": "Vous n'êtes pas autorisé à citer ce message", "status.cannot_reblog": "Ce message ne peut pas être partagé", - "status.context.load_new_replies": "Nouvelles réponses disponibles", - "status.context.loading": "Vérification de plus de réponses", "status.continued_thread": "Suite du fil", "status.copy": "Copier le lien vers le message", "status.delete": "Supprimer", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 31b5196f3edc0b..032cdd870fb4f4 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Berjochten en reaksjes", "account.remove_from_followers": "{name} as folger fuortsmite", "account.report": "@{name} rapportearje", - "account.requested": "Wacht op goedkarring. Klik om it folchfersyk te annulearjen", "account.requested_follow": "{name} hat dy in folchfersyk stjoerd", "account.requests_to_follow_you": "Fersiken om jo te folgjen", "account.share": "Profyl fan @{name} diele", @@ -246,8 +245,6 @@ "confirmations.remove_from_followers.message": "{name} sil jo net mear folgje. Binne jo wis dat jo trochgean wolle?", "confirmations.remove_from_followers.title": "Folger fuortsmite?", "confirmations.unfollow.confirm": "Net mear folgje", - "confirmations.unfollow.message": "Binne jo wis dat jo {name} net mear folgje wolle?", - "confirmations.unfollow.title": "Brûker net mear folgje?", "content_warning.hide": "Berjocht ferstopje", "content_warning.show": "Dochs toane", "content_warning.show_more": "Mear toane", @@ -834,8 +831,6 @@ "status.bookmark": "Blêdwizer tafoegje", "status.cancel_reblog_private": "Net langer booste", "status.cannot_reblog": "Dit berjocht kin net boost wurde", - "status.context.load_new_replies": "Nije reaksjes beskikber", - "status.context.loading": "Op nije reaksjes oan it kontrolearjen", "status.continued_thread": "Ferfolgje it petear", "status.copy": "Copy link to status", "status.delete": "Fuortsmite", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index f1641450df1ffb..74021a0c2e3b0c 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Éirigh as ag cuir mé in eol nuair bpostálann @{name}", "account.domain_blocking": "Fearann a bhlocáil", "account.edit_profile": "Cuir an phróifíl in eagar", + "account.edit_profile_short": "Cuir in Eagar", "account.enable_notifications": "Cuir mé in eol nuair bpostálann @{name}", "account.endorse": "Cuir ar an phróifíl mar ghné", "account.familiar_followers_many": "Ina dhiaidh sin ag {name1}, {name2}, agus {othersCount, plural, \n one {duine eile atá aithnid duit} \n two {# duine eile atá aithnid duit} \n few {# dhuine eile atá aithnid duit} \n many {# nduine eile atá aithnid duit} \n other {# duine eile atá aithnid duit}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Gan aon phoist", "account.follow": "Lean", "account.follow_back": "Leanúint ar ais", + "account.follow_back_short": "Lean ar ais", + "account.follow_request": "Iarratas chun leanúint", + "account.follow_request_cancel": "Cealaigh an t-iarratas", + "account.follow_request_cancel_short": "Cealaigh", + "account.follow_request_short": "Iarratas", "account.followers": "Leantóirí", "account.followers.empty": "Ní leanann éinne an t-úsáideoir seo fós.", "account.followers_counter": "{count, plural, one {{counter} leantóir} other {{counter} leantóirí}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Postálacha agus freagraí", "account.remove_from_followers": "Bain {name} de na leantóirí", "account.report": "Tuairiscigh @{name}", - "account.requested": "Ag fanacht le ceadú. Cliceáil chun an iarratas leanúnaí a chealú", "account.requested_follow": "D'iarr {name} ort do chuntas a leanúint", "account.requests_to_follow_you": "Iarratais chun tú a leanúint", "account.share": "Roinn próifíl @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Bain postáil", "confirmations.revoke_quote.message": "Ní féidir an gníomh seo a chealú.", "confirmations.revoke_quote.title": "Bain postáil?", + "confirmations.unblock.confirm": "Díbhlocáil", + "confirmations.unblock.title": "Díbhlocáil {name}?", "confirmations.unfollow.confirm": "Ná lean", - "confirmations.unfollow.message": "An bhfuil tú cinnte gur mhaith leat {name} a dhíleanúint?", - "confirmations.unfollow.title": "Dílean ​​an t-úsáideoir?", + "confirmations.unfollow.title": "Díleanúint {name}?", + "confirmations.withdraw_request.confirm": "Iarratas ar tharraingt siar", + "confirmations.withdraw_request.title": "Iarratas chun {name} a leanúint a tharraingt siar?", "content_warning.hide": "Folaigh postáil", "content_warning.show": "Taispeáin ar aon nós", "content_warning.show_more": "Taispeáin níos mó", @@ -864,8 +872,14 @@ "status.cancel_reblog_private": "Dímhol", "status.cannot_quote": "Ní cheadaítear duit an post seo a lua", "status.cannot_reblog": "Ní féidir an phostáil seo a mholadh", - "status.context.load_new_replies": "Freagraí nua ar fáil", - "status.context.loading": "Ag seiceáil le haghaidh tuilleadh freagraí", + "status.contains_quote": "Tá luachan ann", + "status.context.loading": "Ag lódáil tuilleadh freagraí", + "status.context.loading_error": "Níorbh fhéidir freagraí nua a lódáil", + "status.context.loading_more": "Ag lódáil tuilleadh freagraí", + "status.context.loading_success": "Luchtaithe na freagraí uile", + "status.context.more_replies_found": "Tuilleadh freagraí aimsithe", + "status.context.retry": "Déan iarracht arís", + "status.context.show": "Taispeáin", "status.continued_thread": "Snáithe ar lean", "status.copy": "Cóipeáil an nasc chuig an bpostáil", "status.delete": "Scrios", @@ -903,11 +917,14 @@ "status.quote_error.revoked": "Post bainte ag an údar", "status.quote_followers_only": "Ní féidir ach le leantóirí an post seo a lua", "status.quote_manual_review": "Déanfaidh an t-údar athbhreithniú de láimh", + "status.quote_noun": "Luachan", "status.quote_policy_change": "Athraigh cé a fhéadann luachan a thabhairt", "status.quote_post_author": "Luaigh mé post le @{name}", "status.quote_private": "Ní féidir poist phríobháideacha a lua", "status.quotes": "{count, plural, one {sliocht} few {sliocht} other {sliocht}}", "status.quotes.empty": "Níl an post seo luaite ag aon duine go fóill. Nuair a dhéanann duine é, taispeánfar anseo é.", + "status.quotes.local_other_disclaimer": "Ní thaispeánfar sleachta ar dhiúltaigh an t-údar dóibh.", + "status.quotes.remote_other_disclaimer": "Níl ráthaíocht ann go dtaispeánfar anseo ach sleachta ó {domain}. Ní thaispeánfar sleachta ar dhiúltaigh an t-údar dóibh.", "status.read_more": "Léan a thuilleadh", "status.reblog": "Treisiú", "status.reblog_or_quote": "Borradh nó luachan", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 2b2efe733c8950..b677f8ec50ab18 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Postaichean ’s freagairtean", "account.remove_from_followers": "Thoir {name} air falbh on luchd-leantainn", "account.report": "Dèan gearan mu @{name}", - "account.requested": "A’ feitheamh air aontachadh. Briog airson sgur dhen iarrtas leantainn", "account.requested_follow": "Dh’iarr {name} ’gad leantainn", "account.requests_to_follow_you": "Iarrtasan leantainn", "account.share": "Co-roinn a’ phròifil aig @{name}", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Cha ghabh seo a neo-dhèanamh.", "confirmations.revoke_quote.title": "A bheil thu airson am post a thoirt air falbh?", "confirmations.unfollow.confirm": "Na lean tuilleadh", - "confirmations.unfollow.message": "A bheil thu cinnteach nach eil thu airson {name} a leantainn tuilleadh?", - "confirmations.unfollow.title": "A bheil thu airson sgur de leantainn a chleachdaiche?", "content_warning.hide": "Falaich am post", "content_warning.show": "Seall e co-dhiù", "content_warning.show_more": "Seall barrachd dheth", @@ -864,8 +861,6 @@ "status.cancel_reblog_private": "Na brosnaich tuilleadh", "status.cannot_quote": "Chan fhaod thu am post seo a luaidh", "status.cannot_reblog": "Cha ghabh am post seo brosnachadh", - "status.context.load_new_replies": "Tha freagairt no dhà ùr ri fhaighinn", - "status.context.loading": "A’ toirt sùil airson barrachd fhreagairtean", "status.continued_thread": "Pàirt de shnàithlean", "status.copy": "Dèan lethbhreac dhen cheangal dhan phost", "status.delete": "Sguab às", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 2d37337a0c10f8..a7e1cfd8c30a81 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Deixar de notificarme cando @{name} publica", "account.domain_blocking": "Bloqueo do dominio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Noficarme cando @{name} publique", "account.endorse": "Amosar no perfil", "account.familiar_followers_many": "Seguida por {name1}, {name2}, e {othersCount, plural, one {outra conta que coñeces} other {outras # contas que coñeces}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sen publicacións", "account.follow": "Seguir", "account.follow_back": "Seguir tamén", + "account.follow_back_short": "Seguir tamén", + "account.follow_request": "Solicitar seguir", + "account.follow_request_cancel": "Desbotar a petición", + "account.follow_request_cancel_short": "Desbotar", + "account.follow_request_short": "Solicitar", "account.followers": "Seguidoras", "account.followers.empty": "Aínda ninguén segue esta usuaria.", "account.followers_counter": "{count, plural, one {{counter} seguidora} other {{counter} seguidoras}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicacións e respostas", "account.remove_from_followers": "Retirar a {name} das seguidoras", "account.report": "Informar sobre @{name}", - "account.requested": "Agardando aprobación. Preme para desbotar a solicitude", "account.requested_follow": "{name} solicitou seguirte", "account.requests_to_follow_you": "Solicita seguirte", "account.share": "Compartir o perfil de @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Eliminar publicación", "confirmations.revoke_quote.message": "Esta acción non se pode desfacer.", "confirmations.revoke_quote.title": "Eliminar publicación?", + "confirmations.unblock.confirm": "Desbloquear", + "confirmations.unblock.title": "Desbloquear a {name}?", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "Tes certeza de querer deixar de seguir a {name}?", - "confirmations.unfollow.title": "Deixar de seguir á usuaria?", + "confirmations.unfollow.title": "Deixa de seguir a {name}?", + "confirmations.withdraw_request.confirm": "Retirar solicitude", + "confirmations.withdraw_request.title": "Retirar a petición de seguimento para {name}?", "content_warning.hide": "Agochar publicación", "content_warning.show": "Mostrar igualmente", "content_warning.show_more": "Mostrar máis", @@ -851,7 +859,7 @@ "server_banner.is_one_of_many": "{domain} é un dos moitos servidores Mastodon independentes que podes usar para participar do Fediverso.", "server_banner.server_stats": "Estatísticas do servidor:", "sign_in_banner.create_account": "Crear conta", - "sign_in_banner.follow_anyone": "Sigue a quen queiras no Fediverso e le as publicacións en orde cronolóxica. Sen algoritmos, publicidade nin titulares engañosos.", + "sign_in_banner.follow_anyone": "Sigue a quen queiras no Fediverso e le as publicacións en orde cronolóxica. Sen algoritmos, publicidade nin titulares enganosos.", "sign_in_banner.mastodon_is": "Mastodon é o mellor xeito de estar ao día do que acontece.", "sign_in_banner.sign_in": "Iniciar sesión", "sign_in_banner.sso_redirect": "Acceder ou Crear conta", @@ -865,8 +873,13 @@ "status.cannot_quote": "Non tes permiso para citar esta publicación", "status.cannot_reblog": "Esta publicación non pode ser promovida", "status.contains_quote": "Contén unha cita", - "status.context.load_new_replies": "Non hai respostas dispoñibles", - "status.context.loading": "Mirando se hai máis respostas", + "status.context.loading": "Cargando máis respostas", + "status.context.loading_error": "Non se puideron mostrar novas respostas", + "status.context.loading_more": "Cargando máis respostas", + "status.context.loading_success": "Móstranse todas as respostas", + "status.context.more_replies_found": "Existen máis respostas", + "status.context.retry": "Volver tentar", + "status.context.show": "Mostrar", "status.continued_thread": "Continua co fío", "status.copy": "Copiar ligazón á publicación", "status.delete": "Eliminar", @@ -910,6 +923,8 @@ "status.quote_private": "As publicacións privadas non se poden citar", "status.quotes": "{count, plural, one {cita} other {citas}}", "status.quotes.empty": "Aínda ninguén citou esta publicación. Cando alguén o faga aparecerá aquí.", + "status.quotes.local_other_disclaimer": "Non se mostrarán as citas rexeitadas pola autora.", + "status.quotes.remote_other_disclaimer": "Só se garante que se mostren as citas do dominio {domain}. Non se mostrarán as citas rexeitadas pola persoa autora.", "status.read_more": "Ler máis", "status.reblog": "Promover", "status.reblog_or_quote": "Promover ou citar", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index bcba5e0b7ce103..f67ee0fd6931b0 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -28,6 +28,7 @@ "account.disable_notifications": "הפסק לשלוח לי התראות כש@{name} מפרסמים", "account.domain_blocking": "רשימת השרתים החסומים", "account.edit_profile": "עריכת פרופיל", + "account.edit_profile_short": "עריכה", "account.enable_notifications": "שלח לי התראות כש@{name} מפרסם", "account.endorse": "קדם את החשבון בפרופיל", "account.familiar_followers_many": "החשבון נעקב על ידי {name1}, {name2} ועוד {othersCount, plural,one {אחד נוסף שמוכר לך}other {# נוספים שמוכרים לך}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "אין חצרוצים", "account.follow": "לעקוב", "account.follow_back": "לעקוב בחזרה", + "account.follow_back_short": "לעקוב בחזרה", + "account.follow_request": "בקשה לעקוב אחרי", + "account.follow_request_cancel": "ביטול בקשה", + "account.follow_request_cancel_short": "ביטול", + "account.follow_request_short": "בקשה", "account.followers": "עוקבים", "account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.", "account.followers_counter": "{count, plural,one {עוקב אחד} other {{counter} עוקבים}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "הודעות ותגובות", "account.remove_from_followers": "הסרת {name} מעוקבי", "account.report": "דווח על @{name}", - "account.requested": "בהמתנה לאישור. לחצי כדי לבטל בקשת מעקב", "account.requested_follow": "{name} ביקשו לעקוב אחריך", "account.requests_to_follow_you": "ביקשו לעקוב אחריך", "account.share": "שתף את הפרופיל של @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "הסרת הודעה", "confirmations.revoke_quote.message": "פעולה זו אינה הפיכה.", "confirmations.revoke_quote.title": "הסרת הודעה?", + "confirmations.unblock.confirm": "הסרת חסימה", + "confirmations.unblock.title": "הסרת חסימה מ־{name}?", "confirmations.unfollow.confirm": "הפסקת מעקב", - "confirmations.unfollow.message": "להפסיק מעקב אחרי {name}?", - "confirmations.unfollow.title": "לבטל מעקב אחר המשתמש.ת?", + "confirmations.unfollow.title": "בטול מעקב אחרי {name}?", + "confirmations.withdraw_request.confirm": "משיכת בקשה", + "confirmations.withdraw_request.title": "משיכת בקשת מעקב אחרי {name}?", "content_warning.hide": "הסתרת חיצרוץ", "content_warning.show": "להציג בכל זאת", "content_warning.show_more": "הצג עוד", @@ -865,8 +873,13 @@ "status.cannot_quote": "אין לך הרשאה לצטט את ההודעה הזו", "status.cannot_reblog": "לא ניתן להדהד חצרוץ זה", "status.contains_quote": "הודעה מכילה ציטוט", - "status.context.load_new_replies": "הגיעו תגובות חדשות", - "status.context.loading": "מחפש תגובות חדשות", + "status.context.loading": "נטענות תשובות נוספות", + "status.context.loading_error": "טעינת תשובות נוספות נכשלה", + "status.context.loading_more": "נטענות תשובות נוספות", + "status.context.loading_success": "כל התשובות נטענו", + "status.context.more_replies_found": "תשובות נוספות נמצאו", + "status.context.retry": "נסה שוב", + "status.context.show": "הצג", "status.continued_thread": "שרשור מתמשך", "status.copy": "העתק/י קישור להודעה זו", "status.delete": "מחיקה", @@ -910,6 +923,8 @@ "status.quote_private": "הודעות פרטיות לא ניתנות לציטוט", "status.quotes": "{count, plural,one {ציטוט}other {ציטוטים}}", "status.quotes.empty": "עוד לא ציטטו את ההודעה הזו. כאשר זה יקרה, הציטוטים יופיעו כאן.", + "status.quotes.local_other_disclaimer": "ציטוטים שיידחו על ידי המחברים המקוריים לא יוצגו.", + "status.quotes.remote_other_disclaimer": "רק ציטוטים מהשרת {domain} מובטחים שיופיעו פה. ציטוטים שנדחו על ידי המצוטטים לא יופיעו.", "status.read_more": "לקרוא עוד", "status.reblog": "הדהוד", "status.reblog_or_quote": "להדהד או לצטט", diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index ca56ad484e2057..63c9be8a8dd006 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -52,7 +52,6 @@ "account.posts": "टूट्स", "account.posts_with_replies": "टूट्स एवं जवाब", "account.report": "रिपोर्ट @{name}", - "account.requested": "मंजूरी का इंतजार। फॉलो रिक्वेस्ट को रद्द करने के लिए क्लिक करें", "account.requested_follow": "{name} ने आपको फॉलो करने के लिए अनुरोध किया है", "account.share": "@{name} की प्रोफाइल शेयर करे", "account.show_reblogs": "@{name} के बूस्ट दिखाए", @@ -169,7 +168,6 @@ "confirmations.redraft.confirm": "मिटायें और पुनःप्रारूपण करें", "confirmations.redraft.message": "क्या आप वाकई इस स्टेटस को हटाना चाहते हैं और इसे फिर से ड्राफ्ट करना चाहते हैं? पसंदीदा और बूस्ट खो जाएंगे, और मूल पोस्ट के उत्तर अनाथ हो जाएंगे।", "confirmations.unfollow.confirm": "अनफॉलो करें", - "confirmations.unfollow.message": "क्या आप वाकई {name} को अनफॉलो करना चाहते हैं?", "conversation.delete": "वार्तालाप हटाएँ", "conversation.mark_as_read": "पढ़ा गया के रूप में चिह्नित करें", "conversation.open": "वार्तालाप देखें", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index cb3f25de0699fe..3f8842d91ffadd 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -51,7 +51,6 @@ "account.posts": "Objave", "account.posts_with_replies": "Objave i odgovori", "account.report": "Prijavi @{name}", - "account.requested": "Čekanje na potvrdu. Kliknite za poništavanje zahtjeva za praćenje", "account.requested_follow": "{name} zatražio/la je praćenje", "account.share": "Podijeli profil @{name}", "account.show_reblogs": "Prikaži boostove od @{name}", @@ -147,7 +146,6 @@ "confirmations.mute.confirm": "Utišaj", "confirmations.redraft.confirm": "Izbriši i ponovno uredi", "confirmations.unfollow.confirm": "Prestani pratiti", - "confirmations.unfollow.message": "Jeste li sigurni da želite prestati pratiti {name}?", "conversation.delete": "Izbriši razgovor", "conversation.mark_as_read": "Označi kao pročitano", "conversation.open": "Prikaži razgovor", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 83fb3e60fe4c6e..1aba0615b4136d 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Bejegyzések és válaszok", "account.remove_from_followers": "{name} eltávolítása a követők közül", "account.report": "@{name} jelentése", - "account.requested": "Jóváhagyásra vár. Kattints a követési kérés visszavonásához", "account.requested_follow": "{name} kérte, hogy követhessen", "account.requests_to_follow_you": "Kéri, hogy követhessen", "account.share": "@{name} profiljának megosztása", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Ez a művelet nem vonható vissza.", "confirmations.revoke_quote.title": "Bejegyzés eltávolítása?", "confirmations.unfollow.confirm": "Követés visszavonása", - "confirmations.unfollow.message": "Biztos, hogy vissza szeretnéd vonni {name} követését?", - "confirmations.unfollow.title": "Megszünteted a felhasználó követését?", "content_warning.hide": "Bejegyzés elrejtése", "content_warning.show": "Megjelenítés mindenképp", "content_warning.show_more": "Több megjelenítése", @@ -865,8 +862,6 @@ "status.cannot_quote": "Nem idézheted ezt a bejegyzést", "status.cannot_reblog": "Ezt a bejegyzést nem lehet megtolni", "status.contains_quote": "Idézést tartalmaz", - "status.context.load_new_replies": "Új válaszok érhetőek el", - "status.context.loading": "További válaszok keresése", "status.continued_thread": "Folytatott szál", "status.copy": "Link másolása bejegyzésbe", "status.delete": "Törlés", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 62669eda9b5d20..0c77efd6cdeb12 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -43,7 +43,6 @@ "account.posts": "Գրառումներ", "account.posts_with_replies": "Գրառումներ եւ պատասխաններ", "account.report": "Բողոքել @{name}֊ի մասին", - "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։", "account.requested_follow": "{name}-ը ցանկանում է հետեւել քեզ", "account.share": "Կիսուել @{name}֊ի էջով", "account.show_reblogs": "Ցուցադրել @{name}֊ի տարածածները", @@ -125,7 +124,6 @@ "confirmations.mute.confirm": "Լռեցնել", "confirmations.redraft.confirm": "Ջնջել եւ խմբագրել նորից", "confirmations.unfollow.confirm": "Ապահետեւել", - "confirmations.unfollow.message": "Վստա՞հ ես, որ ուզում ես այլեւս չհետեւել {name}֊ին։", "conversation.delete": "Ջնջել խօսակցութիւնը", "conversation.mark_as_read": "Նշել որպէս ընթերցուած", "conversation.open": "Դիտել խօսակցութիւնը", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 8291e8ad5f9040..50b836ad6296ec 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Non plus notificar me quando @{name} publica", "account.domain_blocking": "Dominio blocate", "account.edit_profile": "Modificar profilo", + "account.edit_profile_short": "Modificar", "account.enable_notifications": "Notificar me quando @{name} publica", "account.endorse": "Evidentiar sur le profilo", "account.familiar_followers_many": "Sequite per {name1}, {name2}, e {othersCount, plural, one {un altere que tu cognosce} other {# alteres que tu cognosce}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Necun message", "account.follow": "Sequer", "account.follow_back": "Sequer in retorno", + "account.follow_back_short": "Sequer in retorno", + "account.follow_request": "Requestar de sequer", + "account.follow_request_cancel": "Cancellar requesta", + "account.follow_request_cancel_short": "Cancellar", + "account.follow_request_short": "Requesta", "account.followers": "Sequitores", "account.followers.empty": "Necuno seque ancora iste usator.", "account.followers_counter": "{count, plural, one {{counter} sequitor} other {{counter} sequitores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Messages e responsas", "account.remove_from_followers": "Remover {name} del sequitores", "account.report": "Reportar @{name}", - "account.requested": "Attendente le approbation. Clicca pro cancellar le requesta de sequer", "account.requested_follow": "{name} ha requestate de sequer te", "account.requests_to_follow_you": "Requestas de sequer te", "account.share": "Compartir profilo de @{name}", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Iste action non pote esser disfacite.", "confirmations.revoke_quote.title": "Remover message?", "confirmations.unfollow.confirm": "Non plus sequer", - "confirmations.unfollow.message": "Es tu secur que tu vole cessar de sequer {name}?", - "confirmations.unfollow.title": "Cessar de sequer le usator?", "content_warning.hide": "Celar le message", "content_warning.show": "Monstrar in omne caso", "content_warning.show_more": "Monstrar plus", @@ -864,8 +867,14 @@ "status.cancel_reblog_private": "Disfacer impulso", "status.cannot_quote": "Tu non es autorisate a citar iste message", "status.cannot_reblog": "Iste message non pote esser impulsate", - "status.context.load_new_replies": "Nove responsas disponibile", - "status.context.loading": "Cercante plus responsas", + "status.contains_quote": "Contine un citation", + "status.context.loading": "Cargante plus responsas", + "status.context.loading_error": "Non poteva cargar nove responsas", + "status.context.loading_more": "Cargante plus responsas", + "status.context.loading_success": "Tote le responsas cargate", + "status.context.more_replies_found": "Plus responsas trovate", + "status.context.retry": "Tentar de novo", + "status.context.show": "Monstrar", "status.continued_thread": "Continuation del discussion", "status.copy": "Copiar ligamine a message", "status.delete": "Deler", @@ -895,12 +904,15 @@ "status.quote": "Citar", "status.quote.cancel": "Cancellar le citation", "status.quote_error.filtered": "Celate a causa de un de tu filtros", + "status.quote_error.limited_account_hint.action": "Monstrar in omne caso", + "status.quote_error.limited_account_hint.title": "Iste conto ha essite celate per le moderatores de {domain}.", "status.quote_error.not_available": "Message indisponibile", "status.quote_error.pending_approval": "Message pendente", "status.quote_error.pending_approval_popout.body": "Sur Mastodon, tu pote controlar si on pote citar te. Iste message attende ora le approbation del autor original.", "status.quote_error.revoked": "Message removite per le autor", "status.quote_followers_only": "Solmente sequitores pote citar iste message", "status.quote_manual_review": "Le autor lo examinara manualmente", + "status.quote_noun": "Citation", "status.quote_policy_change": "Cambiar qui pote citar", "status.quote_post_author": "Ha citate un message de @{name}", "status.quote_private": "Le messages private non pote esser citate", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 7c5c816c67dde4..c8d7d8ef0011f8 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -64,7 +64,6 @@ "account.posts": "Kiriman", "account.posts_with_replies": "Kiriman dan balasan", "account.report": "Laporkan @{name}", - "account.requested": "Menunggu persetujuan. Klik untuk membatalkan permintaan", "account.requested_follow": "{name} ingin mengikuti Anda", "account.share": "Bagikan profil @{name}", "account.show_reblogs": "Tampilkan boost dari @{name}", @@ -190,8 +189,6 @@ "confirmations.redraft.message": "Apakah anda yakin ingin menghapus postingan ini dan menyusun ulang postingan ini? Favorit dan peningkatan akan hilang, dan balasan ke postingan asli tidak akan terhubung ke postingan manapun.", "confirmations.redraft.title": "Delete & redraft post?", "confirmations.unfollow.confirm": "Berhenti mengikuti", - "confirmations.unfollow.message": "Apakah Anda ingin berhenti mengikuti {name}?", - "confirmations.unfollow.title": "Unfollow user?", "content_warning.hide": "Hide post", "content_warning.show": "Show anyway", "conversation.delete": "Hapus percakapan", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index 4a464ef16e2e0c..e978cfafc4b859 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -52,7 +52,6 @@ "account.posts": "Postas", "account.posts_with_replies": "Postas e replicas", "account.report": "Raportar @{name}", - "account.requested": "Atendent aprobation. Cliccar por anullar li petition de sequer", "account.requested_follow": "{name} ha petit sequer te", "account.share": "Distribuer li profil de @{name}", "account.show_reblogs": "Monstrar boosts de @{name}", @@ -169,7 +168,6 @@ "confirmations.redraft.confirm": "Deleter & redacter", "confirmations.redraft.message": "Esque tu vermen vole deleter ti-ci posta e redacter it? Favorites e boosts va esser perdit, e responses al posta original va esser orfanat.", "confirmations.unfollow.confirm": "Dessequer", - "confirmations.unfollow.message": "Esque tu vermen vole dessequer {name}?", "conversation.delete": "Deleter conversation", "conversation.mark_as_read": "Marcar quam leet", "conversation.open": "Vider conversation", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index b51d32d05a772d..07795897069dc3 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -55,7 +55,6 @@ "account.posts": "Mesaji", "account.posts_with_replies": "Afishi e respondi", "account.report": "Denuncar @{name}", - "account.requested": "Vartante aprobo", "account.requested_follow": "{name} demandis sequar tu", "account.share": "Partigez profilo di @{name}", "account.show_reblogs": "Montrez repeti de @{name}", @@ -219,8 +218,6 @@ "confirmations.redraft.message": "Ka vu certe volas efacar ca posto e riskisigar ol? Favoriziti e repeti esos perdita, e respondi al posto originala esos orfanigita.", "confirmations.redraft.title": "Ka efacar & riskisar posto?", "confirmations.unfollow.confirm": "Desequez", - "confirmations.unfollow.message": "Ka vu certe volas desequar {name}?", - "confirmations.unfollow.title": "Ka dessequar uzanto?", "content_warning.hide": "Celez posto", "content_warning.show": "Montrez nur", "content_warning.show_more": "Montrar plu", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index f380a4b4a50c80..4dbdee7c8c3b0a 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Hætta að láta mig vita þegar @{name} sendir inn", "account.domain_blocking": "Útiloka lén", "account.edit_profile": "Breyta notandasniði", + "account.edit_profile_short": "Breyta", "account.enable_notifications": "Láta mig vita þegar @{name} sendir inn", "account.endorse": "Birta á notandasniði", "account.familiar_followers_many": "Fylgt af {name1}, {name2} og {othersCount, plural, one {einum öðrum sem þú þekkir} other {# öðrum sem þú þekkir}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Engar færslur", "account.follow": "Fylgjast með", "account.follow_back": "Fylgjast með til baka", + "account.follow_back_short": "Fylgjast með til baka", + "account.follow_request": "Beiðni um að fylgjast með", + "account.follow_request_cancel": "Hætta við beiðni", + "account.follow_request_cancel_short": "Hætta við", + "account.follow_request_short": "Beiðni", "account.followers": "Fylgjendur", "account.followers.empty": "Ennþá fylgist enginn með þessum notanda.", "account.followers_counter": "{count, plural, one {Fylgjandi: {counter}} other {Fylgjendur: {counter}}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Færslur og svör", "account.remove_from_followers": "Fjarlægja {name} úr fylgjendum", "account.report": "Kæra @{name}", - "account.requested": "Bíður eftir samþykki. Smelltu til að hætta við beiðni um að fylgjast með", "account.requested_follow": "{name} hefur beðið um að fylgjast með þér", "account.requests_to_follow_you": "Bað um að fylgjast með þér", "account.share": "Deila notandasniði fyrir @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Fjarlægja færslu", "confirmations.revoke_quote.message": "Þessa aðgerð er ekki hægt að afturkalla.", "confirmations.revoke_quote.title": "Fjarlægja færslu?", + "confirmations.unblock.confirm": "Aflétta útilokun", + "confirmations.unblock.title": "Aflétta útilokun á {name}?", "confirmations.unfollow.confirm": "Hætta að fylgja", - "confirmations.unfollow.message": "Ertu viss um að þú viljir hætta að fylgjast með {name}?", - "confirmations.unfollow.title": "Hætta að fylgjast með viðkomandi?", + "confirmations.unfollow.title": "Hætta að fylgjast með {name}?", + "confirmations.withdraw_request.confirm": "Taka beiðni til baka", + "confirmations.withdraw_request.title": "Taka aftur beiðni um að fylgjast með {name}?", "content_warning.hide": "Fela færslu", "content_warning.show": "Birta samt", "content_warning.show_more": "Sýna meira", @@ -864,8 +872,14 @@ "status.cancel_reblog_private": "Taka úr endurbirtingu", "status.cannot_quote": "Þú hefur ekki heimild til að vitna í þessa færslu", "status.cannot_reblog": "Þessa færslu er ekki hægt að endurbirta", - "status.context.load_new_replies": "Ný svör hafa borist", - "status.context.loading": "Athuga með fleiri svör", + "status.contains_quote": "Inniheldur tilvitnun", + "status.context.loading": "Hleð inn fleiri svörum", + "status.context.loading_error": "Gat ekki hlaðið inn nýjum svörum", + "status.context.loading_more": "Hleð inn fleiri svörum", + "status.context.loading_success": "Öllum svörum hlaðið inn", + "status.context.more_replies_found": "Fleiri svör fundust", + "status.context.retry": "Reyna aftur", + "status.context.show": "Sýna", "status.continued_thread": "Hélt samtali áfram", "status.copy": "Afrita tengil í færslu", "status.delete": "Eyða", @@ -895,17 +909,22 @@ "status.quote": "Tilvitnun", "status.quote.cancel": "Hætta við tilvitnun", "status.quote_error.filtered": "Falið vegna einnar síu sem er virk", + "status.quote_error.limited_account_hint.action": "Birta samt", + "status.quote_error.limited_account_hint.title": "Þessi notandaaðgangur hefur verið falinn af stjórnendum á {domain}.", "status.quote_error.not_available": "Færsla ekki tiltæk", "status.quote_error.pending_approval": "Færsla í bið", "status.quote_error.pending_approval_popout.body": "Á Mastodon geturðu stjórnað því hvort aðrir geti vitnað í þig. Þessi færsla bíður eftir samþykki upprunalegs höfundar.", "status.quote_error.revoked": "Færsla fjarlægð af höfundi", "status.quote_followers_only": "Einungis fylgjendur geta vitnað í þessa færslu", "status.quote_manual_review": "Höfundur mun yfirfara handvirkt", + "status.quote_noun": "Tilvitnun", "status.quote_policy_change": "Breyttu því hver getur tilvitnað", "status.quote_post_author": "Vitnaði í færslu frá @{name}", "status.quote_private": "Ekki er hægt að vitna í einkafærslur", "status.quotes": "{count, plural, one {tilvitnun} other {tilvitnanir}}", "status.quotes.empty": "Enginn hefur ennþá vitnað í þessa færslu. Þegar einhver gerir það, mun það birtast hér.", + "status.quotes.local_other_disclaimer": "Tilvitnanir sem höfundur hafnar verða ekki birtar.", + "status.quotes.remote_other_disclaimer": "Aðeins tilvitnanir frá {domain} munu birtast hér. Tilvitnanir sem höfundur hafnar verða ekki birtar.", "status.read_more": "Lesa meira", "status.reblog": "Endurbirting", "status.reblog_or_quote": "Endurbirta eða vitna í færslu", @@ -992,7 +1011,7 @@ "visibility_modal.helper.privacy_private_self_quote": "Tilvitnanir í sjálfan sig úr einkaspjallfærslum er ekki hægt að gera opinberar.", "visibility_modal.helper.private_quoting": "Ekki er hægt að vitna í færslur einungis til fylgjenda sem skrifaðar eru á Mastodon.", "visibility_modal.helper.unlisted_quoting": "Þegar fólk vitnar í þig verða færslurnar þeirr einnig faldar á vinsældatímalínum.", - "visibility_modal.instructions": ". Stýrðu því hverjir geta átt við þessa færslu. Þú getur líka ákvarðað stillingar fyrir allar færslur í framtíðinni með því að fara í Kjörstillingar > Sjálfgefin gildi við gerð færslna.", + "visibility_modal.instructions": "Stýrðu því hverjir geta átt við þessa færslu. Þú getur líka ákvarðað stillingar fyrir allar færslur í framtíðinni með því að fara í Kjörstillingar > Sjálfgefin gildi við gerð færslna.", "visibility_modal.privacy_label": "Sýnileiki", "visibility_modal.quote_followers": "Einungis fylgjendur", "visibility_modal.quote_label": "Hverjir geta gert tilvitnanir", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 448b51944bd349..204d5d8e305c12 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Smetti di avvisarmi quando @{name} pubblica un post", "account.domain_blocking": "Account di un dominio bloccato", "account.edit_profile": "Modifica profilo", + "account.edit_profile_short": "Modifica", "account.enable_notifications": "Avvisami quando @{name} pubblica un post", "account.endorse": "In evidenza sul profilo", "account.familiar_followers_many": "Seguito da {name1}, {name2}, e {othersCount, plural, one {un altro che conosci} other {# altri che conosci}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Nessun post", "account.follow": "Segui", "account.follow_back": "Segui a tua volta", + "account.follow_back_short": "Segui a tua volta", + "account.follow_request": "Richiesta di seguire", + "account.follow_request_cancel": "Annulla la richiesta", + "account.follow_request_cancel_short": "Annulla", + "account.follow_request_short": "Richiesta", "account.followers": "Follower", "account.followers.empty": "Ancora nessuno segue questo utente.", "account.followers_counter": "{count, plural, one {{counter} seguace} other {{counter} seguaci}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Post e risposte", "account.remove_from_followers": "Rimuovi {name} dai seguaci", "account.report": "Segnala @{name}", - "account.requested": "In attesa d'approvazione. Clicca per annullare la richiesta di seguire", "account.requested_follow": "{name} ha richiesto di seguirti", "account.requests_to_follow_you": "Richieste di seguirti", "account.share": "Condividi il profilo di @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Elimina il post", "confirmations.revoke_quote.message": "Questa azione non può essere annullata.", "confirmations.revoke_quote.title": "Rimuovere il post?", + "confirmations.unblock.confirm": "Sblocca", + "confirmations.unblock.title": "Sbloccare {name}?", "confirmations.unfollow.confirm": "Smetti di seguire", - "confirmations.unfollow.message": "Sei sicuro di voler smettere di seguire {name}?", - "confirmations.unfollow.title": "Smettere di seguire l'utente?", + "confirmations.unfollow.title": "Smettere di seguire {name}?", + "confirmations.withdraw_request.confirm": "Annulla la richiesta", + "confirmations.withdraw_request.title": "Annullare la richiesta di seguire {name}?", "content_warning.hide": "Nascondi post", "content_warning.show": "Mostra comunque", "content_warning.show_more": "Mostra di più", @@ -864,8 +872,14 @@ "status.cancel_reblog_private": "Annulla reblog", "status.cannot_quote": "Non ti è consentito citare questo post", "status.cannot_reblog": "Questo post non può essere condiviso", - "status.context.load_new_replies": "Nuove risposte disponibili", - "status.context.loading": "Controllo per altre risposte", + "status.contains_quote": "Contiene una citazione", + "status.context.loading": "Caricamento di altre risposte", + "status.context.loading_error": "Impossibile caricare nuove risposte", + "status.context.loading_more": "Caricamento di altre risposte", + "status.context.loading_success": "Tutte le risposte caricate", + "status.context.more_replies_found": "Sono state trovate altre risposte", + "status.context.retry": "Riprova", + "status.context.show": "Mostra", "status.continued_thread": "Discussione continua", "status.copy": "Copia link al post", "status.delete": "Elimina", @@ -903,11 +917,14 @@ "status.quote_error.revoked": "Post rimosso dall'autore", "status.quote_followers_only": "Solo i seguaci possono citare questo post", "status.quote_manual_review": "L'autore esaminerà manualmente", + "status.quote_noun": "Citazione", "status.quote_policy_change": "Cambia chi può citare", "status.quote_post_author": "Citato un post di @{name}", "status.quote_private": "I post privati non possono essere citati", "status.quotes": "{count, plural, one {citazione} other {citazioni}}", "status.quotes.empty": "Nessuno ha ancora citato questo post. Quando qualcuno lo farà, verrà visualizzato qui.", + "status.quotes.local_other_disclaimer": "Le citazioni rifiutate dall'autore non verranno mostrate.", + "status.quotes.remote_other_disclaimer": "Solo le citazioni provenienti da {domain} saranno mostrate qui. Le citazioni rifiutate dall'autore non saranno mostrate.", "status.read_more": "Leggi di più", "status.reblog": "Reblog", "status.reblog_or_quote": "Condividi o cita", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index f688aadecb6a64..0b130a919174eb 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -83,7 +83,6 @@ "account.posts_with_replies": "投稿と返信", "account.remove_from_followers": "{name}さんをフォロワーから削除", "account.report": "@{name}さんを通報", - "account.requested": "フォロー承認待ちです。クリックしてキャンセル", "account.requested_follow": "{name}さんがあなたにフォローリクエストしました", "account.requests_to_follow_you": "フォローリクエスト", "account.share": "@{name}さんのプロフィールを共有する", @@ -370,8 +369,6 @@ "confirmations.revoke_quote.confirm": "投稿を削除", "confirmations.revoke_quote.title": "投稿を削除しますか?", "confirmations.unfollow.confirm": "フォロー解除", - "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?", - "confirmations.unfollow.title": "フォローを解除しようとしています", "content_warning.hide": "内容を隠す", "content_warning.show": "承知して表示", "content_warning.show_more": "続きを表示", @@ -789,22 +786,22 @@ "notifications.column_settings.emoji_reaction": "絵文字リアクション", "notifications.column_settings.favourite": "お気に入り", "notifications.column_settings.filter_bar.advanced": "すべてのカテゴリを表示", - "notifications.column_settings.filter_bar.category": "クイックフィルターバー:", + "notifications.column_settings.filter_bar.category": "クイックフィルターバー", "notifications.column_settings.follow": "新しいフォロワー", - "notifications.column_settings.follow_request": "新しいフォローリクエスト:", + "notifications.column_settings.follow_request": "新しいフォローリクエスト", "notifications.column_settings.group": "グループ", "notifications.column_settings.mention": "返信", "notifications.column_settings.poll": "アンケート結果", "notifications.column_settings.push": "プッシュ通知", "notifications.column_settings.quote": "引用", - "notifications.column_settings.reblog": "ブースト:", + "notifications.column_settings.reblog": "ブースト", "notifications.column_settings.show": "カラムに表示", "notifications.column_settings.sound": "通知音を再生", "notifications.column_settings.status": "新しい投稿", "notifications.column_settings.status_reference": "リンク", - "notifications.column_settings.unread_notifications.category": "未読の通知:", + "notifications.column_settings.unread_notifications.category": "未読の通知", "notifications.column_settings.unread_notifications.highlight": "未読の通知を強調表示", - "notifications.column_settings.update": "編集:", + "notifications.column_settings.update": "編集", "notifications.filter.all": "すべて", "notifications.filter.boosts": "ブースト", "notifications.filter.emoji_reactions": "絵文字リアクション", @@ -1025,7 +1022,6 @@ "status.cannot_quote": "この投稿は引用できません", "status.cannot_reblog": "この投稿はブーストできません", "status.cannot_reference": "この投稿のサーバーはリンク機能に対応していません", - "status.context.load_new_replies": "新しい返信があります", "status.continued_thread": "続きのスレッド", "status.copy": "投稿へのリンクをコピー", "status.delete": "削除", @@ -1070,6 +1066,7 @@ "status.quote_link": "引用リンクを挿入", "status.quote_error.filtered": "あなたのフィルター設定によって非表示になっています", "status.quote_error.pending_approval": "承認待ちの投稿", + "status.quote_noun": "引用", "status.quotes": "{count, plural, other {引用}}", "status.read_more": "もっと見る", "status.reblog": "ブースト", diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json index ea7375fd00be70..2b495ab0b140e9 100644 --- a/app/javascript/mastodon/locales/ka.json +++ b/app/javascript/mastodon/locales/ka.json @@ -21,7 +21,6 @@ "account.posts": "პოსტები", "account.posts_with_replies": "ტუტები და პასუხები", "account.report": "დაარეპორტე @{name}", - "account.requested": "დამტკიცების მოლოდინში. დააწკაპუნეთ რომ უარყოთ დადევნების მოთხონვა", "account.share": "გააზიარე @{name}-ის პროფილი", "account.show_reblogs": "აჩვენე ბუსტები @{name}-სგან", "account.unblock": "განბლოკე @{name}", @@ -72,7 +71,6 @@ "confirmations.mute.confirm": "დადუმება", "confirmations.redraft.confirm": "გაუქმება და გადანაწილება", "confirmations.unfollow.confirm": "ნუღარ მიჰყვები", - "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?", "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.", "embed.preview": "ესაა თუ როგორც გამოჩნდება:", "emoji_button.activity": "აქტივობა", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index c48480995753b7..0add265b63bc4c 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -63,7 +63,6 @@ "account.posts_with_replies": "Tisuffaɣ d tririyin", "account.remove_from_followers": "Kkes {name} seg ineḍfaren", "account.report": "Cetki ɣef @{name}", - "account.requested": "Di laɛḍil ad yettwaqbel. Ssit i wakken ad yefsex usuter n uḍfar", "account.requested_follow": "{name} yessuter ad k·m-yeḍfer", "account.share": "Bḍu amaɣnu n @{name}", "account.show_reblogs": "Ssken-d inebḍa n @{name}", @@ -195,7 +194,6 @@ "confirmations.revoke_quote.confirm": "Kkes tasuffeɣt", "confirmations.revoke_quote.title": "Kkes tasuffeɣt?", "confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara", - "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teṭafaṛeḍ ara {name}?", "content_warning.hide": "Ffer tasuffeɣt", "content_warning.show": "Ssken-d akken tebɣu tili", "content_warning.show_more": "Sken-d ugar", @@ -267,6 +265,7 @@ "explore.trending_links": "Isallen", "explore.trending_statuses": "Tisuffaɣ", "explore.trending_tags": "Ihacṭagen", + "featured_carousel.header": "{count, plural, one {n tsuffeɣt tunṭiḍt} other {n tsuffaɣ tunṭiḍin}}", "featured_carousel.next": "Uḍfiṛ", "featured_carousel.post": "Tasuffeɣt", "featured_carousel.previous": "Uzwir", @@ -352,6 +351,7 @@ "keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.down": "i kennu ɣer wadda n tebdart", "keyboard_shortcuts.enter": "i tildin n tsuffeɣt", + "keyboard_shortcuts.favourite": "Smenyef tassuɣeft", "keyboard_shortcuts.favourites": "Ldi tabdert n yismenyifen", "keyboard_shortcuts.federated": "i tildin n tsuddemt tamatut n yisallen", "keyboard_shortcuts.heading": "Inegzumen n unasiw", @@ -364,8 +364,9 @@ "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik", "keyboard_shortcuts.notifications": "Ad d-yeldi ajgu n yilɣa", "keyboard_shortcuts.open_media": "i tiɣwalin yeldin", - "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tjewwiqin yettwasentḍen", + "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tsuffaɣ tunṭiḍin", "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar", + "keyboard_shortcuts.quote": "Tanebdurt n tsuffeɣt", "keyboard_shortcuts.reply": "i tririt", "keyboard_shortcuts.requests": "akken ad d-teldiḍ tabdert n yisuturen n teḍfeṛt", "keyboard_shortcuts.search": "to focus search", @@ -444,6 +445,7 @@ "navigation_bar.privacy_and_reach": "Tabḍnit akked wagwaḍ", "navigation_bar.search": "Nadi", "navigation_bar.search_trends": "Anadi / Anezzuɣ", + "navigation_panel.collapse_lists": "Sneḍfes umuɣ n tebdart", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", "notification.admin.report": "Yemla-t-id {name} {target}", "notification.admin.sign_up": "Ijerred {name}", @@ -464,6 +466,7 @@ "notification.moderation_warning.action_suspend": "Yettwaseḥbes umiḍan-ik.", "notification.own_poll": "Tafrant-ik·im tfuk", "notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen", + "notification.reblog.name_and_others_with_link": "{name} akked {count, plural, one {# nnayeḍ} other {# nniḍen}} zzuzren tasuffeɣt-ik·im", "notification.relationships_severance_event.learn_more": "Issin ugar", "notification.status": "{name} akken i d-yessufeɣ", "notification_requests.accept": "Qbel", @@ -555,6 +558,8 @@ "regeneration_indicator.please_stand_by": "Ttxil rǧu.", "regeneration_indicator.preparing_your_home_feed": "Ha-tt-an tsuddemt-ik·im tagejdant tettwaheggay…", "relative_time.days": "{number}u", + "relative_time.full.days": "{number, plural, one {# n wass} other {# n wussan}} aya", + "relative_time.full.hours": "{number, plural, one {# n usrag} other {# n yesragen}} aya", "relative_time.full.just_now": "tura kan", "relative_time.hours": "{number}isr", "relative_time.just_now": "tura", @@ -562,6 +567,7 @@ "relative_time.seconds": "{number}tas", "relative_time.today": "ass-a", "remove_quote_hint.button_label": "Gziɣ-t", + "remove_quote_hint.title": "D tidet, tebɣiḍ ad tekkseḍ tasuffeɣt-inek·inem i d-yettwabedren?", "reply_indicator.attachments": "{count, plural, one {# n umedday} other {# n imeddayen}}", "reply_indicator.cancel": "Sefsex", "reply_indicator.poll": "Afmiḍi", @@ -630,6 +636,7 @@ "search_results.title": "Igemmaḍ n unadi ɣef \"{q}\"", "server_banner.active_users": "iseqdacen urmiden", "server_banner.administered_by": "Yettwadbel sɣur :", + "server_banner.is_one_of_many": "{domain} d yiwen seg seg waṭṭas n iqeddacen imzurag n Mastodon i tzemreḍ ad tsqesdceḍ i wakken ad tettekkiḍ deg fediverse.", "server_banner.server_stats": "Tidaddanin n uqeddac:", "sign_in_banner.create_account": "Snulfu-d amiḍan", "sign_in_banner.sign_in": "Qqen", @@ -639,7 +646,8 @@ "status.bookmark": "Creḍ", "status.cancel_reblog_private": "Sefsex beṭṭu", "status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen", - "status.context.load_new_replies": "Llant tririyin timaynutin", + "status.context.retry": "Ɛreḍ tikkelt nniḍen", + "status.context.show": "Sken-d", "status.continued_thread": "Asqerdec yettkemmil", "status.copy": "Nɣel assaɣ ɣer tasuffeɣt", "status.delete": "Kkes", @@ -668,7 +676,10 @@ "status.quote": "Tanebdurt", "status.quote.cancel": "Semmet tanebdurt", "status.quote_error.limited_account_hint.action": "Sken-d akken ibɣu yili", + "status.quote_error.not_available": "Tasuffeɣt-a ulac-itt", "status.quote_error.revoked": "Tasuffeɣt-a yekkes-itt umeskar-is", + "status.quote_noun": "Tanebdurt", + "status.quote_policy_change": "Snifel anwa i izemren ad d-yebder", "status.quote_post_author": "Yebder-d tasuffeɣt sɣur @{name}", "status.read_more": "Issin ugar", "status.reblog": "Bḍu", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index b04790d9a1200f..8e29c3fbeed207 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -65,7 +65,6 @@ "account.posts_with_replies": "Постар мен жауаптар", "account.remove_from_followers": "{name} жазылушылардан жою", "account.report": "Шағымдану @{name}", - "account.requested": "Растауын күтіңіз. Жазылудан бас тарту үшін басыңыз", "account.requested_follow": "{name} сізге жазылуға сұраныс жіберді", "account.requests_to_follow_you": "Сізге жазылу сұраныстары", "account.share": "@{name} профилін бөлісу\"", @@ -141,7 +140,6 @@ "confirmations.mute.confirm": "Үнсіз қылу", "confirmations.redraft.confirm": "Өшіруді құптау", "confirmations.unfollow.confirm": "Оқымау", - "confirmations.unfollow.message": "\"{name} атты қолданушыға енді жазылғыңыз келмей ме?", "conversation.delete": "Пікірталасты өшіру", "conversation.mark_as_read": "Оқылды деп белгіле", "conversation.open": "Пікірталасты қарау", diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json index ae4e4dd7b33326..1d383c6b276510 100644 --- a/app/javascript/mastodon/locales/kn.json +++ b/app/javascript/mastodon/locales/kn.json @@ -13,7 +13,6 @@ "account.followers": "ಹಿಂಬಾಲಕರು", "account.posts": "ಟೂಟ್‌ಗಳು", "account.posts_with_replies": "Toots and replies", - "account.requested": "Awaiting approval", "account.unblock_domain": "Unhide {domain}", "account_note.placeholder": "Click to add a note", "alert.unexpected.title": "ಅಯ್ಯೋ!", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 40c9ae00044bc5..f468179454286d 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "게시물과 답장", "account.remove_from_followers": "팔로워에서 {name} 제거", "account.report": "@{name} 신고", - "account.requested": "승인 대기 중. 클릭해서 취소하기", "account.requested_follow": "{name} 님이 팔로우 요청을 보냈습니다", "account.requests_to_follow_you": "팔로우 요청", "account.share": "@{name}의 프로필 공유", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "이 작업은 되돌릴 수 없습니다.", "confirmations.revoke_quote.title": "게시물을 지울까요?", "confirmations.unfollow.confirm": "팔로우 해제", - "confirmations.unfollow.message": "정말로 {name} 님을 팔로우 해제하시겠습니까?", - "confirmations.unfollow.title": "사용자를 언팔로우 할까요?", "content_warning.hide": "게시물 숨기기", "content_warning.show": "무시하고 보기", "content_warning.show_more": "더 보기", @@ -861,8 +858,6 @@ "status.bookmark": "북마크", "status.cancel_reblog_private": "부스트 취소", "status.cannot_reblog": "이 게시물은 부스트 할 수 없습니다", - "status.context.load_new_replies": "새 답글 보기", - "status.context.loading": "추가 답글 확인중", "status.continued_thread": "이어지는 글타래", "status.copy": "게시물 링크 복사", "status.delete": "삭제", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index 3518959e2b4c0f..d641c896237e60 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -58,7 +58,6 @@ "account.posts": "Şandî", "account.posts_with_replies": "Şandî û bersiv", "account.report": "@{name} ragihîne", - "account.requested": "Li benda erêkirinê ye. Ji bo betal kirina daxwazê pêl bikin", "account.requested_follow": "{name} dixwaze te bişopîne", "account.share": "Profîla @{name} parve bike", "account.show_reblogs": "Bilindkirinên ji @{name} nîşan bike", @@ -170,7 +169,6 @@ "confirmations.redraft.confirm": "Jê bibe & ji nû ve serrast bike", "confirmations.redraft.message": "Bi rastî tu dixwazî şandî ye jê bibî û ji nû ve reşnivîsek çê bikî? Bijarte û şandî wê wenda bibin û bersivên ji bo şandiyê resen wê sêwî bimînin.", "confirmations.unfollow.confirm": "Neşopîne", - "confirmations.unfollow.message": "Ma tu dixwazî ku dev ji şopa {name} berdî?", "content_warning.show_more": "Bêtir nîşan bide", "conversation.delete": "Axaftinê jê bibe", "conversation.mark_as_read": "Wekî xwendî nîşan bide", diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json index b1116a7a317c81..d21d75c182b1f2 100644 --- a/app/javascript/mastodon/locales/kw.json +++ b/app/javascript/mastodon/locales/kw.json @@ -25,7 +25,6 @@ "account.posts": "Postow", "account.posts_with_replies": "Postow ha gorthebow", "account.report": "Reportya @{name}", - "account.requested": "Ow kortos komendyans. Klyckyewgh dhe hedhi govyn holya", "account.share": "Kevrenna profil @{name}", "account.show_reblogs": "Diskwedhes kenerthow a @{name}", "account.unblock": "Anlettya @{name}", @@ -87,7 +86,6 @@ "confirmations.mute.confirm": "Tawhe", "confirmations.redraft.confirm": "Dilea & daskynskrifa", "confirmations.unfollow.confirm": "Anholya", - "confirmations.unfollow.message": "Owgh hwi sur a vynnes anholya {name}?", "conversation.delete": "Dilea kesklapp", "conversation.mark_as_read": "Merkya vel redys", "conversation.open": "Gweles kesklapp", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 754f6eb3c6b190..147e362f3b0678 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -63,7 +63,6 @@ "account.posts": "Publikasyones", "account.posts_with_replies": "Kon repuestas", "account.report": "Raporta @{name}", - "account.requested": "Asperando achetasion. Klika para anular la solisitud de segimiento", "account.requested_follow": "{name} tiene solisitado segirte", "account.requests_to_follow_you": "Solisita segirte", "account.share": "Partaja el profil de @{name}", @@ -215,8 +214,6 @@ "confirmations.revoke_quote.confirm": "Kita puvlikasyon", "confirmations.revoke_quote.title": "Kitar puvlikasyon?", "confirmations.unfollow.confirm": "Desige", - "confirmations.unfollow.message": "Estas siguro ke keres deshar de segir a {name}?", - "confirmations.unfollow.title": "Desige utilizador?", "content_warning.hide": "Eskonde puvlikasyon", "content_warning.show": "Amostra entanto", "content_warning.show_more": "Amostra mas", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 6276bb80383152..bf13549d46af08 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -64,7 +64,6 @@ "account.posts_with_replies": "Įrašai ir atsakymai", "account.remove_from_followers": "Šalinti {name} iš sekėjų", "account.report": "Pranešti apie @{name}", - "account.requested": "Laukiama patvirtinimo. Spustelėk, kad atšauktum sekimo prašymą", "account.requested_follow": "{name} paprašė tave sekti", "account.requests_to_follow_you": "Prašymai sekti jus", "account.share": "Bendrinti @{name} profilį", @@ -233,8 +232,6 @@ "confirmations.remove_from_followers.message": "{name} nustos jus sekti. Ar tikrai norite tęsti?", "confirmations.remove_from_followers.title": "Šalinti sekėją?", "confirmations.unfollow.confirm": "Nebesekti", - "confirmations.unfollow.message": "Ar tikrai nori nebesekti {name}?", - "confirmations.unfollow.title": "Nebesekti naudotoją?", "content_warning.hide": "Slėpti įrašą", "content_warning.show": "Rodyti vis tiek", "content_warning.show_more": "Rodyti daugiau", @@ -784,8 +781,6 @@ "status.bookmark": "Pridėti į žymės", "status.cancel_reblog_private": "Nebepasidalinti", "status.cannot_reblog": "Šis įrašas negali būti pakeltas.", - "status.context.load_new_replies": "Yra naujų atsakymų", - "status.context.loading": "Tikrinama dėl daugiau atsakymų", "status.continued_thread": "Tęsiama gijoje", "status.copy": "Kopijuoti nuorodą į įrašą", "status.delete": "Ištrinti", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index d80b8daf200e9f..9d289c5c53c239 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Ieraksti un atbildes", "account.remove_from_followers": "Dzēst sekotāju {name}", "account.report": "Ziņot par @{name}", - "account.requested": "Gaida apstiprinājumu. Nospied, lai atceltu sekošanas pieparasījumu", "account.requested_follow": "{name} nosūtīja Tev sekošanas pieprasījumu", "account.requests_to_follow_you": "Sekošanas pieprasījumi", "account.share": "Dalīties ar @{name} profilu", @@ -244,8 +243,6 @@ "confirmations.revoke_quote.message": "Šo darbību nevar atsaukt.", "confirmations.revoke_quote.title": "Noņemt ierakstu?", "confirmations.unfollow.confirm": "Pārstāt sekot", - "confirmations.unfollow.message": "Vai tiešam vairs nevēlies sekot lietotājam {name}?", - "confirmations.unfollow.title": "Pārtraukt sekošanu lietotājam?", "content_warning.hide": "Paslēpt ierakstu", "content_warning.show": "Tomēr rādīt", "content_warning.show_more": "Rādīt vairāk", @@ -725,7 +722,6 @@ "status.bookmark": "Grāmatzīme", "status.cancel_reblog_private": "Nepastiprināt", "status.cannot_reblog": "Šo ierakstu nevar pastiprināt", - "status.context.loading": "Pārbauda, vai ir vairāk atbilžu", "status.continued_thread": "Turpināts pavediens", "status.copy": "Ievietot ieraksta saiti starpliktuvē", "status.delete": "Dzēst", diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json index 67e561dceefd20..21f7d2d5d02e74 100644 --- a/app/javascript/mastodon/locales/mk.json +++ b/app/javascript/mastodon/locales/mk.json @@ -33,7 +33,6 @@ "account.posts": "Тутови", "account.posts_with_replies": "Тутови и реплики", "account.report": "Пријави @{name}", - "account.requested": "Се чека одобрување. Кликни за да одкажиш барање за следење", "account.share": "Сподели @{name} профил", "account.show_reblogs": "Прикажи бустови од @{name}", "account.unblock": "Одблокирај @{name}", @@ -83,7 +82,6 @@ "confirmations.logout.message": "Дали сте сигурни дека сакате да се одјавите?", "confirmations.mute.confirm": "Заќути", "confirmations.unfollow.confirm": "Одследи", - "confirmations.unfollow.message": "Сигурни сте дека ќе го отследите {name}?", "conversation.delete": "Избриши разговор", "conversation.mark_as_read": "Означете како прочитано", "conversation.open": "Прегледај разговор", diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json index 63fc97c8abb85e..017ef1ce3f2b35 100644 --- a/app/javascript/mastodon/locales/ml.json +++ b/app/javascript/mastodon/locales/ml.json @@ -44,7 +44,6 @@ "account.posts": "പോസ്റ്റുകൾ", "account.posts_with_replies": "പോസ്റ്റുകളും മറുപടികളും", "account.report": "റിപ്പോർട്ട് ചെയ്യുക @{name}", - "account.requested": "അനുവാദത്തിനായി കാത്തിരിക്കുന്നു. പിന്തുടരാനുള്ള അപേക്ഷ റദ്ദാക്കുവാൻ ഞെക്കുക", "account.share": "@{name} ന്റെ പ്രൊഫൈൽ പങ്കിടുക", "account.show_reblogs": "@{name} ൽ നിന്നുള്ള ബൂസ്റ്റുകൾ കാണിക്കുക", "account.unblock": "@{name} തടഞ്ഞത് മാറ്റുക", @@ -140,7 +139,6 @@ "confirmations.mute.confirm": "നിശ്ശബ്ദമാക്കുക", "confirmations.redraft.confirm": "മായിച്ച് മാറ്റങ്ങൾ വരുത്തി വീണ്ടും എഴുതുക", "confirmations.unfollow.confirm": "പിന്തുടരുന്നത് നിര്‍ത്തുക", - "confirmations.unfollow.message": "നിങ്ങൾ {name} യെ പിന്തുടരുന്നത് നിർത്തുവാൻ തീർച്ചയായും തീരുമാനിച്ചുവോ?", "conversation.delete": "സംഭാഷണം മായിക്കുക", "conversation.mark_as_read": "വായിച്ചതായി അടയാളപ്പെടുത്തുക", "conversation.open": "സംഭാഷണം കാണുക", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index 28d3ae9ab37a64..6a762c15273a56 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -49,7 +49,6 @@ "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", "account.report": "@{name} ची तक्रार करा", - "account.requested": "Awaiting approval", "account.requested_follow": "{name} ने आपल्याला फॉलो करण्याची रिक्वेस्ट केली आहे", "account.share": "@{name} चे प्रोफाइल शेअर करा", "account.show_reblogs": "{name}चे सर्व बुस्ट्स दाखवा", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 2ffdf6cfd0b4bf..1530736f65bca9 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -58,7 +58,6 @@ "account.posts": "Hantaran", "account.posts_with_replies": "Hantaran dan balasan", "account.report": "Laporkan @{name}", - "account.requested": "Menunggu kelulusan. Klik untuk batalkan permintaan ikut", "account.requested_follow": "{name} has requested to follow you", "account.share": "Kongsi profil @{name}", "account.show_reblogs": "Tunjukkan galakan daripada @{name}", @@ -223,8 +222,6 @@ "confirmations.redraft.message": "Adakah anda pasti anda ingin memadam hantaran ini dan gubal semula? Sukaan dan galakan akan hilang, dan balasan ke hantaran asal akan menjadi yatim.", "confirmations.redraft.title": "Padam & gubah semula hantaran?", "confirmations.unfollow.confirm": "Nyahikut", - "confirmations.unfollow.message": "Adakah anda pasti anda ingin nyahikuti {name}?", - "confirmations.unfollow.title": "Berhenti mengikut pengguna?", "content_warning.hide": "Sorok hantaran", "content_warning.show": "Tunjuk saja", "content_warning.show_more": "Tunjuk lebih", diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index a65eab5aa708cd..56da51abe93580 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -51,7 +51,6 @@ "account.posts": "ပို့စ်များ", "account.posts_with_replies": "ပို့စ်နှင့် ရီပလိုင်းများ", "account.report": "တိုင်ကြားမည်{name}", - "account.requested": "ခွင့်ပြုချက်စောင့်နေသည်။ ဖော်လိုးပယ်ဖျက်ရန်နှိပ်ပါ", "account.requested_follow": "{name} က သင့်ကို စောင့်ကြည့်ရန် တောင်းဆိုထားသည်", "account.share": "{name}၏ပရိုဖိုင်ကိုမျှဝေပါ", "account.show_reblogs": "@{name} မှ မျှ၀ေမှုများကို ပြပါ\n", @@ -153,7 +152,6 @@ "confirmations.redraft.confirm": "ဖျက်ပြီး ပြန်လည်ရေးမည်။", "confirmations.redraft.message": "သင် ဒီပိုစ့်ကိုဖျက်ပြီး ပြန်တည်းဖြတ်မှာ သေချာပြီလား။ ကြယ်ပွင့်​တွေ နဲ့ ပြန်မျှ​ဝေမှု​တွေကိုဆုံးရှုံးမည်။မူရင်းပို့စ်ဆီကို ပြန်စာ​တွေမှာလည်း​ \nပိုစ့်ကို​တွေ့ရမည်မဟုတ်​တော့ပါ။.", "confirmations.unfollow.confirm": "စောင့်ကြည့်ခြင်းအား ပယ်ဖျက်မည်", - "confirmations.unfollow.message": "{name} ကို စောင်ကြည့်ခြင်းအား ပယ်ဖျက်မည်မှာသေချာပါသလား။", "conversation.delete": "ဤစကားပြောဆိုမှုကို ဖျက်ပစ်မည်", "conversation.mark_as_read": "ဖတ်ပြီးသားအဖြစ်မှတ်ထားပါ", "conversation.open": "Conversation ကိုကြည့်မည်", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 9bce13a8dadef2..a4ffafbe5cc542 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -28,6 +28,7 @@ "account.disable_notifications": "停止佇 {name} PO文ê時通知我", "account.domain_blocking": "Teh封鎖ê網域", "account.edit_profile": "編輯個人資料", + "account.edit_profile_short": "編輯", "account.enable_notifications": "佇 {name} PO文ê時通知我", "account.endorse": "用個人資料推薦對方", "account.familiar_followers_many": "Hōo {name1}、{name2},kap {othersCount, plural, other {其他 lí熟似ê # ê lâng}} 跟tuè", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "無PO文", "account.follow": "跟tuè", "account.follow_back": "Tuè tńg去", + "account.follow_back_short": "Tuè tńg去", + "account.follow_request": "請求跟tuè lí", + "account.follow_request_cancel": "取消跟tuè", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "請求", "account.followers": "跟tuè lí ê", "account.followers.empty": "Tsit ê用者iáu bô lâng跟tuè。", "account.followers_counter": "Hōo {count, plural, other {{count} ê lâng}}跟tuè", @@ -70,7 +76,6 @@ "account.posts_with_replies": "PO文kap回應", "account.remove_from_followers": "Kā {name} tuì跟tuè lí ê ê內底suá掉", "account.report": "檢舉 @{name}", - "account.requested": "Teh等待審查。Tshi̍h tsi̍t-ē 通取消跟tuè請求", "account.requested_follow": "{name} 請求跟tuè lí", "account.requests_to_follow_you": "請求跟tuè lí", "account.share": "分享 @{name} ê個人資料", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Tsit ê動作bē當復原。", "confirmations.revoke_quote.title": "Kám beh thâi掉PO文?", "confirmations.unfollow.confirm": "取消跟tuè", - "confirmations.unfollow.message": "Lí kám確定無愛跟tuè {name}?", - "confirmations.unfollow.title": "Kám beh取消跟tuè tsit ê用者?", "content_warning.hide": "Am-khàm PO文", "content_warning.show": "Mā tio̍h顯示", "content_warning.show_more": "其他內容", @@ -864,8 +867,14 @@ "status.cancel_reblog_private": "取消轉送", "status.cannot_quote": "Lí bô允准引用tsit篇PO文。", "status.cannot_reblog": "Tsit篇PO文bē當轉送", - "status.context.load_new_replies": "有新ê回應", - "status.context.loading": "Leh檢查其他ê回應", + "status.contains_quote": "包含引用", + "status.context.loading": "載入其他回應", + "status.context.loading_error": "Bē當載入新回應", + "status.context.loading_more": "載入其他回應", + "status.context.loading_success": "回應lóng載入ah", + "status.context.more_replies_found": "Tshuē-tio̍h其他回應", + "status.context.retry": "Koh試", + "status.context.show": "顯示", "status.continued_thread": "接續ê討論線", "status.copy": "Khóo-pih PO文ê連結", "status.delete": "Thâi掉", @@ -902,6 +911,7 @@ "status.quote_error.revoked": "PO文已經hōo作者thâi掉", "status.quote_followers_only": "Kan-ta tuè我ê ē當引用PO文", "status.quote_manual_review": "作者ē hōo lâng人工審核", + "status.quote_noun": "引用", "status.quote_policy_change": "改通引用ê lâng", "status.quote_post_author": "引用 @{name} ê PO文ah", "status.quote_private": "私人PO文bē當引用", diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index a0b99c597385e0..c686ccfbb579ec 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -57,7 +57,6 @@ "account.posts_with_replies": "पोस्ट र जवाफहरू", "account.remove_from_followers": "{name}लाई फलोअरहरूबाट हटाउनुहोस्", "account.report": "@{name}लाई रिपोर्ट गर्नुहोस्", - "account.requested": "स्वीकृतिको पर्खाइमा। फलो अनुरोध रद्द गर्न क्लिक गर्नुहोस्", "account.requested_follow": "{name} ले तपाईंलाई फलो गर्न अनुरोध गर्नुभएको छ", "account.share": "@{name} को प्रोफाइल सेयर गर्नुहोस्", "account.show_reblogs": "@{name} को बूस्टहरू देखाउनुहोस्", @@ -144,8 +143,6 @@ "confirmations.redraft.confirm": "मेटाएर पुन: ड्राफ्ट गर्नुहोस्", "confirmations.redraft.title": "पोस्ट मेटाएर पुन: ड्राफ्ट गर्ने?", "confirmations.unfollow.confirm": "अनफलो गर्नुहोस्", - "confirmations.unfollow.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाइँ {name}लाई अनफलो गर्न चाहनुहुन्छ?", - "confirmations.unfollow.title": "प्रयोगकर्तालाई अनफलो गर्ने?", "disabled_account_banner.account_settings": "खाता सेटिङहरू", "empty_column.direct": "तपाईंले अहिलेसम्म कुनै पनि प्राइवेट उल्लेखहरू प्राप्त गर्नुभएको छैन। तपाईंले कुनै प्राप्त गरेपछि त्यो यहाँ देखिनेछ।", "empty_column.follow_requests": "तपाईंले अहिलेसम्म कुनै पनि फलो अनुरोधहरू प्राप्त गर्नुभएको छैन। तपाईंले कुनै प्राप्त गरेपछि त्यो यहाँ देखिनेछ।", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index f61f7469d52684..6be053b5cfa7de 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Geen melding meer geven wanneer @{name} een bericht plaatst", "account.domain_blocking": "Server geblokkeerd", "account.edit_profile": "Profiel bewerken", + "account.edit_profile_short": "Bewerken", "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst", "account.endorse": "Op profiel weergeven", "account.familiar_followers_many": "Gevolgd door {name1}, {name2} en {othersCount, plural, one {één ander bekend account} other {# andere bekende accounts}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Geen berichten", "account.follow": "Volgen", "account.follow_back": "Terugvolgen", + "account.follow_back_short": "Terugvolgen", + "account.follow_request": "Volgverzoek", + "account.follow_request_cancel": "Verzoek annuleren", + "account.follow_request_cancel_short": "Annuleren", + "account.follow_request_short": "Verzoek", "account.followers": "Volgers", "account.followers.empty": "Deze gebruiker heeft nog geen volgers of heeft deze verborgen.", "account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Reacties", "account.remove_from_followers": "{name} als volger verwijderen", "account.report": "@{name} rapporteren", - "account.requested": "Wachten op goedkeuring. Klik om het volgverzoek te annuleren", "account.requested_follow": "{name} wil jou graag volgen", "account.requests_to_follow_you": "Wil jou graag volgen", "account.share": "Account van @{name} delen", @@ -242,7 +247,7 @@ "confirmations.quiet_post_quote_info.dismiss": "Herinner me er niet nogmaals aan", "confirmations.quiet_post_quote_info.got_it": "Begrepen", "confirmations.quiet_post_quote_info.message": "Wanneer je een minder openbaar bericht citeert, verschijnt jouw bericht niet onder trends.", - "confirmations.quiet_post_quote_info.title": "Minder openbare berichten citeren", + "confirmations.quiet_post_quote_info.title": "Berichten die minder openbaar zijn citeren", "confirmations.redraft.confirm": "Verwijderen en herschrijven", "confirmations.redraft.message": "Weet je zeker dat je dit bericht wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op het originele bericht raak je kwijt.", "confirmations.redraft.title": "Bericht verwijderen en herschrijven?", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Bericht verwijderen", "confirmations.revoke_quote.message": "Deze actie kan niet ongedaan worden gemaakt.", "confirmations.revoke_quote.title": "Bericht verwijderen?", + "confirmations.unblock.confirm": "Deblokkeren", + "confirmations.unblock.title": "{name} deblokkeren?", "confirmations.unfollow.confirm": "Ontvolgen", - "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?", - "confirmations.unfollow.title": "Gebruiker ontvolgen?", + "confirmations.unfollow.title": "{name} ontvolgen?", + "confirmations.withdraw_request.confirm": "Verzoek intrekken", + "confirmations.withdraw_request.title": "Verzoek intrekken om {name} te volgen?", "content_warning.hide": "Bericht verbergen", "content_warning.show": "Alsnog tonen", "content_warning.show_more": "Meer tonen", @@ -865,8 +873,13 @@ "status.cannot_quote": "Je bent niet gemachtigd om dit bericht te citeren", "status.cannot_reblog": "Dit bericht kan niet geboost worden", "status.contains_quote": "Bevat citaat", - "status.context.load_new_replies": "Nieuwe reacties beschikbaar", - "status.context.loading": "Op nieuwe reacties aan het controleren", + "status.context.loading": "Meer reacties laden", + "status.context.loading_error": "Kon geen nieuwe reacties laden", + "status.context.loading_more": "Meer reacties laden", + "status.context.loading_success": "Alle reacties zijn geladen", + "status.context.more_replies_found": "Meer reacties gevonden", + "status.context.retry": "Opnieuw proberen", + "status.context.show": "Tonen", "status.continued_thread": "Vervolg van gesprek", "status.copy": "Link naar bericht kopiëren", "status.delete": "Verwijderen", @@ -910,6 +923,8 @@ "status.quote_private": "Citeren van berichten aan alleen volgers is niet mogelijk", "status.quotes": "{count, plural, one {citaat} other {citaten}}", "status.quotes.empty": "Niemand heeft dit bericht nog geciteerd. Wanneer iemand dat doet, wordt dat hier getoond.", + "status.quotes.local_other_disclaimer": "Citaten afgewezen door de auteur worden niet getoond.", + "status.quotes.remote_other_disclaimer": "Alleen citaten van {domain} worden hier gegarandeerd weergegeven. Citaten afgewezen door de auteur worden niet getoond.", "status.read_more": "Meer lezen", "status.reblog": "Boosten", "status.reblog_or_quote": "Boosten of citeren", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index caab03e7cd4f71..b9ef0e491c71f4 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Slutt å varsle meg når @{name} skriv innlegg", "account.domain_blocking": "Blokkerer domenet", "account.edit_profile": "Rediger profil", + "account.edit_profile_short": "Rediger", "account.enable_notifications": "Varsle meg når @{name} skriv innlegg", "account.endorse": "Vis på profilen", "account.familiar_followers_many": "Fylgt av {name1}, {name2}, og {othersCount, plural, one {ein annan du kjenner} other {# andre du kjenner}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Ingen innlegg", "account.follow": "Fylg", "account.follow_back": "Fylg tilbake", + "account.follow_back_short": "Fylg tilbake", + "account.follow_request": "Folk som vil fylgja deg", + "account.follow_request_cancel": "Avbrit førespurnaden", + "account.follow_request_cancel_short": "Avbryt", + "account.follow_request_short": "Førespurnad", "account.followers": "Fylgjarar", "account.followers.empty": "Ingen fylgjer denne brukaren enno.", "account.followers_counter": "{count, plural, one {{counter} fylgjar} other {{counter} fylgjarar}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Tut og svar", "account.remove_from_followers": "Fjern {name} frå fylgjarane dine", "account.report": "Rapporter @{name}", - "account.requested": "Ventar på aksept. Klikk for å avbryta fylgjeførespurnaden", "account.requested_follow": "{name} har bedt om å få fylgja deg", "account.requests_to_follow_you": "Folk som vil fylgja deg", "account.share": "Del @{name} sin profil", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Fjern innlegget", "confirmations.revoke_quote.message": "Du kan ikkje angra denne handlinga.", "confirmations.revoke_quote.title": "Fjern innlegget?", + "confirmations.unblock.confirm": "Opphev blokkeringa", + "confirmations.unblock.title": "Opphev blokkeringa av {name}?", "confirmations.unfollow.confirm": "Slutt å fylgja", - "confirmations.unfollow.message": "Er du sikker på at du vil slutta å fylgja {name}?", - "confirmations.unfollow.title": "Slutt å fylgja brukaren?", + "confirmations.unfollow.title": "Slutt å fylgja {name}?", + "confirmations.withdraw_request.confirm": "Trekk attende førespurnad", + "confirmations.withdraw_request.title": "Trekkja tilbake førespurnaden om å fylgja {name}?", "content_warning.hide": "Gøym innlegg", "content_warning.show": "Vis likevel", "content_warning.show_more": "Vis meir", @@ -864,8 +872,14 @@ "status.cancel_reblog_private": "Opphev framheving", "status.cannot_quote": "Du har ikkje løyve til å sitera dette innlegget", "status.cannot_reblog": "Du kan ikkje framheva dette innlegget", - "status.context.load_new_replies": "Nye svar finst", - "status.context.loading": "Ser etter fleire svar", + "status.contains_quote": "Inneheld eit sitat", + "status.context.loading": "Lastar fleire svar", + "status.context.loading_error": "Kunne ikkje lasta nye svar", + "status.context.loading_more": "Lastar fleire svar", + "status.context.loading_success": "Alle svara er lasta", + "status.context.more_replies_found": "Fann fleire svar", + "status.context.retry": "Prøv om att", + "status.context.show": "Vis", "status.continued_thread": "Framhald til tråden", "status.copy": "Kopier lenke til status", "status.delete": "Slett", @@ -895,17 +909,22 @@ "status.quote": "Siter", "status.quote.cancel": "Avbryt siteringa", "status.quote_error.filtered": "Gøymt på grunn av eitt av filtra dine", + "status.quote_error.limited_account_hint.action": "Vis likevel", + "status.quote_error.limited_account_hint.title": "Denne kontoen har vorte skjult av moderatorane på {domain}.", "status.quote_error.not_available": "Innlegget er ikkje tilgjengeleg", "status.quote_error.pending_approval": "Innlegget ventar", "status.quote_error.pending_approval_popout.body": "På Mastodon kan du kontrollera om folk får sitera deg. Innlegget ditt ventar medan me ventar på at opphavspersonen godkjenner det.", "status.quote_error.revoked": "Innlegget er sletta av skribenten", "status.quote_followers_only": "Berre fylgjarar kan sitera dette innlegget", "status.quote_manual_review": "Skribenten ser gjennom manuelt", + "status.quote_noun": "Sitat", "status.quote_policy_change": "Byt kven som kan sitera", "status.quote_post_author": "Siterte eit innlegg av @{name}", "status.quote_private": "Du kan ikkje sitera private innlegg", "status.quotes": "{count, plural, one {sitat} other {sitat}}", "status.quotes.empty": "Ingen har sitert dette innlegget enno. Når nokon gje det, vil det dukka opp her.", + "status.quotes.local_other_disclaimer": "Sitat som skribenten avviser, vil ikkje syna.", + "status.quotes.remote_other_disclaimer": "Berre sitat frå {domain} er garanterte å syna her. Sitat som skribenten har avvist, vil ikkje syna.", "status.read_more": "Les meir", "status.reblog": "Framhev", "status.reblog_or_quote": "Framhev eller siter", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index fa03560194ca4d..2ef8a7085a6e20 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Innlegg med svar", "account.remove_from_followers": "Fjern {name} fra følgere", "account.report": "Rapporter @{name}", - "account.requested": "Venter på godkjennelse. Klikk for å avbryte forespørselen", "account.requested_follow": "{name} har bedt om å få følge deg", "account.requests_to_follow_you": "Forespørsler om å følge deg", "account.share": "Del @{name} sin profil", @@ -239,8 +238,6 @@ "confirmations.remove_from_followers.message": "{name} vil ikke lenger følge deg. Er du sikker på at du vil fortsette?", "confirmations.remove_from_followers.title": "Fjern følger?", "confirmations.unfollow.confirm": "Slutt å følge", - "confirmations.unfollow.message": "Er du sikker på at du vil slutte å følge {name}?", - "confirmations.unfollow.title": "Slutt å følge bruker?", "content_warning.hide": "Skjul innlegg", "content_warning.show": "Vis likevel", "content_warning.show_more": "Vis mer", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 0d87228ea1a412..cdd109a7153a6e 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -50,7 +50,6 @@ "account.posts": "Tuts", "account.posts_with_replies": "Tuts e responsas", "account.report": "Senhalar @{name}", - "account.requested": "Invitacion mandada. Clicatz per anullar", "account.requested_follow": "{name} a demandat a vos sègre", "account.share": "Partejar lo perfil a @{name}", "account.show_reblogs": "Mostrar los partatges de @{name}", @@ -153,7 +152,6 @@ "confirmations.mute.confirm": "Rescondre", "confirmations.redraft.confirm": "Escafar & tornar formular", "confirmations.unfollow.confirm": "Quitar de sègre", - "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", "conversation.delete": "Suprimir la conversacion", "conversation.mark_as_read": "Marcar coma legida", "conversation.open": "Veire la conversacion", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index cd20e4bce04656..760dff557fb0c7 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -56,7 +56,6 @@ "account.posts": "ਪੋਸਟਾਂ", "account.posts_with_replies": "ਪੋਸਟਾਂ ਅਤੇ ਜਵਾਬ", "account.report": "{name} ਬਾਰੇ ਰਿਪੋਰਟ ਕਰੋ", - "account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ", "account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ", "account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ", "account.statuses_counter": "{count, plural, one {{counter} ਪੋਸਟ} other {{counter} ਪੋਸਟਾਂ}}", @@ -170,8 +169,6 @@ "confirmations.remove_from_followers.confirm": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਓ", "confirmations.remove_from_followers.title": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?", "confirmations.unfollow.confirm": "ਅਣ-ਫ਼ਾਲੋ", - "confirmations.unfollow.message": "ਕੀ ਤੁਸੀਂ {name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?", - "confirmations.unfollow.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?", "content_warning.hide": "ਪੋਸਟ ਨੂੰ ਲੁਕਾਓ", "content_warning.show": "ਕਿਵੇਂ ਵੀ ਵੇਖਾਓ", "content_warning.show_more": "ਹੋਰ ਵੇਖਾਓ", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index ab1062ed8b0dd2..d121bea1ae1a94 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Wpisy i odpowiedzi", "account.remove_from_followers": "Usuń {name} z obserwujących", "account.report": "Zgłoś @{name}", - "account.requested": "Oczekująca prośba, kliknij aby anulować", "account.requested_follow": "{name} chce cię zaobserwować", "account.requests_to_follow_you": "Prośby o obserwowanie", "account.share": "Udostępnij profil @{name}", @@ -249,8 +248,6 @@ "confirmations.revoke_quote.message": "Tej akcji nie można cofnąć.", "confirmations.revoke_quote.title": "Usuń post?", "confirmations.unfollow.confirm": "Nie obserwuj", - "confirmations.unfollow.message": "Czy na pewno nie chcesz obserwować {name}?", - "confirmations.unfollow.title": "Cofnąć obserwację?", "content_warning.hide": "Ukryj wpis", "content_warning.show": "Pokaż mimo to", "content_warning.show_more": "Pokaż więcej", @@ -849,8 +846,6 @@ "status.bookmark": "Dodaj zakładkę", "status.cancel_reblog_private": "Cofnij podbicie", "status.cannot_reblog": "Ten wpis nie może zostać podbity", - "status.context.load_new_replies": "Dostępne są nowe odpowiedzi", - "status.context.loading": "Sprawdzanie kolejnych odpowiedzi", "status.continued_thread": "Ciąg dalszy wątku", "status.copy": "Skopiuj odnośnik do wpisu", "status.delete": "Usuń", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index cc46fb4304d29b..5457e91dbab9f1 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Com respostas", "account.remove_from_followers": "Remover {name} dos seguidores", "account.report": "Denunciar @{name}", - "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação", "account.requested_follow": "{name} quer te seguir", "account.requests_to_follow_you": "Pediu para seguir você", "account.share": "Compartilhar perfil de @{name}", @@ -251,8 +250,6 @@ "confirmations.revoke_quote.message": "Essa ação não pode ser desfeita.", "confirmations.revoke_quote.title": "Remover publicação?", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "Você tem certeza de que deseja deixar de seguir {name}?", - "confirmations.unfollow.title": "Deixar de seguir o usuário?", "content_warning.hide": "Ocultar post", "content_warning.show": "Mostrar mesmo assim", "content_warning.show_more": "Mostrar mais", @@ -855,8 +852,6 @@ "status.bookmark": "Salvar", "status.cancel_reblog_private": "Desfazer boost", "status.cannot_reblog": "Este toot não pode receber boost", - "status.context.load_new_replies": "Novas respostas disponíveis", - "status.context.loading": "Verificando mais respostas", "status.continued_thread": "Continuação da conversa", "status.copy": "Copiar link", "status.delete": "Excluir", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index af7c70b9fba6bc..751db6fdd4d11c 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Parar de me notificar das publicações de @{name}", "account.domain_blocking": "A bloquear domínio", "account.edit_profile": "Editar perfil", + "account.edit_profile_short": "Editar", "account.enable_notifications": "Notificar-me das publicações de @{name}", "account.endorse": "Destacar no perfil", "account.familiar_followers_many": "Seguido por {name1}, {name2} e {othersCount, plural,one {mais uma pessoa que conhece} other {# outras pessoas que conhece}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Sem publicações", "account.follow": "Seguir", "account.follow_back": "Seguir também", + "account.follow_back_short": "Seguir de volta", + "account.follow_request": "Pedir para seguir", + "account.follow_request_cancel": "Cancelar pedido", + "account.follow_request_cancel_short": "Cancelar", + "account.follow_request_short": "Pedido", "account.followers": "Seguidores", "account.followers.empty": "Ainda ninguém segue este utilizador.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Publicações e respostas", "account.remove_from_followers": "Remover {name} dos seguidores", "account.report": "Denunciar @{name}", - "account.requested": "A aguardar aprovação. Clica para cancelar o pedido para seguir", "account.requested_follow": "{name} pediu para seguir-te", "account.requests_to_follow_you": "Pediu para seguir-te", "account.share": "Partilhar o perfil @{name}", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Esta ação é irreversível.", "confirmations.revoke_quote.title": "Remover publicação?", "confirmations.unfollow.confirm": "Deixar de seguir", - "confirmations.unfollow.message": "De certeza que queres deixar de seguir {name}?", - "confirmations.unfollow.title": "Deixar de seguir o utilizador?", "content_warning.hide": "Ocultar publicação", "content_warning.show": "Mostrar mesmo assim", "content_warning.show_more": "Mostrar mais", @@ -864,8 +867,14 @@ "status.cancel_reblog_private": "Retirar impulso", "status.cannot_quote": "Não lhe é permitido citar esta publicação", "status.cannot_reblog": "Esta publicação não pode ser impulsionada", - "status.context.load_new_replies": "Novas respostas disponíveis", - "status.context.loading": "A verificar por mais respostas", + "status.contains_quote": "Contém citação", + "status.context.loading": "A carregar mais respostas", + "status.context.loading_error": "Não foi possível carregar novas respostas", + "status.context.loading_more": "A carregar mais respostas", + "status.context.loading_success": "Todas as respostas carregadas", + "status.context.more_replies_found": "Foram encontradas mais respostas", + "status.context.retry": "Repetir", + "status.context.show": "Mostrar", "status.continued_thread": "Continuação da conversa", "status.copy": "Copiar hiperligação da publicação", "status.delete": "Eliminar", @@ -903,6 +912,7 @@ "status.quote_error.revoked": "Publicação removida pelo autor", "status.quote_followers_only": "Apenas seguidores podem citar esta publicação", "status.quote_manual_review": "O autor vai proceder a uma revisão manual", + "status.quote_noun": "Citação", "status.quote_policy_change": "Alterar quem pode citar", "status.quote_post_author": "Citou uma publicação de @{name}", "status.quote_private": "Publicações privadas não podem ser citadas", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index ce3dde8eeaf207..9bf8150ba04378 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -55,7 +55,6 @@ "account.posts": "Postări", "account.posts_with_replies": "Postări și răspunsuri", "account.report": "Raportează pe @{name}", - "account.requested": "Se așteaptă aprobarea. Apasă pentru a anula cererea de urmărire", "account.requested_follow": "{name} A cerut să vă urmărească", "account.share": "Distribuie profilul lui @{name}", "account.show_reblogs": "Afișează distribuirile de la @{name}", @@ -178,7 +177,6 @@ "confirmations.mute.confirm": "Ignoră", "confirmations.redraft.confirm": "Șterge și scrie din nou", "confirmations.unfollow.confirm": "Dezabonează-te", - "confirmations.unfollow.message": "Ești sigur că vrei să te dezabonezi de la {name}?", "conversation.delete": "Șterge conversația", "conversation.mark_as_read": "Marchează ca citit", "conversation.open": "Vizualizează conversația", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index a22268bb62bce2..5571a68d7601a3 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Посты и ответы", "account.remove_from_followers": "Убрать {name} из подписчиков", "account.report": "Пожаловаться на @{name}", - "account.requested": "Ожидает подтверждения. Нажмите для отмены запроса", "account.requested_follow": "{name} отправил(а) вам запрос на подписку", "account.requests_to_follow_you": "Отправил(а) вам запрос на подписку", "account.share": "Поделиться профилем @{name}", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Это действие нельзя будет отменить.", "confirmations.revoke_quote.title": "Удалить публикацию?", "confirmations.unfollow.confirm": "Отписаться", - "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?", - "confirmations.unfollow.title": "Отписаться?", "content_warning.hide": "Скрыть пост", "content_warning.show": "Всё равно показать", "content_warning.show_more": "Развернуть", @@ -863,8 +860,6 @@ "status.bookmark": "Добавить в закладки", "status.cancel_reblog_private": "Отменить продвижение", "status.cannot_reblog": "Этот пост не может быть продвинут", - "status.context.load_new_replies": "Доступны новые ответы", - "status.context.loading": "Проверяем, есть ли ещё ответы", "status.continued_thread": "Продолжение предыдущего поста", "status.copy": "Скопировать ссылку на пост", "status.delete": "Удалить", diff --git a/app/javascript/mastodon/locales/ry.json b/app/javascript/mastodon/locales/ry.json index 20cc8e8e4eb708..9426a9c5c07662 100644 --- a/app/javascript/mastodon/locales/ry.json +++ b/app/javascript/mastodon/locales/ry.json @@ -51,7 +51,6 @@ "account.posts": "Публикації", "account.posts_with_replies": "Публикації тай удповіді", "account.report": "Скарговати ся на {name}", - "account.requested": "Чекат ся на пудтвердженя. Нажміт убы удмінити запрос на слідованя", "account.requested_follow": "Хосновач {name} просит ся пудписати ся на вас", "account.share": "Пошырити профіл хосновача {name}", "account.show_reblogs": "Указати друленя уд {name}", diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index b9c13df0581965..e7352f1c807adb 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -46,7 +46,6 @@ "account.posts": "पत्राणि", "account.posts_with_replies": "पत्राणि प्रत्युत्तराणि च", "account.report": "आविद्यताम् @{name}", - "account.requested": "स्वीकृतिः प्रतीक्ष्यते । नश्यतामित्यस्मिन्नुद्यतां निराकर्तुम् ।", "account.requested_follow": "{name} त्वामनुसर्तुमयाचीत्", "account.share": "@{name} मित्रस्य विवरणं विभाज्यताम्", "account.show_reblogs": "@{name} मित्रस्य प्रकाशनानि दृश्यन्ताम्", @@ -137,7 +136,6 @@ "confirmations.mute.confirm": "निःशब्दम्", "confirmations.redraft.confirm": "मार्जय पुनश्च लिख्यताम्", "confirmations.unfollow.confirm": "अनुसरणं नश्यताम्", - "confirmations.unfollow.message": "निश्चयेनैवाऽनुसरणं नश्यतां {name} मित्रस्य?", "conversation.delete": "वार्तालापं मार्जय", "conversation.mark_as_read": "पठितमित्यङ्क्यताम्", "conversation.open": "वार्तालापो दृश्यताम्", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index d126379384efd4..254af6a6019422 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -69,7 +69,6 @@ "account.posts_with_replies": "Publicatziones e rispostas", "account.remove_from_followers": "Cantzella a {name} dae is sighiduras", "account.report": "Signala @{name}", - "account.requested": "Abetende s'aprovatzione. Incarca pro annullare sa rechesta de sighidura", "account.requested_follow": "{name} at dimandadu de ti sighire", "account.requests_to_follow_you": "Rechestas de sighidura", "account.share": "Cumpartzi su profilu de @{name}", @@ -218,8 +217,6 @@ "confirmations.redraft.message": "Seguru chi boles cantzellare e torrare a fàghere custa publicatzione? As a pèrdere is preferidos e is cumpartziduras, e is rispostas a su messàgiu originale ant a abarrare òrfanas.", "confirmations.redraft.title": "Boles cantzellare e torrare a iscrìere sa publicatzione?", "confirmations.unfollow.confirm": "Non sigas prus", - "confirmations.unfollow.message": "Seguru chi non boles sighire prus a {name}?", - "confirmations.unfollow.title": "Boles tzessare de sighire s'utente?", "content_warning.hide": "Cua sa publicatzione", "content_warning.show": "Ammustra·dda su pròpiu", "conversation.delete": "Cantzella arresonada", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index 52445feced9435..354c3393ed7d83 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -44,7 +44,6 @@ "account.posts": "Posts", "account.posts_with_replies": "Posts an repones", "account.report": "Clype @{name}", - "account.requested": "Haudin fir approval. Chap tae cancel follae request", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Shaw heezes frae @{name}", "account.unblock": "Undingie @{name}", @@ -141,7 +140,6 @@ "confirmations.mute.confirm": "Wheesht", "confirmations.redraft.confirm": "Delete an stert anew", "confirmations.unfollow.confirm": "Unfollae", - "confirmations.unfollow.message": "Ye shuir thit ye'r wantin tae unfollae {name}?", "conversation.delete": "Delete the conversation", "conversation.mark_as_read": "Mairk as seen", "conversation.open": "Luik at conversation", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 107ef3f1cf6901..e326d3fa6a780c 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -68,7 +68,6 @@ "account.posts_with_replies": "ලිපි සහ පිළිතුරු", "account.remove_from_followers": "අනුගාමිකයින්ගෙන් {name} ඉවත් කරන්න", "account.report": "@{name} වාර්තා කරන්න", - "account.requested": "අනුමැතිය බලාපොරොත්තුවෙන්. අනුගමනය කිරීමේ ඉල්ලීම අවලංගු කිරීමට ක්ලික් කරන්න.", "account.requested_follow": "{name} ඔබව අනුගමනය කිරීමට ඉල්ලා ඇත.", "account.requests_to_follow_you": "ඔබව අනුගමනය කිරීමට ඉල්ලීම්", "account.share": "@{name} ගේ පැතිකඩ බෙදාගන්න", @@ -237,8 +236,6 @@ "confirmations.remove_from_followers.message": "{name} ඔබව අනුගමනය කිරීම නවත්වනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍ය බව විශ්වාසද?", "confirmations.remove_from_followers.title": "අනුගාමිකයා ඉවත් කරන්නද?", "confirmations.unfollow.confirm": "අනුගමනය නොකරන්න", - "confirmations.unfollow.message": "ඔබට {name}අනුගමනය කිරීම නවත්වන්න අවශ්‍ය බව ඔබට විශ්වාසද?", - "confirmations.unfollow.title": "පරිශීලකයා අනුගමනය නොකරන්නද?", "content_warning.hide": "සටහන සඟවන්න", "content_warning.show": "කෙසේ වෙතත් පෙන්වන්න", "content_warning.show_more": "තවත් පෙන්වන්න", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 771f52618c4390..ffc3697393178b 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -64,7 +64,6 @@ "account.posts": "Príspevky", "account.posts_with_replies": "Príspevky a odpovede", "account.report": "Nahlásiť @{name}", - "account.requested": "Čaká na schválenie. Žiadosť zrušíte kliknutím sem", "account.requested_follow": "{name} vás chce sledovať", "account.share": "Zdieľaj profil @{name}", "account.show_reblogs": "Zobrazovať zdieľania od @{name}", @@ -214,8 +213,6 @@ "confirmations.redraft.title": "Vymazať a prepísať príspevok?", "confirmations.remove_from_followers.confirm": "Odstrániť nasledovateľa", "confirmations.unfollow.confirm": "Prestať sledovať", - "confirmations.unfollow.message": "Určite chcete prestať sledovať {name}?", - "confirmations.unfollow.title": "Prestať sledovať užívateľa?", "content_warning.hide": "Skryť príspevok", "content_warning.show": "Aj tak zobraziť", "content_warning.show_more": "Ukázať viac", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 9127605dbafed5..43f6c511d37edb 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -55,7 +55,6 @@ "account.posts": "Objave", "account.posts_with_replies": "Objave in odgovori", "account.report": "Prijavi @{name}", - "account.requested": "Čakanje na odobritev. Kliknite, da prekličete prošnjo za sledenje", "account.requested_follow": "{name} vam želi slediti", "account.share": "Deli profil osebe @{name}", "account.show_reblogs": "Pokaži izpostavitve osebe @{name}", @@ -219,8 +218,6 @@ "confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati to objavo in jo preoblikovati? Izkazi priljubljenosti in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.", "confirmations.redraft.title": "Želite izbrisati in preoblikovati objavo?", "confirmations.unfollow.confirm": "Ne sledi več", - "confirmations.unfollow.message": "Ali ste prepričani, da ne želite več slediti {name}?", - "confirmations.unfollow.title": "Želite nehati spremljati uporabnika?", "content_warning.hide": "Skrij objavo", "content_warning.show": "Vseeno pokaži", "content_warning.show_more": "Pokaži več", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 56db9a5ac3645c..3f61ff0a29c33c 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Mesazhe dhe përgjigje", "account.remove_from_followers": "Hiqe {name} nga ndjekësit", "account.report": "Raportojeni @{name}", - "account.requested": "Në pritje të miratimit. Që të anuloni kërkesën për ndjekje, klikojeni", "account.requested_follow": "{name} ka kërkuar t’ju ndjekë", "account.requests_to_follow_you": "Kërkesa për t’ju ndjekur", "account.share": "Ndajeni profilin e @{name} me të tjerët", @@ -241,8 +240,6 @@ "confirmations.remove_from_followers.message": "{name} do të reshtë së ndjekuri ju. Jeni i sigurt se doni të vazhdohet?", "confirmations.remove_from_followers.title": "Të hiqet ndjekësi?", "confirmations.unfollow.confirm": "Resht së ndjekuri", - "confirmations.unfollow.message": "Jeni i sigurt se doni të mos ndiqet më {name}?", - "confirmations.unfollow.title": "Të ndalet ndjekja e përdoruesit?", "content_warning.hide": "Fshihe postimin", "content_warning.show": "Shfaqe, sido qoftë", "content_warning.show_more": "Shfaq më tepër", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 8cb2e30cb5f82e..f91044899e15c7 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -54,7 +54,6 @@ "account.posts": "Objave", "account.posts_with_replies": "Objave i odgovori", "account.report": "Prijavi @{name}", - "account.requested": "Čekanje odobrenja. Kliknite za otkazivanje zahteva za praćenje", "account.requested_follow": "{name} je zatražio da vas prati", "account.share": "Podeli profil korisnika @{name}", "account.show_reblogs": "Prikaži podržavanja od korisnika @{name}", @@ -172,7 +171,6 @@ "confirmations.redraft.confirm": "Izbriši i prepravi", "confirmations.redraft.message": "Da li ste sigurni da želite da izbrišete ovu objavu i da je prepravite? Podržavanja i oznake kao omiljenih će biti izgubljeni, a odgovori će ostati bez originalne objave.", "confirmations.unfollow.confirm": "Otprati", - "confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?", "conversation.delete": "Izbriši razgovor", "conversation.mark_as_read": "Označi kao pročitano", "conversation.open": "Prikaži razgovor", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 485109105b8e7b..117c25949dea35 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -54,7 +54,6 @@ "account.posts": "Објаве", "account.posts_with_replies": "Објаве и одговори", "account.report": "Пријави @{name}", - "account.requested": "Чекање одобрења. Кликните за отказивање захтева за праћење", "account.requested_follow": "{name} је затражио да вас прати", "account.share": "Подели профил корисника @{name}", "account.show_reblogs": "Прикажи подржавања од корисника @{name}", @@ -172,7 +171,6 @@ "confirmations.redraft.confirm": "Избриши и преправи", "confirmations.redraft.message": "Да ли сте сигурни да желите да избришете ову објаву и да је преправите? Подржавања и ознаке као омиљених ће бити изгубљени, а одговори ће остати без оригиналне објаве.", "confirmations.unfollow.confirm": "Отпрати", - "confirmations.unfollow.message": "Да ли сте сигурни да желите да отпратите корисника {name}?", "conversation.delete": "Избриши разговор", "conversation.mark_as_read": "Означи као прочитано", "conversation.open": "Прикажи разговор", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 3bbe6434ed8e39..e34188904199e6 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "Inlägg och svar", "account.remove_from_followers": "Ta bort {name} från följare", "account.report": "Rapportera @{name}", - "account.requested": "Inväntar godkännande. Klicka för att ta tillbaka din begäran om att få följa", "account.requested_follow": "{name} har begärt att följa dig", "account.requests_to_follow_you": "Fråga om att följa dig", "account.share": "Dela @{name}s profil", @@ -250,8 +249,6 @@ "confirmations.revoke_quote.message": "Denna åtgärd kan inte ångras.", "confirmations.revoke_quote.title": "Ta bort inlägg?", "confirmations.unfollow.confirm": "Avfölj", - "confirmations.unfollow.message": "Är du säker på att du vill avfölja {name}?", - "confirmations.unfollow.title": "Avfölj användare?", "content_warning.hide": "Dölj inlägg", "content_warning.show": "Visa ändå", "content_warning.show_more": "Visa mer", @@ -843,8 +840,6 @@ "status.bookmark": "Bokmärk", "status.cancel_reblog_private": "Sluta boosta", "status.cannot_reblog": "Detta inlägg kan inte boostas", - "status.context.load_new_replies": "Nya svar finns", - "status.context.loading": "Letar efter fler svar", "status.continued_thread": "Fortsatt tråd", "status.copy": "Kopiera inläggslänk", "status.delete": "Radera", diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json index 55913be9246493..709b0f9e891273 100644 --- a/app/javascript/mastodon/locales/szl.json +++ b/app/javascript/mastodon/locales/szl.json @@ -19,7 +19,6 @@ "account.mute": "Wycisz @{name}", "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", - "account.requested": "Awaiting approval", "account_note.placeholder": "Click to add a note", "column.pins": "Pinned toot", "community.column_settings.media_only": "Media only", diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json index 10d461e9e83481..7664174c17aded 100644 --- a/app/javascript/mastodon/locales/ta.json +++ b/app/javascript/mastodon/locales/ta.json @@ -37,7 +37,6 @@ "account.posts": "டூட்டுகள்", "account.posts_with_replies": "Toots மற்றும் பதில்கள்", "account.report": "@{name} -ஐப் புகாரளி", - "account.requested": "ஒப்புதலுக்காகக் காத்திருக்கிறது. பின்தொடரும் கோரிக்கையை நீக்க அழுத்தவும்", "account.share": "@{name} உடைய விவரத்தை பகிர்", "account.show_reblogs": "காட்டு boosts இருந்து @{name}", "account.unblock": "@{name} மீது தடை நீக்குக", @@ -129,7 +128,6 @@ "confirmations.mute.confirm": "அமைதியாக்கு", "confirmations.redraft.confirm": "பதிவை நீக்கி மறுவரைவு செய்", "confirmations.unfollow.confirm": "விலகு", - "confirmations.unfollow.message": "{name}-ஐப் பின்தொடர்வதை நிச்சயமாக நீங்கள் நிறுத்த விரும்புகிறீர்களா?", "conversation.delete": "உரையாடலை அழி", "conversation.mark_as_read": "படிக்கபட்டதாகக் குறி", "conversation.open": "உரையாடலைக் காட்டு", diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json index 3799455050865d..507894ba142b76 100644 --- a/app/javascript/mastodon/locales/tai.json +++ b/app/javascript/mastodon/locales/tai.json @@ -8,7 +8,6 @@ "account.mention": "Thê-khí @{name}", "account.posts": "Huah-siann", "account.posts_with_replies": "Huah-siann kah huê-ìng", - "account.requested": "Tán-thāi phue-tsún", "account_note.placeholder": "Tiám tsi̍t-ē ka-thiam pī-tsù", "column.pins": "Tah thâu-tsîng ê huah-siann", "community.column_settings.media_only": "Kan-na muî-thé", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index cbdea417b71543..c4442f74361d94 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -24,7 +24,6 @@ "account.posts": "టూట్లు", "account.posts_with_replies": "టూట్లు మరియు ప్రత్యుత్తరములు", "account.report": "@{name}పై ఫిర్యాదుచేయు", - "account.requested": "ఆమోదం కోసం వేచి ఉంది. అభ్యర్థనను రద్దు చేయడానికి క్లిక్ చేయండి", "account.share": "@{name} యొక్క ప్రొఫైల్ను పంచుకోండి", "account.show_reblogs": "@{name}నుంచి బూస్ట్ లను చూపించు", "account.unblock": "@{name}పై బ్లాక్ ను తొలగించు", @@ -75,7 +74,6 @@ "confirmations.mute.confirm": "మ్యూట్ చేయి", "confirmations.redraft.confirm": "తొలగించు & తిరగరాయు", "confirmations.unfollow.confirm": "అనుసరించవద్దు", - "confirmations.unfollow.message": "{name}ను మీరు ఖచ్చితంగా అనుసరించవద్దనుకుంటున్నారా?", "embed.instructions": "దిగువ కోడ్ను కాపీ చేయడం ద్వారా మీ వెబ్సైట్లో ఈ స్టేటస్ ని పొందుపరచండి.", "embed.preview": "అది ఈ క్రింది విధంగా కనిపిస్తుంది:", "emoji_button.activity": "కార్యకలాపాలు", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index a1fade41ca66c0..1fcaad9b64dd17 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -64,7 +64,6 @@ "account.posts": "โพสต์", "account.posts_with_replies": "โพสต์และการตอบกลับ", "account.report": "รายงาน @{name}", - "account.requested": "กำลังรอการอนุมัติ คลิกเพื่อยกเลิกคำขอติดตาม", "account.requested_follow": "{name} ได้ขอติดตามคุณ", "account.share": "แชร์โปรไฟล์ของ @{name}", "account.show_reblogs": "แสดงการดันจาก @{name}", @@ -226,8 +225,6 @@ "confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบโพสต์นี้แล้วร่างโพสต์ใหม่? รายการโปรดและการดันจะสูญหาย และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน", "confirmations.redraft.title": "ลบแล้วร่างโพสต์ใหม่?", "confirmations.unfollow.confirm": "เลิกติดตาม", - "confirmations.unfollow.message": "คุณแน่ใจหรือไม่ว่าต้องการเลิกติดตาม {name}?", - "confirmations.unfollow.title": "เลิกติดตามผู้ใช้?", "content_warning.hide": "ซ่อนโพสต์", "content_warning.show": "แสดงต่อไป", "content_warning.show_more": "แสดงเพิ่มเติม", diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index dbc6b4b871c73d..d21058a67b02d7 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -70,7 +70,6 @@ "account.posts_with_replies": "toki ale", "account.remove_from_followers": "sijelo kute la o weka e sijelo \"{name}\".", "account.report": "jan @{name} la o toki e ike tawa lawa", - "account.requested": "jan ni o ken e kute sina", "account.requested_follow": "jan {name} li wile kute e sina", "account.requests_to_follow_you": "jan ni li wile kute e sina", "account.share": "o pana e lipu jan @{name}", @@ -248,8 +247,6 @@ "confirmations.revoke_quote.confirm": "o weka e toki tan lipu Mastodon", "confirmations.revoke_quote.title": "sina wile weka ala weka e toki?", "confirmations.unfollow.confirm": "o kute ala", - "confirmations.unfollow.message": "sina o wile ala wile pini kute e jan {name}?", - "confirmations.unfollow.title": "sina wile ala wile pini kute?", "content_warning.hide": "o len", "content_warning.show": "o lukin a", "content_warning.show_more": "o lukin", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index d4f5993b85784a..96fa8ae98637ec 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -28,6 +28,7 @@ "account.disable_notifications": "@{name} kişisinin gönderi bildirimlerini kapat", "account.domain_blocking": "Alan adını engelleme", "account.edit_profile": "Profili düzenle", + "account.edit_profile_short": "Düzenle", "account.enable_notifications": "@{name} kişisinin gönderi bildirimlerini aç", "account.endorse": "Profilimde öne çıkar", "account.familiar_followers_many": "{name1}, {name2}, {othersCount, plural, one {# diğer} other {# diğer}} bildiğiniz kişi tarafından takip ediliyor", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Gönderi yok", "account.follow": "Takip et", "account.follow_back": "Geri takip et", + "account.follow_back_short": "Geri takip et", + "account.follow_request": "Takip isteği gönder", + "account.follow_request_cancel": "İsteği iptal et", + "account.follow_request_cancel_short": "İptal", + "account.follow_request_short": "İstek", "account.followers": "Takipçi", "account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.", "account.followers_counter": "{count, plural, one {{counter} takipçi} other {{counter} takipçi}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Gönderiler ve yanıtlar", "account.remove_from_followers": "{name} takipçilerinden kaldır", "account.report": "@{name} adlı kişiyi bildir", - "account.requested": "Onay bekleniyor. Takip isteğini iptal etmek için tıklayın", "account.requested_follow": "{name} size takip isteği gönderdi", "account.requests_to_follow_you": "Size takip isteği gönderdi", "account.share": "@{name} adlı kişinin profilini paylaş", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Gönderiyi kaldır", "confirmations.revoke_quote.message": "Bu işlem geri alınamaz.", "confirmations.revoke_quote.title": "Gönderiyi silmek ister misiniz?", + "confirmations.unblock.confirm": "Engeli kaldır", + "confirmations.unblock.title": "{name} için engel kaldırılsın mı?", "confirmations.unfollow.confirm": "Takibi bırak", - "confirmations.unfollow.message": "{name} adlı kullanıcıyı takibi bırakmak istediğinden emin misin?", - "confirmations.unfollow.title": "Kullanıcıyı takipten çık?", + "confirmations.unfollow.title": "{name} takipten çıkılsın mı?", + "confirmations.withdraw_request.confirm": "İsteği geri çek", + "confirmations.withdraw_request.title": "{name} takip isteği geri çekilsin mi?", "content_warning.hide": "Gönderiyi gizle", "content_warning.show": "Yine de göster", "content_warning.show_more": "Daha fazla göster", @@ -864,8 +872,14 @@ "status.cancel_reblog_private": "Yeniden paylaşımı geri al", "status.cannot_quote": "Bu gönderiyi alıntılamaya izniniz yok", "status.cannot_reblog": "Bu gönderi yeniden paylaşılamaz", - "status.context.load_new_replies": "Yeni yanıtlar mevcut", - "status.context.loading": "Daha fazla yanıt için kontrol ediliyor", + "status.contains_quote": "Alıntı içeriyor", + "status.context.loading": "Daha fazla yanıt yükleniyor", + "status.context.loading_error": "Yeni yanıtlar yüklenemiyor", + "status.context.loading_more": "Daha fazla yanıt yükleniyor", + "status.context.loading_success": "Tüm yanıtlar yüklendi", + "status.context.more_replies_found": "Daha fazla yanıt bulundu", + "status.context.retry": "Yeniden dene", + "status.context.show": "Göster", "status.continued_thread": "Devam eden akış", "status.copy": "Gönderi bağlantısını kopyala", "status.delete": "Sil", @@ -903,6 +917,7 @@ "status.quote_error.revoked": "Gönderi yazarı tarafından kaldırıldı", "status.quote_followers_only": "Sadece takipçiler bu gönderiyi alıntılayabilir", "status.quote_manual_review": "Yazar manuel olarak gözden geçirecek", + "status.quote_noun": "Alıntı", "status.quote_policy_change": "Kimin alıntı yapabileceğini değiştirin", "status.quote_post_author": "@{name} adlı kullanıcının bir gönderisini alıntıladı", "status.quote_private": "Özel gönderiler alıntılanamaz", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 4c454c37fb23be..6123b1999703d8 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -49,7 +49,6 @@ "account.posts": "Язма", "account.posts_with_replies": "Язма һәм җавап", "account.report": "@{name} кулланучыга шикаять итү", - "account.requested": "Awaiting approval", "account.requested_follow": "{name} Сезгә язылу соравын җиберде", "account.share": "@{name} профиле белән уртаклашу", "account.show_reblogs": "Күрсәтергә көчәйтү нче @{name}", @@ -147,7 +146,6 @@ "confirmations.mute.confirm": "Тавышсыз", "confirmations.redraft.confirm": "Бетерү & эшкәртү", "confirmations.unfollow.confirm": "Язылуны туктату", - "confirmations.unfollow.message": "Сез язылудан баш тартырга телисез {name}?", "conversation.delete": "Сөйләшүне бетерегез", "conversation.mark_as_read": "Укылганны Ничек билгеләргә", "conversation.open": "Сөйләшүне карау", diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json index 527457ca378a60..c87d8ee5dd82f5 100644 --- a/app/javascript/mastodon/locales/ug.json +++ b/app/javascript/mastodon/locales/ug.json @@ -13,7 +13,6 @@ "account.posts": "يازما", "account.posts_with_replies": "يازما ۋە ئىنكاس", "account.report": "@{name} نى پاش قىل", - "account.requested": "Awaiting approval", "account_note.placeholder": "چېكىلسە ئىزاھات قوشىدۇ", "column.pins": "چوققىلانغان يازما", "community.column_settings.media_only": "ۋاسىتەلا", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 913ef41d74495b..45f5b3f4580b9a 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -61,7 +61,6 @@ "account.posts": "Дописи", "account.posts_with_replies": "Дописи й відповіді", "account.report": "Поскаржитися на @{name}", - "account.requested": "Очікує підтвердження. Натисніть, щоб скасувати запит на підписку", "account.requested_follow": "{name} надсилає запит на стеження", "account.share": "Поділитися профілем @{name}", "account.show_reblogs": "Показати поширення від @{name}", @@ -231,8 +230,6 @@ "confirmations.revoke_quote.message": "Цю дію не можна скасувати.", "confirmations.revoke_quote.title": "Видалити публікацію?", "confirmations.unfollow.confirm": "Відписатися", - "confirmations.unfollow.message": "Ви впевнені, що хочете відписатися від {name}?", - "confirmations.unfollow.title": "Відписатися від користувача?", "content_warning.hide": "Сховати допис", "content_warning.show": "Усе одно показати", "content_warning.show_more": "Показати більше", @@ -814,8 +811,6 @@ "status.bookmark": "Додати до закладок", "status.cancel_reblog_private": "Скасувати поширення", "status.cannot_reblog": "Цей допис не може бути поширений", - "status.context.load_new_replies": "Доступні нові відповіді", - "status.context.loading": "Перевірка додаткових відповідей", "status.continued_thread": "Продовження у потоці", "status.copy": "Копіювати посилання на допис", "status.delete": "Видалити", diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index b1db5d819f6ce5..0448f69c63cbbe 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -46,7 +46,6 @@ "account.posts": "ٹوٹ", "account.posts_with_replies": "ٹوٹ اور جوابات", "account.report": "@{name} اطلاع کریں", - "account.requested": "منظوری کا منتظر۔ درخواستِ پیروی منسوخ کرنے کیلئے کلک کریں", "account.requested_follow": "{name} آپ کو فالو کرنا چھاتا ہے۔", "account.share": "@{name} کے مشخص کو بانٹیں", "account.show_reblogs": "@{name} کی افزائشات کو دکھائیں", @@ -123,7 +122,6 @@ "confirmations.mute.confirm": "خاموش", "confirmations.redraft.confirm": "ڈیلیٹ کریں اور دوبارہ ڈرافٹ کریں", "confirmations.unfollow.confirm": "پیروی ترک کریں", - "confirmations.unfollow.message": "کیا واقعی آپ {name} کی پیروی ترک کرنا چاہتے ہیں؟", "conversation.delete": "گفتگو کو ڈیلیٹ کریں", "conversation.mark_as_read": "بطور پڑھا ہوا دکھائیں", "conversation.open": "گفتگو دیکھیں", diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json index d4b37a32f7b7bc..2a8b817576534e 100644 --- a/app/javascript/mastodon/locales/uz.json +++ b/app/javascript/mastodon/locales/uz.json @@ -44,7 +44,6 @@ "account.posts": "Postlar", "account.posts_with_replies": "Xabarlar va javoblar", "account.report": "@{name} xabar berish", - "account.requested": "Tasdiqlash kutilmoqda. Kuzatuv soʻrovini bekor qilish uchun bosing", "account.requested_follow": "{name} sizni kuzatishni soʻradi", "account.share": "@{name} profilini ulashing", "account.show_reblogs": "@{name} dan bootlarni ko'rsatish", @@ -134,7 +133,6 @@ "confirmations.mute.confirm": "Ovozsiz", "confirmations.redraft.confirm": "O'chirish va qayta loyihalash", "confirmations.unfollow.confirm": "Kuzatishni To'xtatish", - "confirmations.unfollow.message": "Haqiqatan ham {name} obunasini bekor qilmoqchimisiz?", "conversation.delete": "Suhbatni o'chirish", "conversation.mark_as_read": "O'qilgan deb belgilang", "conversation.open": "Suhbatni ko'rish", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 99a5dc9051bf3d..08b224e3d64c44 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -28,6 +28,7 @@ "account.disable_notifications": "Tắt thông báo khi @{name} đăng tút", "account.domain_blocking": "Máy chủ đang chủ", "account.edit_profile": "Sửa hồ sơ", + "account.edit_profile_short": "Sửa", "account.enable_notifications": "Nhận thông báo khi @{name} đăng tút", "account.endorse": "Nêu bật người này", "account.familiar_followers_many": "Theo dõi bởi {name1}, {name2} và {othersCount, plural, other {# người khác mà bạn biết}}", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "Chưa có tút", "account.follow": "Theo dõi", "account.follow_back": "Theo dõi lại", + "account.follow_back_short": "Theo dõi lại", + "account.follow_request": "Yêu cầu theo dõi", + "account.follow_request_cancel": "Hủy yêu cầu", + "account.follow_request_cancel_short": "Hủy bỏ", + "account.follow_request_short": "Yêu cầu", "account.followers": "Người theo dõi", "account.followers.empty": "Chưa có người theo dõi nào.", "account.followers_counter": "{count, plural, other {{counter} Người theo dõi}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "Trả lời", "account.remove_from_followers": "Xóa người theo dõi {name}", "account.report": "Báo cáo @{name}", - "account.requested": "Đang chờ chấp thuận. Nhấp vào đây để hủy yêu cầu theo dõi", "account.requested_follow": "{name} yêu cầu theo dõi bạn", "account.requests_to_follow_you": "Yêu cầu theo dõi bạn", "account.share": "Chia sẻ @{name}", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "Gỡ tút", "confirmations.revoke_quote.message": "Hành động này không thể hoàn tác.", "confirmations.revoke_quote.title": "Gỡ tút?", + "confirmations.unblock.confirm": "Bỏ chặn", + "confirmations.unblock.title": "Bỏ chặn {name}?", "confirmations.unfollow.confirm": "Bỏ theo dõi", - "confirmations.unfollow.message": "Bạn có chắc muốn bỏ theo dõi {name}?", - "confirmations.unfollow.title": "Bỏ theo dõi", + "confirmations.unfollow.title": "Bỏ theo dõi {name}?", + "confirmations.withdraw_request.confirm": "Thu hồi yêu cầu", + "confirmations.withdraw_request.title": "Thu hồi yêu cầu theo dõi {name}?", "content_warning.hide": "Thu gọn", "content_warning.show": "Vẫn xem", "content_warning.show_more": "Mở rộng", @@ -865,8 +873,13 @@ "status.cannot_quote": "Bạn không được phép trích dẫn tút này", "status.cannot_reblog": "Không thể đăng lại tút này", "status.contains_quote": "Chứa trích dẫn", - "status.context.load_new_replies": "Có những trả lời mới", - "status.context.loading": "Kiểm tra nhiều trả lời hơn", + "status.context.loading": "Tải thêm các trả lời", + "status.context.loading_error": "Không thể tải những trả lời mới", + "status.context.loading_more": "Tải thêm các trả lời", + "status.context.loading_success": "Đã tải toàn bộ trả lời", + "status.context.more_replies_found": "Có trả lời mới", + "status.context.retry": "Thử lại", + "status.context.show": "Hiện", "status.continued_thread": "Tiếp tục chủ đề", "status.copy": "Sao chép URL", "status.delete": "Xóa", @@ -910,6 +923,8 @@ "status.quote_private": "Không thể trích dẫn nhắn riêng", "status.quotes": "{count, plural, other {trích dẫn}}", "status.quotes.empty": "Tút này chưa có ai trích dẫn. Nếu có, nó sẽ hiển thị ở đây.", + "status.quotes.local_other_disclaimer": "Những trích dẫn bị tác giả từ chối sẽ không được hiển thị.", + "status.quotes.remote_other_disclaimer": "Chỉ những trích dẫn từ {domain} mới được hiển thị ở đây. Những trích dẫn bị tác giả từ chối sẽ không được hiển thị.", "status.read_more": "Đọc tiếp", "status.reblog": "Đăng lại", "status.reblog_or_quote": "Đăng lại hoặc trích dẫn", diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json index ff37d75a07c42b..7317cd7282f6e2 100644 --- a/app/javascript/mastodon/locales/zgh.json +++ b/app/javascript/mastodon/locales/zgh.json @@ -14,7 +14,6 @@ "account.muted": "ⵉⵜⵜⵓⵥⵉⵥⵏ", "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", - "account.requested": "Awaiting approval", "account.share": "ⴱⴹⵓ ⵉⴼⵔⵙ ⵏ @{name}", "account.unfollow": "ⴽⴽⵙ ⴰⴹⴼⴼⵓⵕ", "account_note.placeholder": "Click to add a note", @@ -49,7 +48,6 @@ "confirmations.logout.message": "ⵉⵙ ⵏⵉⵜ ⵜⵅⵙⴷ ⴰⴷ ⵜⴼⴼⵖⴷ?", "confirmations.mute.confirm": "ⵥⵥⵉⵥⵏ", "confirmations.unfollow.confirm": "ⴽⴽⵙ ⴰⴹⴼⴼⵓⵕ", - "confirmations.unfollow.message": "ⵉⵙ ⵏⵉⵜ ⵜⵅⵙⴷ ⴰⴷ ⵜⴽⴽⵙⴷ ⴰⴹⴼⴼⵓⵕ ⵉ {name}?", "conversation.delete": "ⴽⴽⵙ ⴰⵎⵙⴰⵡⴰⵍ", "conversation.with": "ⴰⴽⴷ {names}", "embed.instructions": "Embed this status on your website by copying the code below.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index c69e72eced44ee..1397f28f930d1e 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -28,6 +28,7 @@ "account.disable_notifications": "当 @{name} 发布嘟文时不要通知我", "account.domain_blocking": "正在屏蔽中的域名", "account.edit_profile": "修改个人资料", + "account.edit_profile_short": "编辑", "account.enable_notifications": "当 @{name} 发布嘟文时通知我", "account.endorse": "在个人资料中推荐此用户", "account.familiar_followers_many": "被 {name1}、{name2}、及 {othersCount, plural, other {其他你认识的 # 人}} 关注", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "暂无嘟文", "account.follow": "关注", "account.follow_back": "回关", + "account.follow_back_short": "回关", + "account.follow_request": "请求关注", + "account.follow_request_cancel": "取消请求", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "请求", "account.followers": "关注者", "account.followers.empty": "目前无人关注此用户。", "account.followers_counter": "{count, plural, other {{counter} 关注者}}", @@ -70,7 +76,6 @@ "account.posts_with_replies": "嘟文和回复", "account.remove_from_followers": "从关注者中移除 {name}", "account.report": "举报 @{name}", - "account.requested": "正在等待对方同意。点击取消发送关注请求", "account.requested_follow": "{name} 向你发送了关注请求", "account.requests_to_follow_you": "请求关注你", "account.share": "分享 @{name} 的个人资料", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "移除嘟文", "confirmations.revoke_quote.message": "此操作无法撤销。", "confirmations.revoke_quote.title": "移除嘟文?", + "confirmations.unblock.confirm": "取消屏蔽", + "confirmations.unblock.title": "取消屏蔽 {name} 吗?", "confirmations.unfollow.confirm": "取消关注", - "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?", - "confirmations.unfollow.title": "确定要取消关注用户?", + "confirmations.unfollow.title": "取关 {name} 吗?", + "confirmations.withdraw_request.confirm": "撤回请求", + "confirmations.withdraw_request.title": "撤回关注 {name} 的请求吗?", "content_warning.hide": "隐藏嘟文", "content_warning.show": "仍要显示", "content_warning.show_more": "展开", @@ -862,11 +870,16 @@ "status.block": "屏蔽 @{name}", "status.bookmark": "添加到书签", "status.cancel_reblog_private": "取消转嘟", - "status.cannot_quote": "你无法引用此嘟文", + "status.cannot_quote": "你无权引用这条嘟文", "status.cannot_reblog": "不能转嘟这条嘟文", "status.contains_quote": "包含引用", - "status.context.load_new_replies": "有新回复", - "status.context.loading": "正在检查更多回复", + "status.context.loading": "正在加载更多回复", + "status.context.loading_error": "无法加载新回复", + "status.context.loading_more": "正在加载更多回复", + "status.context.loading_success": "已加载所有回复", + "status.context.more_replies_found": "已找到更多回复", + "status.context.retry": "重试", + "status.context.show": "显示", "status.continued_thread": "上接嘟文串", "status.copy": "复制嘟文链接", "status.delete": "删除", @@ -910,6 +923,8 @@ "status.quote_private": "不能引用私人嘟文", "status.quotes": "{count, plural, other {引用嘟文}}", "status.quotes.empty": "还没有人引用过此条嘟文。引用此嘟文的人会显示在这里。", + "status.quotes.local_other_disclaimer": "遭作者拒绝的引用将不会显示。", + "status.quotes.remote_other_disclaimer": "此处仅保证会显示来自 {domain} 的引用。遭作者拒绝的引用将不会显示。", "status.read_more": "查看更多", "status.reblog": "转嘟", "status.reblog_or_quote": "转嘟或引用", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 1637e1cf589219..3626f7da54aa38 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -65,7 +65,6 @@ "account.posts": "帖文", "account.posts_with_replies": "帖文與回覆", "account.report": "檢舉 @{name}", - "account.requested": "正在等待核准。按一下以取消追蹤請求", "account.requested_follow": "{name} 要求追蹤你", "account.share": "分享 @{name} 的個人檔案", "account.show_reblogs": "顯示 @{name} 的轉推", @@ -192,8 +191,6 @@ "confirmations.redraft.confirm": "刪除並編輯", "confirmations.redraft.message": "你確定要移除並重新起草這篇帖文嗎?你將會失去最愛和轉推,而回覆也會與原始帖文斷開連接。", "confirmations.unfollow.confirm": "取消追蹤", - "confirmations.unfollow.message": "真的不要繼續追蹤 {name} 了嗎?", - "confirmations.unfollow.title": "取消追蹤使用者?", "content_warning.hide": "隱藏嘟文", "content_warning.show": "仍要顯示", "content_warning.show_more": "顯示更多", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index e4f9dcea29bb52..5e340810941562 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -28,6 +28,7 @@ "account.disable_notifications": "取消來自 @{name} 嘟文的通知", "account.domain_blocking": "封鎖中網域", "account.edit_profile": "編輯個人檔案", + "account.edit_profile_short": "編輯", "account.enable_notifications": "當 @{name} 嘟文時通知我", "account.endorse": "於個人檔案推薦對方", "account.familiar_followers_many": "被 {name1}、{name2}、及 {othersCount, plural, other {其他您認識的 # 人}} 跟隨", @@ -40,6 +41,11 @@ "account.featured_tags.last_status_never": "沒有嘟文", "account.follow": "跟隨", "account.follow_back": "跟隨回去", + "account.follow_back_short": "跟隨回去", + "account.follow_request": "要求跟隨您", + "account.follow_request_cancel": "取消跟隨請求", + "account.follow_request_cancel_short": "取消", + "account.follow_request_short": "跟隨請求", "account.followers": "跟隨者", "account.followers.empty": "尚未有人跟隨這位使用者。", "account.followers_counter": "被 {count, plural, other {{count} 人}}跟隨", @@ -70,7 +76,6 @@ "account.posts_with_replies": "嘟文與回覆", "account.remove_from_followers": "自跟隨者中移除 {name}", "account.report": "檢舉 @{name}", - "account.requested": "正在等候審核。按一下以取消跟隨請求", "account.requested_follow": "{name} 要求跟隨您", "account.requests_to_follow_you": "要求跟隨您", "account.share": "分享 @{name} 的個人檔案", @@ -252,9 +257,12 @@ "confirmations.revoke_quote.confirm": "移除嘟文", "confirmations.revoke_quote.message": "此動作無法復原。", "confirmations.revoke_quote.title": "您是否確定移除嘟文?", + "confirmations.unblock.confirm": "解除封鎖", + "confirmations.unblock.title": "解除封鎖 {name}?", "confirmations.unfollow.confirm": "取消跟隨", - "confirmations.unfollow.message": "您確定要取消跟隨 {name} 嗎?", - "confirmations.unfollow.title": "是否取消跟隨該使用者?", + "confirmations.unfollow.title": "取消跟隨 {name}?", + "confirmations.withdraw_request.confirm": "收回跟隨請求", + "confirmations.withdraw_request.title": "收回對 {name} 之跟隨請求?", "content_warning.hide": "隱藏嘟文", "content_warning.show": "仍要顯示", "content_warning.show_more": "顯示更多", @@ -865,8 +873,13 @@ "status.cannot_quote": "您不被允許引用此嘟文", "status.cannot_reblog": "這則嘟文無法被轉嘟", "status.contains_quote": "包含引用嘟文", - "status.context.load_new_replies": "有新回嘟", - "status.context.loading": "正在檢查更多回嘟", + "status.context.loading": "讀取更多回嘟", + "status.context.loading_error": "無法讀取新回嘟", + "status.context.loading_more": "讀取更多回嘟", + "status.context.loading_success": "已讀取所有回嘟", + "status.context.more_replies_found": "已有更多回嘟", + "status.context.retry": "再試一次", + "status.context.show": "顯示", "status.continued_thread": "接續討論串", "status.copy": "複製嘟文連結", "status.delete": "刪除", @@ -910,6 +923,8 @@ "status.quote_private": "無法引用私人嘟文", "status.quotes": "{count, plural, other {# 則引用嘟文}}", "status.quotes.empty": "目前尚無人引用此嘟文。當有人引用時,它將於此顯示。", + "status.quotes.local_other_disclaimer": "被作者拒絕之引用嘟文將不被顯示。", + "status.quotes.remote_other_disclaimer": "僅有來自 {domain} 之引用嘟文保證將於此顯示。被作者拒絕之引用嘟文將不被顯示。", "status.read_more": "閱讀更多", "status.reblog": "轉嘟", "status.reblog_or_quote": "轉嘟或引用", diff --git a/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap b/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap index a579efa406b1ce..ea4561bc610c33 100644 --- a/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap +++ b/app/javascript/mastodon/utils/__tests__/__snapshots__/html-test.ts.snap @@ -26,9 +26,11 @@ exports[`html > htmlStringToComponents > handles nested elements 1`] = ` exports[`html > htmlStringToComponents > ignores empty text nodes 1`] = ` [

+ lorem ipsum +

, ] `; @@ -37,6 +39,7 @@ exports[`html > htmlStringToComponents > respects allowedTags option 1`] = ` [

lorem + dolor diff --git a/app/javascript/mastodon/utils/__tests__/html-test.ts b/app/javascript/mastodon/utils/__tests__/html-test.ts index 6c08cc7cbfc07b..6aacc396dc8873 100644 --- a/app/javascript/mastodon/utils/__tests__/html-test.ts +++ b/app/javascript/mastodon/utils/__tests__/html-test.ts @@ -48,7 +48,7 @@ describe('html', () => { const input = '

lorem ipsum

'; const onText = vi.fn((text: string) => text); html.htmlStringToComponents(input, { onText }); - expect(onText).toHaveBeenCalledExactlyOnceWith('lorem ipsum'); + expect(onText).toHaveBeenCalledExactlyOnceWith('lorem ipsum', {}); }); it('calls onElement callback', () => { @@ -61,6 +61,7 @@ describe('html', () => { expect(onElement).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ tagName: 'P' }), expect.arrayContaining(['lorem ipsum']), + {}, ); }); @@ -71,6 +72,7 @@ describe('html', () => { expect(onElement).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ tagName: 'P' }), expect.arrayContaining(['lorem ipsum']), + {}, ); expect(output).toMatchSnapshot(); }); @@ -88,15 +90,16 @@ describe('html', () => { 'href', 'https://example.com', 'a', + {}, ); - expect(onAttribute).toHaveBeenCalledWith('target', '_blank', 'a'); - expect(onAttribute).toHaveBeenCalledWith('rel', 'nofollow', 'a'); + expect(onAttribute).toHaveBeenCalledWith('target', '_blank', 'a', {}); + expect(onAttribute).toHaveBeenCalledWith('rel', 'nofollow', 'a', {}); }); it('respects allowedTags option', () => { const input = '

lorem ipsum dolor

'; const output = html.htmlStringToComponents(input, { - allowedTags: new Set(['p', 'em']), + allowedTags: { p: {}, em: {} }, }); expect(output).toMatchSnapshot(); }); diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index fc4448740f4080..c666e2c94d7866 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -1,4 +1,4 @@ -import initialState from '../initial_state'; +import { initialState } from '../initial_state'; export function isDevelopment() { if (typeof process !== 'undefined') @@ -12,11 +12,7 @@ export function isProduction() { else return import.meta.env.PROD; } -export type Features = - | 'modern_emojis' - | 'outgoing_quotes' - | 'fasp' - | 'http_message_signatures'; +export type Features = 'modern_emojis' | 'fasp' | 'http_message_signatures'; export function isFeatureEnabled(feature: Features) { return initialState?.features.includes(feature) ?? false; diff --git a/app/javascript/mastodon/utils/html.ts b/app/javascript/mastodon/utils/html.ts index 16863223007abf..971aefa6d16b4b 100644 --- a/app/javascript/mastodon/utils/html.ts +++ b/app/javascript/mastodon/utils/html.ts @@ -1,5 +1,7 @@ import React from 'react'; +import htmlConfig from '../../config/html-tags.json'; + // NB: This function can still return unsafe HTML export const unescapeHTML = (html: string) => { const wrapper = document.createElement('div'); @@ -10,64 +12,49 @@ export const unescapeHTML = (html: string) => { return wrapper.textContent; }; +interface AllowedTag { + /* True means allow, false disallows global attributes, string renames the attribute name for React. */ + attributes?: Record; + /* If false, the tag cannot have children. Undefined or true means allowed. */ + children?: boolean; +} + +type AllowedTagsType = { + [Tag in keyof React.ReactHTML]?: AllowedTag; +}; + +const globalAttributes: Record = htmlConfig.global; +const defaultAllowedTags: AllowedTagsType = htmlConfig.tags; + interface QueueItem { node: Node; parent: React.ReactNode[]; depth: number; } -interface Options { +export interface HTMLToStringOptions> { maxDepth?: number; - onText?: (text: string) => React.ReactNode; + onText?: (text: string, extra: Arg) => React.ReactNode; onElement?: ( element: HTMLElement, children: React.ReactNode[], + extra: Arg, ) => React.ReactNode; onAttribute?: ( name: string, value: string, tagName: string, + extra: Arg, ) => [string, unknown] | null; - allowedTags?: Set; + allowedTags?: AllowedTagsType; + extraArgs?: Arg; } -const DEFAULT_ALLOWED_TAGS: ReadonlySet = new Set([ - 'a', - 'abbr', - 'b', - 'blockquote', - 'br', - 'cite', - 'code', - 'del', - 'dfn', - 'dl', - 'dt', - 'em', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'hr', - 'i', - 'li', - 'ol', - 'p', - 'pre', - 'small', - 'span', - 'strong', - 'sub', - 'sup', - 'time', - 'u', - 'ul', -]); - -export function htmlStringToComponents( + +let uniqueIdCounter = 0; + +export function htmlStringToComponents>( htmlString: string, - options: Options = {}, + options: HTMLToStringOptions = {}, ) { const wrapper = document.createElement('template'); wrapper.innerHTML = htmlString; @@ -79,10 +66,11 @@ export function htmlStringToComponents( const { maxDepth = 10, - allowedTags = DEFAULT_ALLOWED_TAGS, + allowedTags = defaultAllowedTags, onAttribute, onElement, onText, + extraArgs = {} as Arg, } = options; while (queue.length > 0) { @@ -109,9 +97,9 @@ export function htmlStringToComponents( // Text can be added directly if it has any non-whitespace content. case Node.TEXT_NODE: { const text = node.textContent; - if (text && text.trim() !== '') { + if (text) { if (onText) { - parent.push(onText(text)); + parent.push(onText(text, extraArgs)); } else { parent.push(text); } @@ -127,7 +115,9 @@ export function htmlStringToComponents( } // If the tag is not allowed, skip it and its children. - if (!allowedTags.has(node.tagName.toLowerCase())) { + const tagName = node.tagName.toLowerCase(); + const tagInfo = allowedTags[tagName as keyof typeof allowedTags]; + if (!tagInfo) { continue; } @@ -137,7 +127,8 @@ export function htmlStringToComponents( // If onElement is provided, use it to create the element. if (onElement) { - const component = onElement(node, children); + const component = onElement(node, children, extraArgs); + // Check for undefined to allow returning null. if (component !== undefined) { element = component; @@ -147,25 +138,56 @@ export function htmlStringToComponents( // If the element wasn't created, use the default conversion. if (element === undefined) { const props: Record = {}; + props.key = `html-${uniqueIdCounter++}`; // Get the current key and then increment it. for (const attr of node.attributes) { + let name = attr.name.toLowerCase(); + + // Custom attribute handler. if (onAttribute) { const result = onAttribute( - attr.name, + name, attr.value, node.tagName.toLowerCase(), + extraArgs, ); if (result) { - const [name, value] = result; - props[name] = value; + const [cbName, value] = result; + props[cbName] = value; } } else { - props[attr.name] = attr.value; + // Check global attributes first, then tag-specific ones. + const globalAttr = globalAttributes[name]; + const tagAttr = tagInfo.attributes?.[name]; + + // Exit if neither global nor tag-specific attribute is allowed. + if (!globalAttr && !tagAttr) { + continue; + } + + // Rename if needed. + if (typeof tagAttr === 'string') { + name = tagAttr; + } else if (typeof globalAttr === 'string') { + name = globalAttr; + } + + let value: string | boolean | number = attr.value; + + // Handle boolean attributes. + if (value === 'true') { + value = true; + } else if (value === 'false') { + value = false; + } + + props[name] = value; } } + element = React.createElement( - node.tagName.toLowerCase(), + tagName, props, - children, + tagInfo.children !== false ? children : undefined, ); } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 316c714968c20c..3037ddfac9b3c5 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -13,6 +13,7 @@ $content-width: 840px; box-sizing: border-box; width: 100%; min-height: 100vh; + min-height: 100dvh; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); @@ -25,6 +26,7 @@ $content-width: 840px; .sidebar-wrapper { min-height: 100vh; + min-height: 100dvh; overflow: hidden; pointer-events: none; flex: 1 1 auto; @@ -163,7 +165,7 @@ $content-width: 840px; flex: 1 1 auto; } - @media screen and (max-width: $content-width + $sidebar-width) { + @media screen and (max-width: ($content-width + $sidebar-width)) { .sidebar-wrapper--empty { display: none; } @@ -1065,6 +1067,17 @@ a.name-tag, } } + &__action-bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + + &:not(.no-wrap) { + flex-wrap: wrap; + } + } + &__meta { padding: 0 15px; color: $dark-text-color; @@ -1081,10 +1094,8 @@ a.name-tag, } } - &__action-bar { - display: flex; - justify-content: space-between; - align-items: center; + &__actions { + margin-inline-start: auto; } &__permissions { diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 6937f1359c06d4..d70c556ad0dffb 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -98,6 +98,7 @@ body { &.layout-single-column { height: auto; min-height: 100vh; + min-height: 100dvh; overflow-y: scroll; } @@ -214,7 +215,8 @@ button { } & > noscript { - height: 100vh; + min-height: 100vh; + min-height: 100dvh; } } @@ -222,6 +224,7 @@ button { &, & > div { min-height: 100vh; + min-height: 100dvh; } } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b30a967667f566..d1fe7c68364d69 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1621,6 +1621,16 @@ } } } + + .no-reduce-motion &--highlighted-entry::before { + content: ''; + position: absolute; + inset: 0; + background: rgb(from $ui-highlight-color r g b / 20%); + opacity: 0; + animation: fade 0.7s reverse both 0.3s; + pointer-events: none; + } } .status__relative-time { @@ -2341,6 +2351,7 @@ position: relative; border-radius: var(--avatar-border-radius); background: var(--surface-background-color); + flex-shrink: 0; img { width: 100%; @@ -3039,11 +3050,14 @@ a.account__display-name { flex: 1 1 auto; flex-direction: row; justify-content: flex-start; - overflow-x: auto; position: relative; - &.unscrollable { - overflow-x: hidden; + .layout-multiple-columns & { + overflow-x: auto; + + &.unscrollable { + overflow-x: hidden; + } } &__panels { @@ -3054,6 +3068,7 @@ a.account__display-name { width: 100%; height: 100%; min-height: 100vh; + min-height: 100dvh; padding-bottom: env(safe-area-inset-bottom); &__pane { @@ -3227,6 +3242,29 @@ a.account__display-name { } } +.column__alert { + position: sticky; + bottom: 1rem; + z-index: 10; + box-sizing: border-box; + display: grid; + width: 100%; + max-width: 360px; + padding-inline: 10px; + margin-top: 1rem; + margin-inline: auto; + + @media (max-width: #{$mobile-menu-breakpoint - 1}) { + bottom: 4rem; + } + + & > * { + // Make all nested alerts occupy the same space + // rather than stack + grid-area: 1 / 1; + } +} + .ui { --mobile-bottom-nav-height: 55px; --last-content-item-border-width: 2px; @@ -3267,7 +3305,6 @@ a.account__display-name { .column, .drawer { flex: 1 1 100%; - overflow: hidden; } @media screen and (width > $mobile-breakpoint) { @@ -3377,6 +3414,7 @@ a.account__display-name { .columns-area__panels { min-height: 100vh; + min-height: 100dvh; gap: 0; } @@ -6680,7 +6718,10 @@ a.status-card { line-height: 24px; color: $primary-text-color; font-weight: 500; - margin-bottom: 8px; + + &:not(:only-child) { + margin-bottom: 8px; + } } strong { @@ -8837,47 +8878,6 @@ noscript { overflow: hidden; margin-inline-start: -2px; // aligns the pfp with content below - &__buttons { - display: flex; - align-items: center; - gap: 8px; - padding-top: 55px; - overflow: hidden; - - .button { - flex-shrink: 1; - white-space: nowrap; - min-width: 80px; - } - - .icon-button { - border: 1px solid var(--background-border-color); - border-radius: 4px; - box-sizing: content-box; - padding: 5px; - - .icon { - width: 24px; - height: 24px; - } - - &.copied { - border-color: $valid-value-color; - } - } - - .optional { - @container account-header (max-width: 372px) { - display: none; - } - - // Fallback for older browsers with no container queries support - @media screen and (max-width: (372px + 55px)) { - display: none; - } - } - } - &__name { margin-top: 16px; margin-bottom: 16px; @@ -8927,6 +8927,69 @@ noscript { } } + &__follow-button { + flex-grow: 1; + } + + &__buttons { + display: flex; + align-items: center; + gap: 8px; + + $button-breakpoint: 420px; + $button-fallback-breakpoint: #{$button-breakpoint} + 55px; + + &--desktop { + margin-top: 55px; + + @container (width < #{$button-breakpoint}) { + display: none; + } + + @supports (not (container-type: inline-size)) { + @media (max-width: #{$button-fallback-breakpoint}) { + display: none; + } + } + } + + &--mobile { + margin-block: 16px; + + @container (width >= #{$button-breakpoint}) { + display: none; + } + + @supports (not (container-type: inline-size)) { + @media (min-width: (#{$button-fallback-breakpoint} + 1px)) { + display: none; + } + } + } + + .button { + flex-shrink: 1; + white-space: nowrap; + min-width: 80px; + } + + .icon-button { + border: 1px solid var(--background-border-color); + border-radius: 4px; + box-sizing: content-box; + padding: 5px; + + .icon { + width: 24px; + height: 24px; + } + + &.copied { + border-color: $valid-value-color; + } + } + } + &__bio { .account__header__content { color: $primary-text-color; @@ -10848,6 +10911,21 @@ noscript { } } +.notification-bar__loading-indicator { + --spinner-size: 22px; + + position: relative; + height: var(--spinner-size); + width: var(--spinner-size); + margin-inline-start: 2px; + + svg { + color: $white; + height: var(--spinner-size); + width: var(--spinner-size); + } +} + .hashtag-header { border-bottom: 1px solid var(--background-border-color); padding: 15px; @@ -11322,6 +11400,7 @@ noscript { font-weight: 500; color: $primary-text-color; text-decoration: none; + min-width: 0; &:hover, &:focus, diff --git a/app/javascript/types/polymorphic.ts b/app/javascript/types/polymorphic.ts new file mode 100644 index 00000000000000..e58aa7b75eec4a --- /dev/null +++ b/app/javascript/types/polymorphic.ts @@ -0,0 +1,75 @@ +import { forwardRef } from 'react'; +import type { + ElementType, + ComponentPropsWithRef, + ForwardRefRenderFunction, + ReactElement, + Ref, + ForwardRefExoticComponent, +} from 'react'; + +// This complicated type file is based on the following posts: +// - https://www.tsteele.dev/posts/react-polymorphic-forwardref +// - https://www.kripod.dev/blog/behind-the-as-prop-polymorphism-done-well/ +// - https://github.com/radix-ui/primitives/blob/7101e7d6efb2bff13cc6761023ab85aeec73539e/packages/react/polymorphic/src/forwardRefWithAs.ts +// Whenever we upgrade to React 19 or later, we can remove all this because ref is a prop there. + +// Utils +interface AsProp { + as?: As; +} +type PropsOf = ComponentPropsWithRef; + +/** + * Extract the element instance type (e.g. HTMLButtonElement) from ComponentPropsWithRef: + * - For intrinsic elements, look up in JSX.IntrinsicElements + * - For components, infer from `ComponentPropsWithRef` + */ +type ElementRef = + As extends keyof React.JSX.IntrinsicElements + ? React.JSX.IntrinsicElements[As] extends { ref?: Ref } + ? Inst + : never + : ComponentPropsWithRef extends { ref?: Ref } + ? Inst + : never; + +/** + * Merge additional props with intrinsic/element props for `as`. + * Additional props win on conflicts. + */ +type PolymorphicProps< + As extends ElementType, + AdditionalProps extends object = object, +> = AdditionalProps & + AsProp & + Omit, keyof AdditionalProps | 'ref'>; + +/** + * Signature of a component created with `polymorphicForwardRef`. + */ +type PolymorphicWithRef< + DefaultAs extends ElementType, + AdditionalProps extends object = object, +> = ( + props: PolymorphicProps & { ref?: Ref> }, +) => ReactElement | null; + +/** + * The type of `polymorphicForwardRef`. + */ +type PolyRefFunction = < + DefaultAs extends ElementType, + AdditionalProps extends object = object, +>( + render: ForwardRefRenderFunction< + ElementRef, + PolymorphicProps + >, +) => PolymorphicWithRef & + ForwardRefExoticComponent>; + +/** + * Polymorphic `forwardRef` function. + */ +export const polymorphicForwardRef = forwardRef as PolyRefFunction; diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 7b49acd1197702..27dea05bf64313 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -9,7 +9,7 @@ def perform quoted_status = status_from_uri(object_uri) return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? - if Mastodon::Feature.outgoing_quotes_enabled? && StatusPolicy.new(@account, quoted_status).quote? + if StatusPolicy.new(@account, quoted_status).quote? accept_quote_request!(quoted_status) else reject_quote_request!(quoted_status) @@ -36,6 +36,9 @@ def accept_quote_request!(quoted_status) # Ensure the user is notified LocalNotificationWorker.perform_async(quoted_status.account_id, status.quote.id, 'Quote', 'quote') + + # Ensure local followers get to see the post updated with approval + DistributionWorker.perform_async(status.id, { 'update' => true, 'skip_notifications' => true }) end def import_instrument(quoted_status) diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index b8a3cdb57e36eb..54a62e7e9d22bf 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -41,15 +41,27 @@ def uri_for(target, group: false) case target.object_type when :person - target.instance_actor? ? instance_actor_url : account_url(target) + if target.instance_actor? + instance_actor_url + elsif target.numeric_ap_id? + ap_account_url(target.id) + else + account_url(target) + end when :conversation return group_context_url(target.id) if group context_url(target) unless target.parent_account_id.nil? || target.parent_status_id.nil? when :note, :comment, :activity - return activity_account_status_url(target.account, target) if target.reblog? + if target.account.numeric_ap_id? + return activity_ap_account_status_url(target.account, target) if target.reblog? + + ap_account_status_url(target.account.id, target) + else + return activity_account_status_url(target.account, target) if target.reblog? - account_status_url(target.account, target) + account_status_url(target.account, target) + end when :emoji emoji_url(target) when :emoji_reaction @@ -67,7 +79,7 @@ def approval_uri_for(quote, check_approval: true) end return if check_approval && !quote.accepted? - account_quote_authorization_url(quote.quoted_account, quote) + quote.quoted_account.numeric_ap_id? ? ap_account_quote_authorization_url(quote.quoted_account_id, quote) : account_quote_authorization_url(quote.quoted_account, quote) end def key_uri_for(target) @@ -78,6 +90,10 @@ def uri_for_username(username) account_url(username: username) end + def uri_for_account_id(id) + ap_account_url(id: id) + end + def generate_uri_for(_target) URI.join(root_url, 'payloads', SecureRandom.uuid) end @@ -85,7 +101,7 @@ def generate_uri_for(_target) def activity_uri_for(target) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - activity_account_status_url(target.account, target) + target.account.numeric_ap_id? ? activity_ap_account_status_url(target.account.id, target) : activity_account_status_url(target.account, target) end def context_uri_for(target, page_params = nil) @@ -97,7 +113,7 @@ def context_uri_for(target, page_params = nil) def replies_uri_for(target, page_params = nil) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - account_status_replies_url(target.account, target, page_params) + target.account.numeric_ap_id? ? ap_account_status_replies_url(target.account.id, target, page_params) : account_status_replies_url(target.account, target, page_params) end def references_uri_for(target, page_params = nil) @@ -109,43 +125,55 @@ def references_uri_for(target, page_params = nil) def likes_uri_for(target) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - account_status_likes_url(target.account, target) + target.account.numeric_ap_id? ? ap_account_status_likes_url(target.account.id, target) : account_status_likes_url(target.account, target) end def shares_uri_for(target) raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? - account_status_shares_url(target.account, target) + target.account.numeric_ap_id? ? ap_account_status_shares_url(target.account.id, target) : account_status_shares_url(target.account, target) end def following_uri_for(target, ...) raise ArgumentError, 'target must be a local account' unless target.local? - account_following_index_url(target, ...) + target.numeric_ap_id? ? ap_account_following_index_url(target.id, ...) : account_following_index_url(target, ...) end def followers_uri_for(target, ...) return target.followers_url.presence unless target.local? - account_followers_url(target, ...) + target.numeric_ap_id? ? ap_account_followers_url(target.id, ...) : account_followers_url(target, ...) end def collection_uri_for(target, ...) - raise NotImplementedError unless target.local? + raise ArgumentError, 'target must be a local account' unless target.local? - account_collection_url(target, ...) + target.numeric_ap_id? ? ap_account_collection_url(target.id, ...) : account_collection_url(target, ...) end def inbox_uri_for(target) - raise NotImplementedError unless target.local? + raise ArgumentError, 'target must be a local account' unless target.local? - target.instance_actor? ? instance_actor_inbox_url : account_inbox_url(target) + if target.instance_actor? + instance_actor_inbox_url + elsif target.numeric_ap_id? + ap_account_inbox_url(target.id) + else + account_inbox_url(target) + end end def outbox_uri_for(target, ...) - raise NotImplementedError unless target.local? + raise ArgumentError, 'target must be a local account' unless target.local? - target.instance_actor? ? instance_actor_outbox_url(...) : account_outbox_url(target, ...) + if target.instance_actor? + instance_actor_outbox_url(...) + elsif target.numeric_ap_id? + ap_account_outbox_url(target.id, ...) + else + account_outbox_url(target, ...) + end end # Primary audience of a status @@ -375,10 +403,9 @@ def uri_to_local_account_params(uri) path_params = Rails.application.routes.recognize_path(uri) - # TODO: handle numeric IDs case path_params[:controller] when 'accounts' - [:username, path_params[:username]] + path_params.key?(:username) ? [:username, path_params[:username]] : [:id, path_params[:id]] when 'instance_actors' [:id, -99] end diff --git a/app/models/account.rb b/app/models/account.rb index df66fa59be8978..97bf4854b86ae8 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -56,6 +56,7 @@ # avatar_file_size :integer # header_file_size :integer # attribution_domains :string default([]), is an Array +# id_scheme :integer default("username_ap_id") # class Account < ApplicationRecord @@ -112,6 +113,7 @@ class Account < ApplicationRecord enum :protocol, { ostatus: 0, activitypub: 1 } enum :suspension_origin, { local: 0, remote: 1 }, prefix: true enum :searchability, { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 }, suffix: :searchability + enum :id_scheme, { username_ap_id: 0, numeric_ap_id: 1 } validates :username, presence: true validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? } @@ -483,6 +485,7 @@ def emojis before_validation :prepare_contents, if: :local? before_create :generate_keys + before_create :set_id_scheme before_destroy :clean_feed_manager def ensure_keys! @@ -511,6 +514,12 @@ def generate_keys self.public_key = keypair.public_key.to_pem end + def set_id_scheme + return unless local? && Mastodon::Feature.numeric_ap_ids_enabled? + + self.id_scheme = :numeric_ap_id + end + def normalize_domain return if local? diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index 8ba65ee41f2c64..525ad0a516e56f 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -244,8 +244,9 @@ def remote_followers_hash(url) def local_followers_hash Rails.cache.fetch("followers_hash:#{id}:local") do digest = "\x00" * 32 - followers.where(domain: nil).pluck_each(:username) do |username| - Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username))) + followers.where(domain: nil).pluck_each(:id_scheme, :id, :username) do |id_scheme, id, username| + uri = id_scheme == 'numeric_ap_id' ? ActivityPub::TagManager.instance.uri_for_account_id(id) : ActivityPub::TagManager.instance.uri_for_username(username) + Xorcist.xor!(digest, Digest::SHA256.digest(uri)) end digest.unpack1('H*') end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 304bde460f4e60..c5e3a230d8fb05 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -137,14 +137,16 @@ class Form::AdminSettings ).freeze DESCRIPTION_LIMIT = 200 + DOMAIN_BLOCK_AUDIENCES = %w(disabled users all).freeze + REGISTRATION_MODES = %w(open approved none).freeze attr_accessor(*KEYS) - validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) } - validates :site_contact_username, presence: true, existing_username: true, if: -> { defined?(@site_contact_username) } + validates :registrations_mode, inclusion: { in: REGISTRATION_MODES }, if: -> { defined?(@registrations_mode) } + validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) } validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) } - validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) } - validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) } + validates :show_domain_blocks, inclusion: { in: DOMAIN_BLOCK_AUDIENCES }, if: -> { defined?(@show_domain_blocks) } + validates :show_domain_blocks_rationale, inclusion: { in: DOMAIN_BLOCK_AUDIENCES }, if: -> { defined?(@show_domain_blocks_rationale) } validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) } validates :min_age, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@min_age) } validates :site_short_description, length: { maximum: DESCRIPTION_LIMIT }, if: -> { defined?(@site_short_description) } diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 3430df51769813..1ae1c4731f91c4 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -56,10 +56,6 @@ def destroy? owned? end - def list_quotes? - owned? - end - alias unreblog? destroy? def update? diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 7234254c150c33..3693c4c1bb0b1c 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -38,7 +38,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer attribute :quote, key: :quote_uri, if: :quote? attribute :quote_authorization, if: :quote_authorization? - attribute :interaction_policy, if: -> { Mastodon::Feature.outgoing_quotes_enabled? } + attribute :interaction_policy def id ActivityPub::TagManager.instance.uri_for(object) diff --git a/app/serializers/rest/shallow_status_serializer.rb b/app/serializers/rest/shallow_status_serializer.rb index d82ac326216e8c..0b951f6caa6da9 100644 --- a/app/serializers/rest/shallow_status_serializer.rb +++ b/app/serializers/rest/shallow_status_serializer.rb @@ -6,5 +6,5 @@ class REST::ShallowStatusSerializer < REST::StatusSerializer # It looks like redefining one `has_one` requires redefining all inherited ones has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer - has_one :quote_approval, if: -> { Mastodon::Feature.outgoing_quotes_enabled? } + has_one :quote_approval end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index ff0ea362bffdcd..a46d57e0a9aab5 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -38,7 +38,7 @@ class REST::StatusSerializer < ActiveModel::Serializer has_one :quote, key: :quote, serializer: REST::QuoteSerializer has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer - has_one :quote_approval, if: -> { Mastodon::Feature.outgoing_quotes_enabled? } + has_one :quote_approval def quote object.quote if object.quote&.acceptable? diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index ed847ad9daa3b9..994626819088c8 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -103,7 +103,7 @@ def visibility v = :unlisted if %i(public public_unlisted login).include?(v) && @account.silenced? v = :public_unlisted if v == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted && Setting.enable_public_unlisted_visibility v = Setting.enable_public_unlisted_visibility ? :public_unlisted : :unlisted if !Setting.enable_public_visibility && v == :public - v = :private if @quoted_status&.private_visibility? + v = :private if @quoted_status&.private_visibility? && %i(public public_unlisted login unlisted).include?(v) v end @@ -298,8 +298,6 @@ def referred_statuses end def quoted_status_from_text - return unless Mastodon::Feature.outgoing_quotes_enabled? - url = ProcessReferencesService.extract_quote(@text) return unless url diff --git a/app/views/admin/announcements/_announcement.html.haml b/app/views/admin/announcements/_announcement.html.haml index 87ae97cf484797..5944b0b295435c 100644 --- a/app/views/admin/announcements/_announcement.html.haml +++ b/app/views/admin/announcements/_announcement.html.haml @@ -9,7 +9,7 @@ - else = l(announcement.created_at) - %div + .announcements-list__item__actions - if can?(:distribute, announcement) = table_link_to 'mail', t('admin.terms_of_service.notify_users'), admin_announcement_preview_path(announcement) - if can?(:update, announcement) diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index 085bdbd156822f..ddaca5d8a9f8e6 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -26,5 +26,5 @@ = link_to t('admin.roles.assigned_users', count: role.users.count), admin_accounts_path(role_ids: role.id) · %abbr{ title: role.permissions_as_keys.map { |privilege| I18n.t("admin.roles.privileges.#{privilege}") }.join(', ') }= t('admin.roles.permissions_count', count: role.permissions_as_keys.size) - %div + .announcements-list__item__actions = table_link_to 'edit', t('admin.accounts.edit'), edit_admin_role_path(role) if can?(:update, role) diff --git a/app/views/admin/rules/_rule.html.haml b/app/views/admin/rules/_rule.html.haml index 7d84534d59b6e7..d79c1dfa6c64fb 100644 --- a/app/views/admin/rules/_rule.html.haml +++ b/app/views/admin/rules/_rule.html.haml @@ -3,7 +3,7 @@ #{rule_counter + 1}. = truncate(rule.text) - .announcements-list__item__action-bar + .announcements-list__item__action-bar.no-wrap .announcements-list__item__meta = rule.hint diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml index 1eb47a0b54e865..adc8f1ff04ac36 100644 --- a/app/views/admin/settings/about/show.html.haml +++ b/app/views/admin/settings/about/show.html.haml @@ -24,7 +24,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :show_domain_blocks, collection_wrapper_tag: 'ul', - collection: %i(disabled users all), + collection: f.object.class::DOMAIN_BLOCK_AUDIENCES, include_blank: false, item_wrapper_tag: 'li', label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, @@ -32,7 +32,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :show_domain_blocks_rationale, collection_wrapper_tag: 'ul', - collection: %i(disabled users all), + collection: f.object.class::DOMAIN_BLOCK_AUDIENCES, include_blank: false, item_wrapper_tag: 'li', label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml index 4bd384f8b5fd12..a9056ca0108b6c 100644 --- a/app/views/admin/settings/registrations/show.html.haml +++ b/app/views/admin/settings/registrations/show.html.haml @@ -18,7 +18,7 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group = f.input :registrations_mode, - collection: %w(open approved none), + collection: f.object.class::REGISTRATION_MODES, include_blank: false, label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") }, warning_hint: I18n.t('admin.settings.registrations_mode.warning_hint'), diff --git a/app/views/admin/warning_presets/_warning_preset.html.haml b/app/views/admin/warning_presets/_warning_preset.html.haml index 2cc056420f4aa9..6488c3a554b82a 100644 --- a/app/views/admin/warning_presets/_warning_preset.html.haml +++ b/app/views/admin/warning_presets/_warning_preset.html.haml @@ -6,5 +6,5 @@ .announcements-list__item__meta = truncate(warning_preset.text) - %div + .announcements-list__item__actions = table_link_to 'delete', t('admin.warning_presets.delete'), admin_warning_preset_path(warning_preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, warning_preset) diff --git a/app/views/admin/webhooks/_webhook.html.haml b/app/views/admin/webhooks/_webhook.html.haml index dca5abeb7779af..6159d97820abe1 100644 --- a/app/views/admin/webhooks/_webhook.html.haml +++ b/app/views/admin/webhooks/_webhook.html.haml @@ -14,6 +14,6 @@ %abbr{ title: webhook.events.join(', ') }= t('admin.webhooks.enabled_events', count: webhook.events.size) - %div + .announcements-list__item__actions = table_link_to 'edit', t('admin.webhooks.edit'), edit_admin_webhook_path(webhook) if can?(:update, webhook) = table_link_to 'delete', t('admin.webhooks.delete'), admin_webhook_path(webhook), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, webhook) diff --git a/app/views/filters/_filter.html.haml b/app/views/filters/_filter.html.haml index a544ac3a758166..15326f300626e8 100644 --- a/app/views/filters/_filter.html.haml +++ b/app/views/filters/_filter.html.haml @@ -32,10 +32,10 @@ .permissions-list__item__text__type = t('filters.index.statuses_long', count: filter.statuses.size) - .announcements-list__item__action-bar - .announcements-list__item__meta + .filters-list__item__action-bar + .filters-list__item__meta = t('filters.index.contexts', contexts: filter.context.map { |context| I18n.t("filters.contexts.#{context}") }.join(', ')) - %div + .filters-list__item__actions = table_link_to 'edit', t('filters.edit.title'), edit_filter_path(filter) = table_link_to 'close', t('filters.index.delete'), filter_path(filter), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml index 3745ed219ffb0c..b28302a93f72e3 100644 --- a/app/views/oauth/authorized_applications/index.html.haml +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -28,7 +28,7 @@ = t('doorkeeper.authorized_applications.index.authorized_at', date: l(application.created_at.to_date)) - unless application.superapp? || current_account.unavailable? - %div + .announcements-list__item__actions = table_link_to 'close', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') } .announcements-list__item__permissions diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index 40b5c42404fede..ade7175c9d52fc 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -55,7 +55,7 @@ def build_request(http_client) end def synchronization_header - "collectionId=\"#{account_followers_url(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(@inbox_url)}\", url=\"#{account_followers_synchronization_url(@source_account)}\"" + "collectionId=\"#{ActivityPub::TagManager.instance.followers_uri_for(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(@inbox_url)}\", url=\"#{account_followers_synchronization_url(@source_account)}\"" end def perform_request diff --git a/config/locales/ca.yml b/config/locales/ca.yml index eae71f330c8ac4..1c69c680928e32 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -190,6 +190,7 @@ ca: create_relay: Crea un repetidor create_unavailable_domain: Crea un domini no disponible create_user_role: Crea Rol + create_username_block: Crear regla de nom d'usuari demote_user: Degrada l'usuari destroy_announcement: Esborra l'anunci destroy_canonical_email_block: Elimina el blocatge del correu electrònic @@ -203,6 +204,7 @@ ca: destroy_status: Elimina la publicació destroy_unavailable_domain: Esborra domini no disponible destroy_user_role: Destrueix Rol + destroy_username_block: Eliminar regla de nom d'usuari disable_2fa_user: Desactiva 2FA disable_custom_emoji: Desactiva l'emoji personalitzat disable_relay: Desactiva el repetidor @@ -237,6 +239,7 @@ ca: update_report: Actualitza l'informe update_status: Actualitza l'estat update_user_role: Actualitza Rol + update_username_block: Actualitzar regla de nom d'usuari actions: approve_appeal_html: "%{name} ha aprovat l'apel·lació a la decisió de moderació de %{target}" approve_user_html: "%{name} ha aprovat el registre de %{target}" @@ -1083,6 +1086,24 @@ ca: other: Emprat per %{count} persones en la darrera setmana title: Recomanacions i tendències trending: Tendència + username_blocks: + add_new: Afegeix-ne una de nova + comparison: + contains: Conté + equals: És igual a + contains_html: Conté %{string} + created_msg: Creada una regla de nom d'usuari + delete: Elimina + edit: + title: Editar regla de nom d'usuari + matches_exactly_html: És igual a %{string} + new: + create: Crea una regla + title: Crear nova regla de nom d'usuari + no_username_block_selected: No s'ha canviat cap regla de nom d'usuari perquè no se n'havia seleccionat cap + not_permitted: No permés + title: Regles de nom d'usuari + updated_msg: Actualitzada una regla de nom d'usuari warning_presets: add_new: Afegir-ne un de nou delete: Elimina @@ -1712,6 +1733,7 @@ ca: self_vote: No pots votar en les teves propies enquestes too_few_options: ha de tenir més d'una opció too_many_options: no pot contenir més de %{max} opcions + vote: Voteu preferences: other: Altres posting_defaults: Valors per defecte de publicació @@ -1888,6 +1910,7 @@ ca: title: '%{name}: "%{quote}"' visibilities: direct: Menció privada + private: Només seguidors public: Públic public_long: Tothom dins o fora Mastodon unlisted_long: Amagat dels resultats de cerca de Mastodon, de les tendències i de les línies temporals diff --git a/config/locales/da.yml b/config/locales/da.yml index 9f15c2a43f0e44..8ad3ccb1389bac 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1634,7 +1634,7 @@ da: not_found: kunne ikke findes on_cooldown: Du er på nedkøling followers_count: Følgere på flytningstidspunktet - incoming_migrations: Flytter fra en anden konto + incoming_migrations: Flytning fra en anden konto incoming_migrations_html: For at flytte fra en anden konto til denne skal der først oprettes et kontoalias. moved_msg: Din konto omdirigeres nu til %{acct} og dine følgere overflyttes. not_redirecting: Din konto omdirigerer pt. ikke til nogen anden konto. diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 6df26088d7160d..baccc8705b98e2 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -663,6 +663,7 @@ ja: title: モデレーション moderation_notes: create: モデレーションノートを追加 + title: モデレーションメモ private_comment: コメント (非公開) public_comment: コメント (公開) purge: パージ @@ -1335,14 +1336,18 @@ ja: patch: パッチアップデートあり username_blocks: add_new: ルールを作成 + block_registrations: 登録拒否 comparison: contains: 含む equals: 一致 + contains_html: "%{string}を含む" delete: 削除 edit: title: ユーザー名ルールの編集 + matches_exactly_html: "%{string}に一致" new: create: ルールを作成 + title: ユーザー名ルール warning_presets: add_new: 追加 delete: 削除 @@ -1984,6 +1989,7 @@ ja: self_vote: 自分のアンケートには解答できません too_few_options: は複数必要です too_many_options: は%{max}個までです + vote: 投票 preferences: custom_css: カスタムCSS does_not_search: このサーバーでは全文検索機能を利用できません。代わりに、他のkmyblueサーバーであなたの投稿がこの設定に従って検索されます。 diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 9ec00b897aa910..61fb800301dba8 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -33,6 +33,11 @@ kab: new_email: Imayl amaynut submit: Beddel imayl title: Beddel imayl-ik s %{username} + change_role: + edit_roles: Sefrek timlilin n usqdac + label: Snifel tamlilt + no_role: War tamlilt + title: Snifel tamlilt n %{username} confirm: Sentem confirmed: Yettwasentem confirming: Asentem @@ -95,6 +100,7 @@ kab: reset: Wennez reset_password: Beddel awal uffir resubscribe: Ales ajerred + role: Tamlilt search: Nadi search_same_ip: Imseqdacen-nniḍen s tansa IP am tinn-ik security: Taɣellist @@ -121,6 +127,7 @@ kab: whitelisted: Deg tebdert tamellalt action_logs: action_types: + change_role_user: Snifel tamlilt n useqdac confirm_user: Sentem aseqdac create_announcement: Rnu-d ulɣu create_custom_emoji: Rnu imujit udmawan @@ -128,6 +135,7 @@ kab: create_domain_block: Rnu-d asewḥel n taɣult create_ip_block: Rnu alugen n IP create_unavailable_domain: Rnu-d taɣult ur nelli ara + create_user_role: Snulfu-d tamlilt destroy_announcement: Kkes ulɣu destroy_custom_emoji: Kkes imujit udmawan destroy_domain_allow: Kkes taɣult yettusirgen @@ -135,6 +143,7 @@ kab: destroy_ip_block: Kkes alugen n IP destroy_status: Kkes tasufeɣt destroy_unavailable_domain: Kkes taɣult ur nelli ara + destroy_user_role: Senger tamlilt disable_2fa_user: Gdel 2FA disable_custom_emoji: Sens imujit udmawan disable_user: Sens aseqdac @@ -150,6 +159,7 @@ kab: update_custom_emoji: Leqqem imuji udmawan update_domain_block: Leqqem iḥder n taɣult update_status: Leqqem tasufeɣt + update_user_role: Leqqem tamlilt actions: assigned_to_self_report_html: "%{name} imudd aneqqis %{target} i yiman-nsen" create_account_warning_html: "%{name} yuzen alɣu i %{target}" @@ -400,7 +410,10 @@ kab: privileges: administrator: Anedbal manage_federation: Sefrek Tafidiralit + manage_roles: Sefrek ilugan + manage_rules: Sefrek ilugan manage_settings: Asefrek n iɣewwaṛen + manage_users: Sefrek iqeddacen view_dashboard: Timẓriwt n tfelwit rules: add_new: Rnu alugen @@ -566,10 +579,10 @@ kab: migrate_account: Gujj ɣer umiḍan nniḍen or_log_in_with: Neɣ eqqen s progress: - confirm: Sentem imayl - details: Isalli-inek + confirm: Asentem n imayl + details: Isalli-inek·inem review: Tamuɣli-nneɣ - rules: Qbel ilugan + rules: Abal n ilugan providers: cas: CAS saml: SAML @@ -599,6 +612,8 @@ kab: account_status: Addad n umiḍan functional: Amiḍan-inek·inem yetteddu s lekmal-is. use_security_key: Seqdec tasarut n teɣlist + user_agreement_html: Ɣriɣ yerna qebleɣ " target="_blank">tiwtilin ne useqdec akked tsertit n tbaḍnit + user_privacy_agreement_html: Ɣriɣ yerna qebleɣ tasertit n tbaḍnit author_attribution: example_title: Amedya n weḍris more_from_html: Ugar s ɣur %{name} @@ -887,6 +902,8 @@ kab: one: "%{count} n tbidyutt" other: "%{count} n tbidyutin" edited_at_html: Tettwaẓreg ass n %{date} + pin_errors: + reblog: Azuzer ur yezmir ara ad yili d unṭiḍ quote_policies: followers: Imeḍfaṛen kan nobody: Nekki kan @@ -900,6 +917,7 @@ kab: unlisted: Azayez asusam statuses_cleanup: enabled: Tukksa n tsuffaɣ tiqburin s wudem awurman + keep_pinned: Eǧǧ tisuffaɣ tunṭiḍin min_age: '1209600': 2 n yimalasen '15778476': 6 n wayyuren diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 21474c6d70dcfd..20e53b4a865b8b 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -213,7 +213,7 @@ nan: enable_user: 啟用口座 memorialize_account: 設做故人ê口座 promote_user: Kā用者升級 - publish_terms_of_service: 公佈服務ê使用規則 + publish_terms_of_service: 公佈服務規定 reject_appeal: 拒絕申訴 reject_user: 拒絕用者 remove_avatar_user: Thâi掉標頭 @@ -281,7 +281,7 @@ nan: enable_user_html: "%{name} kā 用者 %{target} 設做允准登入" memorialize_account_html: "%{name} kā %{target} 設做故人口座" promote_user_html: "%{name} kā 用者 %{target} 升級" - publish_terms_of_service_html: "%{name} 公佈服務規則ê更新" + publish_terms_of_service_html: "%{name} 公佈服務規定ê更新" reject_appeal_html: "%{name} 拒絕 %{target} 所寫ê tuì管理決定ê投訴" reject_user_html: "%{name} 拒絕 %{target} ê 註冊" remove_avatar_user_html: "%{name} thâi掉 %{target} ê標頭" @@ -787,7 +787,7 @@ nan: add_new: 加添規則 add_translation: 加添翻譯 delete: Thâi掉 - description_html: 雖bóng大部份ê lóng講有讀kap同意使用規則,m̄-koh攏無讀了,直到發生問題ê時。提供in列單ē當hōo tsi̍t kái看服侍器ê規則khah快。請試kā個別ê規則寫kah短koh簡單,m̄-kú m̄通kā in拆做tsē-tsē分開ê項目。 + description_html: 雖bóng大部份ê lóng講有讀kap同意服務規定,m̄-koh攏無讀了,直到發生問題ê時。提供in列單ē當hōo tsi̍t kái看服侍器ê規則khah快。請試kā個別ê規則寫kah短koh簡單,m̄-kú m̄通kā in拆做tsē-tsē分開ê項目。 edit: 編輯規則 empty: Iáu bē定義服侍器ê規則。 move_down: Suá khah落來 @@ -972,7 +972,36 @@ nan: search: Tshiau-tshuē title: Hashtag updated_msg: Hashtag設定更新成功ah + terms_of_service: + back: 轉去服務規定 + changelog: Siánn物有改 + create: 用lí家tī ê + current: 目前ê + draft: 草稿 + generate: 用枋模 + generates: + action: 生成 + chance_to_review_html: "所生成ê服務規定bē自動發布。Lí ē有機會來看結果。請添必要ê詳細來繼續。" + explanation_html: 提供ê服務規定kan-ta做參考用,bē當成做任何法律建議。請照lí ê情形kap有ê特別ê法律問題諮詢lí ê法律顧問。 + title: 設定服務規定 + going_live_on_html: 目前規定,tuì %{date} 施行 + history: 歷史 + live: 目前ê + no_history: Iáu無半項服務規定ê改變記錄。 + no_terms_of_service_html: Lí目前iáu無設定任何服務規定。服務規定是beh提供明確性,兼保護lí佇kap用者ê爭議中毋免承受可能ê責任。 + notified_on_html: 佇 %{date} 通知ê用者 + notify_users: 通知用者 trends: + preview_card_providers: + title: 發布者 + rejected: 拒絕ê + statuses: + allow: 允准PO文 + allow_account: 允准作者 + confirm_allow: Lí kám確定beh允准所揀ê狀態? + confirm_allow_account: Lí kám確定beh允准所揀ê口座? + confirm_disallow: Lí kám確定無愛允准所揀ê狀態? + confirm_disallow_account: Lí kám確定無愛允准所揀ê口座? tags: dashboard: tag_languages_dimension: Tsia̍p用ê語言 diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 0c12fbb476dec1..46ba3ddd59476c 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -56,6 +56,8 @@ ca: scopes: API permeses per a accedir a l'aplicació. Si selecciones un àmbit de nivell superior, no cal que en seleccionis un d'individual. setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts) setting_always_send_emails: Normalment, no s'enviarà cap notificació per correu electrònic mentre facis servir Mastodon + setting_default_quote_policy_private: Altres no poden citar publicacions fetes a Mastodon només per a seguidors. + setting_default_quote_policy_unlisted: Quan la gent et citi la seva publicació estarà amagada de les línies de temps de tendències. setting_default_sensitive: El contingut sensible està ocult per defecte i es pot mostrar fent-hi clic setting_display_media_default: Amaga el contingut gràfic marcat com a sensible setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia @@ -370,6 +372,8 @@ ca: name: Nom permissions_as_keys: Permisos position: Prioritat + username_block: + comparison: Mètode de comparació webhook: events: Esdeveniments activats template: Plantilla de càrrega diff --git a/config/mastodon.yml b/config/mastodon.yml index a58936bdde3785..560b496e52794e 100644 --- a/config/mastodon.yml +++ b/config/mastodon.yml @@ -1,6 +1,6 @@ --- shared: - experimental_features: <%= ENV.fetch('EXPERIMENTAL_FEATURES', 'outgoing_quotes') %> + experimental_features: <%= ENV.fetch('EXPERIMENTAL_FEATURES', nil) %> limited_federation_mode: <%= (ENV.fetch('LIMITED_FEDERATION_MODE', nil) || ENV.fetch('WHITELIST_MODE', nil)) == 'true' %> self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil)&.to_json %> software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://kmy.blue/update-check')&.to_json %> @@ -12,4 +12,4 @@ shared: metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil)&.to_json %> prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil)&.to_json %> test: - experimental_features: <%= [ENV.fetch('EXPERIMENTAL_FEATURES', 'outgoing_quotes'), 'testing_only'].compact.join(',') %> + experimental_features: <%= [ENV.fetch('EXPERIMENTAL_FEATURES', nil), 'testing_only'].compact.join(',') %> diff --git a/config/routes.rb b/config/routes.rb index 0e0bcd9d46e036..50cae2d4d7f060 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,7 +97,20 @@ def redirect_with_vary(path) get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" } - resources :accounts, path: 'users', only: [:show], param: :username do + concern :account_resources do + resources :followers, only: [:index], controller: :follower_accounts + resources :following, only: [:index], controller: :following_accounts + + scope module: :activitypub do + resource :outbox, only: [:show] + resource :inbox, only: [:create] + resources :collections, only: [:show] + resource :followers_synchronization, only: [:show] + resources :quote_authorizations, only: [:show] + end + end + + resources :accounts, path: 'users', only: [:show], param: :username, concerns: :account_resources do resources :statuses, only: [:show] do member do get :activity @@ -109,16 +122,19 @@ def redirect_with_vary(path) resources :likes, only: [:index], module: :activitypub resources :shares, only: [:index], module: :activitypub end + end - resources :followers, only: [:index], controller: :follower_accounts - resources :following, only: [:index], controller: :following_accounts + scope path: 'ap', as: 'ap' do + resources :accounts, path: 'users', only: [:show], param: :id, concerns: :account_resources do + resources :statuses, only: [:show] do + member do + get :activity + end - scope module: :activitypub do - resource :outbox, only: [:show] - resource :inbox, only: [:create] - resources :collections, only: [:show] - resource :followers_synchronization, only: [:show] - resources :quote_authorizations, only: [:show] + resources :replies, only: [:index], module: :activitypub + resources :likes, only: [:index], module: :activitypub + resources :shares, only: [:index], module: :activitypub + end end end diff --git a/db/migrate/20250924170259_add_id_scheme_to_accounts.rb b/db/migrate/20250924170259_add_id_scheme_to_accounts.rb new file mode 100644 index 00000000000000..7dd987dcc0e168 --- /dev/null +++ b/db/migrate/20250924170259_add_id_scheme_to_accounts.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddIdSchemeToAccounts < ActiveRecord::Migration[8.0] + def change + add_column :accounts, :id_scheme, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 485a0ecc78e364..e45637d366a743 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_09_22_234840) do +ActiveRecord::Schema[8.0].define(version: 2025_09_24_170259) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -205,6 +205,7 @@ t.integer "header_file_size" t.string "attribution_domains", default: [], array: true t.string "following_url", default: "", null: false + t.integer "id_scheme", default: 0 t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["domain", "id"], name: "index_accounts_on_domain_and_id" diff --git a/eslint.config.mjs b/eslint.config.mjs index 43aabc51100c4b..5942219cedc5b8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,6 @@ import path from 'node:path'; import js from '@eslint/js'; import { globalIgnores } from 'eslint/config'; import formatjs from 'eslint-plugin-formatjs'; -// @ts-expect-error -- No typings import importPlugin from 'eslint-plugin-import'; import jsdoc from 'eslint-plugin-jsdoc'; import jsxA11Y from 'eslint-plugin-jsx-a11y'; @@ -180,6 +179,7 @@ export default tseslint.config([ 'tmp/**/*', 'vendor/**/*', 'streaming/**/*', + '.bundle/**/*', ]), react.configs.flat.recommended, react.configs.flat['jsx-runtime'], diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 2922ee3e636235..5c6d5af90b856e 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -96,7 +96,7 @@ def dev? def api_versions { - mastodon: Mastodon::Feature.outgoing_quotes_enabled? ? 7 : 6, + mastodon: 7, kmyblue: KMYBLUE_API_VERSION, } end diff --git a/package.json b/package.json index d979d8c40fcb11..5914b11f1a6c56 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/mastodon", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.9.4", + "packageManager": "yarn@4.10.3", "engines": { "node": ">=20" }, @@ -104,7 +104,6 @@ "react-redux-loading-bar": "^5.0.8", "react-router": "^5.3.4", "react-router-dom": "^5.3.4", - "react-router-scroll-4": "^1.0.0-beta.1", "react-select": "^5.7.3", "react-sparklines": "^1.7.0", "react-swipeable-views": "^0.14.0", @@ -116,6 +115,7 @@ "rollup-plugin-gzip": "^4.1.1", "rollup-plugin-visualizer": "^6.0.3", "sass": "^1.62.1", + "scroll-behavior": "^0.11.0", "stacktrace-js": "^2.0.2", "stringz": "^2.1.0", "substring-trie": "^1.0.2", @@ -195,7 +195,7 @@ "stylelint": "^16.19.1", "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-standard-scss": "^15.0.1", - "typescript": "~5.7.3", + "typescript": "~5.9.0", "typescript-eslint": "^8.29.1", "vitest": "^3.2.4" }, diff --git a/spec/lib/activitypub/activity/quote_request_spec.rb b/spec/lib/activitypub/activity/quote_request_spec.rb index 64627cbdfbe266..aae4ce0338329f 100644 --- a/spec/lib/activitypub/activity/quote_request_spec.rb +++ b/spec/lib/activitypub/activity/quote_request_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do +RSpec.describe ActivityPub::Activity::QuoteRequest do let(:sender) { Fabricate(:account, domain: 'example.com') } let(:recipient) { Fabricate(:account) } let(:quoted_post) { Fabricate(:status, account: recipient) } diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index 87f8d568be1479..b825736af980cc 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -7,7 +7,7 @@ subject { described_class.instance } - let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } + let(:host_prefix) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } describe '#public_collection?' do it 'returns true for the special public collection and common shorthands' do @@ -22,18 +22,180 @@ end describe '#url_for' do - it 'returns a string starting with web domain' do - account = Fabricate(:account) - expect(subject.url_for(account)).to be_a(String) - .and start_with(domain) + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(account)) + .to eq("#{host_prefix}/@#{account.username}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(account)) + .to eq("#{host_prefix}/@#{account.username}") + end + end + end + + context 'with a remote account' do + let(:account) { Fabricate(:account, domain: 'example.com', url: 'https://example.com/profiles/dskjfsdf') } + + it 'returns the expected URL' do + expect(subject.url_for(account)).to eq account.url + end + end + + context 'with a local status' do + let(:status) { Fabricate(:status) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(status)) + .to eq("#{host_prefix}/@#{status.account.username}/#{status.id}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.url_for(status)) + .to eq("#{host_prefix}/@#{status.account.username}/#{status.id}") + end + end + end + + context 'with a remote status' do + let(:account) { Fabricate(:account, domain: 'example.com', url: 'https://example.com/profiles/dskjfsdf') } + let(:status) { Fabricate(:status, account: account, url: 'https://example.com/posts/1234') } + + it 'returns the expected URL' do + expect(subject.url_for(status)).to eq status.url + end end end describe '#uri_for' do - it 'returns a string starting with web domain' do - account = Fabricate(:account) - expect(subject.uri_for(account)).to be_a(String) - .and start_with(domain) + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(Account.representative)) + .to eq("#{host_prefix}/actor") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}") + end + end + end + + context 'with a remote account' do + let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/profiles/dskjfsdf') } + + it 'returns the expected URL' do + expect(subject.uri_for(account)).to eq account.uri + end + end + + context 'with a local status' do + let(:status) { Fabricate(:status) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}") + end + end + end + + context 'with a remote status' do + let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/profiles/dskjfsdf') } + let(:status) { Fabricate(:status, account: account, uri: 'https://example.com/posts/1234') } + + it 'returns the expected URL' do + expect(subject.uri_for(status)).to eq status.uri + end + end + + context 'with a local conversation' do + let(:status) { Fabricate(:status) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status.conversation)) + .to eq("#{host_prefix}/contexts/#{status.account.id}-#{status.id}") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.uri_for(status.conversation)) + .to eq("#{host_prefix}/contexts/#{status.account.id}-#{status.id}") + end + end + end + + context 'with a remote conversation' do + let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/profiles/dskjfsdf') } + let(:status) { Fabricate(:status, account: account, uri: 'https://example.com/posts/1234') } + + before do + status.conversation.update!(uri: 'https://example.com/conversations/1234') + end + + it 'returns the expected URL' do + expect(subject.uri_for(status.conversation)).to eq status.conversation.uri + end + end + end + + describe '#key_uri_for' do + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.key_uri_for(Account.representative)) + .to eq("#{host_prefix}/actor#main-key") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.key_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}#main-key") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.key_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}#main-key") + end + end end end @@ -49,7 +211,236 @@ it 'returns a string starting with web domain' do status = Fabricate(:status) expect(subject.uri_for(status)).to be_a(String) - .and start_with(domain) + .and start_with(host_prefix) + end + end + end + + describe '#approval_uri_for' do + context 'with a valid local approval' do + let(:quote) { Fabricate(:quote, state: :accepted) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote)) + .to eq("#{host_prefix}/users/#{quote.quoted_account.username}/quote_authorizations/#{quote.id}") + end + + context 'when using a numeric ID based scheme' do + let(:quoted_account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :accepted, quoted_status: quoted_status) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote)) + .to eq("#{host_prefix}/ap/users/#{quote.quoted_account_id}/quote_authorizations/#{quote.id}") + end + end + end + + context 'with an unapproved local quote' do + let(:quote) { Fabricate(:quote, state: :rejected) } + + it 'returns nil' do + expect(subject.approval_uri_for(quote)) + .to be_nil + end + + context 'when using a numeric ID based scheme' do + let(:quoted_account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :rejected, quoted_status: quoted_status) } + + it 'returns nil' do + expect(subject.approval_uri_for(quote)) + .to be_nil + end + end + end + + context 'with a valid remote approval' do + let(:quoted_account) { Fabricate(:account, domain: 'example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :accepted, quoted_status: quoted_status, approval_uri: 'https://example.com/approvals/1') } + + it 'returns the expected URI' do + expect(subject.approval_uri_for(quote)).to eq quote.approval_uri + end + end + + context 'with an unapproved local quote but check_approval override' do + let(:quote) { Fabricate(:quote, state: :rejected) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote, check_approval: false)) + .to eq("#{host_prefix}/users/#{quote.quoted_account.username}/quote_authorizations/#{quote.id}") + end + + context 'when using a numeric ID based scheme' do + let(:quoted_account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:quote) { Fabricate(:quote, state: :rejected, quoted_status: quoted_status) } + + it 'returns a string with the web domain and expected path' do + expect(subject.approval_uri_for(quote, check_approval: false)) + .to eq("#{host_prefix}/ap/users/#{quote.quoted_account_id}/quote_authorizations/#{quote.id}") + end + end + end + end + + describe '#replies_uri_for' do + context 'with a local status' do + let(:status) { Fabricate(:status) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.replies_uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}/replies") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.replies_uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}/replies") + end + end + end + end + + describe '#likes_uri_for' do + context 'with a local status' do + let(:status) { Fabricate(:status) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.likes_uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}/likes") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.likes_uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}/likes") + end + end + end + end + + describe '#shares_uri_for' do + context 'with a local status' do + let(:status) { Fabricate(:status) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.shares_uri_for(status)) + .to eq("#{host_prefix}/users/#{status.account.username}/statuses/#{status.id}/shares") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + let(:status) { Fabricate(:status, account: account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.shares_uri_for(status)) + .to eq("#{host_prefix}/ap/users/#{status.account.id}/statuses/#{status.id}/shares") + end + end + end + end + + describe '#following_uri_for' do + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.following_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/following") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.following_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/following") + end + end + end + end + + describe '#followers_uri_for' do + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.followers_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/followers") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.followers_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/followers") + end + end + end + end + + describe '#inbox_uri_for' do + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.inbox_uri_for(Account.representative)) + .to eq("#{host_prefix}/actor/inbox") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.inbox_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/inbox") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.inbox_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/inbox") + end + end + end + end + + describe '#outbox_uri_for' do + context 'with the instance actor' do + it 'returns a string starting with web domain and with the expected path' do + expect(subject.outbox_uri_for(Account.representative)) + .to eq("#{host_prefix}/actor/outbox") + end + end + + context 'with a local account' do + let(:account) { Fabricate(:account) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.outbox_uri_for(account)) + .to eq("#{host_prefix}/users/#{account.username}/outbox") + end + + context 'when using a numeric ID based scheme' do + let(:account) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'returns a string starting with web domain and with the expected path' do + expect(subject.outbox_uri_for(account)) + .to eq("#{host_prefix}/ap/users/#{account.id}/outbox") + end end end end @@ -70,16 +461,28 @@ expect(subject.to(status)).to eq [account_followers_url(status.account)] end + it 'returns followers collection for unlisted status when using a numeric ID based scheme' do + status = Fabricate(:status, visibility: :unlisted, account: Fabricate(:account, id_scheme: :numeric_ap_id)) + expect(subject.to(status)).to eq [ap_account_followers_url(status.account_id)] + end + it 'returns followers collection for private status' do status = Fabricate(:status, visibility: :private) expect(subject.to(status)).to eq [account_followers_url(status.account)] end + it 'returns followers collection for private status when using a numeric ID based scheme' do + status = Fabricate(:status, visibility: :private, account: Fabricate(:account, id_scheme: :numeric_ap_id)) + expect(subject.to(status)).to eq [ap_account_followers_url(status.account_id)] + end + it 'returns URIs of mentions for direct status' do status = Fabricate(:status, visibility: :direct) mentioned = Fabricate(:account) + mentioned_numeric = Fabricate(:account, id_scheme: :numeric_ap_id) status.mentions.create(account: mentioned) - expect(subject.to(status)).to eq [subject.uri_for(mentioned)] + status.mentions.create(account: mentioned_numeric) + expect(subject.to(status)).to eq [subject.uri_for(mentioned), subject.uri_for(mentioned_numeric)] end it "returns URIs of mentioned group's followers for direct statuses to groups" do @@ -142,6 +545,11 @@ expect(subject.cc(status)).to eq ['https://www.w3.org/ns/activitystreams#Public'] end + it 'returns followers collection for public status when using a numeric ID based scheme' do + status = Fabricate(:status, visibility: :public, account: Fabricate(:account, id_scheme: :numeric_ap_id)) + expect(subject.cc(status)).to eq [ap_account_followers_url(status.account_id)] + end + it 'returns public collection for unlisted status' do status = Fabricate(:status, visibility: :unlisted) expect(subject.cc(status)).to eq ['https://www.w3.org/ns/activitystreams#Public'] @@ -160,8 +568,10 @@ it 'returns URIs of mentions for non-direct status' do status = Fabricate(:status, visibility: :public) mentioned = Fabricate(:account) + mentioned_numeric = Fabricate(:account, id_scheme: :numeric_ap_id) status.mentions.create(account: mentioned) - expect(subject.cc(status)).to include(subject.uri_for(mentioned)) + status.mentions.create(account: mentioned_numeric) + expect(subject.cc(status)).to include(subject.uri_for(mentioned), subject.uri_for(mentioned_numeric)) end context 'with followers and requested followers' do diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index f450997976a814..085866ef1d35f5 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -28,7 +28,7 @@ end end - context 'when handling a status with a quote policy', feature: :outgoing_quotes do + context 'when handling a status with a quote policy' do let(:status) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) } before do diff --git a/spec/models/concerns/account/interactions_spec.rb b/spec/models/concerns/account/interactions_spec.rb index 5fe0841df902f8..f882acf980616f 100644 --- a/spec/models/concerns/account/interactions_spec.rb +++ b/spec/models/concerns/account/interactions_spec.rb @@ -602,6 +602,22 @@ me.follow!(remote_alice) expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) end + + context 'when using numeric ID based scheme' do + let(:me) { Fabricate(:account, username: 'Me', id_scheme: :numeric_ap_id) } + + it 'returns correct hash for local users' do + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + end + + it 'invalidates cache as needed when removing or adding followers' do + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + me.unfollow!(remote_alice) + expect(remote_alice.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000' + me.follow!(remote_alice) + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + end + end end describe 'muting an account' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3d3e556f353d3b..6be93ecb70ef99 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -30,7 +30,8 @@ # This needs to be defined before Rails is initialized STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020') -ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}" +STREAMING_HOST = ENV.fetch('TEST_STREAMING_HOST', 'localhost') +ENV['STREAMING_API_BASE_URL'] = "http://#{STREAMING_HOST}:#{STREAMING_PORT}" require_relative '../config/environment' diff --git a/spec/requests/accounts_spec.rb b/spec/requests/accounts_spec.rb index 72913ebf22da07..cc2a5be7c5f5d8 100644 --- a/spec/requests/accounts_spec.rb +++ b/spec/requests/accounts_spec.rb @@ -5,6 +5,14 @@ RSpec.describe 'Accounts show response' do let(:account) { Fabricate(:account) } + context 'with numeric-based identifiers' do + it 'returns http success' do + get "/ap/users/#{account.id}" + + expect(response).to have_http_status(200) + end + end + context 'with an unapproved account' do before { account.user.update(approved: false) } diff --git a/spec/requests/activitypub/replies_spec.rb b/spec/requests/activitypub/replies_spec.rb index 313cab2a448aba..4cd02b187d99bd 100644 --- a/spec/requests/activitypub/replies_spec.rb +++ b/spec/requests/activitypub/replies_spec.rb @@ -220,6 +220,12 @@ it_behaves_like 'allowed access' end + context 'with no signature and requesting the numeric AP path' do + subject { get ap_account_status_replies_path(account_id: status.account_id, status_id: status.id, only_other_accounts: only_other_accounts) } + + it_behaves_like 'allowed access' + end + context 'with signature' do subject { get account_status_replies_path(account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts), headers: nil, sign_with: remote_querier } diff --git a/spec/requests/api/v1/statuses/interaction_policies_spec.rb b/spec/requests/api/v1/statuses/interaction_policies_spec.rb index cdc33e40d7e1b6..321a68cd2519a0 100644 --- a/spec/requests/api/v1/statuses/interaction_policies_spec.rb +++ b/spec/requests/api/v1/statuses/interaction_policies_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe 'Interaction policies', feature: :outgoing_quotes do +RSpec.describe 'Interaction policies' do let(:user) { Fabricate(:user) } let(:scopes) { 'write:statuses' } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } @@ -60,7 +60,7 @@ ) expect(DistributionWorker) - .to have_enqueued_sidekiq_job(status.id, { 'update' => true }) + .to have_enqueued_sidekiq_job(status.id, { 'update' => true, 'skip_notifications' => true }) expect(ActivityPub::StatusUpdateDistributionWorker) .to have_enqueued_sidekiq_job(status.id, { 'updated_at' => anything }) end diff --git a/spec/requests/api/v1/statuses/quotes_spec.rb b/spec/requests/api/v1/statuses/quotes_spec.rb index 9456556ce99fad..01e9e17b07785f 100644 --- a/spec/requests/api/v1/statuses/quotes_spec.rb +++ b/spec/requests/api/v1/statuses/quotes_spec.rb @@ -17,7 +17,7 @@ let!(:accepted_quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } let!(:rejected_quote) { Fabricate(:quote, quoted_status: status, state: :rejected) } let!(:pending_quote) { Fabricate(:quote, quoted_status: status, state: :pending) } - let!(:another_accepted_quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + let!(:accepted_private_quote) { Fabricate(:quote, status: Fabricate(:status, visibility: :private), quoted_status: status, state: :accepted) } context 'with an OAuth token' do let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } @@ -30,7 +30,7 @@ expect(response) .to have_http_status(200) .and include_pagination_headers( - prev: api_v1_status_quotes_url(limit: 2, since_id: another_accepted_quote.id), + prev: api_v1_status_quotes_url(limit: 2, since_id: accepted_private_quote.id), next: api_v1_status_quotes_url(limit: 2, max_id: accepted_quote.id) ) expect(response.content_type) @@ -39,7 +39,7 @@ expect(response.parsed_body) .to contain_exactly( include(id: accepted_quote.status.id.to_s), - include(id: another_accepted_quote.status.id.to_s) + include(id: accepted_private_quote.status.id.to_s) ) expect(response.parsed_body) @@ -52,12 +52,29 @@ context 'with a different user than the post owner' do let(:status) { Fabricate(:status) } - it 'returns http forbidden' do + it 'returns http success and statuses but not private ones' do subject - expect(response).to have_http_status(403) + expect(response) + .to have_http_status(200) + .and include_pagination_headers( + prev: api_v1_status_quotes_url(limit: 2, since_id: accepted_private_quote.id), + next: api_v1_status_quotes_url(limit: 2, max_id: accepted_quote.id) + ) expect(response.content_type) .to start_with('application/json') + + expect(response.parsed_body) + .to contain_exactly( + include(id: accepted_quote.status.id.to_s) + ) + + expect(response.parsed_body) + .to_not include( + include(id: rejected_quote.status.id.to_s), + include(id: pending_quote.status.id.to_s), + include(id: accepted_private_quote.id.to_s) + ) end end end diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 2ccc32c495206a..1586d7402a33f1 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -247,7 +247,7 @@ end end - context 'without a quote policy', feature: :outgoing_quotes do + context 'without a quote policy' do let(:user) do Fabricate(:user, settings: { default_quote_policy: 'followers' }) end @@ -269,7 +269,7 @@ end end - context 'without a quote policy and the user defaults to nobody', feature: :outgoing_quotes do + context 'without a quote policy and the user defaults to nobody' do let(:user) do Fabricate(:user, settings: { default_quote_policy: 'nobody' }) end @@ -291,7 +291,7 @@ end end - context 'with a quote policy', feature: :outgoing_quotes do + context 'with a quote policy' do let(:quoted_status) { Fabricate(:status, account: user.account) } let(:params) do { @@ -316,7 +316,7 @@ end end - context 'with a self-quote post', feature: :outgoing_quotes do + context 'with a self-quote post' do let(:quoted_status) { Fabricate(:status, account: user.account) } let(:params) do { @@ -337,7 +337,7 @@ end end - context 'with a self-quote post and a CW but no text', feature: :outgoing_quotes do + context 'with a self-quote post and a CW but no text' do let(:quoted_status) { Fabricate(:status, account: user.account) } let(:params) do { @@ -509,7 +509,7 @@ context 'when updating only the quote policy' do let(:params) { { status: status.text, quote_approval_policy: 'public' } } - it 'updates the status', :aggregate_failures, feature: :outgoing_quotes do + it 'updates the status', :aggregate_failures do expect { subject } .to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) diff --git a/spec/requests/statuses_spec.rb b/spec/requests/statuses_spec.rb index a5e4482dfa6ae2..d12f4f28cf3233 100644 --- a/spec/requests/statuses_spec.rb +++ b/spec/requests/statuses_spec.rb @@ -360,6 +360,30 @@ def sign_in_with_session(user) .to include(content: include(status.text)) end end + + context 'with JSON and querying the new paths' do + subject do + get ap_account_status_path(account_id: status.account_id, id: status.id), + headers: { 'Accept' => 'application/activity+json' }, + sign_with: remote_account + end + + let(:format) { 'json' } + + it 'renders ActivityPub Note object successfully', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') + expect(response.headers).to include( + 'Content-Type' => include('application/activity+json'), + 'Link' => include('activity+json') + ) + expect(response.parsed_body) + .to include(content: include(status.text)) + end + end end context 'when status has private visibility' do diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb index 01ab5367029dd7..c5b617b221aee6 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -189,7 +189,7 @@ def reply_items end end - context 'with a quote policy', feature: :outgoing_quotes do + context 'with a quote policy' do let(:parent) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) } it 'has the expected shape' do diff --git a/spec/services/activitypub/synchronize_followers_service_spec.rb b/spec/services/activitypub/synchronize_followers_service_spec.rb index b0bd02dac887a2..813658d149b889 100644 --- a/spec/services/activitypub/synchronize_followers_service_spec.rb +++ b/spec/services/activitypub/synchronize_followers_service_spec.rb @@ -6,7 +6,7 @@ subject { described_class.new } let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') } - let(:alice) { Fabricate(:account, username: 'alice') } + let(:alice) { Fabricate(:account, username: 'alice', id_scheme: :numeric_ap_id) } let(:bob) { Fabricate(:account, username: 'bob') } let(:eve) { Fabricate(:account, username: 'eve') } let(:mallory) { Fabricate(:account, username: 'mallory') } diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 274ae8114019b4..da293ca60381f2 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -691,6 +691,14 @@ expect(status).to be_private_visibility end + it 'correctly preserves visibility for private mentions self-quoting private posts' do + account = Fabricate(:account) + quoted_status = Fabricate(:status, account: account, visibility: :private) + + status = subject.call(account, text: 'test', quoted_status: quoted_status, visibility: 'direct') + expect(status).to be_direct_visibility + end + it 'returns existing status when used twice with idempotency key' do account = Fabricate(:account) status1 = subject.call(account, text: 'test', idempotency: 'meepmeep') diff --git a/spec/support/streaming_client.rb b/spec/support/streaming_client.rb new file mode 100644 index 00000000000000..02186e781c7d3e --- /dev/null +++ b/spec/support/streaming_client.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require 'websocket/driver' + +class StreamingClient + module AUTHENTICATION + SUBPROTOCOL = 1 + AUTHORIZATION_HEADER = 2 + QUERY_PARAMETER = 3 + end + + class Connection + attr_reader :url, :messages, :last_error + attr_accessor :logger, :protocols + + def initialize(url) + @uri = URI.parse(url) + @query_params = @uri.query.present? ? URI.decode_www_form(@uri.query).to_h : {} + @protocols = nil + @headers = {} + + @dead = false + + @events_queue = Thread::Queue.new + @messages = [] + @last_error = nil + end + + def set_header(key, value) + @headers[key] = value + end + + def set_query_param(key, value) + @query_params[key] = value + end + + def driver + return @driver if defined?(@driver) + + @uri.query = URI.encode_www_form(@query_params) + @url = @uri.to_s + @tcp = TCPSocket.new(@uri.host, @uri.port) + + @driver = WebSocket::Driver.client(self, { + protocols: @protocols, + }) + + @headers.each_pair do |key, value| + @driver.set_header(key, value) + end + + at_exit do + @driver.close + end + + @driver.on(:open) do + @events_queue.enq({ event: :opened }) + end + + @driver.on(:message) do |event| + @events_queue.enq({ event: :message, payload: event.data }) + @messages << event.data + end + + @driver.on(:error) do |event| + logger&.debug(event.message) + @events_queue.enq({ event: :error, payload: event }) + @last_error = event + end + + @driver.on(:close) do |event| + @events_queue.enq({ event: :closing, payload: event }) + finalize(event) + end + + @thread = Thread.new do + @driver.parse(@tcp.read(1)) until @dead || @tcp.closed? + rescue Errno::ECONNRESET + # Create a synthetic close event: + close_event = WebSocket::Driver::CloseEvent.new( + WebSocket::Driver::Hybi::ERRORS[:unexpected_condition], + 'Connection reset' + ) + + finalize(close_event) + end + + @driver + end + + def wait_for_event(expected_event, timeout: 10) + Timeout.timeout(timeout) do + loop do + event = dequeue_event + + return nil if event.nil? && @events_queue.closed? + return event[:payload] unless event.nil? || event[:event] != expected_event + end + end + end + + def write(data) + @tcp.write(data) + rescue Errno::EPIPE => e + logger&.debug("EPIPE: #{e}") + end + + def finalize(event) + @dead = true + @events_queue.enq({ event: :closed, payload: event }) + @events_queue.close + @thread.kill + end + + def dequeue_event + event = @events_queue.pop + logger&.debug(event) unless event.nil? + event + end + end + + def initialize + @logger = Logger.new($stdout) + @logger.level = 'info' + + @connection = Connection.new("ws://#{STREAMING_HOST}:#{STREAMING_PORT}/api/v1/streaming") + @connection.logger = @logger + end + + def debug! + @logger.debug! + end + + def authenticate(access_token, authentication_method = StreamingClient::AUTHENTICATION::SUBPROTOCOL) + raise 'Invalid access_token passed to StreamingClient, expected a string' unless access_token.is_a?(String) + + case authentication_method + when AUTHENTICATION::QUERY_PARAMETER + @connection.set_query_param('access_token', access_token) + when AUTHENTICATION::SUBPROTOCOL + @connection.protocols = access_token + when AUTHENTICATION::AUTHORIZATION_HEADER + @connection.set_header('Authorization', "Bearer #{access_token}") + else + raise 'Invalid authentication method' + end + end + + def connect + @connection.driver.start + @connection.wait_for_event(:opened) + end + + def subscribe(channel, **params) + send(Oj.dump({ type: 'subscribe', stream: channel }.merge(params))) + end + + def wait_for(event = nil) + @connection.wait_for_event(event) + end + + def wait_for_message + message = @connection.wait_for_event(:message) + event = Oj.load(message) + event['payload'] = Oj.load(event['payload']) if event['payload'] + + event.deep_symbolize_keys + end + + delegate :status, :state, to: :'@connection.driver' + delegate :messages, to: :@connection + + def open? + state == :open + end + + def closing? + state == :closing + end + + def closed? + state == :closed + end + + def send(message) + @connection.driver.text(message) if open? + end + + def close + return if closed? + + @connection.driver.close unless closing? + @connection.wait_for_event(:closed) + end +end + +module StreamingClientHelper + def streaming_client + @streaming_client ||= StreamingClient.new + end +end + +RSpec.configure do |config| + config.include StreamingClientHelper, :streaming +end diff --git a/spec/support/streaming_server_manager.rb b/spec/support/streaming_server_manager.rb index d98f7dd960735e..b565ed79a88d56 100644 --- a/spec/support/streaming_server_manager.rb +++ b/spec/support/streaming_server_manager.rb @@ -12,6 +12,11 @@ def start(port: 4020) queue = Queue.new + if ENV['DEBUG_STREAMING_SERVER'].present? + logger = Logger.new($stdout) + logger.level = 'debug' + end + @queue = queue @running_thread = Thread.new do @@ -31,7 +36,7 @@ def start(port: 4020) # Spawn a thread to listen on streaming server output output_thread = Thread.new do stdout_err.each_line do |line| - Rails.logger.info "Streaming server: #{line}" + logger&.info "Streaming server: #{line}" if status == :starting && line.match('Streaming API now listening on') status = :started @@ -115,12 +120,12 @@ def stop self.use_transactional_tests = true end - private - def streaming_server_manager @streaming_server_manager ||= StreamingServerManager.new end + private + def streaming_examples_present? RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[:streaming] == true } end diff --git a/spec/system/streaming/channel_subscriptions_spec.rb b/spec/system/streaming/channel_subscriptions_spec.rb new file mode 100644 index 00000000000000..54e125c293dfa0 --- /dev/null +++ b/spec/system/streaming/channel_subscriptions_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'debug' + +RSpec.describe 'Channel Subscriptions', :inline_jobs, :streaming do + let(:application) { Fabricate(:application, confidential: false) } + let(:scopes) { nil } + let(:access_token) { Fabricate(:accessible_access_token, resource_owner_id: user_account.user.id, application: application, scopes: scopes) } + + let(:user_account) { Fabricate(:account, username: 'alice', domain: nil) } + let(:bob_account) { Fabricate(:account, username: 'bob') } + + after do + streaming_client.close + end + + context 'when the access token has read scope' do + let(:scopes) { 'read' } + + it 'can subscribing to the public:local channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('public:local') + + # We need to publish a status as there is no positive acknowledgement of + # subscriptions: + status = PostStatusService.new.call(bob_account, text: 'Hello @alice') + + # And then we want to receive that status: + message = streaming_client.wait_for_message + + expect(message).to include( + stream: be_an(Array).and(contain_exactly('public:local')), + event: 'update', + payload: include( + id: status.id.to_s + ) + ) + end + end + + context 'when the access token cannot read notifications' do + let(:scopes) { 'read:statuses' } + + it 'cannot subscribing to the user:notifications channel' do + streaming_client.authenticate(access_token.token) + + streaming_client.connect + streaming_client.subscribe('user:notification') + + # We should receive an error back immediately: + message = streaming_client.wait_for_message + + expect(message).to include( + error: 'Access token does not have the required scopes', + status: 401 + ) + end + end +end diff --git a/spec/system/streaming/streaming_spec.rb b/spec/system/streaming/streaming_spec.rb new file mode 100644 index 00000000000000..c12bd1b18fed77 --- /dev/null +++ b/spec/system/streaming/streaming_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' +RSpec.describe 'Streaming', :inline_jobs, :streaming do + let(:authentication_method) { StreamingClient::AUTHENTICATION::SUBPROTOCOL } + let(:user) { Fabricate(:user) } + let(:scopes) { '' } + let(:application) { Fabricate(:application, confidential: false) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: application, scopes: scopes) } + let(:access_token) { token.token } + + before do + streaming_client.authenticate(access_token, authentication_method) + end + + after do + streaming_client.close + end + + context 'when authenticating via subprotocol' do + it 'is able to connect' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + end + end + + context 'when authenticating via authorization header' do + let(:authentication_method) { StreamingClient::AUTHENTICATION::AUTHORIZATION_HEADER } + + it 'is able to connect successfully' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + end + end + + context 'when authenticating via query parameter' do + let(:authentication_method) { StreamingClient::AUTHENTICATION::QUERY_PARAMETER } + + it 'is able to connect successfully' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + end + end + + context 'with a revoked access token' do + before do + token.revoke + end + + it 'receives an 401 unauthorized error' do + streaming_client.connect + + expect(streaming_client.status).to eq(401) + expect(streaming_client.open?).to be(false) + end + end + + context 'when revoking an access token after connection' do + it 'disconnects the client' do + streaming_client.connect + + expect(streaming_client.status).to eq(101) + expect(streaming_client.open?).to be(true) + + token.revoke + + expect(streaming_client.wait_for(:closed).code).to be(1000) + expect(streaming_client.open?).to be(false) + end + end +end diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb index 9e6805c68b4f06..a1eb7ebfa997a0 100644 --- a/spec/workers/activitypub/delivery_worker_spec.rb +++ b/spec/workers/activitypub/delivery_worker_spec.rb @@ -25,12 +25,23 @@ .to have_been_made.once end + context 'when using a numeric ID based scheme' do + let(:sender) { Fabricate(:account, id_scheme: :numeric_ap_id) } + + it 'performs a request to synchronize collection' do + subject.perform(payload, sender.id, url, { synchronize_followers: true }) + + expect(request_to_url) + .to have_been_made.once + end + end + def request_to_url a_request(:post, url) .with( headers: { 'Collection-Synchronization' => <<~VALUES.squish, - collectionId="#{account_followers_url(sender)}", digest="somehash", url="#{account_followers_synchronization_url(sender)}" + collectionId="#{ActivityPub::TagManager.instance.followers_uri_for(sender)}", digest="somehash", url="#{account_followers_synchronization_url(sender)}" VALUES } ) diff --git a/streaming/package.json b/streaming/package.json index 40a737a61dad6a..7df035d6e7e910 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/streaming", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.9.4", + "packageManager": "yarn@4.10.3", "engines": { "node": ">=20" }, @@ -27,7 +27,7 @@ "pino": "^9.0.0", "pino-http": "^10.0.0", "prom-client": "^15.0.0", - "uuid": "^11.0.0", + "uuid": "^13.0.0", "ws": "^8.12.1" }, "devDependencies": { @@ -35,11 +35,10 @@ "@types/cors": "^2.8.16", "@types/express": "^4.17.17", "@types/pg": "^8.6.6", - "@types/uuid": "^10.0.0", "@types/ws": "^8.5.9", "globals": "^16.0.0", "pino-pretty": "^13.0.0", - "typescript": "~5.7.3", + "typescript": "~5.9.0", "typescript-eslint": "^8.28.0" }, "optionalDependencies": { diff --git a/yarn.lock b/yarn.lock index e305123055a67a..bc2bc55b2586e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2608,10 +2608,10 @@ __metadata: languageName: node linkType: hard -"@ioredis/commands@npm:^1.3.0": - version: 1.3.0 - resolution: "@ioredis/commands@npm:1.3.0" - checksum: 10c0/5ab990a8f69c20daf3d7d64307aa9f13ee727c92ab4c7664a6943bb500227667a0c368892e9c4913f06416377db47dba78d58627fe723da476d25f2c04a6d5aa +"@ioredis/commands@npm:1.4.0": + version: 1.4.0 + resolution: "@ioredis/commands@npm:1.4.0" + checksum: 10c0/99afe21fba794f84a2b84cceabcc370a7622e7b8b97a6589456c07c9fa62a15d54c5546f6f7214fb9a2458b1fa87579d5c531aaf48e06cc9be156d5923892c8d languageName: node linkType: hard @@ -2850,7 +2850,6 @@ __metadata: react-redux-loading-bar: "npm:^5.0.8" react-router: "npm:^5.3.4" react-router-dom: "npm:^5.3.4" - react-router-scroll-4: "npm:^1.0.0-beta.1" react-select: "npm:^5.7.3" react-sparklines: "npm:^1.7.0" react-swipeable-views: "npm:^0.14.0" @@ -2863,6 +2862,7 @@ __metadata: rollup-plugin-gzip: "npm:^4.1.1" rollup-plugin-visualizer: "npm:^6.0.3" sass: "npm:^1.62.1" + scroll-behavior: "npm:^0.11.0" stacktrace-js: "npm:^2.0.2" storybook: "npm:^9.1.1" stringz: "npm:^2.1.0" @@ -2873,7 +2873,7 @@ __metadata: tesseract.js: "npm:^6.0.0" tiny-queue: "npm:^0.2.1" twitter-text: "npm:3.1.0" - typescript: "npm:~5.7.3" + typescript: "npm:~5.9.0" typescript-eslint: "npm:^8.29.1" use-debounce: "npm:^10.0.0" vite: "npm:^7.1.1" @@ -2906,7 +2906,6 @@ __metadata: "@types/cors": "npm:^2.8.16" "@types/express": "npm:^4.17.17" "@types/pg": "npm:^8.6.6" - "@types/uuid": "npm:^10.0.0" "@types/ws": "npm:^8.5.9" bufferutil: "npm:^4.0.7" cors: "npm:^2.8.5" @@ -2921,10 +2920,10 @@ __metadata: pino-http: "npm:^10.0.0" pino-pretty: "npm:^13.0.0" prom-client: "npm:^15.0.0" - typescript: "npm:~5.7.3" + typescript: "npm:~5.9.0" typescript-eslint: "npm:^8.28.0" utf-8-validate: "npm:^6.0.3" - uuid: "npm:^11.0.0" + uuid: "npm:^13.0.0" ws: "npm:^8.12.1" dependenciesMeta: bufferutil: @@ -3344,10 +3343,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.35": - version: 1.0.0-beta.35 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.35" - checksum: 10c0/feb6ab8f77ef2bde675099409c3ccd6a168f35a3c3e88482df3ca42494260fd42befe36e8e90ce358847a12aaab94cd8fe7069cf1e905edf91eb411d933906d9 +"@rolldown/pluginutils@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.38" + checksum: 10c0/8353ec2528349f79e27d1a3193806725b85830da334e935cbb606d88c1177c58ea6519c578e4e93e5f677f5b22aecb8738894dbed14603e14b6bffe3facf1002 languageName: node linkType: hard @@ -4502,13 +4501,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "@types/uuid@npm:10.0.0" - checksum: 10c0/9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 - languageName: node - linkType: hard - "@types/warning@npm:^3.0.0": version: 3.0.2 resolution: "@types/warning@npm:3.0.2" @@ -4839,18 +4831,18 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^5.0.0": - version: 5.0.3 - resolution: "@vitejs/plugin-react@npm:5.0.3" + version: 5.0.4 + resolution: "@vitejs/plugin-react@npm:5.0.4" dependencies: "@babel/core": "npm:^7.28.4" "@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.35" + "@rolldown/pluginutils": "npm:1.0.0-beta.38" "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.17.0" peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/3fc071455630a0584c170c544d20fc3edaccfb60a1e03ea14ca76f049f2657eb645aba9c216db016b8d70e4f894285a78fcd92ef63a2fcfa7864da378ac52761 + checksum: 10c0/bb9360a4b4c0abf064d22211756b999faf23889ac150de490590ca7bd029b0ef7f4cd8ba3a32b86682a62d46fb7bebd75b3fa9835c57c78123f4a646de2e0136 languageName: node linkType: hard @@ -6500,16 +6492,7 @@ __metadata: languageName: node linkType: hard -"dom-helpers@npm:^3.4.0": - version: 3.4.0 - resolution: "dom-helpers@npm:3.4.0" - dependencies: - "@babel/runtime": "npm:^7.1.2" - checksum: 10c0/1d2d3e4eadac2c4f4c8c7470a737ab32b7ec28237c4d094ea967ec3184168fd12452196fcc424a5d7860b6176117301aeaecba39467bf1a6e8492a8e5c9639d1 - languageName: node - linkType: hard - -"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.2.0": +"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.4, dom-helpers@npm:^5.2.0": version: 5.2.1 resolution: "dom-helpers@npm:5.2.1" dependencies: @@ -7467,13 +7450,6 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.1.1": - version: 3.3.0 - resolution: "fast-redact@npm:3.3.0" - checksum: 10c0/d81562510681e9ba6404ee5d3838ff5257a44d2f80937f5024c099049ff805437d0fae0124458a7e87535cc9dcf4de305bb075cab8f08d6c720bbc3447861b4e - languageName: node - linkType: hard - "fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" @@ -7693,14 +7669,14 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.3.0": - version: 11.3.0 - resolution: "fs-extra@npm:11.3.0" +"fs-extra@npm:^11.3.2": + version: 11.3.2 + resolution: "fs-extra@npm:11.3.2" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a + checksum: 10c0/f5d629e1bb646d5dedb4d8b24c5aad3deb8cc1d5438979d6f237146cd10e113b49a949ae1b54212c2fbc98e2d0995f38009a9a1d0520f0287943335e65fe919b languageName: node linkType: hard @@ -8378,10 +8354,10 @@ __metadata: linkType: hard "ioredis@npm:^5.3.2": - version: 5.7.0 - resolution: "ioredis@npm:5.7.0" + version: 5.8.0 + resolution: "ioredis@npm:5.8.0" dependencies: - "@ioredis/commands": "npm:^1.3.0" + "@ioredis/commands": "npm:1.4.0" cluster-key-slot: "npm:^1.1.0" debug: "npm:^4.3.4" denque: "npm:^2.1.0" @@ -8390,7 +8366,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/c63c521a953bfaf29f8c8871b122af38e439328336fa238f83bfbb066556f64daf69ed7a4ec01fc7b9ee1f0862059dd188b8c684150125d362d36642399b30ee + checksum: 10c0/66fad6283c6d9052b4aa0987d592c1bf6c9471304eb0edf0c9d18024b1b38028adf29c05f1cf114b90f5bdb516576f897a654946e8c29568f404ac33cd3b9d19 languageName: node linkType: hard @@ -10058,6 +10034,13 @@ __metadata: languageName: node linkType: hard +"page-lifecycle@npm:^0.1.2": + version: 0.1.2 + resolution: "page-lifecycle@npm:0.1.2" + checksum: 10c0/509dbbc2ad2000dffcf591f66ab13d80fb1dba9337d85c76269173f7a5c3959b5a876e3bfb1e4494f6b932c1dc02a0b5824ebd452ab1a7204d4abdf498cb27c5 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -10370,11 +10353,10 @@ __metadata: linkType: hard "pino@npm:^9.0.0": - version: 9.11.0 - resolution: "pino@npm:9.11.0" + version: 9.12.0 + resolution: "pino@npm:9.12.0" dependencies: atomic-sleep: "npm:^1.0.0" - fast-redact: "npm:^3.1.1" on-exit-leak-free: "npm:^2.1.0" pino-abstract-transport: "npm:^2.0.0" pino-std-serializers: "npm:^7.0.0" @@ -10382,11 +10364,12 @@ __metadata: quick-format-unescaped: "npm:^4.0.3" real-require: "npm:^0.2.0" safe-stable-stringify: "npm:^2.3.1" + slow-redact: "npm:^0.3.0" sonic-boom: "npm:^4.0.1" thread-stream: "npm:^3.0.0" bin: pino: bin.js - checksum: 10c0/ba908f95b61fa2c2d6c432e1f39a4394cc0dbf356c4f8837bd9c07538d749699b78204a5557e6050870f2988c25c3f0b6a88693d4bd185ebeef57d75a3b25e38 + checksum: 10c0/5cfe093e972a8471a90f7f380c01379eed3fd937038acb97d1de9180f097c044855ca89a2e70baa699aec3e8dcaec037d03e2c90dde235102a3e17b40f54cc1f languageName: node linkType: hard @@ -11299,21 +11282,6 @@ __metadata: languageName: node linkType: hard -"react-router-scroll-4@npm:^1.0.0-beta.1": - version: 1.0.0-beta.2 - resolution: "react-router-scroll-4@npm:1.0.0-beta.2" - dependencies: - scroll-behavior: "npm:^0.9.1" - warning: "npm:^3.0.0" - peerDependencies: - prop-types: ^15.6.0 - react: ^15.0.0 || ^16.0.0 - react-dom: ^15.0.0 || ^16.0.0 - react-router-dom: ^4.0 - checksum: 10c0/ad195b7359fd3146530cf299ec437f0a619c577b2cacfb2c76a156d3cd9d5d3e97af56e17c300c37ca8c485041e93124fe63f0c86db6aea468caf838281e62cb - languageName: node - linkType: hard - "react-router@npm:5.3.4, react-router@npm:^5.3.4": version: 5.3.4 resolution: "react-router@npm:5.3.4" @@ -12032,8 +12000,8 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.93.0 - resolution: "sass@npm:1.93.0" + version: 1.93.2 + resolution: "sass@npm:1.93.2" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -12044,7 +12012,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/51dcb4e65a69f97b4c200ee154ca45f81b748a45f8ef0ec3236b774bb143590a9304038e9ab09f809f734d4edb3add96a0a690b2e8451ff66b9f57c469b2685e + checksum: 10c0/5a19f12dbe8c142e40c1e0473d1e624898242b1c21010301e169b528be8c580df6356329c798522b525eb11eda4b04b9b77422badc55c47889600f8477201d2b languageName: node linkType: hard @@ -12073,13 +12041,14 @@ __metadata: languageName: node linkType: hard -"scroll-behavior@npm:^0.9.1": - version: 0.9.12 - resolution: "scroll-behavior@npm:0.9.12" +"scroll-behavior@npm:^0.11.0": + version: 0.11.0 + resolution: "scroll-behavior@npm:0.11.0" dependencies: - dom-helpers: "npm:^3.4.0" + dom-helpers: "npm:^5.1.4" invariant: "npm:^2.2.4" - checksum: 10c0/4f438c48b93a1dcc2ab51a18670fac6f5ce41885291d8aa13251b4a187be9d0c6dd518ee974eb52ac9bbe227b9811c2615ecca73192a1a190b78dfdadb9c2cf2 + page-lifecycle: "npm:^0.1.2" + checksum: 10c0/c54010c9fdd9fc360fd7887ecf64f16972f9557ac679723709612cd54fc4778c7433ab46a9637933179ef31471f78e2591fb35351dc0e15537fecf1c8c89d32c languageName: node linkType: hard @@ -12325,6 +12294,13 @@ __metadata: languageName: node linkType: hard +"slow-redact@npm:^0.3.0": + version: 0.3.0 + resolution: "slow-redact@npm:0.3.0" + checksum: 10c0/bb2f77830f64fb01079849e0c6433c15e782b88cccb82d4b0d62ce216307cf514ea3f92e9b2c6ae1b1d613ac7743305d5f0324e94c9dc8e41908939456248f9a + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -13473,43 +13449,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.0": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 - languageName: node - linkType: hard - -"typescript@npm:~5.7.3": - version: 5.7.3 - resolution: "typescript@npm:5.7.3" +"typescript@npm:^5.6.0, typescript@npm:~5.9.0": + version: 5.9.2 + resolution: "typescript@npm:5.9.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/b7580d716cf1824736cc6e628ab4cd8b51877408ba2be0869d2866da35ef8366dd6ae9eb9d0851470a39be17cbd61df1126f9e211d8799d764ea7431d5435afa + checksum: 10c0/cd635d50f02d6cf98ed42de2f76289701c1ec587a363369255f01ed15aaf22be0813226bff3c53e99d971f9b540e0b3cc7583dbe05faded49b1b0bed2f638a18 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" +"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin, typescript@patch:typescript@npm%3A~5.9.0#optional!builtin": + version: 5.9.2 + resolution: "typescript@patch:typescript@npm%3A5.9.2#optional!builtin::version=5.9.2&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A~5.7.3#optional!builtin": - version: 5.7.3 - resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/6fd7e0ed3bf23a81246878c613423730c40e8bdbfec4c6e4d7bf1b847cbb39076e56ad5f50aa9d7ebd89877999abaee216002d3f2818885e41c907caaa192cc4 + checksum: 10c0/34d2a8e23eb8e0d1875072064d5e1d9c102e0bdce56a10a25c0b917b8aa9001a9cf5c225df12497e99da107dc379360bc138163c66b55b95f5b105b50578067e languageName: node linkType: hard @@ -13825,12 +13781,12 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^11.0.0": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" +"uuid@npm:^13.0.0": + version: 13.0.0 + resolution: "uuid@npm:13.0.0" bin: - uuid: dist/esm/bin/uuid - checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + uuid: dist-node/bin/uuid + checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67 languageName: node linkType: hard @@ -13892,17 +13848,17 @@ __metadata: linkType: hard "vite-plugin-static-copy@npm:^3.1.1": - version: 3.1.2 - resolution: "vite-plugin-static-copy@npm:3.1.2" + version: 3.1.3 + resolution: "vite-plugin-static-copy@npm:3.1.3" dependencies: chokidar: "npm:^3.6.0" - fs-extra: "npm:^11.3.0" + fs-extra: "npm:^11.3.2" p-map: "npm:^7.0.3" picocolors: "npm:^1.1.1" - tinyglobby: "npm:^0.2.14" + tinyglobby: "npm:^0.2.15" peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/1a65f4c9d291cc27483a5b225b1ac5610edc3aa2f13fa3a76a77327874c83bbee52e1011ee0bf5b0168b9b7b974213d49fe800e44af398cfbcb6607814b45c5b + checksum: 10c0/f58bf609246c440b4e3c0db10abf5965658c34ee03e72b94d4fc6ff35fa4568b5baa0fe36057234a4b1e84a9b4b3c2cdbff9f943b9e69d883d3a05353cbf9090 languageName: node linkType: hard @@ -14055,15 +14011,6 @@ __metadata: languageName: node linkType: hard -"warning@npm:^3.0.0": - version: 3.0.0 - resolution: "warning@npm:3.0.0" - dependencies: - loose-envify: "npm:^1.0.0" - checksum: 10c0/6a2a56ab3139d3927193d926a027e74e1449fa47cc692feea95f8a81a4bb5b7f10c312def94cce03f3b58cb26ba3247858e75d17d596451d2c483a62e8204705 - languageName: node - linkType: hard - "warning@npm:^4.0.1, warning@npm:^4.0.3": version: 4.0.3 resolution: "warning@npm:4.0.3"