about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/controllers/admin/pending_accounts_controller.rb52
-rw-r--r--app/controllers/api/v1/accounts/follower_accounts_controller.rb8
-rw-r--r--app/controllers/api/v1/accounts/following_accounts_controller.rb8
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb10
-rw-r--r--app/controllers/api/v1/accounts_controller.rb5
-rw-r--r--app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb1
-rw-r--r--app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb2
-rw-r--r--app/controllers/home_controller.rb2
-rw-r--r--app/controllers/shares_controller.rb2
-rw-r--r--app/helpers/stream_entries_helper.rb2
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js2
-rw-r--r--app/javascript/flavours/glitch/styles/stream_entries.scss17
-rw-r--r--app/javascript/mastodon/components/status.js2
-rw-r--r--app/javascript/mastodon/features/account/components/header.js4
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js22
-rw-r--r--app/javascript/mastodon/features/followers/index.js8
-rw-r--r--app/javascript/mastodon/features/following/index.js8
-rw-r--r--app/javascript/mastodon/features/status/index.js2
-rw-r--r--app/javascript/mastodon/locales/co.json1
-rw-r--r--app/javascript/mastodon/locales/cs.json3
-rw-r--r--app/javascript/mastodon/locales/de.json1
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json52
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/ja.json6
-rw-r--r--app/javascript/mastodon/locales/ko.json1
-rw-r--r--app/javascript/mastodon/locales/nl.json28
-rw-r--r--app/javascript/mastodon/locales/pl.json5
-rw-r--r--app/javascript/mastodon/locales/sk.json74
-rw-r--r--app/javascript/styles/mastodon/components.scss3
-rw-r--r--app/javascript/styles/mastodon/stream_entries.scss17
-rw-r--r--app/lib/proof_provider/keybase/config_serializer.rb2
-rw-r--r--app/models/account.rb1
-rw-r--r--app/models/concerns/account_finder_concern.rb2
-rw-r--r--app/models/export.rb6
-rw-r--r--app/models/form/account_batch.rb19
-rw-r--r--app/presenters/account_relationships_presenter.rb6
-rw-r--r--app/presenters/instance_presenter.rb2
-rw-r--r--app/serializers/rest/relationship_serializer.rb6
-rw-r--r--app/services/account_search_service.rb10
-rw-r--r--app/services/import_service.rb12
-rw-r--r--app/services/search_service.rb2
-rw-r--r--app/validators/existing_username_validator.rb10
-rw-r--r--app/views/accounts/show.html.haml4
-rw-r--r--app/views/admin/accounts/index.html.haml2
-rw-r--r--app/views/admin/pending_accounts/_account.html.haml14
-rw-r--r--app/views/admin/pending_accounts/index.html.haml30
-rw-r--r--app/views/follower_accounts/index.html.haml2
-rw-r--r--app/views/following_accounts/index.html.haml2
-rw-r--r--app/workers/import/relationship_worker.rb7
-rw-r--r--config/initializers/rack_attack.rb4
-rw-r--r--config/locales/activerecord.ja.yml2
-rw-r--r--config/locales/activerecord.nl.yml3
-rw-r--r--config/locales/co.yml7
-rw-r--r--config/locales/cs.yml12
-rw-r--r--config/locales/de.yml24
-rw-r--r--config/locales/devise.fr.yml10
-rw-r--r--config/locales/devise.nl.yml3
-rw-r--r--config/locales/devise.pl.yml3
-rw-r--r--config/locales/doorkeeper.cs.yml2
-rw-r--r--config/locales/en.yml7
-rw-r--r--config/locales/fr.yml42
-rw-r--r--config/locales/ja.yml11
-rw-r--r--config/locales/ko.yml5
-rw-r--r--config/locales/nl.yml62
-rw-r--r--config/locales/pl.yml69
-rw-r--r--config/locales/simple_form.fr.yml2
-rw-r--r--config/navigation.rb2
-rw-r--r--config/routes.rb7
-rw-r--r--lib/mastodon/accounts_cli.rb111
-rw-r--r--lib/mastodon/emoji_cli.rb6
-rw-r--r--lib/mastodon/statuses_cli.rb10
-rw-r--r--spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb29
-rw-r--r--spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb29
-rw-r--r--spec/controllers/settings/exports/following_accounts_controller_spec.rb2
-rw-r--r--spec/fixtures/files/new-following-imports.txt4
-rw-r--r--spec/models/export_spec.rb7
-rw-r--r--spec/services/account_search_service_spec.rb17
-rw-r--r--spec/services/import_service_spec.rb91
-rw-r--r--spec/services/search_service_spec.rb30
82 files changed, 799 insertions, 313 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91e66c400..8670c2658 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,6 +50,7 @@ All notable changes to this project will be documented in this file.
 - Change Webpack to not use @babel/preset-env to compile node_modules ([ykzts](https://github.com/tootsuite/mastodon/pull/10289))
 - Change web UI to use new Web Share Target API ([gol-cha](https://github.com/tootsuite/mastodon/pull/9963))
 - Change ActivityPub reports to have persistent URIs ([ThibG](https://github.com/tootsuite/mastodon/pull/10303))
+- Change `tootctl accounts cull --dry-run` to list accounts that would be deleted ([BenLubar](https://github.com/tootsuite/mastodon/pull/10460))
 
 ### Removed
 
@@ -75,6 +76,7 @@ All notable changes to this project will be documented in this file.
 - Fix race condition when streaming out deleted statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10280))
 - Fix performance of admin federation UI by caching account counts ([Gargron](https://github.com/tootsuite/mastodon/pull/10374))
 - Fix JS error on pages that don't define a CSRF token ([hinaloe](https://github.com/tootsuite/mastodon/pull/10383))
+- Fix `tootctl accounts cull` sometimes removing accounts that are temporarily unreachable ([BenLubar](https://github.com/tootsuite/mastodon/pull/10460))
 
 ## [2.7.4] - 2019-03-05
 ### Fixed
diff --git a/Gemfile b/Gemfile
index 5f0e3ab2c..242721733 100644
--- a/Gemfile
+++ b/Gemfile
@@ -128,7 +128,7 @@ group :development do
   gem 'letter_opener', '~> 1.7'
   gem 'letter_opener_web', '~> 1.3'
   gem 'memory_profiler'
-  gem 'rubocop', '~> 0.66', require: false
+  gem 'rubocop', '~> 0.67', require: false
   gem 'brakeman', '~> 4.5', require: false
   gem 'bundler-audit', '~> 0.6', require: false
   gem 'scss_lint', '~> 0.57', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index a4b76c796..7d98a2fdc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -392,7 +392,7 @@ GEM
     paperclip-av-transcoder (0.6.4)
       av (~> 0.9.0)
       paperclip (>= 2.5.2)
-    parallel (1.14.0)
+    parallel (1.17.0)
     parallel_tests (2.28.0)
       parallel
     parser (2.6.2.0)
@@ -528,7 +528,7 @@ GEM
       rspec-core (~> 3.0, >= 3.0.0)
       sidekiq (>= 2.4.0)
     rspec-support (3.8.0)
-    rubocop (0.66.0)
+    rubocop (0.67.1)
       jaro_winkler (~> 1.5.1)
       parallel (~> 1.10)
       parser (>= 2.5, != 2.5.1.1)
@@ -750,7 +750,7 @@ DEPENDENCIES
   rqrcode (~> 0.10)
   rspec-rails (~> 3.8)
   rspec-sidekiq (~> 3.0)
-  rubocop (~> 0.66)
+  rubocop (~> 0.67)
   sanitize (~> 5.0)
   scss_lint (~> 0.57)
   sidekiq (~> 5.2)
diff --git a/app/controllers/admin/pending_accounts_controller.rb b/app/controllers/admin/pending_accounts_controller.rb
new file mode 100644
index 000000000..8429d3585
--- /dev/null
+++ b/app/controllers/admin/pending_accounts_controller.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Admin
+  class PendingAccountsController < BaseController
+    before_action :set_accounts, only: :index
+
+    def index
+      @form = Form::AccountBatch.new
+    end
+
+    def update
+      @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
+      @form.save
+    rescue ActionController::ParameterMissing
+      # Do nothing
+    ensure
+      redirect_to admin_pending_accounts_path(current_params)
+    end
+
+    def approve_all
+      Form::AccountBatch.new(account_ids: User.pending.pluck(:account_id), action: 'approve').save
+      redirect_to admin_pending_accounts_path(current_params)
+    end
+
+    def reject_all
+      Form::AccountBatch.new(account_ids: User.pending.pluck(:account_id), action: 'reject').save
+      redirect_to admin_pending_accounts_path(current_params)
+    end
+
+    private
+
+    def set_accounts
+      @accounts = Account.joins(:user).merge(User.pending).page(params[:page])
+    end
+
+    def form_account_batch_params
+      params.require(:form_account_batch).permit(:action, account_ids: [])
+    end
+
+    def action_from_button
+      if params[:approve]
+        'approve'
+      elsif params[:reject]
+        'reject'
+      end
+    end
+
+    def current_params
+      params.slice(:page).permit(:page)
+    end
+  end
+end
diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
index 7a45e6dd2..2dabb8398 100644
--- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
@@ -19,13 +19,17 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
   end
 
   def load_accounts
-    return [] if @account.user_hides_network? && current_account.id != @account.id
+    return [] if hide_results?
 
     default_accounts.merge(paginated_follows).to_a
   end
 
+  def hide_results?
+    (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
+  end
+
   def default_accounts
-    Account.without_blocking(current_account).includes(:active_relationships, :account_stat).references(:active_relationships)
+    Account.includes(:active_relationships, :account_stat).references(:active_relationships)
   end
 
   def paginated_follows
diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb
index 0369cb25e..44e89804b 100644
--- a/app/controllers/api/v1/accounts/following_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb
@@ -19,13 +19,17 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
   end
 
   def load_accounts
-    return [] if @account.user_hides_network? && current_account.id != @account.id
+    return [] if hide_results?
 
     default_accounts.merge(paginated_follows).to_a
   end
 
+  def hide_results?
+    (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
+  end
+
   def default_accounts
-    Account.without_blocking(current_account).includes(:passive_relationships, :account_stat).references(:passive_relationships)
+    Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
   end
 
   def paginated_follows
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 7aba2d0bd..8cd8f8e79 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -3,8 +3,6 @@
 class Api::V1::Accounts::StatusesController < Api::BaseController
   before_action -> { authorize_if_got_token! :read, :'read:statuses' }
   before_action :set_account
-  before_action :check_account_suspension
-  before_action :check_account_block
   after_action :insert_pagination_headers
 
   respond_to :json
@@ -20,14 +18,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
     @account = Account.find(params[:account_id])
   end
 
-  def check_account_suspension
-    gone if @account.suspended?
-  end
-
-  def check_account_block
-    gone if current_account.present? && @account.blocking?(current_account)
-  end
-
   def load_statuses
     cached_account_statuses
   end
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 685e044c3..b0c62778e 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -10,7 +10,6 @@ class Api::V1::AccountsController < Api::BaseController
   before_action :require_user!, except: [:show, :create]
   before_action :set_account, except: [:create]
   before_action :check_account_suspension, only: [:show]
-  before_action :check_account_block, only: [:show]
   before_action :check_enabled_registrations, only: [:create]
 
   respond_to :json
@@ -76,10 +75,6 @@ class Api::V1::AccountsController < Api::BaseController
     gone if @account.suspended?
   end
 
-  def check_account_block
-    gone if current_account.present? && @account.blocking?(current_account)
-  end
-
   def account_params
     params.permit(:username, :email, :password, :agreement, :locale)
   end
diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
index e00c4d708..657e57831 100644
--- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
@@ -22,7 +22,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
 
   def default_accounts
     Account
-      .without_blocking(current_account)
       .includes(:favourites, :account_stat)
       .references(:favourites)
       .where(favourites: { status_id: @status.id })
diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
index 9b2d0e59e..6851099f6 100644
--- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
@@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
   end
 
   def default_accounts
-    Account.without_blocking(current_account).includes(:statuses, :account_stat).references(:statuses)
+    Account.includes(:statuses, :account_stat).references(:statuses)
   end
 
   def paginated_statuses
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 82e5265f5..06ca03e34 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -56,7 +56,7 @@ class HomeController < ApplicationController
       push_subscription: current_account.user.web_push_subscription(current_session),
       current_account: current_account,
       token: current_session.token,
-      admin: Account.find_local(Setting.site_contact_username),
+      admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
     }
   end
 
diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb
index 4624c29a6..ada4eec54 100644
--- a/app/controllers/shares_controller.rb
+++ b/app/controllers/shares_controller.rb
@@ -22,7 +22,7 @@ class SharesController < ApplicationController
       push_subscription: current_account.user.web_push_subscription(current_session),
       current_account: current_account,
       token: current_session.token,
-      admin: Account.find_local(Setting.site_contact_username),
+      admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
       text: text,
     }
   end
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index 5ad2c438e..6e646ab84 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -23,7 +23,7 @@ module StreamEntriesHelper
           safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')])
         end
       elsif !(account.memorial? || account.moved?)
-        link_to account_follow_path(account), class: 'button logo-button', data: { method: :post } do
+        link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
           safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')])
         end
       end
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 13f7741c8..43c4f0d32 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -22,8 +22,6 @@ const messages = defineMessages({
   account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
   mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
   direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
-  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
   block: { id: 'account.block', defaultMessage: 'Block @{name}' },
   mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
diff --git a/app/javascript/flavours/glitch/styles/stream_entries.scss b/app/javascript/flavours/glitch/styles/stream_entries.scss
index 6735049b9..e18696fb7 100644
--- a/app/javascript/flavours/glitch/styles/stream_entries.scss
+++ b/app/javascript/flavours/glitch/styles/stream_entries.scss
@@ -109,6 +109,23 @@
     }
   }
 
+  &:disabled,
+  &.disabled {
+    svg path:last-child {
+      fill: $ui-primary-color;
+    }
+
+    &:active,
+    &:focus,
+    &:hover {
+      background: $ui-primary-color;
+
+      svg path:last-child {
+        fill: $ui-primary-color;
+      }
+    }
+  }
+
   &.button--destructive {
     &:active,
     &:focus,
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index e10faedf8..cea9a0c2e 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -351,7 +351,7 @@ class Status extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={handlers}>
-        <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))} ref={this.handleRef}>
+        <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
           {prepend}
 
           <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 9d15bc28f..e5b60e33e 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -22,8 +22,6 @@ const messages = defineMessages({
   account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
   mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
   direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
-  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
   block: { id: 'account.block', defaultMessage: 'Block @{name}' },
   mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
@@ -111,7 +109,7 @@ class Header extends ImmutablePureComponent {
       } else if (account.getIn(['relationship', 'requested'])) {
         actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
       } else if (!account.getIn(['relationship', 'blocking'])) {
-        actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
+        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
       } else if (account.getIn(['relationship', 'blocking'])) {
         actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
       }
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index 883f40d77..a01f1dd9a 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -14,14 +14,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
 import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
 
+const emptyList = ImmutableList();
+
 const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
   const path = withReplies ? `${accountId}:with_replies` : accountId;
 
   return {
-    statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
-    featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
+    statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
+    featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
     isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
-    hasMore:   state.getIn(['timelines', `account:${path}`, 'hasMore']),
+    hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
+    blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
   };
 };
 
@@ -37,6 +40,7 @@ class AccountTimeline extends ImmutablePureComponent {
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
     withReplies: PropTypes.bool,
+    blockedBy: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -44,9 +48,11 @@ class AccountTimeline extends ImmutablePureComponent {
 
     this.props.dispatch(fetchAccount(accountId));
     this.props.dispatch(fetchAccountIdentityProofs(accountId));
+
     if (!withReplies) {
       this.props.dispatch(expandAccountFeaturedTimeline(accountId));
     }
+
     this.props.dispatch(expandAccountTimeline(accountId, { withReplies }));
   }
 
@@ -54,9 +60,11 @@ class AccountTimeline extends ImmutablePureComponent {
     if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
       this.props.dispatch(fetchAccount(nextProps.params.accountId));
       this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
+
       if (!nextProps.withReplies) {
         this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
       }
+
       this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
     }
   }
@@ -66,7 +74,7 @@ class AccountTimeline extends ImmutablePureComponent {
   }
 
   render () {
-    const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
+    const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy } = this.props;
 
     if (!statusIds && isLoading) {
       return (
@@ -76,6 +84,8 @@ class AccountTimeline extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
+
     return (
       <Column>
         <ColumnBackButton />
@@ -84,13 +94,13 @@ class AccountTimeline extends ImmutablePureComponent {
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
           alwaysPrepend
           scrollKey='account_timeline'
-          statusIds={statusIds}
+          statusIds={blockedBy ? emptyList : statusIds}
           featuredStatusIds={featuredStatusIds}
           isLoading={isLoading}
           hasMore={hasMore}
           onLoadMore={this.handleLoadMore}
           shouldUpdateScroll={shouldUpdateScroll}
-          emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
+          emptyMessage={emptyMessage}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js
index ce56f270c..ce6357c4c 100644
--- a/app/javascript/mastodon/features/followers/index.js
+++ b/app/javascript/mastodon/features/followers/index.js
@@ -20,6 +20,7 @@ import ScrollableList from '../../components/scrollable_list';
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
   hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
+  blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
 });
 
 export default @connect(mapStateToProps)
@@ -31,6 +32,7 @@ class Followers extends ImmutablePureComponent {
     shouldUpdateScroll: PropTypes.func,
     accountIds: ImmutablePropTypes.list,
     hasMore: PropTypes.bool,
+    blockedBy: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -50,7 +52,7 @@ class Followers extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { shouldUpdateScroll, accountIds, hasMore } = this.props;
+    const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props;
 
     if (!accountIds) {
       return (
@@ -60,7 +62,7 @@ class Followers extends ImmutablePureComponent {
       );
     }
 
-    const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
+    const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
 
     return (
       <Column>
@@ -75,7 +77,7 @@ class Followers extends ImmutablePureComponent {
           alwaysPrepend
           emptyMessage={emptyMessage}
         >
-          {accountIds.map(id =>
+          {blockedBy ? [] : accountIds.map(id =>
             <AccountContainer key={id} id={id} withNote={false} />
           )}
         </ScrollableList>
diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js
index bda0438a0..70e7fde06 100644
--- a/app/javascript/mastodon/features/following/index.js
+++ b/app/javascript/mastodon/features/following/index.js
@@ -20,6 +20,7 @@ import ScrollableList from '../../components/scrollable_list';
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
   hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
+  blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
 });
 
 export default @connect(mapStateToProps)
@@ -31,6 +32,7 @@ class Following extends ImmutablePureComponent {
     shouldUpdateScroll: PropTypes.func,
     accountIds: ImmutablePropTypes.list,
     hasMore: PropTypes.bool,
+    blockedBy: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -50,7 +52,7 @@ class Following extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { shouldUpdateScroll, accountIds, hasMore } = this.props;
+    const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props;
 
     if (!accountIds) {
       return (
@@ -60,7 +62,7 @@ class Following extends ImmutablePureComponent {
       );
     }
 
-    const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
+    const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
 
     return (
       <Column>
@@ -75,7 +77,7 @@ class Following extends ImmutablePureComponent {
           alwaysPrepend
           emptyMessage={emptyMessage}
         >
-          {accountIds.map(id =>
+          {blockedBy ? [] : accountIds.map(id =>
             <AccountContainer key={id} id={id} withNote={false} />
           )}
         </ScrollableList>
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index c0ea460e8..567af6be9 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -442,7 +442,7 @@ class Status extends ImmutablePureComponent {
             {ancestors}
 
             <HotKeys handlers={handlers}>
-              <div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false, !status.get('hidden'))}>
+              <div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
                 <DetailedStatus
                   status={status}
                   onOpenVideo={this.handleOpenVideo}
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 154feab98..016be39b3 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -117,6 +117,7 @@
   "emoji_button.symbols": "Simbuli",
   "emoji_button.travel": "Lochi è Viaghju",
   "empty_column.account_timeline": "Nisun statutu quì!",
+  "empty_column.account_unavailable": "Prufile micca dispunibule",
   "empty_column.blocks": "Per avà ùn avete bluccatu manc'un utilizatore.",
   "empty_column.community": "Ùn c'hè nunda indè a linea lucale. Scrivete puru qualcosa!",
   "empty_column.direct": "Ùn avete ancu nisun missaghju direttu. S'è voi mandate o ricevete unu, u vidarete quì.",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 9701bd695..61d865154 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -117,6 +117,7 @@
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestování a místa",
   "empty_column.account_timeline": "Tady nejsou žádné tooty!",
+  "empty_column.account_unavailable": "Profil nedostupný",
   "empty_column.blocks": "Ještě jste nezablokoval/a žádného uživatele.",
   "empty_column.community": "Místní časová osa je prázdná. Napište něco veřejně a rozhýbejte to tu!",
   "empty_column.direct": "Ještě nemáte žádné přímé zprávy. Pokud nějakou pošlete nebo dostanete, zobrazí se zde.",
@@ -327,7 +328,7 @@
   "status.more": "Více",
   "status.mute": "Skrýt uživatele @{name}",
   "status.mute_conversation": "Skrýt konverzaci",
-  "status.open": "Rozbalit tento toot",
+  "status.open": "Otevřít tento toot",
   "status.pin": "Připnout na profil",
   "status.pinned": "Připnutý toot",
   "status.read_more": "Číst více",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index a335af1bd..13b8ccafa 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -117,6 +117,7 @@
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Reisen und Orte",
   "empty_column.account_timeline": "Keine Beiträge!",
+  "empty_column.account_unavailable": "Konto nicht verfügbar",
   "empty_column.blocks": "Du hast keine Profile blockiert.",
   "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!",
   "empty_column.direct": "Du hast noch keine Direktnachrichten erhalten. Wenn du eine sendest oder empfängst, wird sie hier zu sehen sein.",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 951745120..76d4351d0 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -204,26 +204,6 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Moments remaining",
-        "id": "time_remaining.moments"
-      },
-      {
-        "defaultMessage": "{number, plural, one {# second} other {# seconds}} left",
-        "id": "time_remaining.seconds"
-      },
-      {
-        "defaultMessage": "{number, plural, one {# minute} other {# minutes}} left",
-        "id": "time_remaining.minutes"
-      },
-      {
-        "defaultMessage": "{number, plural, one {# hour} other {# hours}} left",
-        "id": "time_remaining.hours"
-      },
-      {
-        "defaultMessage": "{number, plural, one {# day} other {# days}} left",
-        "id": "time_remaining.days"
-      },
-      {
         "defaultMessage": "Closed",
         "id": "poll.closed"
       },
@@ -263,6 +243,26 @@
       {
         "defaultMessage": "{number}d",
         "id": "relative_time.days"
+      },
+      {
+        "defaultMessage": "Moments remaining",
+        "id": "time_remaining.moments"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# second} other {# seconds}} left",
+        "id": "time_remaining.seconds"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# minute} other {# minutes}} left",
+        "id": "time_remaining.minutes"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# hour} other {# hours}} left",
+        "id": "time_remaining.hours"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# day} other {# days}} left",
+        "id": "time_remaining.days"
       }
     ],
     "path": "app/javascript/mastodon/components/relative_timestamp.json"
@@ -552,8 +552,8 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "You are blocked",
-        "id": "empty_column.account_timeline_blocked"
+        "defaultMessage": "Profile unavailable",
+        "id": "empty_column.account_unavailable"
       },
       {
         "defaultMessage": "No toots here!",
@@ -1256,8 +1256,8 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "You are blocked",
-        "id": "empty_column.account_timeline_blocked"
+        "defaultMessage": "Profile unavailable",
+        "id": "empty_column.account_unavailable"
       },
       {
         "defaultMessage": "No one follows this user yet.",
@@ -1269,8 +1269,8 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "You are blocked",
-        "id": "empty_column.account_timeline_blocked"
+        "defaultMessage": "Profile unavailable",
+        "id": "empty_column.account_unavailable"
       },
       {
         "defaultMessage": "This user doesn't follow anyone yet.",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index de3c3da9d..79e0c504b 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -121,7 +121,7 @@
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
   "empty_column.account_timeline": "No toots here!",
-  "empty_column.account_timeline_blocked": "You are blocked",
+  "empty_column.account_unavailable": "Profile unavailable",
   "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 84a65c6d2..0bf9a22c4 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -77,8 +77,8 @@
   "compose_form.placeholder": "今なにしてる?",
   "compose_form.poll.add_option": "追加",
   "compose_form.poll.duration": "アンケート期間",
-  "compose_form.poll.option_placeholder": "選択肢 {number}",
-  "compose_form.poll.remove_option": "この選択肢を削除",
+  "compose_form.poll.option_placeholder": "項目 {number}",
+  "compose_form.poll.remove_option": "この項目を削除",
   "compose_form.publish": "トゥート",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "メディアに閲覧注意が設定されています",
@@ -121,7 +121,7 @@
   "emoji_button.symbols": "記号",
   "emoji_button.travel": "旅行と場所",
   "empty_column.account_timeline": "トゥートがありません!",
-  "empty_column.account_timeline_blocked": "ブロックされています",
+  "empty_column.account_unavailable": "プロフィールは利用できません",
   "empty_column.blocks": "まだ誰もブロックしていません。",
   "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
   "empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 433592ffd..a1e81a9d0 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -117,6 +117,7 @@
   "emoji_button.symbols": "기호",
   "emoji_button.travel": "여행과 장소",
   "empty_column.account_timeline": "여긴 툿이 없어요!",
+  "empty_column.account_unavailable": "프로필 사용 불가",
   "empty_column.blocks": "아직 아무도 차단하지 않았습니다.",
   "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!",
   "empty_column.direct": "아직 다이렉트 메시지가 없습니다. 다이렉트 메시지를 보내거나 받은 경우, 여기에 표시 됩니다.",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index e5e4748d7..cac676c7b 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -71,10 +71,10 @@
   "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de toots zien die je alleen aan jouw volgers hebt gericht.",
   "compose_form.lock_disclaimer.lock": "besloten",
   "compose_form.placeholder": "Wat wil je kwijt?",
-  "compose_form.poll.add_option": "Add a choice",
-  "compose_form.poll.duration": "Poll duration",
-  "compose_form.poll.option_placeholder": "Choice {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.poll.add_option": "Keuze toevoegen",
+  "compose_form.poll.duration": "Duur van de poll",
+  "compose_form.poll.option_placeholder": "Keuze {number}",
+  "compose_form.poll.remove_option": "Deze keuze verwijderen",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is als gevoelig gemarkeerd",
@@ -83,7 +83,7 @@
   "compose_form.spoiler.unmarked": "Tekst is niet verborgen",
   "compose_form.spoiler_placeholder": "Waarschuwingstekst",
   "confirmation_modal.cancel": "Annuleren",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "Blokkeren en rapporteren",
   "confirmations.block.confirm": "Blokkeren",
   "confirmations.block.message": "Weet je het zeker dat je {name} wilt blokkeren?",
   "confirmations.delete.confirm": "Verwijderen",
@@ -154,9 +154,9 @@
   "home.column_settings.basic": "Algemeen",
   "home.column_settings.show_reblogs": "Boosts tonen",
   "home.column_settings.show_replies": "Reacties tonen",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.days": "{number, plural, one {# dag} other {# dagen}}",
+  "intervals.full.hours": "{number, plural, one {# uur} other {# uur}}",
+  "intervals.full.minutes": "{number, plural, one {# minuut} other {# minuten}}",
   "introduction.federation.action": "Volgende",
   "introduction.federation.federated.headline": "Globaal",
   "introduction.federation.federated.text": "Openbare toots van mensen op andere servers in de fediverse verschijnen op de globale tijdlijn.",
@@ -246,7 +246,7 @@
   "notification.favourite": "{name} voegde jouw toot als favoriet toe",
   "notification.follow": "{name} volgt jou nu",
   "notification.mention": "{name} vermeldde jou",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "Een poll waaraan jij hebt meegedaan is beëindigd",
   "notification.reblog": "{name} boostte jouw toot",
   "notifications.clear": "Meldingen verwijderen",
   "notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
@@ -257,7 +257,7 @@
   "notifications.column_settings.filter_bar.show": "Tonen",
   "notifications.column_settings.follow": "Nieuwe volgers:",
   "notifications.column_settings.mention": "Vermeldingen:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Pollresultaten:",
   "notifications.column_settings.push": "Pushmeldingen",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "In kolom tonen",
@@ -267,14 +267,14 @@
   "notifications.filter.favourites": "Favorieten",
   "notifications.filter.follows": "Die jij volgt",
   "notifications.filter.mentions": "Vermeldingen",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.polls": "Pollresultaten",
   "notifications.group": "{count} meldingen",
   "poll.closed": "Gesloten",
   "poll.refresh": "Vernieuwen",
   "poll.total_votes": "{count, plural, one {# stem} other {# stemmen}}",
   "poll.vote": "Stemmen",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll_button.add_poll": "Poll toevoegen",
+  "poll_button.remove_poll": "Poll verwijderen",
   "privacy.change": "Zichtbaarheid toot aanpassen",
   "privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
   "privacy.direct.short": "Direct",
@@ -366,7 +366,7 @@
   "upload_area.title": "Hierin slepen om te uploaden",
   "upload_button.label": "Media toevoegen (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Uploadlimiet van bestand overschreden.",
-  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_error.poll": "Het uploaden van bestanden is in polls niet toegestaan.",
   "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
   "upload_form.focus": "Voorvertoning aanpassen",
   "upload_form.undo": "Verwijderen",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 107ac8757..505f35286 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -121,6 +121,7 @@
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Podróże i miejsca",
   "empty_column.account_timeline": "Brak wpisów tutaj!",
+  "empty_column.account_timeline_blocked": "Jesteś zablokowany(-a)",
   "empty_column.blocks": "Nie zablokowałeś(-aś) jeszcze żadnego użytkownika.",
   "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
   "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.",
@@ -251,7 +252,7 @@
   "notification.favourite": "{name} dodał(a) Twój wpis do ulubionych",
   "notification.follow": "{name} zaczął(-ęła) Cię śledzić",
   "notification.mention": "{name} wspomniał(a) o tobie",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyła się",
   "notification.reblog": "{name} podbił(a) Twój wpis",
   "notifications.clear": "Wyczyść powiadomienia",
   "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
@@ -262,7 +263,7 @@
   "notifications.column_settings.filter_bar.show": "Pokaż",
   "notifications.column_settings.follow": "Nowi śledzący:",
   "notifications.column_settings.mention": "Wspomnienia:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Wyniki głosowania:",
   "notifications.column_settings.push": "Powiadomienia push",
   "notifications.column_settings.reblog": "Podbicia:",
   "notifications.column_settings.show": "Pokaż w kolumnie",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 5542198f7..52c79c0bb 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -6,7 +6,7 @@
   "account.blocked": "Blokovaný/á",
   "account.direct": "Súkromná správa pre @{name}",
   "account.domain_blocked": "Doména ukrytá",
-  "account.edit_profile": "Upraviť profil",
+  "account.edit_profile": "Uprav profil",
   "account.endorse": "Zobrazuj na profile",
   "account.follow": "Následuj",
   "account.followers": "Sledujúci",
@@ -14,38 +14,38 @@
   "account.follows": "Následuje",
   "account.follows.empty": "Tento užívateľ ešte nikoho nenásleduje.",
   "account.follows_you": "Následuje ťa",
-  "account.hide_reblogs": "Skryť povýšenia od @{name}",
+  "account.hide_reblogs": "Skry vyzdvihnutia od @{name}",
   "account.link_verified_on": "Vlastníctvo tohto odkazu bolo skontrolované {date}",
   "account.locked_info": "Stav súkromia pre tento účet je nastavený na zamknutý. Jeho vlastník sám prehodnocuje, kto ho môže sledovať.",
   "account.media": "Médiá",
   "account.mention": "Spomeň @{name}",
   "account.moved_to": "{name} sa presunul/a na:",
   "account.mute": "Ignorovať @{name}",
-  "account.mute_notifications": "Stĺmiť oboznámenia od @{name}",
+  "account.mute_notifications": "Stĺm oboznámenia od @{name}",
   "account.muted": "Utíšený/á",
   "account.posts": "Príspevky",
   "account.posts_with_replies": "Príspevky aj s odpoveďami",
   "account.report": "Nahlás @{name}",
-  "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
-  "account.share": "Zdieľať @{name} profil",
+  "account.requested": "Čaká na schválenie. Klikni pre zrušenie žiadosti",
+  "account.share": "Zdieľaj @{name} profil",
   "account.show_reblogs": "Ukáž vyzdvihnutia od @{name}",
   "account.unblock": "Odblokuj @{name}",
   "account.unblock_domain": "Prestaň skrývať {domain}",
   "account.unendorse": "Nezobrazuj na profile",
   "account.unfollow": "Prestaň následovať",
   "account.unmute": "Prestaň ignorovať @{name}",
-  "account.unmute_notifications": "Zrušiť stlmenie oznámení od @{name}",
+  "account.unmute_notifications": "Zruš stĺmenie oboznámení od @{name}",
   "alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
   "alert.unexpected.title": "Oops!",
   "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} pre preskočenie",
   "bundle_column_error.body": "Pri načítaní tohto prvku nastala nejaká chyba.",
   "bundle_column_error.retry": "Skús to znova",
   "bundle_column_error.title": "Chyba siete",
-  "bundle_modal_error.close": "Zatvoriť",
+  "bundle_modal_error.close": "Zatvor",
   "bundle_modal_error.message": "Nastala chyba pri načítaní tohto komponentu.",
   "bundle_modal_error.retry": "Skúsiť znova",
   "column.blocks": "Blokovaní užívatelia",
-  "column.community": "Lokálna časová os",
+  "column.community": "Miestna časová os",
   "column.direct": "Súkromné správy",
   "column.domain_blocks": "Skryté domény",
   "column.favourites": "Obľúbené",
@@ -64,11 +64,11 @@
   "column_header.show_settings": "Ukáž nastavenia",
   "column_header.unpin": "Odopnúť",
   "column_subheading.settings": "Nastavenia",
-  "community.column_settings.media_only": "Iba media",
+  "community.column_settings.media_only": "Iba médiá",
   "compose_form.direct_message_warning": "Tento príspevok bude videný výhradne iba spomenutými užívateľmi. Ber ale na vedomie že správci tvojej a všetkých iných zahrnutých instancií majú možnosť skontrolovať túto správu.",
   "compose_form.direct_message_warning_learn_more": "Zistiť viac",
   "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.",
-  "compose_form.lock_disclaimer": "Váš účet nie je {locked}. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
+  "compose_form.lock_disclaimer": "Tvoj účet nie je {locked}. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
   "compose_form.lock_disclaimer.lock": "zamknutý",
   "compose_form.placeholder": "Čo máš na mysli?",
   "compose_form.poll.add_option": "Pridaj voľbu",
@@ -90,29 +90,29 @@
   "confirmations.delete.message": "Si si istý/á, že chceš vymazať túto správu?",
   "confirmations.delete_list.confirm": "Vymaž",
   "confirmations.delete_list.message": "Si si istý/á, že chceš natrvalo vymazať tento zoznam?",
-  "confirmations.domain_block.confirm": "Skryť celú doménu",
-  "confirmations.domain_block.message": "Si si naozaj istý, že chceš blokovať celú {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať pár konkrétnych užívateľov, čo sa doporučuje. Neuvidíš obsah z tejto domény v žiadnej verejnej časovej osi, ani v oznámeniach. Tvoji následovníci pochádzajúci z tejto domény budú odstránení.",
+  "confirmations.domain_block.confirm": "Skry celú doménu",
+  "confirmations.domain_block.message": "Si si naozaj istý/á, že chceš blokovať celú doménu {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať pár konkrétnych užívateľov, čo sa doporučuje. Neuvidíš obsah z tejto domény v žiadnej verejnej časovej osi, ani v oznámeniach. Tvoji následovníci pochádzajúci z tejto domény budú odstránení.",
   "confirmations.mute.confirm": "Ignoruj",
-  "confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
-  "confirmations.redraft.confirm": "Vyčistiť a prepísať",
+  "confirmations.mute.message": "Naozaj chceš ignorovať {name}?",
+  "confirmations.redraft.confirm": "Vyčisti a prepíš",
   "confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté vyzdvihnutia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
   "confirmations.reply.confirm": "Odpovedz",
   "confirmations.reply.message": "Odpovedaním akurát teraz prepíšeš správu, ktorú máš práve rozpísanú. Si si istý/á, že chceš pokračovať?",
-  "confirmations.unfollow.confirm": "Nesledovať",
-  "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
+  "confirmations.unfollow.confirm": "Nesleduj",
+  "confirmations.unfollow.message": "Naozaj chceš prestať sledovať {name}?",
   "embed.instructions": "Umiestni kód uvedený nižšie pre pridanie tohto statusu na tvoju web stránku.",
   "embed.preview": "Tu je ako to bude vyzerať:",
   "emoji_button.activity": "Aktivita",
   "emoji_button.custom": "Vlastné",
   "emoji_button.flags": "Vlajky",
   "emoji_button.food": "Jedlá a nápoje",
-  "emoji_button.label": "Vložiť emotikony",
+  "emoji_button.label": "Vlož emotikony",
   "emoji_button.nature": "Prírodné",
   "emoji_button.not_found": "Nie emotikony!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Predmety",
   "emoji_button.people": "Ľudia",
   "emoji_button.recent": "Často používané",
-  "emoji_button.search": "Hľadať...",
+  "emoji_button.search": "Hľadaj...",
   "emoji_button.search_results": "Nájdené",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestovanie a miesta",
@@ -158,16 +158,16 @@
   "intervals.full.hours": "{number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodiny}}",
   "intervals.full.minutes": "{number, plural, one {# minúta} few {# minút} many {# minút} other {# minúty}}",
   "introduction.federation.action": "Ďalej",
-  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.headline": "Federovaná",
   "introduction.federation.federated.text": "Verejné príspevky z ostatných serverov vo fediverse budú zobrazenie vo federovanej časovej osi.",
-  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.headline": "Domov",
   "introduction.federation.home.text": "Príspevky od ľudí ktorých následuješ sa zobrazia na tvojej domovskej nástenke. Môžeš následovať hocikoho na ktoromkoľvek serveri!",
-  "introduction.federation.local.headline": "Local",
+  "introduction.federation.local.headline": "Miestna",
   "introduction.federation.local.text": "Verejné príspevky od ľudí v rámci toho istého serveru na akom si aj ty, budú zobrazované na miestnej časovej osi.",
   "introduction.interactions.action": "Ukonči návod!",
   "introduction.interactions.favourite.headline": "Obľúbené",
   "introduction.interactions.favourite.text": "Obľúbením si môžeš príspevok uložiť na neskôr, a zároveň dať jeho autorovi vedieť, že sa ti páčil.",
-  "introduction.interactions.reblog.headline": "Povýš",
+  "introduction.interactions.reblog.headline": "Vyzdvihni",
   "introduction.interactions.reblog.text": "Môžeš zdieľať príspevky iných ľudí s vašimi následovateľmi tým, že ich povýšiš.",
   "introduction.interactions.reply.headline": "Odpovedz",
   "introduction.interactions.reply.text": "Odpovedať môžeš na príspevky iných ľudí, aj na svoje vlastné, čím sa spolu prepoja do konverzácie.",
@@ -210,7 +210,7 @@
   "lightbox.previous": "Predchádzajúci",
   "lists.account.add": "Pridať do zoznamu",
   "lists.account.remove": "Odobrať zo zoznamu",
-  "lists.delete": "Vymazať list",
+  "lists.delete": "Vymaž list",
   "lists.edit": "Uprav zoznam",
   "lists.edit.submit": "Zmeň názov",
   "lists.new.create": "Pridaj zoznam",
@@ -259,7 +259,7 @@
   "notifications.column_settings.mention": "Zmienenia:",
   "notifications.column_settings.poll": "Výsledky ankiet:",
   "notifications.column_settings.push": "Push notifikácie",
-  "notifications.column_settings.reblog": "Boosty:",
+  "notifications.column_settings.reblog": "Vyzdvihnutia:",
   "notifications.column_settings.show": "Zobraziť v stĺpci",
   "notifications.column_settings.sound": "Prehrať zvuk",
   "notifications.filter.all": "Všetky",
@@ -278,7 +278,7 @@
   "privacy.change": "Uprav súkromie príspevku",
   "privacy.direct.long": "Pošli iba spomenutým používateľom",
   "privacy.direct.short": "Súkromne",
-  "privacy.private.long": "Poslať iba následovateľom",
+  "privacy.private.long": "Pošli iba následovateľom",
   "privacy.private.short": "Iba pre sledujúcich",
   "privacy.public.long": "Poslať všetkým verejne",
   "privacy.public.short": "Verejné",
@@ -311,9 +311,9 @@
   "search_results.total": "{count, number} {count, plural, one {výsledok} many {výsledkov} other {výsledky}}",
   "status.admin_account": "Otvor moderovacie rozhranie užívateľa @{name}",
   "status.admin_status": "Otvor tento príspevok v moderovacom rozhraní",
-  "status.block": "Blokovať @{name}",
+  "status.block": "Blokuj @{name}",
   "status.cancel_reblog_private": "Nezdieľaj",
-  "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
+  "status.cannot_reblog": "Tento príspevok nemôže byť zdieľaný",
   "status.copy": "Skopíruj odkaz na príspevok",
   "status.delete": "Zmazať",
   "status.detailed_status": "Podrobný náhľad celej konverzácie",
@@ -325,14 +325,14 @@
   "status.media_hidden": "Skryté médiá",
   "status.mention": "Spomeň @{name}",
   "status.more": "Viac",
-  "status.mute": "Utíšiť @{name}",
-  "status.mute_conversation": "Ignorovať konverzáciu",
+  "status.mute": "Utíš @{name}",
+  "status.mute_conversation": "Ignoruj konverzáciu",
   "status.open": "Otvoriť tento status",
   "status.pin": "Pripni na profil",
   "status.pinned": "Pripnutý príspevok",
   "status.read_more": "Čítaj ďalej",
   "status.reblog": "Povýšiť",
-  "status.reblog_private": "Povýš k pôvodnému publiku",
+  "status.reblog_private": "Vyzdvihni k pôvodnému publiku",
   "status.reblogged_by": "{name} povýšil/a",
   "status.reblogs.empty": "Nikto ešte nepovýšil tento príspevok. Keď tak niekto urobí, bude to zobrazené práve tu.",
   "status.redraft": "Vymaž a prepíš",
@@ -341,7 +341,7 @@
   "status.report": "Nahlásiť @{name}",
   "status.sensitive_toggle": "Klikni pre zobrazenie",
   "status.sensitive_warning": "Chúlostivý obsah",
-  "status.share": "Zdieľať",
+  "status.share": "Zdieľaj",
   "status.show_less": "Zobraz menej",
   "status.show_less_all": "Všetkým ukáž menej",
   "status.show_more": "Ukáž viac",
@@ -353,8 +353,8 @@
   "suggestions.header": "Mohlo by ťa zaujímať…",
   "tabs_bar.federated_timeline": "Federovaná",
   "tabs_bar.home": "Domov",
-  "tabs_bar.local_timeline": "Lokálna",
-  "tabs_bar.notifications": "Notifikácie",
+  "tabs_bar.local_timeline": "Miestna",
+  "tabs_bar.notifications": "Oboznámenia",
   "tabs_bar.search": "Hľadaj",
   "time_remaining.days": "Zostáva {number, plural, one {# deň} few {# dní} many {# dni} other {# dni}}",
   "time_remaining.hours": "Zostáva {number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodiny}}",
@@ -371,13 +371,13 @@
   "upload_form.focus": "Pozmeň náhľad",
   "upload_form.undo": "Vymaž",
   "upload_progress.label": "Nahráva sa...",
-  "video.close": "Zavrieť video",
+  "video.close": "Zavri video",
   "video.exit_fullscreen": "Vpnúť zobrazenie na celú obrazovku",
-  "video.expand": "Zväčšiť video",
+  "video.expand": "Zväčši video",
   "video.fullscreen": "Zobraziť na celú obrazovku",
   "video.hide": "Skryť video",
   "video.mute": "Vypnúť zvuk",
   "video.pause": "Pauza",
-  "video.play": "Prehrať",
-  "video.unmute": "Zapnúť zvuk"
+  "video.play": "Prehraj",
+  "video.unmute": "Zapni zvuk"
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 2ad93c59d..9fcaeb8dd 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -46,7 +46,8 @@
     }
   }
 
-  &:disabled {
+  &:disabled,
+  &.disabled {
     background-color: $ui-primary-color;
     cursor: default;
   }
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss
index d8bd30377..63eeffe25 100644
--- a/app/javascript/styles/mastodon/stream_entries.scss
+++ b/app/javascript/styles/mastodon/stream_entries.scss
@@ -109,6 +109,23 @@
     }
   }
 
+  &:disabled,
+  &.disabled {
+    svg path:last-child {
+      fill: $ui-primary-color;
+    }
+
+    &:active,
+    &:focus,
+    &:hover {
+      background: $ui-primary-color;
+
+      svg path:last-child {
+        fill: $ui-primary-color;
+      }
+    }
+  }
+
   &.button--destructive {
     &:active,
     &:focus,
diff --git a/app/lib/proof_provider/keybase/config_serializer.rb b/app/lib/proof_provider/keybase/config_serializer.rb
index 5241d201f..2840f1823 100644
--- a/app/lib/proof_provider/keybase/config_serializer.rb
+++ b/app/lib/proof_provider/keybase/config_serializer.rb
@@ -34,7 +34,7 @@ class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer
   end
 
   def username
-    { min: 1, max: 30, re: '[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?' }
+    { min: 1, max: 30, re: '[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?' }
   end
 
   def prefill_url
diff --git a/app/models/account.rb b/app/models/account.rb
index 983d38e0e..a82251d2e 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -102,7 +102,6 @@ class Account < ApplicationRecord
   scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
   scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
   scope :popular, -> { order('account_stats.followers_count desc') }
-  scope :without_blocking, ->(account) { account.nil? ? all : where.not(id: Block.where(target_account_id: account.id).pluck(:account_id)) }
 
   delegate :email,
            :unconfirmed_email,
diff --git a/app/models/concerns/account_finder_concern.rb b/app/models/concerns/account_finder_concern.rb
index 7e3bbde09..0ac49cc12 100644
--- a/app/models/concerns/account_finder_concern.rb
+++ b/app/models/concerns/account_finder_concern.rb
@@ -13,7 +13,7 @@ module AccountFinderConcern
     end
 
     def representative
-      find_local(Setting.site_contact_username.gsub(/\A@/, '')) || Account.local.find_by(suspended: false)
+      find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) || Account.local.find_by(suspended: false)
     end
 
     def find_local(username)
diff --git a/app/models/export.rb b/app/models/export.rb
index b35632c60..cab01f11a 100644
--- a/app/models/export.rb
+++ b/app/models/export.rb
@@ -22,7 +22,11 @@ class Export
   end
 
   def to_following_accounts_csv
-    to_csv account.following.select(:username, :domain)
+    CSV.generate(headers: ['Account address', 'Show boosts'], write_headers: true) do |csv|
+      account.active_relationships.includes(:target_account).reorder(id: :desc).each do |follow|
+        csv << [acct(follow.target_account), follow.show_reblogs]
+      end
+    end
   end
 
   def to_lists_csv
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
index 60eaaf0e2..5bc44e809 100644
--- a/app/models/form/account_batch.rb
+++ b/app/models/form/account_batch.rb
@@ -2,6 +2,7 @@
 
 class Form::AccountBatch
   include ActiveModel::Model
+  include Authorization
 
   attr_accessor :account_ids, :action, :current_account
 
@@ -13,6 +14,10 @@ class Form::AccountBatch
       remove_from_followers!
     when 'block_domains'
       block_domains!
+    when 'approve'
+      approve!
+    when 'reject'
+      reject!
     end
   end
 
@@ -57,4 +62,18 @@ class Form::AccountBatch
 
     ActivityPub::DeliveryWorker.perform_async(json, current_account.id, follow.account.inbox_url)
   end
+
+  def approve!
+    users = accounts.includes(:user).map(&:user)
+
+    users.each { |user| authorize(user, :approve?) }
+         .each(&:approve!)
+  end
+
+  def reject!
+    records = accounts.includes(:user)
+
+    records.each { |account| authorize(account.user, :reject?) }
+           .each { |account| SuspendAccountService.new.call(account, including_user: true, destroy: true, skip_distribution: true) }
+  end
 end
diff --git a/app/presenters/account_relationships_presenter.rb b/app/presenters/account_relationships_presenter.rb
index e4aaa65f6..b05673a3d 100644
--- a/app/presenters/account_relationships_presenter.rb
+++ b/app/presenters/account_relationships_presenter.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class AccountRelationshipsPresenter
-  attr_reader :following, :followed_by, :blocking,
+  attr_reader :following, :followed_by, :blocking, :blocked_by,
               :muting, :requested, :domain_blocking,
               :endorsed
 
@@ -12,6 +12,7 @@ class AccountRelationshipsPresenter
     @following       = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
     @followed_by     = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id))
     @blocking        = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id))
+    @blocked_by      = cached[:blocked_by].merge(Account.blocked_by_map(@uncached_account_ids, @current_account_id))
     @muting          = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
     @requested       = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
     @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
@@ -22,6 +23,7 @@ class AccountRelationshipsPresenter
     @following.merge!(options[:following_map] || {})
     @followed_by.merge!(options[:followed_by_map] || {})
     @blocking.merge!(options[:blocking_map] || {})
+    @blocked_by.merge!(options[:blocked_by_map] || {})
     @muting.merge!(options[:muting_map] || {})
     @requested.merge!(options[:requested_map] || {})
     @domain_blocking.merge!(options[:domain_blocking_map] || {})
@@ -37,6 +39,7 @@ class AccountRelationshipsPresenter
       following: {},
       followed_by: {},
       blocking: {},
+      blocked_by: {},
       muting: {},
       requested: {},
       domain_blocking: {},
@@ -64,6 +67,7 @@ class AccountRelationshipsPresenter
         following:       { account_id => following[account_id] },
         followed_by:     { account_id => followed_by[account_id] },
         blocking:        { account_id => blocking[account_id] },
+        blocked_by:      { account_id => blocked_by[account_id] },
         muting:          { account_id => muting[account_id] },
         requested:       { account_id => requested[account_id] },
         domain_blocking: { account_id => domain_blocking[account_id] },
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index d234516e0..534752932 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -13,7 +13,7 @@ class InstancePresenter
   )
 
   def contact_account
-    Account.find_local(Setting.site_contact_username.gsub(/\A@/, ''))
+    Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
   end
 
   def user_count
diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb
index c6c722a54..1a3fd915c 100644
--- a/app/serializers/rest/relationship_serializer.rb
+++ b/app/serializers/rest/relationship_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class REST::RelationshipSerializer < ActiveModel::Serializer
-  attributes :id, :following, :showing_reblogs, :followed_by, :blocking,
+  attributes :id, :following, :showing_reblogs, :followed_by, :blocking, :blocked_by,
              :muting, :muting_notifications, :requested, :domain_blocking,
              :endorsed
 
@@ -27,6 +27,10 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
     instance_options[:relationships].blocking[object.id] || false
   end
 
+  def blocked_by
+    instance_options[:relationships].blocked_by[object.id] || false
+  end
+
   def muting
     instance_options[:relationships].muting[object.id] ? true : false
   end
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index c47b1c094..7bdffbbd2 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -10,15 +10,7 @@ class AccountSearchService < BaseService
     @options = options
     @account = account
 
-    results = search_service_results
-
-    unless account.nil?
-      account_ids    = results.map(&:id)
-      blocked_by_map = Account.blocked_by_map(account_ids, account.id)
-      results.reject! { |item| blocked_by_map[item.id] }
-    end
-
-    results
+    search_service_results
   end
 
   private
diff --git a/app/services/import_service.rb b/app/services/import_service.rb
index c1c88e0dd..4ee431ea3 100644
--- a/app/services/import_service.rb
+++ b/app/services/import_service.rb
@@ -25,7 +25,7 @@ class ImportService < BaseService
 
   def import_follows!
     parse_import_data!(['Account address'])
-    import_relationships!('follow', 'unfollow', @account.following, follow_limit)
+    import_relationships!('follow', 'unfollow', @account.following, follow_limit, reblogs: 'Show boosts')
   end
 
   def import_blocks!
@@ -35,7 +35,7 @@ class ImportService < BaseService
 
   def import_mutes!
     parse_import_data!(['Account address'])
-    import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT)
+    import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT, notifications: 'Hide notifications')
   end
 
   def import_domain_blocks!
@@ -63,8 +63,8 @@ class ImportService < BaseService
     end
   end
 
-  def import_relationships!(action, undo_action, overwrite_scope, limit)
-    items = @data.take(limit).map { |row| [row['Account address']&.strip, row['Hide notifications']&.strip] }.reject { |(id, _)| id.blank? }
+  def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {})
+    items = @data.take(limit).map { |row| [row['Account address']&.strip, Hash[extra_fields.map { |key, header| [key, row[header]&.strip] }]] }.reject { |(id, _)| id.blank? }
 
     if @import.overwrite?
       presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] }
@@ -73,7 +73,7 @@ class ImportService < BaseService
         if presence_hash[target_account.acct]
           items.delete(target_account.acct)
           extra = presence_hash[target_account.acct][1]
-          Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, ActiveModel::Type::Boolean.new.cast(extra))
+          Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, extra)
         else
           Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action)
         end
@@ -81,7 +81,7 @@ class ImportService < BaseService
     end
 
     Import::RelationshipWorker.push_bulk(items) do |acct, extra|
-      [@account.id, acct, action, ActiveModel::Type::Boolean.new.cast(extra)]
+      [@account.id, acct, action, extra]
     end
   end
 
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index a8442654c..e0da61dac 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -12,8 +12,6 @@ class SearchService < BaseService
     default_results.tap do |results|
       if url_query?
         results.merge!(url_resource_results) unless url_resource.nil?
-        results[:accounts].reject! { |item| item.blocking?(@account) }
-        results[:statuses].reject! { |status| StatusFilter.new(status, @account).filtered? }
       elsif @query.present?
         results[:accounts] = perform_accounts_search! if account_searchable?
         results[:statuses] = perform_statuses_search! if full_text_searchable?
diff --git a/app/validators/existing_username_validator.rb b/app/validators/existing_username_validator.rb
index 4388a0c98..b31d09827 100644
--- a/app/validators/existing_username_validator.rb
+++ b/app/validators/existing_username_validator.rb
@@ -5,16 +5,10 @@ class ExistingUsernameValidator < ActiveModel::EachValidator
     return if value.blank?
 
     if options[:multiple]
-      missing_usernames = value.split(',').map { |username| username unless Account.find_local(username) }.compact
+      missing_usernames = value.split(',').map { |username| username.strip.gsub(/\A@/, '') }.map { |username| username unless Account.find_local(username) }.compact
       record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
     else
-      record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value)
+      record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value.strip.gsub(/\A@/, ''))
     end
   end
-
-  private
-
-  def valid_html?(str)
-    Nokogiri::HTML.fragment(str).to_s == str
-  end
 end
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 0da69728f..e4223119c 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -33,7 +33,9 @@
         = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
         = active_link_to t('accounts.media'), short_account_media_url(@account)
 
-      - if @statuses.empty?
+      - if user_signed_in? && @account.blocking?(current_account)
+        .nothing-here.nothing-here--under-tabs= t('accounts.unavailable')
+      - elsif @statuses.empty?
         = nothing_here 'nothing-here--under-tabs'
       - else
         .activity-stream
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 66808add7..7e9adb3ff 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -10,7 +10,7 @@
   .filter-subset
     %strong= t('admin.accounts.moderation.title')
     %ul
-      %li= filter_link_to t('admin.accounts.moderation.pending'), pending: '1', silenced: nil, suspended: nil
+      %li= link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), admin_pending_accounts_path
       %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil, pending: nil
       %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil, pending: nil
       %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil, pending: nil
diff --git a/app/views/admin/pending_accounts/_account.html.haml b/app/views/admin/pending_accounts/_account.html.haml
new file mode 100644
index 000000000..c520dc065
--- /dev/null
+++ b/app/views/admin/pending_accounts/_account.html.haml
@@ -0,0 +1,14 @@
+.batch-table__row
+  %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
+    = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
+  .batch-table__row__content.batch-table__row__content--unpadded
+    %table.accounts-table
+      %tbody
+        %tr
+          %td
+            = account.user_email
+            = "(@#{account.username})"
+            %br/
+            = account.user_current_sign_in_ip
+          %td.accounts-table__count
+            = table_link_to 'pencil', t('admin.accounts.edit'), admin_account_path(account.id)
diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml
new file mode 100644
index 000000000..1bfd3824f
--- /dev/null
+++ b/app/views/admin/pending_accounts/index.html.haml
@@ -0,0 +1,30 @@
+- content_for :page_title do
+  = t('admin.pending_accounts.title', count: User.pending.count)
+
+= form_for(@form, url: admin_pending_accounts_path, method: :patch) do |f|
+  = hidden_field_tag :page, params[:page] || 1
+
+  .batch-table
+    .batch-table__toolbar
+      %label.batch-table__toolbar__select.batch-checkbox-all
+        = check_box_tag :batch_checkbox_all, nil, false
+      .batch-table__toolbar__actions
+        = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+    .batch-table__body
+      - if @accounts.empty?
+        = nothing_here 'nothing-here--under-tabs'
+      - else
+        = render partial: 'account', collection: @accounts, locals: { f: f }
+
+= paginate @accounts
+
+%hr.spacer/
+
+%div{ style: 'overflow: hidden' }
+  %div{ style: 'float: right' }
+    = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
+
+  %div
+    = link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml
index 31dab68bf..645dd2de1 100644
--- a/app/views/follower_accounts/index.html.haml
+++ b/app/views/follower_accounts/index.html.haml
@@ -9,6 +9,8 @@
 
 - if @account.user_hides_network?
   .nothing-here= t('accounts.network_hidden')
+- elsif user_signed_in? && @account.blocking?(current_account)
+  .nothing-here= t('accounts.unavailable')
 - elsif @follows.empty?
   = nothing_here
 - else
diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml
index 8b49b529b..17fe79018 100644
--- a/app/views/following_accounts/index.html.haml
+++ b/app/views/following_accounts/index.html.haml
@@ -9,6 +9,8 @@
 
 - if @account.user_hides_network?
   .nothing-here= t('accounts.network_hidden')
+- elsif user_signed_in? && @account.blocking?(current_account)
+  .nothing-here= t('accounts.unavailable')
 - elsif @follows.empty?
   = nothing_here
 - else
diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb
index 43ec09ea2..616da6da9 100644
--- a/app/workers/import/relationship_worker.rb
+++ b/app/workers/import/relationship_worker.rb
@@ -5,15 +5,16 @@ class Import::RelationshipWorker
 
   sidekiq_options queue: 'pull', retry: 8, dead: false
 
-  def perform(account_id, target_account_uri, relationship, extra = nil)
+  def perform(account_id, target_account_uri, relationship, options = {})
     from_account   = Account.find(account_id)
     target_account = ResolveAccountService.new.call(target_account_uri)
+    options.symbolize_keys!
 
     return if target_account.nil?
 
     case relationship
     when 'follow'
-      FollowService.new.call(from_account, target_account)
+      FollowService.new.call(from_account, target_account, options)
     when 'unfollow'
       UnfollowService.new.call(from_account, target_account)
     when 'block'
@@ -21,7 +22,7 @@ class Import::RelationshipWorker
     when 'unblock'
       UnblockService.new.call(from_account, target_account)
     when 'mute'
-      MuteService.new.call(from_account, target_account, notifications: extra)
+      MuteService.new.call(from_account, target_account, options)
     when 'unmute'
       UnmuteService.new.call(from_account, target_account)
     end
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index 28201cc64..ae3eede66 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -57,6 +57,10 @@ class Rack::Attack
     req.authenticated_user_id if req.post? && req.path.start_with?('/api/v1/media')
   end
 
+  throttle('throttle_media_proxy', limit: 30, period: 30.minutes) do |req|
+    req.ip if req.path.start_with?('/media_proxy')
+  end
+
   throttle('throttle_api_sign_up', limit: 5, period: 30.minutes) do |req|
     req.ip if req.post? && req.path == '/api/v1/accounts'
   end
diff --git a/config/locales/activerecord.ja.yml b/config/locales/activerecord.ja.yml
index c3b4b7484..ce147819a 100644
--- a/config/locales/activerecord.ja.yml
+++ b/config/locales/activerecord.ja.yml
@@ -4,7 +4,7 @@ ja:
     attributes:
       poll:
         expires_at: 期限
-        options: 選択肢
+        options: 項目
       user:
         email: メールアドレス
     errors:
diff --git a/config/locales/activerecord.nl.yml b/config/locales/activerecord.nl.yml
index eeabab34a..bdb87e8e5 100644
--- a/config/locales/activerecord.nl.yml
+++ b/config/locales/activerecord.nl.yml
@@ -1,6 +1,9 @@
 ---
 nl:
   activerecord:
+    attributes:
+      status:
+        owned_poll: Poll
     errors:
       models:
         account:
diff --git a/config/locales/co.yml b/config/locales/co.yml
index 90c60e292..7fcb087cf 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -68,6 +68,7 @@ co:
       admin: Amministratore
       bot: Bot
       moderator: Muderatore
+    unavailable: Prufile micca dispunibule
     unfollow: Ùn siguità più
   admin:
     account_actions:
@@ -80,6 +81,7 @@ co:
       destroyed_msg: Nota di muderazione sguassata!
     accounts:
       approve: Appruvà
+      approve_all: Appruvà tutti
       are_you_sure: Site sicuru·a?
       avatar: Ritrattu di prufile
       by_domain: Duminiu
@@ -144,6 +146,7 @@ co:
       push_subscription_expires: Spirata di l’abbunamentu PuSH
       redownload: Mette à ghjornu u prufile
       reject: Righjittà
+      reject_all: Righjittà tutti
       remove_avatar: Toglie l’avatar
       remove_header: Toglie l'intistatura
       resend_confirmation:
@@ -330,6 +333,8 @@ co:
         expired: Spirati
         title: Filtrà
       title: Invitazione
+    pending_accounts:
+      title: Conti in attesa (%{count})
     relays:
       add_new: Aghjustà un ripetitore
       delete: Sguassà
@@ -633,7 +638,7 @@ co:
     all: Tuttu
     changes_saved_msg: Cambiamenti salvati!
     copy: Cupià
-    order_by: Urdinà per
+    order_by: Urdinà da
     save_changes: Salvà e mudificazione
     validation_errors:
       one: Qualcosa ùn và bè! Verificate u prublemu quì sottu
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 96b31cab4..15cc025f2 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -23,7 +23,7 @@ cs:
     federation_hint_html: S účtem na %{instance} můžete sledovat lidi na jakémkoliv serveru Mastodon a jiných službách.
     generic_description: "%{domain} je jedním ze serverů v síti"
     get_apps: Vyzkoušejte mobilní aplikaci
-    hosted_on: Server Mastodon na adrese %{domain}
+    hosted_on: Mastodon na adrese %{domain}
     learn_more: Zjistit více
     privacy_policy: Zásady soukromí
     see_whats_happening: Podívejte se, co se děje
@@ -72,6 +72,7 @@ cs:
       admin: Administrátor
       bot: Robot
       moderator: Moderátor
+    unavailable: Profil nedostupný
     unfollow: Přestat sledovat
   admin:
     account_actions:
@@ -84,6 +85,7 @@ cs:
       destroyed_msg: Moderátorská poznámka byla úspěšně zničena!
     accounts:
       approve: Schválit
+      approve_all: Schválit vše
       are_you_sure: Jste si jistý/á?
       avatar: Avatar
       by_domain: Doména
@@ -148,6 +150,7 @@ cs:
       push_subscription_expires: Odebírání PuSH expiruje
       redownload: Obnovit profil
       reject: Zamítnout
+      reject_all: Zamítnout vše
       remove_avatar: Odstranit avatar
       remove_header: Odstranit záhlaví
       resend_confirmation:
@@ -336,6 +339,8 @@ cs:
         expired: Vypršelé
         title: Filtrovat
       title: Pozvánky
+    pending_accounts:
+      title: Čekající účty (%{count})
     relays:
       add_new: Přidat nový most
       delete: Smazat
@@ -764,6 +769,7 @@ cs:
           quadrillion: bld
           thousand: tis
           trillion: bil
+          unit: ''
   pagination:
     newer: Novější
     next: Další
@@ -1058,8 +1064,8 @@ cs:
       edit_profile_action: Nastavit profil
       edit_profile_step: Můžete si přizpůsobit svůj profil nahráním avataru a obrázku záhlaví, změnou zobrazovaného jména a dalších. Chcete-li posoudit nové sledující předtím, než vás mohou sledovat, můžete svůj účet uzamknout.
       explanation: Zde je pár tipů na začátek
-      final_action: Začněte přispívat
-      final_step: 'Začněte psát! I když nemáte sledující, mohou vaše zprávy vidět jiní lidé, například na místní časové ose a mezi hashtagy. Můžete se ostatním představit pomocí hashtagu #introductions.'
+      final_action: Začít psát
+      final_step: 'Začněte psát! I když nemáte sledující, mohou vaše zprávy vidět jiní lidé, například na místní časové ose a v hashtazích. Můžete se ostatním představit pomocí hashtagu #introductions.'
       full_handle: Vaše celá adresa profilu
       full_handle_hint: Tohle je, co byste řekl/a svým přátelům, aby vám mohli posílat zprávy nebo vás sledovat z jiného serveru.
       review_preferences_action: Změnit nastavení
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 6ac6e346b..5b51f9d85 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -3,7 +3,7 @@ de:
   about:
     about_hashtag_html: Dies sind öffentliche Beiträge, die mit <strong>#%{hashtag}</strong> getaggt wurden. Wenn du irgendwo im Fediversum ein Konto besitzt, kannst du mit ihnen interagieren.
     about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!).
