diff --git a/.nvmrc b/.nvmrc index 6e77d0a7496..403f75d0382 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.19 +22.20 diff --git a/Gemfile b/Gemfile index 5dd5d5bf268..0d9ab342715 100644 --- a/Gemfile +++ b/Gemfile @@ -4,12 +4,12 @@ source 'https://rubygems.org' ruby '>= 3.2.0', '< 3.5.0' gem 'propshaft' -gem 'puma', '~> 6.3' +gem 'puma', '~> 7.0' 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 b3364ba38d4..cd96d60e1d5 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) @@ -626,10 +626,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 +637,13 @@ GEM date stringio public_suffix (6.0.2) - puma (6.6.1) + 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 +669,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 +693,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 +720,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 +766,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 +774,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 +790,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 +812,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 +880,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 +901,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 +920,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 +990,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) @@ -1052,7 +1054,7 @@ DEPENDENCIES prometheus_exporter (~> 2.2) propshaft public_suffix (~> 6.0) - puma (~> 6.3) + puma (~> 7.0) pundit (~> 2.3) rack-attack (~> 6.6) rack-cors @@ -1100,6 +1102,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 c3131edce93..efd0c92cef2 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -71,6 +71,10 @@ class AccountsController < ApplicationController 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 4aa6a4a771f..e875517b021 100644 --- a/app/controllers/activitypub/likes_controller.rb +++ b/app/controllers/activitypub/likes_controller.rb @@ -28,7 +28,7 @@ class ActivityPub::LikesController < ActivityPub::BaseController 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 a9476b806f5..928977768b9 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -73,6 +73,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController 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 0a19275d38e..1959f50d676 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -37,7 +37,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController 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 @@ class ActivityPub::RepliesController < ActivityPub::BaseController 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 @@ class ActivityPub::RepliesController < ActivityPub::BaseController # 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 @@ class ActivityPub::RepliesController < ActivityPub::BaseController # 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 65b4a5b3831..2d1e389885a 100644 --- a/app/controllers/activitypub/shares_controller.rb +++ b/app/controllers/activitypub/shares_controller.rb @@ -28,7 +28,7 @@ class ActivityPub::SharesController < ActivityPub::BaseController 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 b8ec4fe1400..5cfb2d0e8fd 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 @@ class Api::V1::Statuses::InteractionPoliciesController < Api::V1::Statuses::Base 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 962855884ec..be3a4edc83d 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 @@ class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController 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 @@ class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController 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 93dbd8f9d1c..6c4e7619b7b 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -157,8 +157,6 @@ class Api::V1::StatusesController < Api::BaseController 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 2b132417f7c..7b3cd4d3ea6 100644 --- a/app/controllers/concerns/account_owned_concern.rb +++ b/app/controllers/concerns/account_owned_concern.rb @@ -18,7 +18,11 @@ module AccountOwnedConcern 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 5b63705a9bf..f1e1480c0c0 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 f4c7b37088a..e9727b756a4 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -60,17 +60,17 @@ class FollowerAccountsController < ApplicationController 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.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.followers_count, first: page_url(1) diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 268fad96d09..803d6e342a9 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -49,7 +49,7 @@ class FollowingAccountsController < ApplicationController 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 @@ class FollowingAccountsController < ApplicationController 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.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.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 00000000000..c788113487c --- /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 96c0c43c74c..ccb69f0a3d3 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -194,8 +194,10 @@ export function submitCompose(successCallback) { const status = getState().getIn(['compose', 'text'], ''); const media = getState().getIn(['compose', 'media_attachments']); const statusId = getState().getIn(['compose', 'id'], null); + 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; } @@ -227,11 +229,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: getState().getIn(['compose', 'sensitive']), - spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '', visibility: visibility, poll: getState().getIn(['compose', 'poll'], null), language: getState().getIn(['compose', 'language']), diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index b720b4746d0..b5ff686f864 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 4d5f8acb65b..f12f06751d7 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 1009e77524b..72fee0a4a30 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 3a66fe5042c..bb5a0936593 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 3190c4384b2..375f4932b2e 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 00000000000..9fda5714d97 --- /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 08d62b2c37a..a6ecc869c1d 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 00000000000..e070eb30dd8 --- /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 00000000000..ab0642b8b23 --- /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/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 98ef3ba3f1c..97aaecd1aac 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 } 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,60 @@ 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) { + } 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) { label = intl.formatMessage(messages.followBack); } else { - label = intl.formatMessage(messages.follow); + label = intl.formatMessage(followMessage); } if (accountId === me) { @@ -88,7 +156,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 +170,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 4f88385bef9..f6b72d43404 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 00000000000..9c104ba45cb --- /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 00000000000..51baea614d7 --- /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 815b4b59abd..1dc1d45083d 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 22ec18afa9c..38c3cd991bc 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 8664320abe0..196da7c99a8 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -118,6 +118,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, @@ -567,6 +568,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 e81d334a938..402695a8295 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 49bdc953e14..337eca50717 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'; @@ -11,7 +11,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'; @@ -47,10 +46,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) => @@ -192,65 +188,3 @@ const ReblogMenuItem: FC = ({ ); }; - -// Legacy helpers - -// Switch between the legacy and new reblog button based on feature flag. -export const BoostButton: FC = (props) => { - if (isFeatureEnabled('outgoing_quotes')) { - return ; - } - 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 0e72a8cefe7..3e82912ab19 100644 --- a/app/javascript/mastodon/components/status_action_bar/index.jsx +++ b/app/javascript/mastodon/components/status_action_bar/index.jsx @@ -23,7 +23,6 @@ import { Dropdown } from 'mastodon/components/dropdown_menu'; import { me } 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'; @@ -281,7 +280,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 af0059c7d62..d766793d87c 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 a2513cc552d..3e6d20c74c4 100644 --- a/app/javascript/mastodon/containers/compose_container.jsx +++ b/app/javascript/mastodon/containers/compose_container.jsx @@ -5,7 +5,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 8dcda3b0a94..ee861366a50 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 { fetchCustomEmojis } from 'mastodon/actions/custom_emojis'; import { hydrateStore } from 'mastodon/actions/store'; @@ -14,12 +13,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); @@ -45,10 +46,6 @@ export default class Mastodon extends PureComponent { } } - shouldUpdateScroll (prevRouterProps, { location }) { - return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey); - } - render () { return ( @@ -56,7 +53,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 d21ff63687d..00000000000 --- 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 00000000000..b8726a1a751 --- /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 00000000000..0d0ab364dc0 --- /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 00000000000..a7eb7808003 --- /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 00000000000..fe8a208aae4 --- /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 ba280ffd231..baf4157f96e 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -47,8 +47,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(); @@ -81,9 +79,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 f58f1f4a8c4..776157ccf51 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, @@ -383,7 +383,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) { @@ -605,6 +605,15 @@ export const AccountHeader: React.FC<{ handleUnblockDomain, ]); + const menu = accountId !== me && ( + + ); + if (!account) { return null; } @@ -718,21 +727,16 @@ export const AccountHeader: React.FC<{ ); } - if (relationship?.blocking) { + const isMovedAndUnfollowedAccount = account.moved && !relationship?.following; + + if (!isMovedAndUnfollowedAccount) { 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 8bab174f67a..7c38af3277d 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'; @@ -150,6 +151,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 () { @@ -462,6 +468,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)} /> )); } @@ -495,11 +502,20 @@ class Status extends ImmutablePureComponent { } componentDidUpdate (prevProps) { - const { status, ancestorsIds } = this.props; + const { status, ancestorsIds, descendantsIds } = this.props; if (status && (ancestorsIds.length > prevProps.ancestorsIds.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 () { @@ -510,9 +526,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; } @@ -586,7 +602,7 @@ class Status extends ImmutablePureComponent { )} /> - +
{ancestors} @@ -632,8 +648,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 19ffe2bae52..47f9fca8904 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 63c93b54b65..9aff30eeac1 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts @@ -5,7 +5,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 00000000000..e2154ef48df --- /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 58e39da07bf..45b8d458b10 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 00000000000..a0bd2366374 --- /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 b78d5b64c4f..0290b01d2f5 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 caaa4e590bd..944feb325e9 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -32,7 +32,9 @@ import { ConfirmDeleteListModal, ConfirmReplyModal, ConfirmEditStatusModal, + ConfirmUnblockModal, ConfirmUnfollowModal, + ConfirmWithdrawRequestModal, ConfirmClearNotificationsModal, ConfirmLogOutModal, ConfirmFollowToListModal, @@ -57,7 +59,9 @@ export const MODAL_COMPONENTS = { 'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }), '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/hooks/useBreakpoint.tsx b/app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx index af96ab37663..cb7b3551f22 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 efec38caf4b..04c7f33dfd5 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 a19852e0d26..e46ede3553f 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 160fe18503f..00e1dd9bb44 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 4a078b86f5b..00000000000 --- a/app/javascript/mastodon/initial_state.js +++ /dev/null @@ -1,145 +0,0 @@ -// @ts-check - -/** - * @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=} boost_modal - * @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} display_media - * @property {string} domain - * @property {boolean=} expand_spoilers - * @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} reduce_motion - * @property {string} repository - * @property {boolean} search_enabled - * @property {boolean} trends_enabled - * @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]; - -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'); - -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 00000000000..b6d7d55483f --- /dev/null +++ b/app/javascript/mastodon/initial_state.ts @@ -0,0 +1,141 @@ +import type { ApiAccountJSON } from './api_types/accounts'; + +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; +} + +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]; +} + +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'); + +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 6df46085280..7ab1d793ed4 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 661a1bebdb3..6733b1b929b 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 3af126d5e71..514a8d7f289 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 892a5f7ae81..50b0122297d 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 be7541011e3..720719d84df 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 bf4f493831e..e51b5a56b1d 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 618ce28c61b..60256398848 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 4caa8bc3953..526248e76c6 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 0f2f63c9ce7..c33d64a54e5 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 5f3d6913ab2..1d33a2fec5f 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -70,7 +70,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}", @@ -249,8 +248,6 @@ "confirmations.revoke_quote.message": "Aquesta acció no es pot desfer.", "confirmations.revoke_quote.title": "Eliminar la publicació?", "confirmations.unfollow.confirm": "Deixa de seguir", - "confirmations.unfollow.message": "Segur que vols deixar de seguir {name}?", - "confirmations.unfollow.title": "Deixar de seguir l'usuari?", "content_warning.hide": "Amaga la publicació", "content_warning.show": "Mostra-la igualment", "content_warning.show_more": "Mostra'n més", @@ -844,8 +841,6 @@ "status.bookmark": "Marca", "status.cancel_reblog_private": "Desfés l'impuls", "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.continued_thread": "Continuació del fil", "status.copy": "Copia l'enllaç al tut", "status.delete": "Elimina", diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index 0188b67e274..ea27df84921 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 7a0306bc31c..ce58faf5e00 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 d1aaffe2f2a..7b69cdc6542 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 4c24eaa3877..843e2ae1879 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 9521401bca0..5c4dc9ff1d4 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 0e18f9a79a3..2ee15010ccf 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 feefaabeea6..6df9c3c1e2e 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 60f4e0d8f9d..908c0509efc 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 7721cc36d3f..a88a2f1801b 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -28,6 +28,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}}", @@ -40,6 +41,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_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", @@ -70,7 +76,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", @@ -252,9 +257,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", @@ -865,8 +873,13 @@ "status.cannot_quote": "You are not allowed to quote this post", "status.cannot_reblog": "This post cannot be boosted", "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", @@ -910,6 +923,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 34967d20b09..6ce220a5384 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 3f582452d51..485ac9036ad 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 6b48c21d361..3875b0187d0 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 f84cb415114..3123f55b85f 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 791736f1310..9175a6cc29e 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 579701a525e..945ae1cd278 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 b2b0a6c2de3..48d686a4d6b 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 8bf69ab31d8..27b5c209e6d 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 f8425dd8efd..37db5a1b159 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 a8db9baeca0..52a385e9528 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -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", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Hendan atgerðin kann ikki angrast.", "confirmations.revoke_quote.title": "Strika post?", "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?", "content_warning.hide": "Fjal post", "content_warning.show": "Vís kortini", "content_warning.show_more": "Vís meiri", @@ -865,8 +868,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", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 2bc6195c55c..63e8fdf2ab7 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 e579dbe09b9..e16e177ca49 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 31b5196f3ed..032cdd870fb 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 f1641450df1..cec7a6e8f3d 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}", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Ní féidir an gníomh seo a chealú.", "confirmations.revoke_quote.title": "Bain postáil?", "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?", "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 +867,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,6 +912,7 @@ "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", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 2b2efe733c8..b677f8ec50a 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 2d37337a0c1..a7e1cfd8c30 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 bcba5e0b7ce..f67ee0fd693 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 ca56ad484e2..63c9be8a8dd 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 cb3f25de069..3f8842d91ff 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 83fb3e60fe4..1aba0615b41 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 62669eda9b5..0c77efd6cde 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 8291e8ad5f9..50b836ad629 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 7c5c816c67d..c8d7d8ef001 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 4a464ef16e2..e978cfafc4b 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 b51d32d05a7..07795897069 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 f380a4b4a50..4dbdee7c8c3 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 448b51944bd..af4b5d4866f 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -70,7 +70,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}", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Questa azione non può essere annullata.", "confirmations.revoke_quote.title": "Rimuovere il post?", "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?", "content_warning.hide": "Nascondi post", "content_warning.show": "Mostra comunque", "content_warning.show_more": "Mostra di più", @@ -864,8 +861,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,6 +906,7 @@ "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", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 8cd1bafa4ad..ee4cc5c7b3b 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.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.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": "続きを表示", @@ -642,21 +639,21 @@ "notifications.column_settings.alert": "デスクトップ通知", "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.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.favourites": "お気に入り", @@ -844,7 +841,6 @@ "status.cancel_reblog_private": "ブースト解除", "status.cannot_quote": "この投稿は引用できません", "status.cannot_reblog": "この投稿はブーストできません", - "status.context.load_new_replies": "新しい返信があります", "status.continued_thread": "続きのスレッド", "status.copy": "投稿へのリンクをコピー", "status.delete": "削除", @@ -875,6 +871,7 @@ "status.quote.cancel": "引用をキャンセル", "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 ea7375fd00b..2b495ab0b14 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 c4848099575..0add265b63b 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 b04790d9a12..8e29c3fbeed 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 ae4e4dd7b33..1d383c6b276 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 40c9ae00044..f4681794542 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 3518959e2b4..d641c896237 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 b1116a7a317..d21d75c182b 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 754f6eb3c6b..147e362f3b0 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 6276bb80383..bf13549d46a 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 d80b8daf200..9d289c5c53c 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 67e561dceef..21f7d2d5d02 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 63fc97c8abb..017ef1ce3f2 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 28d3ae9ab37..6a762c15273 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 2ffdf6cfd0b..1530736f65b 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 a65eab5aa70..56da51abe93 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 9bce13a8dad..a4ffafbe5cc 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 a0b99c59738..c686ccfbb57 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 f61f7469d52..54f14fde278 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", @@ -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 caab03e7cd4..f13b1297a71 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -70,7 +70,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", @@ -253,8 +252,6 @@ "confirmations.revoke_quote.message": "Du kan ikkje angra denne handlinga.", "confirmations.revoke_quote.title": "Fjern innlegget?", "confirmations.unfollow.confirm": "Slutt å fylgja", - "confirmations.unfollow.message": "Er du sikker på at du vil slutta å fylgja {name}?", - "confirmations.unfollow.title": "Slutt å fylgja brukaren?", "content_warning.hide": "Gøym innlegg", "content_warning.show": "Vis likevel", "content_warning.show_more": "Vis meir", @@ -864,8 +861,6 @@ "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.continued_thread": "Framhald til tråden", "status.copy": "Kopier lenke til status", "status.delete": "Slett", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index fa03560194c..2ef8a7085a6 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 0d87228ea1a..cdd109a7153 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 cd20e4bce04..760dff557fb 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 ab1062ed8b0..d121bea1ae1 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 cc46fb4304d..5457e91dbab 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 af7c70b9fba..751db6fdd4d 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 ce3dde8eeaf..9bf8150ba04 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 a22268bb62b..5571a68d760 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 20cc8e8e4eb..9426a9c5c07 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 b9c13df0581..e7352f1c807 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 d126379384e..254af6a6019 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 52445feced9..354c3393ed7 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 107ef3f1cf6..e326d3fa6a7 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 771f52618c4..ffc36973931 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 9127605dbaf..43f6c511d37 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 56db9a5ac36..3f61ff0a29c 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 8cb2e30cb5f..f91044899e1 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 485109105b8..117c25949de 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 3bbe6434ed8..e3418890419 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 55913be9246..709b0f9e891 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 10d461e9e83..7664174c17a 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 37994550508..507894ba142 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 cbdea417b71..c4442f74361 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 a1fade41ca6..1fcaad9b64d 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 dbc6b4b871c..d21058a67b0 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 d4f5993b857..d8f07d12c9e 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ş", @@ -253,8 +258,6 @@ "confirmations.revoke_quote.message": "Bu işlem geri alınamaz.", "confirmations.revoke_quote.title": "Gönderiyi silmek ister misiniz?", "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?", "content_warning.hide": "Gönderiyi gizle", "content_warning.show": "Yine de göster", "content_warning.show_more": "Daha fazla göster", @@ -864,8 +867,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 +912,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 4c454c37fb2..6123b199970 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 527457ca378..c87d8ee5dd8 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 913ef41d744..45f5b3f4580 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 b1db5d819f6..0448f69c63c 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 d4b37a32f7b..2a8b8175765 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 99a5dc9051b..08b224e3d64 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 ff37d75a07c..7317cd7282f 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 c69e72eced4..1397f28f930 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 1637e1cf589..3626f7da54a 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 e4f9dcea29b..5e340810941 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 a579efa406b..ea4561bc610 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 6c08cc7cbfc..6aacc396dc8 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 fc4448740f4..c666e2c94d7 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 16863223007..971aefa6d16 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 2f3b96411d8..e2a3f0c0afe 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 0dce0b9b3ea..3d8bba7c00c 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -69,6 +69,7 @@ body { &.layout-single-column { height: auto; min-height: 100vh; + min-height: 100dvh; overflow-y: scroll; } @@ -185,7 +186,8 @@ button { } & > noscript { - height: 100vh; + min-height: 100vh; + min-height: 100dvh; } } @@ -193,6 +195,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 acfc906dc65..bf518d72fcf 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1597,6 +1597,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 { @@ -2267,6 +2277,7 @@ position: relative; border-radius: var(--avatar-border-radius); background: var(--surface-background-color); + flex-shrink: 0; img { width: 100%; @@ -2960,11 +2971,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 { @@ -2975,6 +2989,7 @@ a.account__display-name { width: 100%; height: 100%; min-height: 100vh; + min-height: 100dvh; padding-bottom: env(safe-area-inset-bottom); &__pane { @@ -3136,6 +3151,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; @@ -3176,7 +3214,6 @@ a.account__display-name { .column, .drawer { flex: 1 1 100%; - overflow: hidden; } @media screen and (width > $mobile-breakpoint) { @@ -3285,6 +3322,7 @@ a.account__display-name { .columns-area__panels { min-height: 100vh; + min-height: 100dvh; gap: 0; } @@ -6367,7 +6405,10 @@ a.status-card { line-height: 24px; color: $primary-text-color; font-weight: 500; - margin-bottom: 8px; + + &:not(:only-child) { + margin-bottom: 8px; + } } strong { @@ -8372,47 +8413,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; @@ -8461,6 +8461,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; @@ -10388,6 +10451,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; @@ -10862,6 +10940,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 00000000000..e58aa7b75ee --- /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 7b49acd1197..27dea05bf64 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -9,7 +9,7 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity 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 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity # 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 870cbea7e40..43574d3657e 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -39,13 +39,25 @@ class ActivityPub::TagManager 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 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? - account_status_url(target.account, target) + 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) + end when :emoji emoji_url(target) when :flag @@ -57,7 +69,7 @@ class ActivityPub::TagManager return quote.approval_uri unless quote.quoted_account&.local? 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) @@ -68,6 +80,10 @@ class ActivityPub::TagManager 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 @@ -75,7 +91,7 @@ class ActivityPub::TagManager 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) @@ -87,49 +103,61 @@ class ActivityPub::TagManager 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 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 @@ -262,10 +290,9 @@ class ActivityPub::TagManager 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 01644fdc92b..a837cc6a6ff 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -52,6 +52,7 @@ # requested_review_at :datetime # indexable :boolean default(FALSE), not null # attribution_domains :string default([]), is an Array +# id_scheme :integer default("username_ap_id") # class Account < ApplicationRecord @@ -105,6 +106,7 @@ class Account < ApplicationRecord enum :protocol, { ostatus: 0, activitypub: 1 } enum :suspension_origin, { local: 0, remote: 1 }, prefix: true + enum :id_scheme, { username_ap_id: 0, numeric_ap_id: 1 } validates :username, presence: true validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? } @@ -444,6 +446,7 @@ class Account < ApplicationRecord before_validation :prepare_contents, if: :local? before_create :generate_keys + before_create :set_id_scheme before_destroy :clean_feed_manager def ensure_keys! @@ -468,6 +471,12 @@ class Account < ApplicationRecord 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 4eab55ca3e6..7f1d91a1602 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -215,8 +215,9 @@ module Account::Interactions 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 5f23e683b99..a19a6308fac 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -82,15 +82,17 @@ 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 :registrations_mode, inclusion: { in: REGISTRATION_MODES }, if: -> { defined?(@registrations_mode) } validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) } 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 b7463869959..03ceae718e5 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -36,10 +36,6 @@ class StatusPolicy < ApplicationPolicy 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 ab4743bab4e..4c5d3f4cf8f 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -36,7 +36,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 d82ac326216..0b951f6caa6 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 1e267df82a4..a06ddc68617 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -34,7 +34,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 e6d0c407307..c55a54df378 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -69,7 +69,7 @@ class PostStatusService < BaseService @text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present? && @quoted_status.blank? @visibility = @options[:visibility] || @account.user&.setting_default_privacy @visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced? - @visibility = :private if @quoted_status&.private_visibility? + @visibility = :private if @quoted_status&.private_visibility? && %i(public unlisted).include?(@visibility&.to_sym) @scheduled_at = @options[:scheduled_at]&.to_datetime @scheduled_at = nil if scheduled_in_the_past? rescue ArgumentError diff --git a/app/views/admin/announcements/_announcement.html.haml b/app/views/admin/announcements/_announcement.html.haml index 87ae97cf484..5944b0b2954 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 085bdbd1568..ddaca5d8a9f 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 7d84534d59b..d79c1dfa6c6 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 1eb47a0b54e..adc8f1ff04a 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 cb5a3eb6baf..7303eca662a 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 2cc056420f4..6488c3a554b 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 dca5abeb777..6159d97820a 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 a544ac3a758..15326f30062 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 3745ed219ff..b28302a93f7 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 40b5c42404f..ade7175c9d5 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -55,7 +55,7 @@ class ActivityPub::DeliveryWorker 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/da.yml b/config/locales/da.yml index 9f15c2a43f0..8ad3ccb1389 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 28502bdd9c8..184ab506d64 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -572,6 +572,7 @@ ja: title: モデレーション moderation_notes: create: モデレーションノートを追加 + title: モデレーションメモ private_comment: コメント (非公開) public_comment: コメント (公開) purge: パージ @@ -1067,14 +1068,18 @@ ja: trending: トレンド 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: 削除 @@ -1679,6 +1684,7 @@ ja: self_vote: 自分のアンケートには解答できません too_few_options: は複数必要です too_many_options: は%{max}個までです + vote: 投票 preferences: other: その他 posting_defaults: デフォルトの投稿設定 diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 9ec00b897aa..61fb800301d 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 21474c6d70d..20e53b4a865 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/puma.rb b/config/puma.rb index 16c481a2aee..d34c14b425d 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -27,7 +27,7 @@ if ENV['MASTODON_PROMETHEUS_EXPORTER_ENABLED'] == 'true' end end - on_worker_boot do + before_worker_boot do # Ruby process metrics (memory, GC, etc) PrometheusExporter::Instrumentation::Process.start(type: 'puma') @@ -44,7 +44,7 @@ if ENV['MASTODON_PROMETHEUS_EXPORTER_ENABLED'] == 'true' end end -on_worker_boot do +before_worker_boot do ActiveSupport.on_load(:active_record) do ActiveRecord::Base.establish_connection end diff --git a/config/routes.rb b/config/routes.rb index 227b229a5bb..412372600ef 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -95,7 +95,20 @@ Rails.application.routes.draw do 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 @@ -106,16 +119,19 @@ Rails.application.routes.draw do 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 00000000000..7dd987dcc0e --- /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 3b3c1bdfe5f..78e75f787af 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_12_082651) 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" @@ -199,6 +199,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_12_082651) do t.boolean "indexable", default: false, null: false 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 43aabc51100..5942219cedc 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 a6bbfcd24d1..043f22b28e5 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -45,7 +45,7 @@ module Mastodon def api_versions { - mastodon: Mastodon::Feature.outgoing_quotes_enabled? ? 7 : 6, + mastodon: 7, } end diff --git a/package.json b/package.json index fcc74dc83f8..0fd14de6565 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" }, @@ -99,7 +99,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", @@ -111,6 +110,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", @@ -190,7 +190,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 64627cbdfbe..aae4ce03383 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 7a4cf3c1b86..70e084a9c92 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -7,7 +7,7 @@ RSpec.describe ActivityPub::TagManager do 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 @@ RSpec.describe ActivityPub::TagManager do 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 @@ RSpec.describe ActivityPub::TagManager do 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 @@ -65,16 +456,28 @@ RSpec.describe ActivityPub::TagManager do 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 @@ -115,6 +518,11 @@ RSpec.describe ActivityPub::TagManager do expect(subject.cc(status)).to eq [account_followers_url(status.account)] 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'] @@ -133,8 +541,10 @@ RSpec.describe ActivityPub::TagManager do 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 f450997976a..085866ef1d3 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -28,7 +28,7 @@ RSpec.describe StatusCacheHydrator do 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 e6e9076edb6..b683259c8c8 100644 --- a/spec/models/concerns/account/interactions_spec.rb +++ b/spec/models/concerns/account/interactions_spec.rb @@ -563,6 +563,22 @@ RSpec.describe Account::Interactions do 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 3d3e556f353..6be93ecb70e 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -30,7 +30,8 @@ end # 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 72913ebf22d..cc2a5be7c5f 100644 --- a/spec/requests/accounts_spec.rb +++ b/spec/requests/accounts_spec.rb @@ -5,6 +5,14 @@ require 'rails_helper' 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 313cab2a448..4cd02b187d9 100644 --- a/spec/requests/activitypub/replies_spec.rb +++ b/spec/requests/activitypub/replies_spec.rb @@ -220,6 +220,12 @@ RSpec.describe 'ActivityPub Replies' do 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 cdc33e40d7e..321a68cd251 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 @@ RSpec.describe 'Interaction policies', feature: :outgoing_quotes do ) 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 9456556ce99..01e9e17b077 100644 --- a/spec/requests/api/v1/statuses/quotes_spec.rb +++ b/spec/requests/api/v1/statuses/quotes_spec.rb @@ -17,7 +17,7 @@ RSpec.describe 'API V1 Statuses Quotes' do 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 @@ RSpec.describe 'API V1 Statuses Quotes' do 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 @@ RSpec.describe 'API V1 Statuses Quotes' do 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 @@ RSpec.describe 'API V1 Statuses Quotes' do 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 eb3e8aed5bc..249abc24402 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -158,7 +158,7 @@ RSpec.describe '/api/v1/statuses' do 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 @@ -180,7 +180,7 @@ RSpec.describe '/api/v1/statuses' do 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 @@ -202,7 +202,7 @@ RSpec.describe '/api/v1/statuses' do 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 { @@ -227,7 +227,7 @@ RSpec.describe '/api/v1/statuses' do 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 { @@ -248,7 +248,7 @@ RSpec.describe '/api/v1/statuses' do 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 { @@ -420,7 +420,7 @@ RSpec.describe '/api/v1/statuses' do 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 a5e4482dfa6..d12f4f28cf3 100644 --- a/spec/requests/statuses_spec.rb +++ b/spec/requests/statuses_spec.rb @@ -360,6 +360,30 @@ RSpec.describe 'Statuses' do .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 336f394337e..04179e9bf4d 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -58,7 +58,7 @@ RSpec.describe ActivityPub::NoteSerializer do 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 b0bd02dac88..813658d149b 100644 --- a/spec/services/activitypub/synchronize_followers_service_spec.rb +++ b/spec/services/activitypub/synchronize_followers_service_spec.rb @@ -6,7 +6,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do 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 c434d0cb6ef..96289cdeee1 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -321,6 +321,14 @@ RSpec.describe PostStatusService do 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 00000000000..02186e781c7 --- /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 d98f7dd9607..b565ed79a88 100644 --- a/spec/support/streaming_server_manager.rb +++ b/spec/support/streaming_server_manager.rb @@ -12,6 +12,11 @@ class StreamingServerManager 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 @@ class StreamingServerManager # 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 @@ RSpec.configure do |config| 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 00000000000..54e125c293d --- /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 00000000000..c12bd1b18fe --- /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 9e6805c68b4..a1eb7ebfa99 100644 --- a/spec/workers/activitypub/delivery_worker_spec.rb +++ b/spec/workers/activitypub/delivery_worker_spec.rb @@ -25,12 +25,23 @@ RSpec.describe ActivityPub::DeliveryWorker do .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 40a737a61da..7df035d6e7e 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 384a553387f..9e977816978 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2595,10 +2595,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 @@ -2836,7 +2836,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" @@ -2849,6 +2848,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" @@ -2859,7 +2859,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" @@ -2892,7 +2892,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" @@ -2907,10 +2906,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: @@ -3330,10 +3329,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 @@ -4488,13 +4487,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" @@ -4825,18 +4817,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 @@ -6486,16 +6478,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: @@ -7453,13 +7436,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" @@ -7679,14 +7655,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 @@ -8364,10 +8340,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" @@ -8376,7 +8352,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 @@ -10044,6 +10020,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" @@ -10356,11 +10339,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" @@ -10368,11 +10350,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 @@ -11285,21 +11268,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" @@ -12018,8 +11986,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" @@ -12030,7 +11998,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/51dcb4e65a69f97b4c200ee154ca45f81b748a45f8ef0ec3236b774bb143590a9304038e9ab09f809f734d4edb3add96a0a690b2e8451ff66b9f57c469b2685e + checksum: 10c0/5a19f12dbe8c142e40c1e0473d1e624898242b1c21010301e169b528be8c580df6356329c798522b525eb11eda4b04b9b77422badc55c47889600f8477201d2b languageName: node linkType: hard @@ -12059,13 +12027,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 @@ -12311,6 +12280,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" @@ -13459,43 +13435,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.0": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" +"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/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 + checksum: 10c0/cd635d50f02d6cf98ed42de2f76289701c1ec587a363369255f01ed15aaf22be0813226bff3c53e99d971f9b540e0b3cc7583dbe05faded49b1b0bed2f638a18 languageName: node linkType: hard -"typescript@npm:~5.7.3": - version: 5.7.3 - resolution: "typescript@npm:5.7.3" +"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/b7580d716cf1824736cc6e628ab4cd8b51877408ba2be0869d2866da35ef8366dd6ae9eb9d0851470a39be17cbd61df1126f9e211d8799d764ea7431d5435afa - 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" - 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 @@ -13811,12 +13767,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 @@ -13878,17 +13834,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 @@ -14041,15 +13997,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"