-    about_this: Über diese Instanz
+    about_this: Über diesen Server
     active_count_after: aktiv
     active_footnote: Monatlich Aktive User (MAU)
     administered_by: 'Administriert von:'
@@ -11,7 +11,7 @@ de:
     apps: Mobile Apps
     apps_platforms: Benutze Mastodon auf iOS, Android und anderen Plattformen
     browse_directory: Durchsuche ein Profilverzeichnis und filtere nach Interessen
-    browse_public_posts: Durchsuche einen Zeitleiste an öffentlichen Beiträgen auf Mastodon
+    browse_public_posts: Durchsuche eine Zeitleiste an öffentlichen Beiträgen auf Mastodon
     contact: Kontakt
     contact_missing: Nicht angegeben
     contact_unavailable: N/A
@@ -68,6 +68,7 @@ de:
       admin: Admin
       bot: Bot
       moderator: Moderator
+    unavailable: Profil nicht verfügbar
     unfollow: Entfolgen
   admin:
     account_actions:
@@ -80,6 +81,7 @@ de:
       destroyed_msg: Moderationsnotiz erfolgreich gelöscht!
     accounts:
       approve: Aktzeptieren
+      approve_all: Alle aktzeptieren
       are_you_sure: Bist du sicher?
       avatar: Profilbild
       by_domain: Domain
@@ -144,6 +146,7 @@ de:
       push_subscription_expires: PuSH-Abonnement läuft aus
       redownload: Profil neu laden
       reject: Ablehnen
+      reject_all: Alle ablehnen
       remove_avatar: Profilbild entfernen
       remove_header: Header entfernen
       resend_confirmation:
@@ -329,6 +332,8 @@ de:
         expired: Ausgelaufen
         title: Filter
       title: Einladungen
+    pending_accounts:
+      title: Ausstehende Konten (%{count})
     relays:
       add_new: Neues Relay hinzufügen
       delete: Löschen
@@ -422,17 +427,17 @@ de:
           open: Jeder kann sich registrieren
         title: Registrierungsmodus
       show_known_fediverse_at_about_page:
-        desc_html: Wenn aktiviert, wird es alle Beiträge aus dem bereits bekannten Teil des Fediversums auf der Startseite anzeigen. Andernfalls werden lokale Beitrage der Instanz angezeigt.
+        desc_html: Wenn aktiviert, wird es alle Beiträge aus dem bereits bekannten Teil des Fediversums auf der Startseite anzeigen. Andernfalls werden lokale Beitrage des Servers angezeigt.
         title: Verwende öffentliche Zeitleiste für die Vorschau
       show_staff_badge:
         desc_html: Zeige Mitarbeiter-Badge auf Benutzerseite
         title: Zeige Mitarbeiter-Badge
       site_description:
-        desc_html: Einleitungsabschnitt auf der Frontseite. Beschreibe, was diese Mastodon-Instanz ausmacht. Du kannst HTML-Tags benutzen, insbesondere <code>&lt;a&gt;</code> und <code>&lt;em&gt;</code>.
+        desc_html: Einleitungsabschnitt auf der Frontseite. Beschreibe, was diesen Mastodon-Server ausmacht. Du kannst HTML-Tags benutzen, insbesondere <code>&lt;a&gt;</code> und <code>&lt;em&gt;</code>.
         title: Beschreibung des Servers
       site_description_extended:
         desc_html: Bietet sich für Verhaltenskodizes, Regeln, Richtlinien und weiteres an, was deinen Server auszeichnet. Du kannst HTML-Tags benutzen
-        title: Erweiterte Beschreibung der Instanz
+        title: Erweiterte Beschreibung des Servers
       site_short_description:
         desc_html: Wird angezeigt in der Seitenleiste und in Meta-Tags. Beschreibe in einem einzigen Abschnitt, was Mastodon ist und was diesen Server ausmacht. Falls leer, wird die Server-Beschreibung verwendet.
         title: Kurze Server-Beschreibung
@@ -446,7 +451,7 @@ de:
       timeline_preview:
         desc_html: Auf der Frontseite die öffentliche Zeitleiste anzeigen
         title: Zeitleisten-Vorschau
-      title: Instanz-Einstellungen
+      title: Server-Einstellungen
     statuses:
       back_to_account: Zurück zum Konto
       batch:
@@ -530,7 +535,7 @@ de:
     reset_password: Passwort zurücksetzen
     security: Sicherheit
     set_new_password: Neues Passwort setzen
-    trouble_logging_in: Schwierigkeiten beim anmelden?
+    trouble_logging_in: Schwierigkeiten beim Anmelden?
   authorize_follow:
     already_following: Du folgst diesem Konto bereits
     error: Das Remote-Konto konnte nicht geladen werden
@@ -632,6 +637,7 @@ de:
     all: Alle
     changes_saved_msg: Änderungen gespeichert!
     copy: Kopieren
+    order_by: Sortieren nach
     save_changes: Änderungen speichern
     validation_errors:
       one: Etwas ist noch nicht ganz richtig! Bitte korrigiere den Fehler
@@ -773,6 +779,8 @@ de:
   relationships:
     activity: Kontoaktivität
     dormant: Inaktiv
+    last_active: Zuletzt aktiv
+    most_recent: Neuste
     moved: Umgezogen
     mutual: Bekannt
     primary: Primär
@@ -982,7 +990,7 @@ de:
 
       <h3 id="changes">Änderung an unserer Datenschutzerklärung</h3>
 
-      <p>Wenn wir uns entscheiden, Änderungen an unserer Datenschutzerklärung vorzunehmen, werden wird diese Änderungen auf dieser Seite bekannt gegeben.</p>
+      <p>Wenn wir uns entscheiden, Änderungen an unserer Datenschutzerklärung vorzunehmen, werden wir diese Änderungen auf dieser Seite bekannt gegeben.</p>
 
       <p>Dies ist eine Übersetzung, Irrtümer und Übersetzungsfehler vorbehalten. Im Zweifelsfall gilt die englische Originalversion.</p>
 
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index f0a5414b8..944f9b556 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -8,10 +8,10 @@ fr:
     failure:
       already_authenticated: Vous êtes déjà connecté⋅e.
       inactive: Votre compte n’est pas encore activé.
-      invalid: "%{authentication_keys} invalide."
+      invalid: "%{authentication_keys} ou mot de passe invalide."
       last_attempt: Vous avez droit à une tentative avant que votre compte ne soit verrouillé.
       locked: Votre compte est verrouillé.
-      not_found_in_database: "%{authentication_keys} invalide."
+      not_found_in_database: "%{authentication_keys} ou mot de passe invalide."
       timeout: Votre session a expiré. Veuillez vous reconnecter pour continuer.
       unauthenticated: Vous devez vous connecter ou vous inscrire pour continuer.
       unconfirmed: Vous devez valider votre compte pour continuer.
@@ -20,17 +20,17 @@ fr:
         action: Vérifier l’adresse courriel
         action_with_app: Confirmer et retourner à %{app}
         explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de l’activer. Si ce n’était pas vous, veuillez ignorer ce courriel.
-        extra_html: Merci de consultez également <a href="%{terms_path}">les règles de l’instance</a> et <a href="%{policy_path}">nos conditions d’utilisation</a>.
+        extra_html: Merci de consultez également <a href="%{terms_path}">les règles du serveur</a> et <a href="%{policy_path}">nos conditions d’utilisation</a>.
         subject: 'Mastodon : Merci de confirmer votre inscription sur %{instance}'
         title: Vérifier l’adresse courriel
       email_changed:
         explanation: 'L’adresse courriel de votre compte est en cours de modification pour devenir :'
-        extra: Si vous n’avez pas changé votre adresse courriel, il est probable que quelqu’un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l’administrateur·rice de l’instance si vous êtes bloqué·e hors de votre compte.
+        extra: Si vous n’avez pas changé votre adresse courriel, il est probable que quelqu’un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l’administrateur·rice du serveur si vous êtes bloqué·e hors de votre compte.
         subject: 'Mastodon : Courriel modifié'
         title: Nouvelle adresse courriel
       password_change:
         explanation: Le mot de passe de votre compte a été changé.
-        extra: Si vous n’avez pas changé votre mot de passe, il est probable que quelqu’un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l’administrateur·rice de l’instance si vous êtes bloqué·e hors de votre compte.
+        extra: Si vous n’avez pas changé votre mot de passe, il est probable que quelqu’un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l’administrateur·rice du serveur si vous êtes bloqué·e hors de votre compte.
         subject: 'Mastodon : Votre mot de passe a été modifié avec succès'
         title: Mot de passe modifié
       reconfirmation_instructions:
diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml
index a768d3c1d..96d14d9d2 100644
--- a/config/locales/devise.nl.yml
+++ b/config/locales/devise.nl.yml
@@ -13,6 +13,7 @@ nl:
       last_attempt: Je hebt nog één poging over voordat jouw account wordt opgeschort.
       locked: Jouw account is opgeschort.
       not_found_in_database: "%{authentication_keys} of wachtwoord ongeldig."
+      pending: Jouw account moet nog steeds worden beoordeeld.
       timeout: Jouw sessie is verlopen, log opnieuw in.
       unauthenticated: Je dient in te loggen of te registreren.
       unconfirmed: Je dient eerst jouw account te bevestigen.
@@ -21,6 +22,7 @@ nl:
         action: E-mailadres verifiëren
         action_with_app: Bevestigen en naar %{app} teruggaan
         explanation: Je hebt een account op %{host} aangemaakt en met één klik kun je deze activeren. Wanneer jij dit account niet hebt aangemaakt, mag je deze e-mail negeren.
+        explanation_when_pending: Je vroeg met dit e-mailadres een uitnodiging aan voor %{host}. Nadat je jouw e-mailadres hebt bevestigd, beoordelen we jouw aanvraag. Je kunt tot dan nog niet inloggen. Wanneer jouw aanvraag wordt afgekeurd, worden jouw gegevens verwijderd en hoef je daarna verder niets meer te doen. Wanneer jij dit niet was, kun je deze e-mail negeren.
         extra_html: Bekijk ook de <a href="%{terms_path}">regels van de Mastodonserver</a> en <a href="%{policy_path}">onze gebruiksvoorwaarden</a>.
         subject: 'Mastodon: E-mail bevestigen voor %{instance}'
         title: E-mailadres verifiëren
@@ -61,6 +63,7 @@ nl:
       signed_up: Je bent geregistreerd.
       signed_up_but_inactive: Je bent geregistreerd. Je kon alleen niet automatisch ingelogd worden omdat jouw account nog niet geactiveerd is.
       signed_up_but_locked: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat jouw account is opgeschort.
+      signed_up_but_pending: Er is een bericht met een bevestigingslink naar jouw e-mailadres verzonden. Nadat je op deze link hebt geklikt nemen we jouw aanvraag in behandeling. Je wordt op de hoogte gesteld wanneer deze wordt goedgekeurd.
       signed_up_but_unconfirmed: Je ontvangt via e-mail instructies hoe je jouw account kunt activeren. Kijk tussen je spam wanneer niks werd ontvangen.
       update_needs_confirmation: Je hebt je e-mailadres succesvol gewijzigd, maar we moeten je nieuwe mailadres nog bevestigen. Controleer jouw e-mail en klik op de link in de mail om jouw e-mailadres te bevestigen. Kijk tussen je spam wanneer niks werd ontvangen.
       updated: Jouw accountgegevens zijn opgeslagen.
diff --git a/config/locales/devise.pl.yml b/config/locales/devise.pl.yml
index 4f9007e1d..a0af51c32 100644
--- a/config/locales/devise.pl.yml
+++ b/config/locales/devise.pl.yml
@@ -12,6 +12,7 @@ pl:
       last_attempt: Masz jeszcze jedną próbę; Twoje konto zostanie zablokowane jeśli się nie powiedzie.
       locked: Twoje konto zostało zablokowane.
       not_found_in_database: Nieprawidłowy %{authentication_keys} lub hasło.
+      pending: Twoje konto oczekuje na przegląd.
       timeout: Twoja sesja wygasła. Zaloguj się ponownie, aby kontynuować..
       unauthenticated: Zapisz się lub zaloguj, aby kontynuować.
       unconfirmed: Zweryfikuj adres e-mail, aby kontynuować.
@@ -20,6 +21,7 @@ pl:
         action: Zweryfikuj adres e-mail
         action_with_app: Potwierdź i wróć do %{app}
         explanation: Utworzyłeś(-aś) konto na %{host} podając ten adres e-mail. Jedno kliknięcie dzieli Cię od aktywacji tego konta. Jeżeli to nie Ty, zignoruj ten e-mail.
+        explanation_when_pending: Poprosiłeś(-aś) o zaproszenie na %{host} używajac tego adresu e-mail. Kiedy potwierdzisz swój adres e-mail, przejrzymy Twoje podanie. Do tego czasu nie możesz się zalogować. Jeżeli Twoje podanie zostanie odrzucone, Twoje dane zostaną usunięte i nie będziesz musiał(-a) podejmować żadnych dodatkowych działań. Jeżeli to nie Ty, zignoruj ten e-mail.
         extra_html: Przeczytaj też <a href="%{terms_path}">regulamin serwera</a> i <a href="%{policy_path}">nasze zasady użytkowania</a>.
         subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail na %{instance}'
         title: Zweryfikuj adres e-mail
@@ -60,6 +62,7 @@ pl:
       signed_up: Witamy! Twoje konto zostało utworzone.
       signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane.
       signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane.
+      signed_up_but_pending: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Po kliknięciu w odnośnik, przejrzymy Twoje podanie. Zostaniesz poinformowany(-a), gdy zostanie ono przyjęte.
       signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik, aby aktywować konto. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
       update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
       updated: Konto zostało zaktualizowane.
diff --git a/config/locales/doorkeeper.cs.yml b/config/locales/doorkeeper.cs.yml
index 99172656a..f523e125d 100644
--- a/config/locales/doorkeeper.cs.yml
+++ b/config/locales/doorkeeper.cs.yml
@@ -79,7 +79,7 @@ cs:
       messages:
         access_denied: Vlastník zdroje či autorizační server zamítl požadavek.
         credential_flow_not_configured: Proud Resource Owner Password Credentials selhal, protože Doorkeeper.configure.resource_owner_from_credentials nebylo nakonfigurováno.
-        invalid_client: Ověření klienta selhalo kvůli neznámému klientovi, chybějící klientské autentikaci či nepodporované autentikační metodě.
+        invalid_client: Ověření klienta selhalo kvůli neznámému klientovi, chybějící klientské autentizaci či nepodporované autentizační metodě.
         invalid_grant: Poskytnuté oprávnění je neplatné, vypršelé, zamítnuté, neshoduje se s URI přesměrování použitým v požadavku o autorizaci, nebo bylo uděleno jinému klientu.
         invalid_redirect_uri: Přesměrovací URI není platné.
         invalid_request: Požadavku chybí pžadovaný parametr, obsahuje nepodporovanou hodnotu parametru, či je jinak malformovaný.
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a8a19f85d..bf06dc9ad 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -68,6 +68,7 @@ en:
       admin: Admin
       bot: Bot
       moderator: Mod
+    unavailable: Profile unavailable
     unfollow: Unfollow
   admin:
     account_actions:
@@ -80,6 +81,7 @@ en:
       destroyed_msg: Moderation note successfully destroyed!
     accounts:
       approve: Approve
+      approve_all: Approve all
       are_you_sure: Are you sure?
       avatar: Avatar
       by_domain: Domain
@@ -144,6 +146,7 @@ en:
       push_subscription_expires: PuSH subscription expires
       redownload: Refresh profile
       reject: Reject
+      reject_all: Reject all
       remove_avatar: Remove avatar
       remove_header: Remove header
       resend_confirmation:
@@ -330,6 +333,8 @@ en:
         expired: Expired
         title: Filter
       title: Invites
+    pending_accounts:
+      title: Pending accounts (%{count})
     relays:
       add_new: Add new relay
       delete: Delete
@@ -499,7 +504,7 @@ en:
     salutation: "%{name},"
     settings: 'Change e-mail preferences: %{link}'
     view: 'View:'
-    view_profile: View Profile
+    view_profile: View profile
     view_status: View status
   applications:
     created: Application successfully created
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 0d5dd08ad..421ba1da9 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -15,7 +15,7 @@ fr:
       <h3>Un bon endroit pour les règles</h3>
       <p>La description étendue n’a pas été remplie.</p>
     generic_description: "%{domain} est seulement un serveur du réseau"
-    hosted_on: Instance Mastodon hébergée par %{domain}
+    hosted_on: Serveur Mastodon hébergée par %{domain}
     learn_more: En savoir plus
     privacy_policy: Politique de vie privée
     source_code: Code source
@@ -317,7 +317,7 @@ fr:
     relays:
       add_new: Ajouter un nouveau relais
       delete: Effacer
-      description_html: Un <strong>relai de fédération</strong> est un serveur intermédiaire qui échange de grandes quantités de pouets entre les serveurs qui publient dessus et ceux qui y sont abonnés. <strong>Il peut aider les petites et moyennes instances à découvrir du contenu sur le fediverse</strong>, ce qui normalement nécessiterait que les membres locaux suivent des gens inscrits sur des serveurs distants.
+      description_html: Un <strong>relai de fédération</strong> est un serveur intermédiaire qui échange de grandes quantités de pouets entre les serveurs qui publient dessus et ceux qui y sont abonnés. <strong>Il peut aider les petits et moyen serveurs à découvrir du contenu sur le fediverse</strong>, ce qui normalement nécessiterait que les membres locaux suivent des gens inscrits sur des serveurs distants.
       disable: Désactiver
       disabled: Désactivé
       enable: Activé
@@ -376,14 +376,14 @@ fr:
         desc_html: Modifier l'apparence avec une CSS chargée sur chaque page
         title: CSS personnalisé
       hero:
-        desc_html: Affichée sur la page d’accueil. Au moins 600x100px recommandé. Lorsqu’elle n’est pas définie, se rabat sur la vignette de l’instance
+        desc_html: Affichée sur la page d’accueil. Au moins 600x100px recommandé. Lorsqu’elle n’est pas définie, se rabat sur la vignette du serveur
         title: Image d’en-tête
       mascot:
         desc_html: Affiché sur plusieurs pages. Au moins 293×205px recommandé. Lorsqu'il n'est pas défini, retombe à la mascotte par défaut
         title: Image de la mascotte
       peers_api_enabled:
-        desc_html: Noms des domaines que cette instance a découvert dans le fediverse
-        title: Publier la liste des instances découvertes
+        desc_html: Noms des domaines que ce serveur a découvert dans le fediverse
+        title: Publier la liste des serveurs découverts
       preview_sensitive_media:
         desc_html: Les liens de prévisualisation sur les autres sites web afficheront une vignette même si le média est sensible
         title: Afficher les médias sensibles dans les prévisualisations OpenGraph
@@ -401,31 +401,31 @@ fr:
           disabled: Personne
           title: Autoriser les invitations par
       show_known_fediverse_at_about_page:
-        desc_html: Lorsque l’option est activée, les pouets provenant de toutes les instances connues sont affichés dans la prévisualisation. Sinon, seuls les pouets locaux sont affichés.
+        desc_html: Lorsque l’option est activée, les pouets provenant de toutes les serveurs connues sont affichés dans la prévisualisation. Sinon, seuls les pouets locaux sont affichés.
         title: Afficher le fediverse connu dans la prévisualisation du fil
       show_staff_badge:
         desc_html: Montrer un badge de responsable sur une page utilisateur·ice
         title: Montrer un badge de responsable
       site_description:
         desc_html: Paragraphe introductif sur la page d’accueil. Décrivez ce qui rend spécifique ce serveur Mastodon et toute autre chose importante. Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>.
-        title: Description de l'instance
+        title: Description du serveur
       site_description_extended:
-        desc_html: L'endroit idéal pour afficher votre code de conduite, les règles, les guides et autres choses qui rendent votre instance différente. Vous pouvez utiliser des balises HTML
-        title: Description étendue du site
+        desc_html: L'endroit idéal pour afficher votre code de conduite, les règles, les guides et autres choses qui rendent votre serveur différent. Vous pouvez utiliser des balises HTML
+        title: Description étendue du serveur
       site_short_description:
-        desc_html: Affichée dans la barre latérale et dans les méta-tags. Décrivez ce qui rend spécifique cette instance Mastodon en un seul paragraphe. Si laissée vide, la description de l’instance sera affiché par défaut.
-        title: Description courte de l’instance
+        desc_html: Affichée dans la barre latérale et dans les méta-tags. Décrivez ce qui rend spécifique ce serveur Mastodon en un seul paragraphe. Si laissée vide, la description du serveur sera affiché par défaut.
+        title: Description courte du serveur
       site_terms:
         desc_html: Affichée sur la page des conditions d’utilisation du site<br>Vous pouvez utiliser des balises HTML
         title: Politique de confidentialité
-      site_title: Nom de l'instance
+      site_title: Nom du serveur
       thumbnail:
         desc_html: Utilisée pour les prévisualisations via OpenGraph et l’API. 1200x630px recommandé
-        title: Vignette de l’instance
+        title: Vignette du serveur
       timeline_preview:
         desc_html: Afficher le fil public sur la page d’accueil
         title: Prévisualisation du fil global
-      title: Paramètres du site
+      title: Paramètres du serveur
     statuses:
       back_to_account: Retour à la page du compte
       batch:
@@ -482,7 +482,7 @@ fr:
     warning: Soyez prudent⋅e avec ces données. Ne les partagez pas !
     your_token: Votre jeton d’accès
   auth:
-    agreement_html: En cliquant sur "S'inscrire" ci-dessous, vous souscrivez <a href="%{rules_path}">aux règles de l’instance</a> et à <a href="%{terms_path}">nos conditions d’utilisation</a>.
+    agreement_html: En cliquant sur "S'inscrire" ci-dessous, vous souscrivez <a href="%{rules_path}">aux règles du serveur</a> et à <a href="%{terms_path}">nos conditions d’utilisation</a>.
     change_password: Mot de passe
     confirm_email: Confirmer mon adresse mail
     delete_account: Supprimer le compte
@@ -534,7 +534,7 @@ fr:
     description_html: Cela va supprimer votre compte et le désactiver de manière <strong>permanente et irréversible</strong>. Votre nom d’utilisateur⋅ice restera réservé afin d’éviter la confusion.
     proceed: Supprimer compte
     success_msg: Votre compte a été supprimé avec succès
-    warning_html: Seule la suppression du contenu depuis cette instance est garantie. Le contenu qui a été partagé est susceptible de laisser des traces. Les instances hors-ligne ainsi que ceux n’étant plus abonnées à vos publications ne mettront pas leur base de données à jour.
+    warning_html: Seule la suppression du contenu depuis ce serveur est garantie. Le contenu qui a été partagé est susceptible de laisser des traces. Les serveurs hors-ligne ainsi que ceux n’étant plus abonnées à vos publications ne mettront pas leur base de données à jour.
     warning_title: Disponibilité du contenu disséminé
   directories:
     directory: Annuaire des profils
@@ -610,11 +610,11 @@ fr:
       merge_long: Garder les enregistrements existants et ajouter les nouveaux
       overwrite: Réécrire
       overwrite_long: Remplacer les enregistrements actuels par les nouveaux
-    preface: Vous pouvez importer certaines données que vous avez exporté d'une autre instance, comme une liste des personnes que vous suivez ou bloquez sur votre compte.
+    preface: Vous pouvez importer certaines données que vous avez exporté d'un autre serveur, comme une liste des personnes que vous suivez ou bloquez sur votre compte.
     success: Vos données ont été importées avec succès et seront traitées en temps et en heure
     types:
       blocking: Liste d’utilisateur⋅ice⋅s bloqué⋅e⋅s
-      domain_blocking: Liste des instances bloquées
+      domain_blocking: Liste des serveurs bloquées
       following: Liste d’utilisateur⋅ice⋅s suivi⋅e⋅s
       muting: Liste d’utilisateur⋅ice⋅s que vous masquez
     upload: Importer
@@ -636,7 +636,7 @@ fr:
       one: 1 usage
       other: "%{count} usages"
     max_uses_prompt: Pas de limite
-    prompt: Générer et partager des liens avec les autres pour donner accès à cette instance
+    prompt: Générer et partager des liens avec les autres pour donner accès à ce serveur
     table:
       expires_at: Expire
       uses: Utilise
@@ -722,7 +722,7 @@ fr:
     publishing: Publication
     web: Web
   remote_follow:
-    acct: Entrez l’adresse profil@instance depuis laquelle vous voulez vous abonner
+    acct: Entrez l’adresse profil@serveur depuis laquelle vous voulez vous abonner
     missing_resource: L’URL de redirection n’a pas pu être trouvée
     no_account_html: Vous n’avez pas de compte ? Vous pouvez <a href='%{sign_up_path}' target='_blank'>vous inscrire ici</a>
     proceed: Confirmer l’abonnement
@@ -978,7 +978,7 @@ fr:
       final_action: Commencer à publier
       final_step: 'Commencez à poster ! Même sans abonné·e·s, vos messages publics peuvent être vus par d’autres, par exemple sur le fil public local et dans les hashtags. Vous pouvez vous présenter sur le hashtag #introductions.'
       full_handle: Votre identifiant complet
-      full_handle_hint: C’est ce que vous diriez à vos ami·e·s pour leur permettre de vous envoyer un message ou vous suivre à partir d’une autre instance.
+      full_handle_hint: C’est ce que vous diriez à vos ami·e·s pour leur permettre de vous envoyer un message ou vous suivre à partir d’un autre serveur.
       review_preferences_action: Modifier les préférences
       review_preferences_step: Assurez-vous de définir vos préférences, telles que les courriels que vous aimeriez recevoir ou le niveau de confidentialité auquel vous aimeriez que vos messages soient soumis par défaut. Si vous n’avez pas le mal des transports, vous pouvez choisir d’activer la lecture automatique des GIF.
       subject: Bienvenue sur Mastodon
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 85a310953..f13dbdb68 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -68,6 +68,7 @@ ja:
       admin: Admin
       bot: Bot
       moderator: Mod
+    unavailable: プロフィールは利用できません
     unfollow: フォロー解除
   admin:
     account_actions:
@@ -80,6 +81,7 @@ ja:
       destroyed_msg: モデレーションメモを削除しました!
     accounts:
       approve: 承認
+      approve_all: すべて承認
       are_you_sure: 本当に実行しますか?
       avatar: アイコン
       by_domain: ドメイン
@@ -125,7 +127,7 @@ ja:
       moderation:
         active: アクティブ
         all: すべて
-        pending: 保留中
+        pending: 承認待ち
         silenced: サイレンス済み
         suspended: 停止済み
         title: モデレーション
@@ -135,7 +137,7 @@ ja:
       no_limits_imposed: 制限なし
       not_subscribed: 購読していない
       outbox_url: Outbox URL
-      pending: 審査待ち
+      pending: 承認待ち
       perform_full_suspension: 活動を完全に停止させる
       profile_url: プロフィールURL
       promote: 昇格
@@ -144,6 +146,7 @@ ja:
       push_subscription_expires: PuSH購読期限
       redownload: プロフィールを更新
       reject: 却下
+      reject_all: すべて却下
       remove_avatar: アイコンを削除
       remove_header: ヘッダーを削除
       resend_confirmation:
@@ -330,6 +333,8 @@ ja:
         expired: 期限切れ
         title: フィルター
       title: 招待
+    pending_accounts:
+      title: 承認待ちアカウント (%{count})
     relays:
       add_new: リレーを追加
       delete: 削除
@@ -486,7 +491,7 @@ ja:
   admin_mailer:
     new_pending_account:
       body: 新しいアカウントの詳細は以下の通りです。この申請を承認または却下することができます。
-      subject: "%{instance} で新しいアカウント (%{username}) が審査待ちです"
+      subject: "%{instance} で新しいアカウント (%{username}) が承認待ちです"
     new_report:
       body: "%{reporter} が %{target} を通報しました"
       body_remote: "%{domain} の誰かが %{target} を通報しました"
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index fd1470d2d..90996b466 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -68,6 +68,7 @@ ko:
       admin: 관리자
       bot: 봇
       moderator: 모더레이터
+    unavailable: 프로필 사용 불가
     unfollow: 팔로우 해제
   admin:
     account_actions:
@@ -80,6 +81,7 @@ ko:
       destroyed_msg: 모더레이션 기록이 성공적으로 삭제되었습니다!
     accounts:
       approve: 승인
+      approve_all: 모두 승인
       are_you_sure: 정말로 실행하시겠습니까?
       avatar: 아바타
       by_domain: 도메인
@@ -144,6 +146,7 @@ ko:
       push_subscription_expires: PuSH 구독 기간 만료
       redownload: 프로필 업데이트
       reject: 거부
+      reject_all: 모두 거부
       remove_avatar: 아바타 지우기
       remove_header: 헤더 삭제
       resend_confirmation:
@@ -332,6 +335,8 @@ ko:
         expired: 만료됨
         title: 필터
       title: 초대
+    pending_accounts:
+      title: 대기중인 계정 (%{count})
     relays:
       add_new: 릴레이 추가
       delete: 삭제
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 36a030fa4..2bfab2454 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -4,25 +4,36 @@ nl:
     about_hashtag_html: Dit zijn openbare toots die getagged zijn met <strong>#%{hashtag}</strong>. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt.
     about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd.
     about_this: Over deze server
+    active_count_after: actief
+    active_footnote: Actieve gebruikers per maand (MAU)
     administered_by: 'Beheerd door:'
     api: API
     apps: Mobiele apps
+    apps_platforms: Gebruik Mastodon op iOS, Android en op andere platformen
+    browse_directory: Gebruikersgids doorbladeren en op interesses filteren
+    browse_public_posts: Livestream van openbare Mastodonberichten bekijken
     contact: Contact
     contact_missing: Niet ingesteld
     contact_unavailable: n.v.t
+    discover_users: Gebruikers ontdekken
     documentation: Documentatie
     extended_description_html: |
       <h3>Een goede plek voor richtlijnen</h3>
       <p>De uitgebreide omschrijving is nog niet ingevuld.</p>
+    federation_hint_html: Met een account op %{instance} ben je in staat om mensen die zich op andere Mastodonservers (en op andere plekken) bevinden te volgen.
     generic_description: "%{domain} is een server in het Mastodonnetwerk"
+    get_apps: Mobiele apps
     hosted_on: Mastodon op %{domain}
     learn_more: Meer leren
     privacy_policy: Privacybeleid
+    see_whats_happening: Kijk wat er aan de hand is
+    server_stats: 'Serverstatistieken:'
     source_code: Broncode
     status_count_after:
       one: toot
       other: toots
     status_count_before: Zij schreven
+    tagline: Vrienden volgen en nieuwe ontdekken
     terms: Gebruiksvoorwaarden
     user_count_after:
       one: gebruiker
@@ -68,6 +79,7 @@ nl:
       delete: Verwijderen
       destroyed_msg: Verwijderen van opmerking voor moderatoren geslaagd!
     accounts:
+      approve: Goedkeuren
       are_you_sure: Weet je het zeker?
       avatar: Avatar
       by_domain: Domein
@@ -113,6 +125,7 @@ nl:
       moderation:
         active: Actief
         all: Alles
+        pending: In afwachting
         silenced: Genegeerd
         suspended: Opgeschort
         title: Moderatie
@@ -122,6 +135,7 @@ nl:
       no_limits_imposed: Geen limieten ingesteld
       not_subscribed: Niet geabonneerd
       outbox_url: Outbox-URL
+      pending: Moet nog beoordeeld worden
       perform_full_suspension: Opschorten
       profile_url: Profiel-URL
       promote: Promoveren
@@ -129,6 +143,7 @@ nl:
       public: Openbaar
       push_subscription_expires: PuSH-abonnement verloopt op
       redownload: Profiel vernieuwen
+      reject: Afkeuren
       remove_avatar: Avatar verwijderen
       remove_header: Omslagfoto verwijderen
       resend_confirmation:
@@ -400,6 +415,12 @@ nl:
         min_invite_role:
           disabled: Niemand
           title: Uitnodigingen toestaan door
+      registrations_mode:
+        modes:
+          approved: Goedkeuring vereist om te kunnen registreren
+          none: Niemand kan zich registreren
+          open: Iedereen kan zich registreren
+        title: Registratiemodus
       show_known_fediverse_at_about_page:
         desc_html: Wanneer ingeschakeld wordt de globale tijdlijn op de voorpagina getoond en wanneer uitgeschakeld de lokale tijdljn.
         title: De globale tijdlijn op de voorpagina tonen
@@ -462,6 +483,9 @@ nl:
       edit_preset: Voorinstelling van waarschuwing bewerken
       title: Voorinstellingen van waarschuwingen beheren
   admin_mailer:
+    new_pending_account:
+      body: Zie hieronder de details van het nieuwe account. Je kunt de aanvraag goedkeuren of afkeuren.
+      subject: Er dient een nieuw account op %{instance} te worden beoordeeld (%{username})
     new_report:
       body: "%{reporter} heeft %{target} gerapporteerd"
       body_remote: Iemand van %{domain} heeft %{target} gerapporteerd
@@ -483,10 +507,12 @@ nl:
     your_token: Jouw toegangscode
   auth:
     agreement_html: Wanneer je op registreren klikt ga je akkoord met het opvolgen van <a href="%{rules_path}">de regels van deze server</a> en <a href="%{terms_path}">onze gebruiksvoorwaarden</a>.
+    apply_for_account: Een uitnodiging aanvragen
     change_password: Wachtwoord
+    checkbox_agreement_html: Ik ga akkoord met de <a href="%{rules_path}" target="_blank">regels van deze server</a> en de <a href="%{terms_path}" target="_blank">gebruiksvoorwaarden</a>
     confirm_email: E-mail bevestigen
     delete_account: Account verwijderen
-    delete_account_html: Wanneer je jouw account graag wilt verwijderen, kan je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.
+    delete_account_html: Wanneer je jouw account graag wilt verwijderen, kun je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.
     didnt_get_confirmation: Geen bevestigingsinstructies ontvangen?
     forgot_password: Wachtwoord vergeten?
     invalid_reset_password_token: De code om jouw wachtwoord opnieuw in te stellen is verlopen. Vraag een nieuwe aan.
@@ -499,10 +525,12 @@ nl:
       cas: CAS
       saml: SAML
     register: Registreren
+    registration_closed: "%{instance} laat geen nieuwe gebruikers toe"
     resend_confirmation: Verstuur de bevestigingsinstructies nogmaals
     reset_password: Wachtwoord opnieuw instellen
     security: Beveiliging
     set_new_password: Nieuw wachtwoord instellen
+    trouble_logging_in: Problemen met inloggen?
   authorize_follow:
     already_following: Je volgt dit account al
     error: Helaas, er is een fout opgetreden bij het opzoeken van de externe account
@@ -598,12 +626,29 @@ nl:
     more: Meer…
     resources: Hulpmiddelen
   generic:
+    all: Alles
     changes_saved_msg: Wijzigingen succesvol opgeslagen!
     copy: Kopiëren
     save_changes: Wijzigingen opslaan
     validation_errors:
       one: Er is iets niet helemaal goed! Bekijk onderstaande fout
       other: Er is iets niet helemaal goed! Bekijk onderstaande %{count} fouten
+  identity_proofs:
+    active: Actief
+    authorize: Ja, autoriseren
+    authorize_connection_prompt: Deze cryptografische verbinding autoriseren?
+    errors:
+      failed: De cryptografische verbinding is mislukt. Probeer het opnieuw vanaf %{provider}.
+      keybase:
+        invalid_token: Keybasetokens zijn hashes van handtekeningen en moeten een lengte hebben van 66 hexadecimale tekens
+        verification_failed: Keybase herkent deze token niet als een handtekening van Keybasegebruiker %{kb_username}. Probeer het opnieuw vanuit Keybase.
+      wrong_user: Er kan geen bewijs worden aangemaakt voor %{proving}   terwijl je bent ingelogd als %{current}. Log in als %{proving} en probeer het opnieuw.
+    explanation_html: Hier kun je met behulp van cryptografie jouw andere identiteiten verbinden, zoals een Keybaseprofiel. Hiermee kunnen andere mensen jou versleutelde berichten sturen en inhoud die jij verstuurt vertrouwen.
+    i_am_html: Ik ben %{username} op %{service}.
+    identity: Identiteit
+    inactive: Inactief
+    status: Verificatiestatus
+    view_proof: Bekijk bewijs
   imports:
     modes:
       merge: Samenvoegen
@@ -721,6 +766,17 @@ nl:
     other: Overig
     publishing: Publiceren
     web: Webapp
+  relationships:
+    activity: Accountactiviteit
+    dormant: Sluimerend
+    moved: Verhuisd
+    mutual: Wederzijds
+    primary: Primair
+    relationship: Relatie
+    remove_selected_domains: Alle volgers van de geselecteerde domeinen verwijderen
+    remove_selected_followers: Geselecteerde volgers verwijderen
+    remove_selected_follows: Geselecteerde gebruikers ontvolgen
+    status: Accountstatus
   remote_follow:
     acct: Geef jouw account@domein op die je wilt gebruiken
     missing_resource: Kon vereiste doorverwijzings-URL voor jouw account niet vinden
@@ -795,10 +851,12 @@ nl:
     edit_profile: Profiel bewerken
     export: Exporteren
     featured_tags: Uitgelichte hashtags
+    identity_proofs: Identiteitsbewijzen
     import: Importeren
     migrate: Accountmigratie
     notifications: Meldingen
     preferences: Voorkeuren
+    relationships: Volgers en gevolgden
     settings: Instellingen
     two_factor_authentication: Tweestapsverificatie
     your_apps: Jouw toepassingen
@@ -944,7 +1002,7 @@ nl:
     generate_recovery_codes: Herstelcodes genereren
     instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon</strong>. Van nu af aan genereert deze app aanmeldcodes die je bij het inloggen moet invoeren."
     lost_recovery_codes: Met herstelcodes kun je toegang tot jouw account krijgen wanneer je jouw telefoon bent kwijtgeraakt. Wanneer je jouw herstelcodes bent kwijtgeraakt, kan je ze hier opnieuw genereren. Jouw oude herstelcodes zijn daarna ongeldig.
-    manual_instructions: Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren, vind je hieronder geheime code in platte tekst.
+    manual_instructions: Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren, vind je hieronder de geheime code in platte tekst.
     recovery_codes: Herstelcodes back-uppen
     recovery_codes_regenerated: Opnieuw genereren herstelcodes geslaagd
     recovery_instructions_html: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. <strong>Zorg ervoor dat je de herstelcodes op een veilige plek bewaard</strong>. Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 9470e875a..e67a55edb 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -4,20 +4,30 @@ pl:
     about_hashtag_html: Znajdują się tu publiczne wpisy oznaczone hashtagiem <strong>#%{hashtag}</strong>. Możesz dołączyć do dyskusji, jeżeli posiadasz konto gdziekolwiek w Fediwersum.
     about_mastodon_html: Mastodon jest wolną i otwartą siecią społecznościową, zdecentralizowaną alternatywą dla zamkniętych, komercyjnych platform.
     about_this: O tej instancji
+    active_count_after: aktywni
+    active_footnote: Aktywni użytkownicy miesięcznie (MAU)
     administered_by: 'Administrowana przez:'
     api: API
     apps: Aplikacje
+    apps_platforms: Korzystaj z Mastodona z poziomu iOS-a, Androida i innych
+    browse_directory: Przeglądaj katalog profilów i filtruj z uwzględnieniem zainteresowań
+    browse_public_posts: Przeglądaj strumień publicznych wpisów na Mastodonie na żywo
     contact: Kontakt
     contact_missing: Nie ustawiono
     contact_unavailable: Nie dotyczy
+    discover_users: Odkrywaj użytkowników
     documentation: Dokumentacja
     extended_description_html: |
       <h3>Dobre miejsce na zasady użytkowania</h3>
       <p>Nie ustawiono jeszcze szczegółowego opisu</p>
+    federation_hint_html: Z kontem na %{instance}, możesz śledzić użytkowników każdego serwera Mastodona i nie tylko.
     generic_description: "%{domain} jest jednym z serwerów sieci"
+    get_apps: Spróbuj aplikacji mobilnej
     hosted_on: Mastodon uruchomiony na %{domain}
     learn_more: Dowiedz się więcej
     privacy_policy: Polityka prywatności
+    see_whats_happening: Zobacz co się dzieje
+    server_stats: 'Statystyki serwera:'
     source_code: Kod źródłowy
     status_count_after:
       few: wpisów
@@ -25,6 +35,7 @@ pl:
       one: wpisu
       other: wpisów
     status_count_before: Są autorami
+    tagline: Śledź znajomych i poznawal nowych
     terms: Zasady użytkowania
     user_count_after:
       few: użytkowników
@@ -76,6 +87,7 @@ pl:
       delete: Usuń
       destroyed_msg: Pomyślnie usunięto notatkę moderacyjną!
     accounts:
+      approve: Przyjmij
       are_you_sure: Jesteś tego pewien?
       avatar: Awatar
       by_domain: Domena
@@ -121,6 +133,7 @@ pl:
       moderation:
         active: Aktywne
         all: Wszystkie
+        pending: Oczekujące
         silenced: Wyciszone
         suspended: Zawieszone
         title: Moderacja
@@ -130,6 +143,7 @@ pl:
       no_limits_imposed: Nie nałożono ograniczeń
       not_subscribed: Nie zasubskrybowano
       outbox_url: Adres skrzynki nadawczej
+      pending: Oczekuje na przegląd
       perform_full_suspension: Zawieś
       profile_url: Adres profilu
       promote: Podnieś uprawnienia
@@ -137,6 +151,7 @@ pl:
       public: Publiczne
       push_subscription_expires: Subskrypcja PuSH wygasa
       redownload: Odśwież profil
+      reject: Odrzuć
       remove_avatar: Usun awatar
       remove_header: Usuń nagłówek
       resend_confirmation:
@@ -238,6 +253,7 @@ pl:
       feature_profile_directory: Katalog profilów
       feature_registrations: Rejestracja
       feature_relay: Przekazywanie federacji
+      feature_timeline_preview: Podgląd osi czasu
       features: Możliwości
       hidden_service: Federowanie z ukrytymi usługami
       open_reports: otwarte zgłoszenia
@@ -412,6 +428,12 @@ pl:
         min_invite_role:
           disabled: Nikt
           title: Kto może zapraszać użytkowników
+      registrations_mode:
+        modes:
+          approved: Przyjęcie jest wymagane do rejestracji
+          none: Nikt nie może się zarejestrować
+          open: Każdy może się zarejestrować
+        title: Tryb rejestracji
       show_known_fediverse_at_about_page:
         desc_html: Jeśli włączone, podgląd instancji będzie wyświetlał wpisy z całego Fediwersum. W innym przypadku, będą wyświetlane tylko lokalne wpisy.
         title: Pokazuj wszystkie znane wpisy na podglądzie instancji
@@ -474,6 +496,9 @@ pl:
       edit_preset: Edytuj szablon ostrzeżenia
       title: Zarządzaj szablonami ostrzeżeń
   admin_mailer:
+    new_pending_account:
+      body: Poniżej znajdują się szczegóły dotycząće nowego konta. Możesz przyjąć lub odrzucić to podanie.
+      subject: Nowe konto czeka na przegląd na %{instance} (%{username})
     new_report:
       body: Użytkownik %{reporter} zgłosił(a) %{target}
       body_remote: Użytkownik instancji %{domain} zgłosił(a) %{target}
@@ -495,7 +520,9 @@ pl:
     your_token: Twój token dostępu
   auth:
     agreement_html: Rejestrując się, oświadczasz, że zapoznałeś(-aś) się z <a href="%{rules_path}">informacjami o serwerze</a> i <a href="%{terms_path}">zasadami korzystania z usługi</a>.
+    apply_for_account: Poproś o zaproszenie
     change_password: Hasło
+    checkbox_agreement_html: Zgadzam się z <a href="%{rules_path}" target="_blank">regułami serwera</a> i <a href="%{terms_path}" target="_blank">zasadami korzystania z usługi</a>
     confirm_email: Potwierdź adres e-mail
     delete_account: Usunięcie konta
     delete_account_html: Jeżeli chcesz usunąć konto, <a href="%{path}">przejdź tutaj</a>. Otrzymasz prośbę o potwierdzenie.
@@ -511,10 +538,12 @@ pl:
       cas: CAS
       saml: SAML
     register: Rejestracja
+    registration_closed: "%{instance} nie przyjmuje nowych członków"
     resend_confirmation: Ponownie prześlij instrukcje weryfikacji
     reset_password: Zresetuj hasło
     security: Bezpieczeństwo
     set_new_password: Ustaw nowe hasło
+    trouble_logging_in: Masz problem z zalogowaniem się?
   authorize_follow:
     already_following: Już śledzisz to konto
     error: Niestety, podczas sprawdzania zdalnego konta wystąpił błąd
@@ -572,6 +601,9 @@ pl:
       content: Przepraszamy, coś poszło nie tak, po naszej stronie.
       title: Ta strona jest nieprawidłowa
     noscript_html: Aby korzystać z aplikacji Mastodon, włącz JavaScript. Możesz też skorzystać z jednej z <a href="%{apps_path}">natywnych aplikacji</a> obsługującej Twoje urządzenie.
+  existing_username_validator:
+    not_found: nie znaleziono lokalnego użytkownika o tej nazwie
+    not_found_multiple: nie znaleziono %{usernames}
   exports:
     archive_takeout:
       date: Data
@@ -612,8 +644,10 @@ pl:
     more: Więcej…
     resources: Zasoby
   generic:
+    all: Wszystkie
     changes_saved_msg: Ustawienia zapisane!
     copy: Kopiuj
+    order_by: Uporządkuj według
     save_changes: Zapisz zmiany
     use_this: Użyj tego
     validation_errors:
@@ -621,6 +655,26 @@ pl:
       many: Coś jest wciąż nie tak! Przejrzyj %{count} poniższych błędów
       one: Coś jest wciąż nie tak! Przyjrzyj się poniższemu błędowi
       other: Coś jest wciąż nie tak! Przejrzyj poniższe błędy (%{count})
+  html_validator:
+    invalid_markup: 'zawiera nieprawidłową składnię HTML: %{error}'
+  identity_proofs:
+    active: Aktywny
+    authorize: Tak, autoryzuj
+    authorize_connection_prompt: Czy chcesz autoryzować to połączenie kryptograficzne?
+    errors:
+      failed: Połączenioe kryptograficzne nie powiodło się. Spróbuj ponownie z poziomu %{provider}.
+      keybase:
+        invalid_token: Tokeny Keybase są hashami podpisów i musza składać się z 66 znaków heksadecymalnych
+        verification_failed: Keybase nie rozpoznaje tego tokenu jako podpisu użytkownika Keybase %{kb_username}. Spróbuj ponownie z poziomu Keybase.
+      wrong_user: Nie można utworzyć dowodu dla %{proving}, gdy jesteś zalogowany(-a) jako %{current}. Zaloguj się jako %{proving} i spróbuj ponownie.
+    explanation_html: Tutaj możesz połączyć kryptograficznie swoje inne tożsamości, takie jak profil Keybase. To pozwoli innym wysłać Ci szyfrowane wiadomości i zaufać zawartości którą im wysyłasz.
+    i_am_html: Jestem %{username} na %{service}.
+    identity: Tożsamość
+    inactive: Niekatywny
+    publicize_checkbox: 'I opublikuj to:'
+    publicize_toot: 'Udowodnione! Jestem %{username} na %{service}: %{url}'
+    status: Stan weryfikacji
+    view_proof: Wyświetl dowód
   imports:
     modes:
       merge: Połącz
@@ -744,6 +798,19 @@ pl:
     other: Pozostałe
     publishing: Publikowanie
     web: Sieć
+  relationships:
+    activity: Aktywność konta
+    dormant: Uśpione
+    last_active: Ostatnia aktywność
+    most_recent: Ostatnie
+    moved: Przeniesione
+    mutual: Wspólna
+    primary: Jednostronna
+    relationship: Relacja
+    remove_selected_domains: Usuń wszystkich śledzących z zaznaczonych domen
+    remove_selected_followers: Usuń zaznaczonych śledzących
+    remove_selected_follows: Przestań śledzić zaznaczonych użytkowników
+    status: Stan konta
   remote_follow:
     acct: Podaj swój adres (nazwa@domena), z którego chcesz wykonać działanie
     missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny
@@ -819,10 +886,12 @@ pl:
     export: Eksportowanie danych
     featured_tags: Wyróżnione hashtagi
     flavours: Odmiany
+    identity_proofs: Dowody tożsamości
     import: Importowanie danych
     migrate: Migracja konta
     notifications: Powiadomienia
     preferences: Preferencje
+    relationships: Śledzeni i śledzący
     settings: Ustawienia
     two_factor_authentication: Uwierzytelnianie dwuetapowe
     your_apps: Twoje aplikacje
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index f493b746b..0469ebe06 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -40,7 +40,7 @@ fr:
       featured_tag:
         name: 'Vous pourriez utiliser l''un d''entre eux :'
       imports:
-        data: Un fichier CSV généré par une autre instance de Mastodon
+        data: Un fichier CSV généré par un autre serveur de Mastodon
       sessions:
         otp: 'Entrez le code d’authentification à deux facteurs généré par l’application de votre téléphone ou utilisez un de vos codes de récupération :'
       user:
diff --git a/config/navigation.rb b/config/navigation.rb
index 34b566188..553a672ed 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -34,7 +34,7 @@ SimpleNavigation::Configuration.run do |navigation|
     primary.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin|
       admin.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
       admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
-      admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
+      admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
       admin.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
       admin.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path
       admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.admin? }
diff --git a/config/routes.rb b/config/routes.rb
index 5a51cc6e8..260ea63ab 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -217,6 +217,13 @@ Rails.application.routes.draw do
       end
     end
 
+    resources :pending_accounts, only: [:index, :update] do
+      collection do
+        post :approve_all
+        post :reject_all
+      end
+    end
+
     resources :users, only: [] do
       resource :two_factor_authentication, only: [:destroy]
     end
diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb
index f02b2149b..9dc84f1b5 100644
--- a/lib/mastodon/accounts_cli.rb
+++ b/lib/mastodon/accounts_cli.rb
@@ -219,12 +219,14 @@ module Mastodon
     def cull
       skip_threshold = 7.days.ago
       culled         = 0
+      dry_run_culled = []
       skip_domains   = Set.new
       dry_run        = options[:dry_run] ? ' (DRY RUN)' : ''
 
       Account.remote.where(protocol: :activitypub).partitioned.find_each do |account|
         next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold)
 
+        code = 0
         unless skip_domains.include?(account.domain)
           begin
             code = Request.new(:head, account.uri).perform(&:code)
@@ -236,7 +238,11 @@ module Mastodon
         end
 
         if [404, 410].include?(code)
-          SuspendAccountService.new.call(account, destroy: true) unless options[:dry_run]
+          if options[:dry_run]
+            dry_run_culled << account.acct
+          else
+            SuspendAccountService.new.call(account, destroy: true)
+          end
           culled += 1
           say('+', :green, false)
         else
@@ -252,6 +258,11 @@ module Mastodon
         say('The following servers were not available during the check:', :yellow)
         skip_domains.each { |domain| say('    ' + domain) }
       end
+
+      unless dry_run_culled.empty?
+        say('The following accounts would have been deleted:', :green)
+        dry_run_culled.each { |account| say('    ' + account) }
+      end
     end
 
     option :all, type: :boolean
@@ -356,6 +367,104 @@ module Mastodon
       say("OK, unfollowed target from #{processed} accounts, skipped #{failed}", :green)
     end
 
+    option :follows, type: :boolean, default: false
+    option :followers, type: :boolean, default: false
+    desc 'reset-relationships USERNAME', 'Reset all follows and/or followers for a user'
+    long_desc <<-LONG_DESC
+      Reset all follows and/or followers for a user specified by USERNAME.
+
+      With the --follows option, the command unfollows everyone that the account follows,
+      and then re-follows the users that would be followed by a brand new account.
+
+      With the --followers option, the command removes all followers of the account.
+    LONG_DESC
+    def reset_relationships(username)
+      unless options[:follows] || options[:followers]
+        say('Please specify either --follows or --followers, or both', :red)
+        exit(1)
+      end
+
+      account = Account.find_local(username)
+
+      if account.nil?
+        say('No user with such username', :red)
+        exit(1)
+      end
+
+      if options[:follows]
+        processed = 0
+        failed    = 0
+
+        say("Unfollowing #{account.username}'s followees, this might take a while...")
+
+        Account.where(id: ::Follow.where(account: account).select(:target_account_id)).find_each do |target_account|
+          begin
+            UnfollowService.new.call(account, target_account)
+            processed += 1
+            say('.', :green, false)
+          rescue StandardError
+            failed += 1
+            say('.', :red, false)
+          end
+        end
+
+        BootstrapTimelineWorker.perform_async(account.id)
+
+        say("OK, unfollowed #{processed} followees, skipped #{failed}", :green)
+      end
+
+      if options[:followers]
+        processed = 0
+        failed    = 0
+
+        say("Removing #{account.username}'s followers, this might take a while...")
+
+        Account.where(id: ::Follow.where(target_account: account).select(:account_id)).find_each do |target_account|
+          begin
+            UnfollowService.new.call(target_account, account)
+            processed += 1
+            say('.', :green, false)
+          rescue StandardError
+            failed += 1
+            say('.', :red, false)
+          end
+        end
+
+        say("OK, removed #{processed} followers, skipped #{failed}", :green)
+      end
+    end
+
+    option :number, type: :numeric, aliases: [:n]
+    option :all, type: :boolean
+    desc 'approve [USERNAME]', 'Approve pending accounts'
+    long_desc <<~LONG_DESC
+      When registrations require review from staff, approve pending accounts,
+      either all of them with the --all option, or a specific number of them
+      specified with the --number (-n) option, or only a single specific
+      account identified by its username.
+    LONG_DESC
+    def approve(username = nil)
+      if options[:all]
+        User.pending.find_each(&:approve!)
+        say('OK', :green)
+      elsif options[:number]
+        User.pending.limit(options[:number]).each(&:approve!)
+        say('OK', :green)
+      elsif username.present?
+        account = Account.find_local(username)
+
+        if account.nil?
+          say('No such account', :red)
+          exit(1)
+        end
+
+        account.user&.approve!
+        say('OK', :green)
+      else
+        exit(1)
+      end
+    end
+
     private
 
     def rotate_keys_for_account(account, delay = 0)
diff --git a/lib/mastodon/emoji_cli.rb b/lib/mastodon/emoji_cli.rb
index 2262040d4..32827dd45 100644
--- a/lib/mastodon/emoji_cli.rb
+++ b/lib/mastodon/emoji_cli.rb
@@ -66,6 +66,12 @@ module Mastodon
       say("Imported #{imported}, skipped #{skipped}, failed to import #{failed}", color(imported, skipped, failed))
     end
 
+    desc 'purge', 'Remove all custom emoji'
+    def purge
+      CustomEmoji.in_batches.destroy_all
+      say('OK', :green)
+    end
+
     private
 
     def color(green, _yellow, red)
diff --git a/lib/mastodon/statuses_cli.rb b/lib/mastodon/statuses_cli.rb
index 5881ba260..7f2fbfa85 100644
--- a/lib/mastodon/statuses_cli.rb
+++ b/lib/mastodon/statuses_cli.rb
@@ -13,7 +13,15 @@ module Mastodon
     end
 
     option :days, type: :numeric, default: 90
-    desc 'remove', 'Remove statuses'
+    desc 'remove', 'Remove unreferenced statuses'
+    long_desc <<~LONG_DESC
+      Remove statuses that are not referenced by local user activity, such as
+      ones that came from relays, or belonging to users that were once followed
+      by someone locally but no longer are.
+
+      This is a computationally heavy procedure that creates extra database
+      indicides before commencing, and removes them afterward.
+    LONG_DESC
     def remove
       say('Creating temporary database indices...')
 
diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
index 42a18cdc3..75e0570e9 100644
--- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
@@ -7,40 +7,15 @@ describe Api::V1::Accounts::FollowerAccountsController do
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
 
   before do
+    Fabricate(:follow, target_account: user.account)
     allow(controller).to receive(:doorkeeper_token) { token }
   end
 
   describe 'GET #index' do
-    let(:simon) { Fabricate(:account, username: 'simon') }
-    let(:lewis) { Fabricate(:account, username: 'lewis') }
-
-    before do
-      simon.follow!(lewis)
-    end
-
     it 'returns http success' do
-      get :index, params: { account_id: lewis.id, limit: 1 }
+      get :index, params: { account_id: user.account.id, limit: 1 }
 
       expect(response).to have_http_status(200)
     end
-
-    it 'returns JSON with correct data' do
-      get :index, params: { account_id: lewis.id, limit: 1 }
-
-      json = body_as_json
-
-      expect(json).to be_a Enumerable
-      expect(json.first[:username]).to eq 'simon'
-    end
-
-    it 'does not return accounts blocking you' do
-      simon.block!(user.account)
-      get :index, params: { account_id: lewis.id, limit: 1 }
-
-      json = body_as_json
-
-      expect(json).to be_a Enumerable
-      expect(json.size).to eq 0
-    end
   end
 end
diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
index 911b381fe..7f7105ad3 100644
--- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
@@ -7,40 +7,15 @@ describe Api::V1::Accounts::FollowingAccountsController do
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
 
   before do
+    Fabricate(:follow, account: user.account)
     allow(controller).to receive(:doorkeeper_token) { token }
   end
 
   describe 'GET #index' do
-    let(:simon) { Fabricate(:account, username: 'simon') }
-    let(:lewis) { Fabricate(:account, username: 'lewis') }
-
-    before do
-      lewis.follow!(simon)
-    end
-
     it 'returns http success' do
-      get :index, params: { account_id: lewis.id, limit: 1 }
+      get :index, params: { account_id: user.account.id, limit: 1 }
 
       expect(response).to have_http_status(200)
     end
-
-    it 'returns JSON with correct data' do
-      get :index, params: { account_id: lewis.id, limit: 1 }
-
-      json = body_as_json
-
-      expect(json).to be_a Enumerable
-      expect(json.first[:username]).to eq 'simon'
-    end
-
-    it 'does not return accounts blocking you' do
-      simon.block!(user.account)
-      get :index, params: { account_id: lewis.id, limit: 1 }
-
-      json = body_as_json
-
-      expect(json).to be_a Enumerable
-      expect(json.size).to eq 0
-    end
   end
 end
diff --git a/spec/controllers/settings/exports/following_accounts_controller_spec.rb b/spec/controllers/settings/exports/following_accounts_controller_spec.rb
index 786769d24..78858e772 100644
--- a/spec/controllers/settings/exports/following_accounts_controller_spec.rb
+++ b/spec/controllers/settings/exports/following_accounts_controller_spec.rb
@@ -11,7 +11,7 @@ describe Settings::Exports::FollowingAccountsController do
       sign_in user, scope: :user
       get :index, format: :csv
 
-      expect(response.body).to eq "username@domain\n"
+      expect(response.body).to eq "Account address,Show boosts\nusername@domain,true\n"
     end
   end
 end
diff --git a/spec/fixtures/files/new-following-imports.txt b/spec/fixtures/files/new-following-imports.txt
new file mode 100644
index 000000000..5ea6c7346
--- /dev/null
+++ b/spec/fixtures/files/new-following-imports.txt
@@ -0,0 +1,4 @@
+Account address,Show boosts
+bob,true
+eve@example.com,false
+
diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb
index 0553f4098..4e6b824bb 100644
--- a/spec/models/export_spec.rb
+++ b/spec/models/export_spec.rb
@@ -32,10 +32,11 @@ describe Export do
       target_accounts.each(&account.method(:follow!))
 
       export = Export.new(account).to_following_accounts_csv
-      results = export.strip.split
+      results = export.strip.split("\n")
 
-      expect(results.size).to eq 2
-      expect(results.first).to eq 'one@local.host'
+      expect(results.size).to eq 3
+      expect(results.first).to eq 'Account address,Show boosts'
+      expect(results.second).to eq 'one@local.host,true'
     end
   end
 
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index 40ef4b84a..7b071b378 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -156,22 +156,5 @@ describe AccountSearchService, type: :service do
         expect(results).to eq []
       end
     end
-
-    describe 'should not include accounts blocking the requester' do
-      let!(:blocked) { Fabricate(:account) }
-      let!(:blocker) { Fabricate(:account, username: 'exact') }
-
-      before do
-        blocker.block!(blocked)
-      end
-
-      it 'returns the fuzzy match first, and does not return suspended exacts' do
-        partial = Fabricate(:account, username: 'exactness')
-
-        results = subject.call('exact', blocked, limit: 10)
-        expect(results.size).to eq 1
-        expect(results).to eq [partial]
-      end
-    end
   end
 end
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
index bd23781ce..5cf2dadf0 100644
--- a/spec/services/import_service_spec.rb
+++ b/spec/services/import_service_spec.rb
@@ -1,9 +1,9 @@
 require 'rails_helper'
 
 RSpec.describe ImportService, type: :service do
-  let!(:account) { Fabricate(:account) }
-  let!(:bob)     { Fabricate(:account, username: 'bob') }
-  let!(:eve)     { Fabricate(:account, username: 'eve', domain: 'example.com') }
+  let!(:account) { Fabricate(:account, locked: false) }
+  let!(:bob)     { Fabricate(:account, username: 'bob', locked: false) }
+  let!(:eve)     { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false) }
 
   context 'import old-style list of muted users' do
     subject { ImportService.new }
@@ -81,4 +81,89 @@ RSpec.describe ImportService, type: :service do
       end
     end
   end
+
+  context 'import old-style list of followed users' do
+    subject { ImportService.new }
+
+    let(:csv) { attachment_fixture('mute-imports.txt') }
+
+    before do
+      allow(NotificationWorker).to receive(:perform_async)
+    end
+
+    describe 'when no accounts are followed' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+      it 'follows the listed accounts, including boosts' do
+        subject.call(import)
+        expect(account.following.count).to eq 2
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is not set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+      it 'follows the listed accounts, including notifications' do
+        account.follow!(bob, reblogs: false)
+        subject.call(import)
+        expect(account.following.count).to eq 2
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
+
+      it 'mutes the listed accounts, including notifications' do
+        account.follow!(bob, reblogs: false)
+        subject.call(import)
+        expect(account.following.count).to eq 2
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+      end
+    end
+  end
+
+  context 'import new-style list of followed users' do
+    subject { ImportService.new }
+
+    let(:csv) { attachment_fixture('new-following-imports.txt') }
+
+    before do
+      allow(NotificationWorker).to receive(:perform_async)
+    end
+
+    describe 'when no accounts are followed' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+      it 'follows the listed accounts, respecting boosts' do
+        subject.call(import)
+        expect(account.following.count).to eq 2
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+        expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is not set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        account.follow!(bob, reblogs: true)
+        subject.call(import)
+        expect(account.following.count).to eq 2
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+        expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
+      end
+    end
+
+    describe 'when some accounts are already followed and overwrite is set' do
+      let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
+
+      it 'mutes the listed accounts, respecting notifications' do
+        account.follow!(bob, reblogs: true)
+        subject.call(import)
+        expect(account.following.count).to eq 2
+        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
+        expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
+      end
+    end
+  end
 end
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 900533e71..d064cd9b8 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -3,8 +3,6 @@
 require 'rails_helper'
 
 describe SearchService, type: :service do
-  let(:current_account) { Fabricate(:user).account }
-
   subject { described_class.new }
 
   describe '#call' do
@@ -12,7 +10,7 @@ describe SearchService, type: :service do
       it 'returns empty results without searching' do
         allow(AccountSearchService).to receive(:new)
         allow(Tag).to receive(:search_for)
-        results = subject.call('', current_account, 10)
+        results = subject.call('', nil, 10)
 
         expect(results).to eq(empty_results)
         expect(AccountSearchService).not_to have_received(:new)
@@ -29,33 +27,33 @@ describe SearchService, type: :service do
         it 'returns the empty results' do
           service = double(call: nil)
           allow(ResolveURLService).to receive(:new).and_return(service)
-          results = subject.call(@query, current_account, 10)
+          results = subject.call(@query, nil, 10)
 
-          expect(service).to have_received(:call).with(@query, on_behalf_of: current_account)
+          expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
           expect(results).to eq empty_results
         end
       end
 
       context 'that finds an account' do
         it 'includes the account in the results' do
-          account = Fabricate(:account)
+          account = Account.new
           service = double(call: account)
           allow(ResolveURLService).to receive(:new).and_return(service)
 
-          results = subject.call(@query, current_account, 10)
-          expect(service).to have_received(:call).with(@query, on_behalf_of: current_account)
+          results = subject.call(@query, nil, 10)
+          expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
           expect(results).to eq empty_results.merge(accounts: [account])
         end
       end
 
       context 'that finds a status' do
         it 'includes the status in the results' do
-          status = Fabricate(:status)
+          status = Status.new
           service = double(call: status)
           allow(ResolveURLService).to receive(:new).and_return(service)
 
-          results = subject.call(@query, current_account, 10)
-          expect(service).to have_received(:call).with(@query, on_behalf_of: current_account)
+          results = subject.call(@query, nil, 10)
+          expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
           expect(results).to eq empty_results.merge(statuses: [status])
         end
       end
@@ -65,12 +63,12 @@ describe SearchService, type: :service do
       context 'that matches an account' do
         it 'includes the account in the results' do
           query = 'username'
-          account = Fabricate(:account)
+          account = Account.new
           service = double(call: [account])
           allow(AccountSearchService).to receive(:new).and_return(service)
 
-          results = subject.call(query, current_account, 10)
-          expect(service).to have_received(:call).with(query, current_account, limit: 10, offset: 0, resolve: false)
+          results = subject.call(query, nil, 10)
+          expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false)
           expect(results).to eq empty_results.merge(accounts: [account])
         end
       end
@@ -81,7 +79,7 @@ describe SearchService, type: :service do
           tag = Tag.new
           allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag])
 
-          results = subject.call(query, current_account, 10)
+          results = subject.call(query, nil, 10)
           expect(Tag).to have_received(:search_for).with('tag', 10, 0)
           expect(results).to eq empty_results.merge(hashtags: [tag])
         end
@@ -89,7 +87,7 @@ describe SearchService, type: :service do
           query = '@username'
           allow(Tag).to receive(:search_for)
 
-          results = subject.call(query, current_account, 10)
+          results = subject.call(query, nil, 10)
           expect(Tag).not_to have_received(:search_for)
           expect(results).to eq empty_results
         end