about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin/accounts_controller.rb4
-rw-r--r--app/controllers/admin/dashboard_controller.rb1
-rw-r--r--app/controllers/admin/disputes/appeals_controller.rb40
-rw-r--r--app/controllers/api/web/push_subscriptions_controller.rb23
-rw-r--r--app/controllers/auth/registrations_controller.rb11
-rw-r--r--app/controllers/concerns/authorization.rb2
-rw-r--r--app/controllers/concerns/localized.rb2
-rw-r--r--app/controllers/disputes/appeals_controller.rb26
-rw-r--r--app/controllers/disputes/base_controller.rb23
-rw-r--r--app/controllers/disputes/strikes_controller.rb17
-rw-r--r--app/helpers/admin/account_moderation_notes_helper.rb4
-rw-r--r--app/helpers/admin/action_logs_helper.rb2
-rw-r--r--app/helpers/admin/trends/statuses_helper.rb20
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js3
-rw-r--r--app/javascript/flavours/glitch/actions/reports.js115
-rw-r--r--app/javascript/flavours/glitch/actions/rules.js27
-rw-r--r--app/javascript/flavours/glitch/components/check.js9
-rw-r--r--app/javascript/flavours/glitch/features/emoji_picker/index.js15
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/admin_signup.js101
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.js16
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notification.js14
-rw-r--r--app/javascript/flavours/glitch/features/report/category.js93
-rw-r--r--app/javascript/flavours/glitch/features/report/comment.js83
-rw-r--r--app/javascript/flavours/glitch/features/report/components/option.js60
-rw-r--r--app/javascript/flavours/glitch/features/report/components/status_check_box.js56
-rw-r--r--app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js22
-rw-r--r--app/javascript/flavours/glitch/features/report/rules.js64
-rw-r--r--app/javascript/flavours/glitch/features/report/statuses.js58
-rw-r--r--app/javascript/flavours/glitch/features/report/thanks.js84
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/report_modal.js239
-rw-r--r--app/javascript/flavours/glitch/packs/public.js8
-rw-r--r--app/javascript/flavours/glitch/packs/settings.js8
-rw-r--r--app/javascript/flavours/glitch/reducers/index.js4
-rw-r--r--app/javascript/flavours/glitch/reducers/reports.js77
-rw-r--r--app/javascript/flavours/glitch/reducers/rules.js13
-rw-r--r--app/javascript/flavours/glitch/reducers/settings.js3
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss113
-rw-r--r--app/javascript/flavours/glitch/styles/components/composer.scss4
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss22
-rw-r--r--app/javascript/flavours/glitch/styles/components/modal.scss186
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss51
-rw-r--r--app/javascript/flavours/glitch/styles/footer.scss17
-rw-r--r--app/javascript/mastodon/actions/notifications.js3
-rw-r--r--app/javascript/mastodon/actions/reports.js115
-rw-r--r--app/javascript/mastodon/actions/rules.js27
-rw-r--r--app/javascript/mastodon/components/check.js9
-rw-r--r--app/javascript/mastodon/components/loading_indicator.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js15
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_compressed.js2
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js16
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js25
-rw-r--r--app/javascript/mastodon/features/report/category.js93
-rw-r--r--app/javascript/mastodon/features/report/comment.js83
-rw-r--r--app/javascript/mastodon/features/report/components/option.js60
-rw-r--r--app/javascript/mastodon/features/report/components/status_check_box.js61
-rw-r--r--app/javascript/mastodon/features/report/containers/status_check_box_container.js22
-rw-r--r--app/javascript/mastodon/features/report/rules.js64
-rw-r--r--app/javascript/mastodon/features/report/statuses.js58
-rw-r--r--app/javascript/mastodon/features/report/thanks.js84
-rw-r--r--app/javascript/mastodon/features/ui/components/report_modal.js241
-rw-r--r--app/javascript/mastodon/locales/af.json2
-rw-r--r--app/javascript/mastodon/locales/ar.json40
-rw-r--r--app/javascript/mastodon/locales/ast.json2
-rw-r--r--app/javascript/mastodon/locales/bg.json2
-rw-r--r--app/javascript/mastodon/locales/bn.json2
-rw-r--r--app/javascript/mastodon/locales/br.json4
-rw-r--r--app/javascript/mastodon/locales/ca.json34
-rw-r--r--app/javascript/mastodon/locales/co.json2
-rw-r--r--app/javascript/mastodon/locales/cs.json2
-rw-r--r--app/javascript/mastodon/locales/cy.json2
-rw-r--r--app/javascript/mastodon/locales/da.json30
-rw-r--r--app/javascript/mastodon/locales/de.json32
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json8
-rw-r--r--app/javascript/mastodon/locales/el.json16
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/eo.json2
-rw-r--r--app/javascript/mastodon/locales/es-AR.json32
-rw-r--r--app/javascript/mastodon/locales/es-MX.json32
-rw-r--r--app/javascript/mastodon/locales/es.json32
-rw-r--r--app/javascript/mastodon/locales/et.json2
-rw-r--r--app/javascript/mastodon/locales/eu.json32
-rw-r--r--app/javascript/mastodon/locales/fa.json4
-rw-r--r--app/javascript/mastodon/locales/fi.json34
-rw-r--r--app/javascript/mastodon/locales/fr.json42
-rw-r--r--app/javascript/mastodon/locales/ga.json2
-rw-r--r--app/javascript/mastodon/locales/gd.json2
-rw-r--r--app/javascript/mastodon/locales/gl.json32
-rw-r--r--app/javascript/mastodon/locales/he.json2
-rw-r--r--app/javascript/mastodon/locales/hi.json2
-rw-r--r--app/javascript/mastodon/locales/hr.json2
-rw-r--r--app/javascript/mastodon/locales/hu.json36
-rw-r--r--app/javascript/mastodon/locales/hy.json2
-rw-r--r--app/javascript/mastodon/locales/id.json30
-rw-r--r--app/javascript/mastodon/locales/io.json2
-rw-r--r--app/javascript/mastodon/locales/is.json204
-rw-r--r--app/javascript/mastodon/locales/it.json32
-rw-r--r--app/javascript/mastodon/locales/ja.json34
-rw-r--r--app/javascript/mastodon/locales/ka.json2
-rw-r--r--app/javascript/mastodon/locales/kab.json12
-rw-r--r--app/javascript/mastodon/locales/kk.json2
-rw-r--r--app/javascript/mastodon/locales/kmr.json40
-rw-r--r--app/javascript/mastodon/locales/kn.json2
-rw-r--r--app/javascript/mastodon/locales/ko.json34
-rw-r--r--app/javascript/mastodon/locales/ku.json2
-rw-r--r--app/javascript/mastodon/locales/kw.json2
-rw-r--r--app/javascript/mastodon/locales/lt.json2
-rw-r--r--app/javascript/mastodon/locales/lv.json34
-rw-r--r--app/javascript/mastodon/locales/mk.json2
-rw-r--r--app/javascript/mastodon/locales/ml.json2
-rw-r--r--app/javascript/mastodon/locales/mr.json2
-rw-r--r--app/javascript/mastodon/locales/ms.json2
-rw-r--r--app/javascript/mastodon/locales/nl.json2
-rw-r--r--app/javascript/mastodon/locales/nn.json2
-rw-r--r--app/javascript/mastodon/locales/no.json2
-rw-r--r--app/javascript/mastodon/locales/oc.json2
-rw-r--r--app/javascript/mastodon/locales/pa.json2
-rw-r--r--app/javascript/mastodon/locales/pl.json22
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json2
-rw-r--r--app/javascript/mastodon/locales/pt-PT.json32
-rw-r--r--app/javascript/mastodon/locales/ro.json2
-rw-r--r--app/javascript/mastodon/locales/ru.json38
-rw-r--r--app/javascript/mastodon/locales/sa.json2
-rw-r--r--app/javascript/mastodon/locales/sc.json2
-rw-r--r--app/javascript/mastodon/locales/si.json2
-rw-r--r--app/javascript/mastodon/locales/sk.json2
-rw-r--r--app/javascript/mastodon/locales/sl.json30
-rw-r--r--app/javascript/mastodon/locales/sq.json34
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json2
-rw-r--r--app/javascript/mastodon/locales/sr.json2
-rw-r--r--app/javascript/mastodon/locales/sv.json24
-rw-r--r--app/javascript/mastodon/locales/szl.json2
-rw-r--r--app/javascript/mastodon/locales/ta.json2
-rw-r--r--app/javascript/mastodon/locales/tai.json2
-rw-r--r--app/javascript/mastodon/locales/te.json2
-rw-r--r--app/javascript/mastodon/locales/th.json32
-rw-r--r--app/javascript/mastodon/locales/tr.json34
-rw-r--r--app/javascript/mastodon/locales/tt.json2
-rw-r--r--app/javascript/mastodon/locales/ug.json2
-rw-r--r--app/javascript/mastodon/locales/uk.json30
-rw-r--r--app/javascript/mastodon/locales/ur.json2
-rw-r--r--app/javascript/mastodon/locales/vi.json32
-rw-r--r--app/javascript/mastodon/locales/zgh.json2
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json34
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json2
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json34
-rw-r--r--app/javascript/mastodon/reducers/index.js4
-rw-r--r--app/javascript/mastodon/reducers/reports.js64
-rw-r--r--app/javascript/mastodon/reducers/rules.js13
-rw-r--r--app/javascript/mastodon/reducers/settings.js3
-rw-r--r--app/javascript/mastodon/service_worker/web_push_locales.js1
-rw-r--r--app/javascript/packs/public.js8
-rw-r--r--app/javascript/styles/mastodon/admin.scss113
-rw-r--r--app/javascript/styles/mastodon/components.scss258
-rw-r--r--app/javascript/styles/mastodon/footer.scss17
-rw-r--r--app/lib/activitypub/activity/announce.rb1
-rw-r--r--app/lib/admin/metrics/dimension/base_dimension.rb32
-rw-r--r--app/lib/admin/metrics/dimension/languages_dimension.rb4
-rw-r--r--app/lib/admin/metrics/dimension/servers_dimension.rb4
-rw-r--r--app/lib/admin/metrics/dimension/software_versions_dimension.rb6
-rw-r--r--app/lib/admin/metrics/dimension/sources_dimension.rb4
-rw-r--r--app/lib/admin/metrics/dimension/space_usage_dimension.rb6
-rw-r--r--app/lib/admin/metrics/dimension/tag_languages_dimension.rb6
-rw-r--r--app/lib/admin/metrics/dimension/tag_servers_dimension.rb6
-rw-r--r--app/lib/admin/metrics/measure/active_users_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/base_measure.rb52
-rw-r--r--app/lib/admin/metrics/measure/interactions_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/new_users_measure.rb8
-rw-r--r--app/lib/admin/metrics/measure/opened_reports_measure.rb8
-rw-r--r--app/lib/admin/metrics/measure/resolved_reports_measure.rb8
-rw-r--r--app/lib/admin/metrics/measure/tag_accounts_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/tag_servers_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/tag_uses_measure.rb10
-rw-r--r--app/lib/admin/metrics/retention.rb26
-rw-r--r--app/lib/feed_manager.rb4
-rw-r--r--app/lib/search_query_transformer.rb37
-rw-r--r--app/lib/video_metadata_extractor.rb3
-rw-r--r--app/mailers/admin_mailer.rb10
-rw-r--r--app/mailers/user_mailer.rb24
-rw-r--r--app/models/account.rb4
-rw-r--r--app/models/account_filter.rb4
-rw-r--r--app/models/account_warning.rb8
-rw-r--r--app/models/admin/action_log_filter.rb2
-rw-r--r--app/models/admin/appeal_filter.rb49
-rw-r--r--app/models/admin/status_filter.rb2
-rw-r--r--app/models/appeal.rb60
-rw-r--r--app/models/media_attachment.rb13
-rw-r--r--app/models/notification.rb14
-rw-r--r--app/models/user.rb6
-rw-r--r--app/policies/account_warning_policy.rb17
-rw-r--r--app/policies/appeal_policy.rb13
-rw-r--r--app/services/appeal_service.rb28
-rw-r--r--app/services/approve_appeal_service.rb74
-rw-r--r--app/services/bootstrap_timeline_service.rb7
-rw-r--r--app/services/fetch_link_card_service.rb2
-rw-r--r--app/services/notify_service.rb44
-rw-r--r--app/views/admin/account_moderation_notes/_account_moderation_note.html.haml7
-rw-r--r--app/views/admin/account_warnings/_account_warning.html.haml31
-rw-r--r--app/views/admin/accounts/show.html.haml21
-rw-r--r--app/views/admin/dashboard/index.html.haml7
-rw-r--r--app/views/admin/disputes/appeals/_appeal.html.haml21
-rw-r--r--app/views/admin/disputes/appeals/index.html.haml19
-rw-r--r--app/views/admin/report_notes/_report_note.html.haml2
-rw-r--r--app/views/admin/reports/show.html.haml2
-rw-r--r--app/views/admin_mailer/new_appeal.text.erb9
-rw-r--r--app/views/admin_mailer/new_trending_tags.text.erb2
-rw-r--r--app/views/auth/registrations/_account_warning.html.haml20
-rw-r--r--app/views/auth/registrations/_status.html.haml31
-rw-r--r--app/views/disputes/strikes/show.html.haml128
-rw-r--r--app/views/layouts/public.html.haml4
-rw-r--r--app/views/settings/preferences/notifications/show.html.haml1
-rw-r--r--app/views/statuses/_detailed_status.html.haml2
-rw-r--r--app/views/user_mailer/appeal_approved.html.haml59
-rw-r--r--app/views/user_mailer/appeal_approved.text.erb7
-rw-r--r--app/views/user_mailer/appeal_rejected.html.haml59
-rw-r--r--app/views/user_mailer/appeal_rejected.text.erb7
-rw-r--r--app/views/user_mailer/warning.html.haml6
216 files changed, 4199 insertions, 1427 deletions
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index e7f56e243..e0ae71b9f 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -28,7 +28,7 @@ module Admin
       @deletion_request        = @account.deletion_request
       @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
       @moderation_notes        = @account.targeted_moderation_notes.latest
-      @warnings                = @account.strikes.custom.latest
+      @warnings                = @account.strikes.includes(:target_account, :account, :appeal).latest
       @domain_block            = DomainBlock.rule_for(@account.domain)
     end
 
@@ -146,7 +146,7 @@ module Admin
     end
 
     def filter_params
-      params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
+      params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS)
     end
 
     def form_account_batch_params
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index f0a935411..e376baab2 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -8,6 +8,7 @@ module Admin
       @pending_users_count   = User.pending.count
       @pending_reports_count = Report.unresolved.count
       @pending_tags_count    = Tag.pending_review.count
+      @pending_appeals_count = Appeal.pending.count
     end
 
     private
diff --git a/app/controllers/admin/disputes/appeals_controller.rb b/app/controllers/admin/disputes/appeals_controller.rb
new file mode 100644
index 000000000..32e5e2f6f
--- /dev/null
+++ b/app/controllers/admin/disputes/appeals_controller.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class Admin::Disputes::AppealsController < Admin::BaseController
+  before_action :set_appeal, except: :index
+
+  def index
+    authorize :appeal, :index?
+
+    @appeals = filtered_appeals.page(params[:page])
+  end
+
+  def approve
+    authorize @appeal, :approve?
+    log_action :approve, @appeal
+    ApproveAppealService.new.call(@appeal, current_account)
+    redirect_to disputes_strike_path(@appeal.strike)
+  end
+
+  def reject
+    authorize @appeal, :approve?
+    log_action :reject, @appeal
+    @appeal.reject!(current_account)
+    UserMailer.appeal_rejected(@appeal.account.user, @appeal)
+    redirect_to disputes_strike_path(@appeal.strike)
+  end
+
+  private
+
+  def filtered_appeals
+    Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account)
+  end
+
+  def filter_params
+    params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS)
+  end
+
+  def set_appeal
+    @appeal = Appeal.find(params[:id])
+  end
+end
diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb
index db2512e5f..5167928e9 100644
--- a/app/controllers/api/web/push_subscriptions_controller.rb
+++ b/app/controllers/api/web/push_subscriptions_controller.rb
@@ -17,17 +17,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
 
     data = {
       policy: 'all',
-
-      alerts: {
-        follow: alerts_enabled,
-        follow_request: alerts_enabled,
-        favourite: alerts_enabled,
-        reblog: alerts_enabled,
-        mention: alerts_enabled,
-        poll: alerts_enabled,
-        status: alerts_enabled,
-        update: alerts_enabled,
-      },
+      alerts: Notification::TYPES.index_with { alerts_enabled },
     }
 
     data.deep_merge!(data_params) if params[:data]
@@ -62,15 +52,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
   end
 
   def data_params
-    @data_params ||= params.require(:data).permit(:policy, alerts: [
-      :follow,
-      :follow_request,
-      :favourite,
-      :reblog,
-      :mention,
-      :poll,
-      :status,
-      :update,
-    ])
+    @data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
   end
 end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 6b1f3fa82..5d32fe66e 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -10,6 +10,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   before_action :configure_sign_up_params, only: [:create]
   before_action :set_pack
   before_action :set_sessions, only: [:edit, :update]
+  before_action :set_strikes, only: [:edit, :update]
   before_action :set_instance_presenter, only: [:new, :create, :update]
   before_action :set_body_classes, only: [:new, :create, :edit, :update]
   before_action :require_not_suspended!, only: [:update]
@@ -116,8 +117,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   end
 
   def set_invite
-    invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
-    @invite = invite&.valid_for_use? ? invite : nil
+    @invite = begin
+      invite = Invite.find_by(code: invite_code) if invite_code.present?
+      invite if invite&.valid_for_use?
+    end
   end
 
   def determine_layout
@@ -128,6 +131,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
     @sessions = current_user.session_activations
   end
 
+  def set_strikes
+    @strikes = current_account.strikes.active.latest
+  end
+
   def require_not_suspended!
     forbidden if current_account.suspended?
   end
diff --git a/app/controllers/concerns/authorization.rb b/app/controllers/concerns/authorization.rb
index 95a37e379..05260cc8b 100644
--- a/app/controllers/concerns/authorization.rb
+++ b/app/controllers/concerns/authorization.rb
@@ -3,7 +3,7 @@
 module Authorization
   extend ActiveSupport::Concern
 
-  include Pundit
+  include Pundit::Authorization
 
   def pundit_user
     current_account
diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb
index f7b62f09c..173316800 100644
--- a/app/controllers/concerns/localized.rb
+++ b/app/controllers/concerns/localized.rb
@@ -14,7 +14,7 @@ module Localized
   private
 
   def requested_locale
-    requested_locale_name   = available_locale_or_nil(params[:locale])
+    requested_locale_name   = available_locale_or_nil(params[:lang])
     requested_locale_name ||= available_locale_or_nil(current_user.locale) if respond_to?(:user_signed_in?) && user_signed_in?
     requested_locale_name ||= http_accept_language if ENV['DEFAULT_LOCALE'].blank?
     requested_locale_name
diff --git a/app/controllers/disputes/appeals_controller.rb b/app/controllers/disputes/appeals_controller.rb
new file mode 100644
index 000000000..eefd92b5a
--- /dev/null
+++ b/app/controllers/disputes/appeals_controller.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class Disputes::AppealsController < Disputes::BaseController
+  before_action :set_strike
+
+  def create
+    authorize @strike, :appeal?
+
+    @appeal = AppealService.new.call(@strike, appeal_params[:text])
+
+    redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg')
+  rescue ActiveRecord::RecordInvalid => e
+    @appeal = e.record
+    render template: 'disputes/strikes/show'
+  end
+
+  private
+
+  def set_strike
+    @strike = current_account.strikes.find(params[:strike_id])
+  end
+
+  def appeal_params
+    params.require(:appeal).permit(:text)
+  end
+end
diff --git a/app/controllers/disputes/base_controller.rb b/app/controllers/disputes/base_controller.rb
new file mode 100644
index 000000000..7830c5524
--- /dev/null
+++ b/app/controllers/disputes/base_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class Disputes::BaseController < ApplicationController
+  include Authorization
+
+  layout 'admin'
+
+  skip_before_action :require_functional!
+
+  before_action :set_body_classes
+  before_action :authenticate_user!
+  before_action :set_pack
+
+  private
+
+  def set_pack
+    use_pack 'admin'
+  end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
+end
diff --git a/app/controllers/disputes/strikes_controller.rb b/app/controllers/disputes/strikes_controller.rb
new file mode 100644
index 000000000..d41c5c727
--- /dev/null
+++ b/app/controllers/disputes/strikes_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class Disputes::StrikesController < Disputes::BaseController
+  before_action :set_strike
+
+  def show
+    authorize @strike, :show?
+
+    @appeal = @strike.appeal || @strike.build_appeal
+  end
+
+  private
+
+  def set_strike
+    @strike = AccountWarning.find(params[:id])
+  end
+end
diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb
index 40b2a5289..2f08538ca 100644
--- a/app/helpers/admin/account_moderation_notes_helper.rb
+++ b/app/helpers/admin/account_moderation_notes_helper.rb
@@ -1,10 +1,10 @@
 # frozen_string_literal: true
 
 module Admin::AccountModerationNotesHelper
-  def admin_account_link_to(account)
+  def admin_account_link_to(account, path: nil)
     return if account.nil?
 
-    link_to admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
+    link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
       safe_join([
                   image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
                   content_tag(:span, account.acct, class: 'username'),
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index f3aa4be4f..47eeeaac3 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -33,6 +33,8 @@ module Admin::ActionLogsHelper
       "#{record.ip}/#{record.ip.prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{record.severity}")})"
     when 'Instance'
       record.domain
+    when 'Appeal'
+      link_to record.account.acct, disputes_strike_path(record.strike)
     end
   end
 
diff --git a/app/helpers/admin/trends/statuses_helper.rb b/app/helpers/admin/trends/statuses_helper.rb
new file mode 100644
index 000000000..d16e3dd12
--- /dev/null
+++ b/app/helpers/admin/trends/statuses_helper.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Admin::Trends::StatusesHelper
+  def one_line_preview(status)
+    text = begin
+      if status.local?
+        status.text.split("\n").first
+      else
+        Nokogiri::HTML(status.text).css('html > body > *').first&.text
+      end
+    end
+
+    return '' if text.blank?
+
+    html = Formatter.instance.send(:encode, text)
+    html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
+
+    html.html_safe # rubocop:disable Rails/OutputSafety
+  end
+end
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 40430102c..42ad39efa 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -57,7 +57,7 @@ defineMessages({
 });
 
 const fetchRelatedRelationships = (dispatch, notifications) => {
-  const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
+  const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
 
   if (accountIds > 0) {
     dispatch(fetchRelationships(accountIds));
@@ -144,6 +144,7 @@ const excludeTypesFromFilter = filter => {
     'poll',
     'status',
     'update',
+    'admin.sign_up',
   ]);
 
   return allTypes.filterNot(item => item === filter).toJS();
diff --git a/app/javascript/flavours/glitch/actions/reports.js b/app/javascript/flavours/glitch/actions/reports.js
index 80c3b3280..333bc71f4 100644
--- a/app/javascript/flavours/glitch/actions/reports.js
+++ b/app/javascript/flavours/glitch/actions/reports.js
@@ -1,89 +1,38 @@
 import api from 'flavours/glitch/util/api';
-import { openModal, closeModal } from './modal';
-
-export const REPORT_INIT   = 'REPORT_INIT';
-export const REPORT_CANCEL = 'REPORT_CANCEL';
+import { openModal } from './modal';
 
 export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
 export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
 export const REPORT_SUBMIT_FAIL    = 'REPORT_SUBMIT_FAIL';
 
-export const REPORT_STATUS_TOGGLE  = 'REPORT_STATUS_TOGGLE';
-export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
-export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
-
-export function initReport(account, status) {
-  return dispatch => {
-    dispatch({
-      type: REPORT_INIT,
-      account,
-      status,
-    });
-
-    dispatch(openModal('REPORT'));
-  };
-};
-
-export function cancelReport() {
-  return {
-    type: REPORT_CANCEL,
-  };
-};
-
-export function toggleStatusReport(statusId, checked) {
-  return {
-    type: REPORT_STATUS_TOGGLE,
-    statusId,
-    checked,
-  };
-};
-
-export function submitReport() {
-  return (dispatch, getState) => {
-    dispatch(submitReportRequest());
-
-    api(getState).post('/api/v1/reports', {
-      account_id: getState().getIn(['reports', 'new', 'account_id']),
-      status_ids: getState().getIn(['reports', 'new', 'status_ids']),
-      comment: getState().getIn(['reports', 'new', 'comment']),
-      forward: getState().getIn(['reports', 'new', 'forward']),
-    }).then(response => {
-      dispatch(closeModal());
-      dispatch(submitReportSuccess(response.data));
-    }).catch(error => dispatch(submitReportFail(error)));
-  };
-};
-
-export function submitReportRequest() {
-  return {
-    type: REPORT_SUBMIT_REQUEST,
-  };
-};
-
-export function submitReportSuccess(report) {
-  return {
-    type: REPORT_SUBMIT_SUCCESS,
-    report,
-  };
-};
-
-export function submitReportFail(error) {
-  return {
-    type: REPORT_SUBMIT_FAIL,
-    error,
-  };
-};
-
-export function changeReportComment(comment) {
-  return {
-    type: REPORT_COMMENT_CHANGE,
-    comment,
-  };
-};
-
-export function changeReportForward(forward) {
-  return {
-    type: REPORT_FORWARD_CHANGE,
-    forward,
-  };
-};
+export const initReport = (account, status) => dispatch =>
+  dispatch(openModal('REPORT', {
+    accountId: account.get('id'),
+    statusId: status?.get('id'),
+  }));
+
+export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
+  dispatch(submitReportRequest());
+
+  api(getState).post('/api/v1/reports', params).then(response => {
+    dispatch(submitReportSuccess(response.data));
+    if (onSuccess) onSuccess();
+  }).catch(error => {
+    dispatch(submitReportFail(error));
+    if (onFail) onFail();
+  });
+};
+
+export const submitReportRequest = () => ({
+  type: REPORT_SUBMIT_REQUEST,
+});
+
+export const submitReportSuccess = report => ({
+  type: REPORT_SUBMIT_SUCCESS,
+  report,
+});
+
+export const submitReportFail = error => ({
+  type: REPORT_SUBMIT_FAIL,
+  error,
+});
diff --git a/app/javascript/flavours/glitch/actions/rules.js b/app/javascript/flavours/glitch/actions/rules.js
new file mode 100644
index 000000000..b95045e81
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/rules.js
@@ -0,0 +1,27 @@
+import api from 'flavours/glitch/util/api';
+
+export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
+export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
+export const RULES_FETCH_FAIL    = 'RULES_FETCH_FAIL';
+
+export const fetchRules = () => (dispatch, getState) => {
+  dispatch(fetchRulesRequest());
+
+  api(getState)
+    .get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules)))
+    .catch(err => dispatch(fetchRulesFail(err)));
+};
+
+const fetchRulesRequest = () => ({
+  type: RULES_FETCH_REQUEST,
+});
+
+const fetchRulesSuccess = rules => ({
+  type: RULES_FETCH_SUCCESS,
+  rules,
+});
+
+const fetchRulesFail = error => ({
+  type: RULES_FETCH_FAIL,
+  error,
+});
diff --git a/app/javascript/flavours/glitch/components/check.js b/app/javascript/flavours/glitch/components/check.js
new file mode 100644
index 000000000..ee2ef1595
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/check.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const Check = () => (
+  <svg width='14' height='11' viewBox='0 0 14 11'>
+    <path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
+  </svg>
+);
+
+export default Check;
diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js
index 78f691c98..5de9fe107 100644
--- a/app/javascript/flavours/glitch/features/emoji_picker/index.js
+++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js
@@ -250,7 +250,7 @@ class EmojiPickerMenu extends React.PureComponent {
 
   state = {
     modifierOpen: false,
-    placement: null,
+    readyToFocus: false,
   };
 
   handleDocumentClick = e => {
@@ -262,6 +262,16 @@ class EmojiPickerMenu extends React.PureComponent {
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
+
+    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
+    // to wait for a frame before focusing
+    requestAnimationFrame(() => {
+      this.setState({ readyToFocus: true });
+      if (this.node) {
+        const element = this.node.querySelector('input[type="search"]');
+        if (element) element.focus();
+      }
+    });
   }
 
   componentWillUnmount () {
@@ -361,7 +371,7 @@ class EmojiPickerMenu extends React.PureComponent {
           showSkinTones={false}
           backgroundImageFn={backgroundImageFn}
           notFound={notFoundFn}
-          autoFocus
+          autoFocus={this.state.readyToFocus}
           emojiTooltip
           native={useSystemEmojiFont}
         />
@@ -396,6 +406,7 @@ class EmojiPickerDropdown extends React.PureComponent {
   state = {
     active: false,
     loading: false,
+    placement: null,
   };
 
   setRef = (c) => {
diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
new file mode 100644
index 000000000..355ebef94
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
@@ -0,0 +1,101 @@
+//  Package imports.
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { HotKeys } from 'react-hotkeys';
+import classNames from 'classnames';
+
+// Our imports.
+import Permalink from 'flavours/glitch/components/permalink';
+import AccountContainer from 'flavours/glitch/containers/account_container';
+import NotificationOverlayContainer from '../containers/overlay_container';
+import Icon from 'flavours/glitch/components/icon';
+
+export default class NotificationFollow extends ImmutablePureComponent {
+
+  static propTypes = {
+    hidden: PropTypes.bool,
+    id: PropTypes.string.isRequired,
+    account: ImmutablePropTypes.map.isRequired,
+    notification: ImmutablePropTypes.map.isRequired,
+    unread: PropTypes.bool,
+  };
+
+  handleMoveUp = () => {
+    const { notification, onMoveUp } = this.props;
+    onMoveUp(notification.get('id'));
+  }
+
+  handleMoveDown = () => {
+    const { notification, onMoveDown } = this.props;
+    onMoveDown(notification.get('id'));
+  }
+
+  handleOpen = () => {
+    this.handleOpenProfile();
+  }
+
+  handleOpenProfile = () => {
+    const { notification } = this.props;
+    this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
+  }
+
+  handleMention = e => {
+    e.preventDefault();
+
+    const { notification, onMention } = this.props;
+    onMention(notification.get('account'), this.context.router.history);
+  }
+
+  getHandlers () {
+    return {
+      moveUp: this.handleMoveUp,
+      moveDown: this.handleMoveDown,
+      open: this.handleOpen,
+      openProfile: this.handleOpenProfile,
+      mention: this.handleMention,
+      reply: this.handleMention,
+    };
+  }
+
+  render () {
+    const { account, notification, hidden, unread } = this.props;
+
+    //  Links to the display name.
+    const displayName = account.get('display_name_html') || account.get('username');
+    const link = (
+      <bdi><Permalink
+        className='notification__display-name'
+        href={account.get('url')}
+        title={account.get('acct')}
+        to={`/@${account.get('acct')}`}
+        dangerouslySetInnerHTML={{ __html: displayName }}
+      /></bdi>
+    );
+
+    //  Renders.
+    return (
+      <HotKeys handlers={this.getHandlers()}>
+        <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0'>
+          <div className='notification__message'>
+            <div className='notification__favourite-icon-wrapper'>
+              <Icon fixedWidth id='user-plus' />
+            </div>
+
+            <FormattedMessage
+              id='notification.admin.sign_up'
+              defaultMessage='{name} signed up'
+              values={{ name: link }}
+            />
+          </div>
+
+          <AccountContainer hidden={hidden} id={account.get('id')} withNote={false} />
+          <NotificationOverlayContainer notification={notification} />
+        </div>
+      </HotKeys>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
index 569ba4db0..0dad079ad 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -6,6 +6,7 @@ import ClearColumnButton from './clear_column_button';
 import GrantPermissionButton from './grant_permission_button';
 import SettingToggle from './setting_toggle';
 import PillBarButton from './pill_bar_button';
+import { isStaff } from 'flavours/glitch/util/initial_state';
 
 export default class ColumnSettings extends React.PureComponent {
 
@@ -156,7 +157,7 @@ export default class ColumnSettings extends React.PureComponent {
         </div>
 
         <div role='group' aria-labelledby='notifications-update'>
-          <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
+          <span id='notifications-update' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
 
           <div className='column-settings__pillbar'>
             <PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
@@ -165,6 +166,19 @@ export default class ColumnSettings extends React.PureComponent {
             <PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
           </div>
         </div>
+
+        {isStaff && (
+          <div role='group' aria-labelledby='notifications-admin-sign-up'>
+            <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
+
+            <div className='column-settings__pillbar'>
+              <PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'admin.sign_up']} onChange={onChange} label={alertStr} />
+              {showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'admin.sign_up']} onChange={this.onPushChange} label={pushStr} />}
+              <PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'admin.sign_up']} onChange={onChange} label={showStr} />
+              <PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'admin.sign_up']} onChange={onChange} label={soundStr} />
+            </div>
+          </div>
+        )}
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js
index 1cf205898..e0cd3c7a6 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js
@@ -8,6 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import StatusContainer from 'flavours/glitch/containers/status_container';
 import NotificationFollow from './follow';
 import NotificationFollowRequestContainer from '../containers/follow_request_container';
+import NotificationAdminSignup from './admin_signup';
 
 export default class Notification extends ImmutablePureComponent {
 
@@ -63,6 +64,19 @@ export default class Notification extends ImmutablePureComponent {
           unread={this.props.unread}
         />
       );
+    case 'admin.sign_up':
+      return (
+        <NotificationAdminSignup
+          hidden={hidden}
+          id={notification.get('id')}
+          account={notification.get('account')}
+          notification={notification}
+          onMoveDown={onMoveDown}
+          onMoveUp={onMoveUp}
+          onMention={onMention}
+          unread={this.props.unread}
+        />
+      );
     case 'mention':
       return (
         <StatusContainer
diff --git a/app/javascript/flavours/glitch/features/report/category.js b/app/javascript/flavours/glitch/features/report/category.js
new file mode 100644
index 000000000..ddbc82563
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/report/category.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Button from 'flavours/glitch/components/button';
+import Option from './components/option';
+
+const messages = defineMessages({
+  dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
+  dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
+  spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
+  spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
+  violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
+  violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
+  other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
+  other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' },
+  status: { id: 'report.category.title_status', defaultMessage: 'post' },
+  account: { id: 'report.category.title_account', defaultMessage: 'profile' },
+});
+
+export default @injectIntl
+class Category extends React.PureComponent {
+
+  static propTypes = {
+    onNextStep: PropTypes.func.isRequired,
+    category: PropTypes.string,
+    onChangeCategory: PropTypes.func.isRequired,
+    startedFrom: PropTypes.oneOf(['status', 'account']),
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleNextClick = () => {
+    const { onNextStep, category } = this.props;
+
+    switch(category) {
+    case 'dislike':
+      onNextStep('thanks');
+      break;
+    case 'violation':
+      onNextStep('rules');
+      break;
+    default:
+      onNextStep('statuses');
+      break;
+    }
+  };
+
+  handleCategoryToggle = (value, checked) => {
+    const { onChangeCategory } = this.props;
+
+    if (checked) {
+      onChangeCategory(value);
+    }
+  };
+
+  render () {
+    const { category, startedFrom, intl } = this.props;
+
+    const options = [
+      'dislike',
+      'spam',
+      'violation',
+      'other',
+    ];
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p>
+
+        <div>
+          {options.map(item => (
+            <Option
+              key={item}
+              name='category'
+              value={item}
+              checked={category === item}
+              onToggle={this.handleCategoryToggle}
+              label={intl.formatMessage(messages[item])}
+              description={intl.formatMessage(messages[`${item}_description`])}
+            />
+          ))}
+        </div>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleNextClick} disabled={category === null}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/report/comment.js b/app/javascript/flavours/glitch/features/report/comment.js
new file mode 100644
index 000000000..b2663bbf2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/report/comment.js
@@ -0,0 +1,83 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+import Button from 'flavours/glitch/components/button';
+import Toggle from 'react-toggle';
+
+const messages = defineMessages({
+  placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
+});
+
+export default @injectIntl
+class Comment extends React.PureComponent {
+
+  static propTypes = {
+    onSubmit: PropTypes.func.isRequired,
+    comment: PropTypes.string.isRequired,
+    onChangeComment: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    isSubmitting: PropTypes.bool,
+    forward: PropTypes.bool,
+    isRemote: PropTypes.bool,
+    domain: PropTypes.string,
+    onChangeForward: PropTypes.func.isRequired,
+  };
+
+  handleClick = () => {
+    const { onSubmit } = this.props;
+    onSubmit();
+  };
+
+  handleChange = e => {
+    const { onChangeComment } = this.props;
+    onChangeComment(e.target.value);
+  };
+
+  handleKeyDown = e => {
+    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+      this.handleClick();
+    }
+  };
+
+  handleForwardChange = e => {
+    const { onChangeForward } = this.props;
+    onChangeForward(e.target.checked);
+  };
+
+  render () {
+    const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>
+
+        <textarea
+          className='report-dialog-modal__textarea'
+          placeholder={intl.formatMessage(messages.placeholder)}
+          value={comment}
+          onChange={this.handleChange}
+          onKeyDown={this.handleKeyDown}
+          disabled={isSubmitting}
+        />
+
+        {isRemote && (
+          <React.Fragment>
+            <p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
+
+            <label className='report-dialog-modal__toggle'>
+              <Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
+              <FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} />
+            </label>
+          </React.Fragment>
+        )}
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/report/components/option.js b/app/javascript/flavours/glitch/features/report/components/option.js
new file mode 100644
index 000000000..7e94f0654
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/report/components/option.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import Check from 'flavours/glitch/components/check';
+
+export default class Option extends React.PureComponent {
+
+  static propTypes = {
+    name: PropTypes.string.isRequired,
+    value: PropTypes.string.isRequired,
+    checked: PropTypes.bool,
+    label: PropTypes.node,
+    description: PropTypes.node,
+    onToggle: PropTypes.func,
+    multiple: PropTypes.bool,
+    labelComponent: PropTypes.node,
+  };
+
+  handleKeyPress = e => {
+    const { value, checked, onToggle } = this.props;
+
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.stopPropagation();
+      e.preventDefault();
+      onToggle(value, !checked);
+    }
+  }
+
+  handleChange = e => {
+    const { value, onToggle } = this.props;
+    onToggle(value, e.target.checked);
+  }
+
+  render () {
+    const { name, value, checked, label, labelComponent, description, multiple } = this.props;
+
+    return (
+      <label className='dialog-option poll__option selectable'>
+        <input type={multiple ? 'checkbox' : 'radio'} name={name} value={value} checked={checked} onChange={this.handleChange} />
+
+        <span
+          className={classNames('poll__input', { active: checked, checkbox: multiple })}
+          tabIndex='0'
+          role='radio'
+          onKeyPress={this.handleKeyPress}
+          aria-checked={checked}
+          aria-label={label}
+        >{checked && <Check />}</span>
+
+        {labelComponent ? labelComponent : (
+          <span className='poll__option__text'>
+            <strong>{label}</strong>
+            {description}
+          </span>
+        )}
+      </label>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.js b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
index cc49042fc..adb5e77a7 100644
--- a/app/javascript/flavours/glitch/features/report/components/status_check_box.js
+++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
@@ -1,23 +1,32 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import Toggle from 'react-toggle';
 import noop from 'lodash/noop';
 import StatusContent from 'flavours/glitch/components/status_content';
 import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
 import Bundle from 'flavours/glitch/features/ui/components/bundle';
+import Avatar from 'flavours/glitch/components/avatar';
+import DisplayName from 'flavours/glitch/components/display_name';
+import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
+import Option from './option';
 
 export default class StatusCheckBox extends React.PureComponent {
 
   static propTypes = {
+    id: PropTypes.string.isRequired,
     status: ImmutablePropTypes.map.isRequired,
     checked: PropTypes.bool,
     onToggle: PropTypes.func.isRequired,
-    disabled: PropTypes.bool,
+  };
+
+  handleStatusesToggle = (value, checked) => {
+    const { onToggle } = this.props;
+    onToggle(value, checked);
   };
 
   render () {
-    const { status, checked, onToggle, disabled } = this.props;
+    const { status, checked } = this.props;
+
     let media = null;
 
     if (status.get('reblog')) {
@@ -51,26 +60,45 @@ export default class StatusCheckBox extends React.PureComponent {
       } else {
         media = (
           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
-            {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} revealed={false} height={110} onOpenMedia={noop} />}
+            {Component => (
+              <Component
+                media={status.get('media_attachments')}
+                sensitive={status.get('sensitive')}
+                revealed={false}
+                height={110}
+                onOpenMedia={noop}
+              />
+            )}
           </Bundle>
         );
       }
     }
 
-    return (
-      <div className='status-check-box'>
-        <div className='status-check-box__status'>
-          <StatusContent
-            status={status}
-            media={media}
-          />
-        </div>
+    const labelComponent = (
+      <div className='status-check-box__status poll__option__text'>
+        <div className='detailed-status__display-name'>
+          <div className='detailed-status__display-avatar'>
+            <Avatar account={status.get('account')} size={46} />
+          </div>
 
-        <div className='status-check-box-toggle'>
-          <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
+          <div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
         </div>
+
+        <StatusContent status={status} media={media} />
       </div>
     );
+
+    return (
+      <Option
+        name='status_ids'
+        value={status.get('id')}
+        checked={checked}
+        onToggle={this.handleStatusesToggle}
+        label={status.get('search_index')}
+        labelComponent={labelComponent}
+        multiple
+      />
+    );
   }
 
 }
diff --git a/app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js b/app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js
index 9bfd41ffc..aa34b3efd 100644
--- a/app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js
+++ b/app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js
@@ -1,19 +1,15 @@
 import { connect } from 'react-redux';
 import StatusCheckBox from '../components/status_check_box';
-import { toggleStatusReport } from 'flavours/glitch/actions/reports';
-import { Set as ImmutableSet } from 'immutable';
+import { makeGetStatus } from 'flavours/glitch/selectors';
 
-const mapStateToProps = (state, { id }) => ({
-  status: state.getIn(['statuses', id]),
-  checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id),
-});
+const makeMapStateToProps = () => {
+  const getStatus = makeGetStatus();
 
-const mapDispatchToProps = (dispatch, { id }) => ({
+  const mapStateToProps = (state, { id }) => ({
+    status: getStatus(state, { id }),
+  });
 
-  onToggle (e) {
-    dispatch(toggleStatusReport(id, e.target.checked));
-  },
+  return mapStateToProps;
+};
 
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox);
+export default connect(makeMapStateToProps)(StatusCheckBox);
diff --git a/app/javascript/flavours/glitch/features/report/rules.js b/app/javascript/flavours/glitch/features/report/rules.js
new file mode 100644
index 000000000..4772e04a2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/report/rules.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+import { FormattedMessage } from 'react-intl';
+import Button from 'flavours/glitch/components/button';
+import Option from './components/option';
+
+const mapStateToProps = state => ({
+  rules: state.get('rules'),
+});
+
+export default @connect(mapStateToProps)
+class Rules extends React.PureComponent {
+
+  static propTypes = {
+    onNextStep: PropTypes.func.isRequired,
+    rules: ImmutablePropTypes.list,
+    selectedRuleIds: ImmutablePropTypes.set.isRequired,
+    onToggle: PropTypes.func.isRequired,
+  };
+
+  handleNextClick = () => {
+    const { onNextStep } = this.props;
+    onNextStep('statuses');
+  };
+
+  handleRulesToggle = (value, checked) => {
+    const { onToggle } = this.props;
+    onToggle(value, checked);
+  };
+
+  render () {
+    const { rules, selectedRuleIds } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p>
+
+        <div>
+          {rules.map(item => (
+            <Option
+              key={item.get('id')}
+              name='rule_ids'
+              value={item.get('id')}
+              checked={selectedRuleIds.includes(item.get('id'))}
+              onToggle={this.handleRulesToggle}
+              label={item.get('text')}
+              multiple
+            />
+          ))}
+        </div>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleNextClick} disabled={selectedRuleIds.size < 1}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/report/statuses.js b/app/javascript/flavours/glitch/features/report/statuses.js
new file mode 100644
index 000000000..69cfbb3e5
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/report/statuses.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container';
+import { OrderedSet } from 'immutable';
+import { FormattedMessage } from 'react-intl';
+import Button from 'flavours/glitch/components/button';
+
+const mapStateToProps = (state, { accountId }) => ({
+  availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
+});
+
+export default @connect(mapStateToProps)
+class Statuses extends React.PureComponent {
+
+  static propTypes = {
+    onNextStep: PropTypes.func.isRequired,
+    accountId: PropTypes.string.isRequired,
+    availableStatusIds: ImmutablePropTypes.set.isRequired,
+    selectedStatusIds: ImmutablePropTypes.set.isRequired,
+    onToggle: PropTypes.func.isRequired,
+  };
+
+  handleNextClick = () => {
+    const { onNextStep } = this.props;
+    onNextStep('comment');
+  };
+
+  render () {
+    const { availableStatusIds, selectedStatusIds, onToggle } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
+
+        <div className='report-dialog-modal__statuses'>
+          {availableStatusIds.union(selectedStatusIds).map(statusId => (
+            <StatusCheckBox
+              id={statusId}
+              key={statusId}
+              checked={selectedStatusIds.includes(statusId)}
+              onToggle={onToggle}
+            />
+          ))}
+        </div>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleNextClick}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/report/thanks.js b/app/javascript/flavours/glitch/features/report/thanks.js
new file mode 100644
index 000000000..9c41baa7f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/report/thanks.js
@@ -0,0 +1,84 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+import Button from 'flavours/glitch/components/button';
+import { connect } from 'react-redux';
+import {
+  unfollowAccount,
+  muteAccount,
+  blockAccount,
+} from 'mastodon/actions/accounts';
+
+const mapStateToProps = () => ({});
+
+export default @connect(mapStateToProps)
+class Thanks extends React.PureComponent {
+
+  static propTypes = {
+    submitted: PropTypes.bool,
+    onClose: PropTypes.func.isRequired,
+    account: ImmutablePropTypes.map.isRequired,
+    dispatch: PropTypes.func.isRequired,
+  };
+
+  handleCloseClick = () => {
+    const { onClose } = this.props;
+    onClose();
+  };
+
+  handleUnfollowClick = () => {
+    const { dispatch, account, onClose } = this.props;
+    dispatch(unfollowAccount(account.get('id')));
+    onClose();
+  };
+
+  handleMuteClick = () => {
+    const { dispatch, account, onClose } = this.props;
+    dispatch(muteAccount(account.get('id')));
+    onClose();
+  };
+
+  handleBlockClick = () => {
+    const { dispatch, account, onClose } = this.props;
+    dispatch(blockAccount(account.get('id')));
+    onClose();
+  };
+
+  render () {
+    const { account, submitted } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3>
+        <p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p>
+
+        {account.getIn(['relationship', 'following']) && (
+          <React.Fragment>
+            <h4 className='report-dialog-modal__subtitle'><FormattedMessage id='report.unfollow' defaultMessage='Unfollow @{name}' values={{ name: account.get('username') }} /></h4>
+            <p className='report-dialog-modal__lead'><FormattedMessage id='report.unfollow_explanation' defaultMessage='You are following this account. To not see their posts in your home feed anymore, unfollow them.' /></p>
+            <Button secondary onClick={this.handleUnfollowClick}><FormattedMessage id='account.unfollow' defaultMessage='Unfollow' /></Button>
+            <hr />
+          </React.Fragment>
+        )}
+
+        <h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.mute' defaultMessage='Mute @{name}' values={{ name: account.get('username') }} /></h4>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.mute_explanation' defaultMessage='You will not see their posts. They can still follow you and see your posts and will not know that they are muted.' /></p>
+        <Button secondary onClick={this.handleMuteClick}>{!account.getIn(['relationship', 'muting']) ? <FormattedMessage id='report.mute' defaultMessage='Mute' /> : <FormattedMessage id='account.muted' defaultMessage='Muted' />}</Button>
+
+        <hr />
+
+        <h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.block' defaultMessage='Block @{name}' values={{ name: account.get('username') }} /></h4>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.block_explanation' defaultMessage='You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.' /></p>
+        <Button secondary onClick={this.handleBlockClick}>{!account.getIn(['relationship', 'blocking']) ? <FormattedMessage id='report.block' defaultMessage='Block' /> : <FormattedMessage id='account.blocked' defaultMessage='Blocked' />}</Button>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleCloseClick}><FormattedMessage id='report.close' defaultMessage='Done' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
index 5cb7c5d07..dcbe94929 100644
--- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
@@ -1,38 +1,32 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { changeReportComment, changeReportForward, submitReport } from 'flavours/glitch/actions/reports';
+import { submitReport } from 'flavours/glitch/actions/reports';
 import { expandAccountTimeline } from 'flavours/glitch/actions/timelines';
+import { fetchRules } from 'flavours/glitch/actions/rules';
+import { fetchRelationships } from 'flavours/glitch/actions/accounts';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { makeGetAccount } from 'flavours/glitch/selectors';
 import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container';
 import { OrderedSet } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import Button from 'flavours/glitch/components/button';
-import Toggle from 'react-toggle';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'flavours/glitch/components/icon_button';
+import Category from 'flavours/glitch/features/report/category';
+import Statuses from 'flavours/glitch/features/report/statuses';
+import Rules from 'flavours/glitch/features/report/rules';
+import Comment from 'flavours/glitch/features/report/comment';
+import Thanks from 'flavours/glitch/features/report/thanks';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
-  placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
-  submit: { id: 'report.submit', defaultMessage: 'Submit' },
 });
 
 const makeMapStateToProps = () => {
   const getAccount = makeGetAccount();
 
-  const mapStateToProps = state => {
-    const accountId = state.getIn(['reports', 'new', 'account_id']);
-
-    return {
-      isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
-      account: getAccount(state, accountId),
-      comment: state.getIn(['reports', 'new', 'comment']),
-      forward: state.getIn(['reports', 'new', 'forward']),
-      statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
-    };
-  };
+  const mapStateToProps = (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+  });
 
   return mapStateToProps;
 };
@@ -42,92 +36,183 @@ export default @connect(makeMapStateToProps)
 class ReportModal extends ImmutablePureComponent {
 
   static propTypes = {
-    isSubmitting: PropTypes.bool,
-    account: ImmutablePropTypes.map,
-    statusIds: ImmutablePropTypes.orderedSet.isRequired,
-    comment: PropTypes.string.isRequired,
-    forward: PropTypes.bool,
+    accountId: PropTypes.string.isRequired,
+    statusId: PropTypes.string,
     dispatch: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
+    account: ImmutablePropTypes.map.isRequired,
   };
 
-  handleCommentChange = e => {
-    this.props.dispatch(changeReportComment(e.target.value));
-  }
-
-  handleForwardChange = e => {
-    this.props.dispatch(changeReportForward(e.target.checked));
-  }
+  state = {
+    step: 'category',
+    selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
+    comment: '',
+    category: null,
+    selectedRuleIds: OrderedSet(),
+    forward: true,
+    isSubmitting: false,
+    isSubmitted: false,
+  };
 
   handleSubmit = () => {
-    this.props.dispatch(submitReport());
-  }
+    const { dispatch, accountId } = this.props;
+    const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
+
+    this.setState({ isSubmitting: true });
+
+    dispatch(submitReport({
+      account_id: accountId,
+      status_ids: selectedStatusIds.toArray(),
+      comment,
+      forward,
+      category,
+      rule_ids: selectedRuleIds.toArray(),
+    }, this.handleSuccess, this.handleFail));
+  };
+
+  handleSuccess = () => {
+    this.setState({ isSubmitting: false, isSubmitted: true, step: 'thanks' });
+  };
+
+  handleFail = () => {
+    this.setState({ isSubmitting: false });
+  };
+
+  handleStatusToggle = (statusId, checked) => {
+    const { selectedStatusIds } = this.state;
+
+    if (checked) {
+      this.setState({ selectedStatusIds: selectedStatusIds.add(statusId) });
+    } else {
+      this.setState({ selectedStatusIds: selectedStatusIds.remove(statusId) });
+    }
+  };
 
-  handleKeyDown = e => {
-    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
-      this.handleSubmit();
+  handleRuleToggle = (ruleId, checked) => {
+    const { selectedRuleIds } = this.state;
+
+    if (checked) {
+      this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
+    } else {
+      this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
     }
   }
 
+  handleChangeCategory = category => {
+    this.setState({ category });
+  };
+
+  handleChangeComment = comment => {
+    this.setState({ comment });
+  };
+
+  handleChangeForward = forward => {
+    this.setState({ forward });
+  };
+
+  handleNextStep = step => {
+    this.setState({ step });
+  };
+
   componentDidMount () {
-    this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true }));
-  }
+    const { dispatch, accountId } = this.props;
 
-  componentWillReceiveProps (nextProps) {
-    if (this.props.account !== nextProps.account && nextProps.account) {
-      this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true }));
-    }
+    dispatch(fetchRelationships([accountId]));
+    dispatch(expandAccountTimeline(accountId, { withReplies: true }));
+    dispatch(fetchRules());
   }
 
   render () {
-    const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
+    const {
+      accountId,
+      account,
+      intl,
+      onClose,
+    } = this.props;
 
     if (!account) {
       return null;
     }
 
-    const domain = account.get('acct').split('@')[1];
+    const {
+      step,
+      selectedStatusIds,
+      selectedRuleIds,
+      comment,
+      forward,
+      category,
+      isSubmitting,
+      isSubmitted,
+    } = this.state;
+
+    const domain   = account.get('acct').split('@')[1];
+    const isRemote = !!domain;
+
+    let stepComponent;
+
+    switch(step) {
+    case 'category':
+      stepComponent = (
+        <Category
+          onNextStep={this.handleNextStep}
+          startedFrom={this.props.statusId ? 'status' : 'account'}
+          category={category}
+          onChangeCategory={this.handleChangeCategory}
+        />
+      );
+      break;
+    case 'rules':
+      stepComponent = (
+        <Rules
+          onNextStep={this.handleNextStep}
+          selectedRuleIds={selectedRuleIds}
+          onToggle={this.handleRuleToggle}
+        />
+      );
+      break;
+    case 'statuses':
+      stepComponent = (
+        <Statuses
+          onNextStep={this.handleNextStep}
+          accountId={accountId}
+          selectedStatusIds={selectedStatusIds}
+          onToggle={this.handleStatusToggle}
+        />
+      );
+      break;
+    case 'comment':
+      stepComponent = (
+        <Comment
+          onSubmit={this.handleSubmit}
+          isSubmitting={isSubmitting}
+          isRemote={isRemote}
+          comment={comment}
+          forward={forward}
+          domain={domain}
+          onChangeComment={this.handleChangeComment}
+          onChangeForward={this.handleChangeForward}
+        />
+      );
+      break;
+    case 'thanks':
+      stepComponent = (
+        <Thanks
+          submitted={isSubmitted}
+          account={account}
+          onClose={onClose}
+        />
+      );
+    }
 
     return (
-      <div className='modal-root__modal report-modal'>
+      <div className='modal-root__modal report-dialog-modal'>
         <div className='report-modal__target'>
           <IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
           <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
         </div>
 
-        <div className='report-modal__container'>
-          <div className='report-modal__comment'>
-            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
-
-            <textarea
-              className='setting-text light'
-              placeholder={intl.formatMessage(messages.placeholder)}
-              value={comment}
-              onChange={this.handleCommentChange}
-              onKeyDown={this.handleKeyDown}
-              disabled={isSubmitting}
-              autoFocus
-            />
-
-            {domain && (
-              <div>
-                <p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
-
-                <div className='setting-toggle'>
-                  <Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
-                  <label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
-                </div>
-              </div>
-            )}
-
-            <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
-          </div>
-
-          <div className='report-modal__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
+        <div className='report-dialog-modal__container'>
+          {stepComponent}
         </div>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js
index a92f3d5a8..84ec9fce7 100644
--- a/app/javascript/flavours/glitch/packs/public.js
+++ b/app/javascript/flavours/glitch/packs/public.js
@@ -147,13 +147,7 @@ function main() {
   });
 
   delegate(document, '.sidebar__toggle__icon', 'click', () => {
-    const target = document.querySelector('.sidebar ul');
-
-    if (target.style.display === 'block') {
-      target.style.display = 'none';
-    } else {
-      target.style.display = 'block';
-    }
+    document.querySelector('.sidebar ul').classList.toggle('visible');
   });
 
   // Empty the honeypot fields in JS in case something like an extension
diff --git a/app/javascript/flavours/glitch/packs/settings.js b/app/javascript/flavours/glitch/packs/settings.js
index 9c4d119c1..0a53e1c25 100644
--- a/app/javascript/flavours/glitch/packs/settings.js
+++ b/app/javascript/flavours/glitch/packs/settings.js
@@ -7,13 +7,7 @@ function main() {
   const { delegate } = require('@rails/ujs');
 
   delegate(document, '.sidebar__toggle__icon', 'click', () => {
-    const target = document.querySelector('.sidebar ul');
-
-    if (target.style.display === 'block') {
-      target.style.display = 'none';
-    } else {
-      target.style.display = 'block';
-    }
+    document.querySelector('.sidebar ul').classList.toggle('visible');
   });
 }
 
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js
index d9123b103..92348c0c5 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.js
@@ -17,7 +17,7 @@ import push_notifications from './push_notifications';
 import status_lists from './status_lists';
 import mutes from './mutes';
 import blocks from './blocks';
-import reports from './reports';
+import rules from './rules';
 import boosts from './boosts';
 import contexts from './contexts';
 import compose from './compose';
@@ -64,7 +64,7 @@ const reducers = {
   push_notifications,
   mutes,
   blocks,
-  reports,
+  rules,
   boosts,
   contexts,
   compose,
diff --git a/app/javascript/flavours/glitch/reducers/reports.js b/app/javascript/flavours/glitch/reducers/reports.js
deleted file mode 100644
index 1f7f3f273..000000000
--- a/app/javascript/flavours/glitch/reducers/reports.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import {
-  REPORT_INIT,
-  REPORT_SUBMIT_REQUEST,
-  REPORT_SUBMIT_SUCCESS,
-  REPORT_SUBMIT_FAIL,
-  REPORT_CANCEL,
-  REPORT_STATUS_TOGGLE,
-  REPORT_COMMENT_CHANGE,
-  REPORT_FORWARD_CHANGE,
-} from 'flavours/glitch/actions/reports';
-import {
-  TIMELINE_DELETE,
-} from 'flavours/glitch/actions/timelines';
-import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
-
-const initialState = ImmutableMap({
-  new: ImmutableMap({
-    isSubmitting: false,
-    account_id: null,
-    status_ids: ImmutableSet(),
-    comment: '',
-    forward: false,
-  }),
-});
-
-const deleteStatus = (state, id, references) => {
-  references.forEach(ref => {
-    state = deleteStatus(state, ref[0], []);
-  });
-
-  return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.remove(id));
-};
-
-export default function reports(state = initialState, action) {
-  switch(action.type) {
-  case REPORT_INIT:
-    return state.withMutations(map => {
-      map.setIn(['new', 'isSubmitting'], false);
-      map.setIn(['new', 'account_id'], action.account.get('id'));
-
-      if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
-        map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet());
-        map.setIn(['new', 'comment'], '');
-      } else if (action.status) {
-        map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
-      }
-    });
-  case REPORT_STATUS_TOGGLE:
-    return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => {
-      if (action.checked) {
-        return set.add(action.statusId);
-      }
-
-      return set.remove(action.statusId);
-    });
-  case REPORT_COMMENT_CHANGE:
-    return state.setIn(['new', 'comment'], action.comment);
-  case REPORT_FORWARD_CHANGE:
-    return state.setIn(['new', 'forward'], action.forward);
-  case REPORT_SUBMIT_REQUEST:
-    return state.setIn(['new', 'isSubmitting'], true);
-  case REPORT_SUBMIT_FAIL:
-    return state.setIn(['new', 'isSubmitting'], false);
-  case REPORT_CANCEL:
-  case REPORT_SUBMIT_SUCCESS:
-    return state.withMutations(map => {
-      map.setIn(['new', 'account_id'], null);
-      map.setIn(['new', 'status_ids'], ImmutableSet());
-      map.setIn(['new', 'comment'], '');
-      map.setIn(['new', 'isSubmitting'], false);
-    });
-  case TIMELINE_DELETE:
-    return deleteStatus(state, action.id, action.references);
-  default:
-    return state;
-  }
-};
diff --git a/app/javascript/flavours/glitch/reducers/rules.js b/app/javascript/flavours/glitch/reducers/rules.js
new file mode 100644
index 000000000..6cc2230bc
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/rules.js
@@ -0,0 +1,13 @@
+import { RULES_FETCH_SUCCESS } from 'flavours/glitch/actions/rules';
+import { List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableList();
+
+export default function rules(state = initialState, action) {
+  switch (action.type) {
+  case RULES_FETCH_SUCCESS:
+    return fromJS(action.rules);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js
index 48587ce64..676a1ccc1 100644
--- a/app/javascript/flavours/glitch/reducers/settings.js
+++ b/app/javascript/flavours/glitch/reducers/settings.js
@@ -41,6 +41,7 @@ const initialState = ImmutableMap({
       poll: false,
       status: false,
       update: false,
+      'admin.sign_up': false,
     }),
 
     quickFilter: ImmutableMap({
@@ -61,6 +62,7 @@ const initialState = ImmutableMap({
       poll: true,
       status: true,
       update: true,
+      'admin.sign_up': true,
     }),
 
     sounds: ImmutableMap({
@@ -72,6 +74,7 @@ const initialState = ImmutableMap({
       poll: true,
       status: true,
       update: true,
+      'admin.sign_up': true,
     }),
   }),
 
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 66ce92ce2..a1b99636c 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -322,6 +322,10 @@ $content-width: 840px;
 
       & > ul {
         display: none;
+
+        &.visible {
+          display: block;
+        }
       }
 
       ul a,
@@ -594,12 +598,16 @@ body,
 }
 
 .log-entry {
+  display: block;
   line-height: 20px;
   padding: 15px;
   padding-left: 15px * 2 + 40px;
   background: $ui-base-color;
   border-bottom: 1px solid darken($ui-base-color, 8%);
   position: relative;
+  text-decoration: none;
+  color: $darker-text-color;
+  font-size: 14px;
 
   &:first-child {
     border-top-left-radius: 4px;
@@ -612,15 +620,12 @@ body,
     border-bottom: 0;
   }
 
-  &:hover {
+  &:hover,
+  &:focus,
+  &:active {
     background: lighten($ui-base-color, 4%);
   }
 
-  &__header {
-    color: $darker-text-color;
-    font-size: 14px;
-  }
-
   &__avatar {
     position: absolute;
     left: 15px;
@@ -1278,6 +1283,30 @@ a.sparkline {
       background: linear-gradient(to left, $ui-base-color, transparent);
       pointer-events: none;
     }
+
+    a {
+      color: $secondary-text-color;
+      text-decoration: none;
+      unicode-bidi: isolate;
+
+      &:hover {
+        text-decoration: underline;
+
+        .fa {
+          color: lighten($dark-text-color, 7%);
+        }
+      }
+
+      &.mention {
+        &:hover {
+          text-decoration: none;
+
+          span {
+            text-decoration: underline;
+          }
+        }
+      }
+    }
   }
 
   &__actions {
@@ -1467,3 +1496,75 @@ a.sparkline {
     }
   }
 }
+
+.strike-card {
+  padding: 15px;
+  border-radius: 4px;
+  background: $ui-base-color;
+  font-size: 15px;
+  line-height: 20px;
+  word-wrap: break-word;
+  font-weight: 400;
+  color: $primary-text-color;
+
+  p {
+    margin-bottom: 20px;
+    unicode-bidi: plaintext;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    strong {
+      font-weight: 700;
+    }
+  }
+
+  &__rules {
+    list-style: disc;
+    padding-left: 15px;
+    margin-bottom: 20px;
+    color: $darker-text-color;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    &__text {
+      color: $primary-text-color;
+    }
+  }
+
+  &__statuses-list {
+    border-radius: 4px;
+    border: 1px solid darken($ui-base-color, 8%);
+    font-size: 13px;
+    line-height: 18px;
+    overflow: hidden;
+
+    &__item {
+      padding: 16px;
+      background: lighten($ui-base-color, 2%);
+      border-bottom: 1px solid darken($ui-base-color, 8%);
+
+      &:last-child {
+        border-bottom: 0;
+      }
+
+      &__meta {
+        color: $darker-text-color;
+      }
+
+      a {
+        color: inherit;
+        text-decoration: none;
+
+        &:hover,
+        &:focus,
+        &:active {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index fd62bb651..937751d00 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -652,14 +652,14 @@
   & > .primary {
     display: inline-block;
     margin: 0;
-    padding: 0 10px;
+    padding: 7px 10px;
     text-align: center;
   }
 
   & > .side_arm {
     display: inline-block;
     margin: 0 2px;
-    padding: 0;
+    padding: 7px 0;
     width: 36px;
     text-align: center;
   }
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 656d8f25d..55abd6e1e 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -41,16 +41,14 @@
   cursor: pointer;
   display: inline-block;
   font-family: inherit;
-  font-size: 14px;
+  font-size: 17px;
   font-weight: 500;
-  height: 36px;
   letter-spacing: 0;
-  line-height: 36px;
+  line-height: 22px;
   overflow: hidden;
-  padding: 0 16px;
+  padding: 7px 18px;
   position: relative;
   text-align: center;
-  text-transform: uppercase;
   text-decoration: none;
   text-overflow: ellipsis;
   transition: all 100ms ease-in;
@@ -82,17 +80,6 @@
     cursor: default;
   }
 
-  &.button-primary,
-  &.button-alternative,
-  &.button-secondary,
-  &.button-alternative-2 {
-    font-size: 16px;
-    line-height: 36px;
-    height: auto;
-    text-transform: none;
-    padding: 4px 16px;
-  }
-
   &.button-alternative {
     color: $inverted-text-color;
     background: $ui-primary-color;
@@ -121,8 +108,7 @@
     color: $darker-text-color;
     text-transform: none;
     background: transparent;
-    padding: 3px 15px;
-    border-radius: 4px;
+    padding: 6px 17px;
     border: 1px solid $ui-primary-color;
 
     &:active,
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index fb2445a17..ae1afc320 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -537,6 +537,192 @@
   max-width: 700px;
 }
 
+.report-dialog-modal {
+  max-width: 90vw;
+  width: 480px;
+  height: 80vh;
+  background: lighten($ui-secondary-color, 8%);
+  color: $inverted-text-color;
+  border-radius: 8px;
+  overflow: hidden;
+  position: relative;
+  flex-direction: column;
+  display: flex;
+
+  &__container {
+    box-sizing: border-box;
+    border-top: 1px solid $ui-secondary-color;
+    padding: 20px;
+    flex-grow: 1;
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+    overflow: auto;
+  }
+
+  &__title {
+    font-size: 28px;
+    line-height: 33px;
+    font-weight: 700;
+    margin-bottom: 15px;
+
+    @media screen and (max-height: 800px) {
+      font-size: 22px;
+    }
+  }
+
+  &__subtitle {
+    font-size: 17px;
+    font-weight: 600;
+    line-height: 22px;
+    margin-bottom: 4px;
+  }
+
+  &__lead {
+    font-size: 17px;
+    line-height: 22px;
+    color: lighten($inverted-text-color, 16%);
+    margin-bottom: 30px;
+  }
+
+  &__actions {
+    margin-top: 30px;
+    display: flex;
+
+    .button {
+      flex: 1 1 auto;
+    }
+  }
+
+  &__statuses {
+    flex-grow: 1;
+    min-height: 0;
+    overflow: auto;
+  }
+
+  .status__content a {
+    color: $highlight-text-color;
+  }
+
+  .status__content,
+  .status__content p {
+    color: $inverted-text-color;
+  }
+
+  .dialog-option .poll__input {
+    border-color: $inverted-text-color;
+    color: $ui-secondary-color;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+
+    svg {
+      width: 8px;
+      height: auto;
+    }
+
+    &:active,
+    &:focus,
+    &:hover {
+      border-color: lighten($inverted-text-color, 15%);
+      border-width: 4px;
+    }
+
+    &.active {
+      border-color: $inverted-text-color;
+      background: $inverted-text-color;
+    }
+  }
+
+  .poll__option.dialog-option {
+    padding: 15px 0;
+    flex: 0 0 auto;
+    border-bottom: 1px solid $ui-secondary-color;
+
+    &:last-child {
+      border-bottom: 0;
+    }
+
+    & > .poll__option__text {
+      font-size: 13px;
+      color: lighten($inverted-text-color, 16%);
+
+      strong {
+        font-size: 17px;
+        font-weight: 500;
+        line-height: 22px;
+        color: $inverted-text-color;
+        display: block;
+        margin-bottom: 4px;
+
+        &:last-child {
+          margin-bottom: 0;
+        }
+      }
+    }
+  }
+
+  .flex-spacer {
+    background: transparent;
+  }
+
+  &__textarea {
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    margin: 0;
+    color: $inverted-text-color;
+    background: $simple-background-color;
+    padding: 10px;
+    font-family: inherit;
+    font-size: 17px;
+    line-height: 22px;
+    resize: vertical;
+    border: 0;
+    outline: 0;
+    border-radius: 4px;
+    margin: 20px 0;
+
+    &::placeholder {
+      color: $dark-text-color;
+    }
+
+    &:focus {
+      outline: 0;
+    }
+  }
+
+  &__toggle {
+    display: flex;
+    align-items: center;
+
+    & > span {
+      font-size: 17px;
+      font-weight: 500;
+      margin-left: 10px;
+    }
+  }
+
+  .button.button-secondary {
+    border-color: $inverted-text-color;
+    color: $inverted-text-color;
+    flex: 0 0 auto;
+
+    &:hover,
+    &:focus,
+    &:active {
+      border-color: lighten($inverted-text-color, 15%);
+      color: lighten($inverted-text-color, 15%);
+    }
+  }
+
+  hr {
+    border: 0;
+    background: transparent;
+    margin: 15px 0;
+  }
+}
+
 .report-modal__container {
   display: flex;
   border-top: 1px solid $ui-secondary-color;
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index d9154e4c7..77541ab74 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -521,42 +521,39 @@
   justify-content: flex-start;
 }
 
-.status-check-box {
-  border-bottom: 1px solid $ui-secondary-color;
-  display: flex;
+.status-check-box__status {
+  display: block;
+  box-sizing: border-box;
+  width: 100%;
+  padding: 0 10px;
 
-  .status-check-box__status {
-    margin: 10px 0 10px 10px;
-    flex: 1;
-    overflow: hidden;
+  .detailed-status__display-name {
+    color: lighten($inverted-text-color, 16%);
 
-    .media-gallery {
-      max-width: 250px;
+    span {
+      display: inline;
     }
 
-    .status__content {
-      padding: 0;
-      white-space: normal;
+    &:hover strong {
+      text-decoration: none;
     }
+  }
 
-    .video-player,
-    .audio-player {
-      margin-top: 8px;
-      max-width: 250px;
-    }
+  .media-gallery,
+  .audio-player,
+  .video-player {
+    margin-top: 8px;
+    max-width: 250px;
+  }
 
-    .media-gallery__item-thumbnail {
-      cursor: default;
-    }
+  .status__content {
+    padding: 0;
+    white-space: normal;
   }
-}
 
-.status-check-box-toggle {
-  align-items: center;
-  display: flex;
-  flex: 0 0 auto;
-  justify-content: center;
-  padding: 10px;
+  .media-gallery__item-thumbnail {
+    cursor: default;
+  }
 }
 
 .status__prepend {
diff --git a/app/javascript/flavours/glitch/styles/footer.scss b/app/javascript/flavours/glitch/styles/footer.scss
index 00d290883..073ebda7e 100644
--- a/app/javascript/flavours/glitch/styles/footer.scss
+++ b/app/javascript/flavours/glitch/styles/footer.scss
@@ -90,6 +90,20 @@
         .column-4 {
           display: none;
         }
+
+        .column-2 h4 {
+          display: none;
+        }
+      }
+    }
+
+    .legal-xs {
+      display: none;
+      text-align: center;
+      padding-top: 20px;
+
+      @media screen and (max-width: $no-gap-breakpoint) {
+        display: block;
       }
     }
 
@@ -105,7 +119,8 @@
       }
     }
 
-    ul a {
+    ul a,
+    .legal-xs a {
       text-decoration: none;
       color: lighten($ui-base-color, 34%);
 
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 9370811e0..00e8d74d7 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -45,7 +45,7 @@ defineMessages({
 });
 
 const fetchRelatedRelationships = (dispatch, notifications) => {
-  const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
+  const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
 
   if (accountIds.length > 0) {
     dispatch(fetchRelationships(accountIds));
@@ -132,6 +132,7 @@ const excludeTypesFromFilter = filter => {
     'poll',
     'status',
     'update',
+    'admin.sign_up',
   ]);
 
   return allTypes.filterNot(item => item === filter).toJS();
diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js
index afa0c3412..fbe5b3791 100644
--- a/app/javascript/mastodon/actions/reports.js
+++ b/app/javascript/mastodon/actions/reports.js
@@ -1,89 +1,38 @@
 import api from '../api';
-import { openModal, closeModal } from './modal';
-
-export const REPORT_INIT   = 'REPORT_INIT';
-export const REPORT_CANCEL = 'REPORT_CANCEL';
+import { openModal } from './modal';
 
 export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
 export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
 export const REPORT_SUBMIT_FAIL    = 'REPORT_SUBMIT_FAIL';
 
-export const REPORT_STATUS_TOGGLE  = 'REPORT_STATUS_TOGGLE';
-export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
-export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
-
-export function initReport(account, status) {
-  return dispatch => {
-    dispatch({
-      type: REPORT_INIT,
-      account,
-      status,
-    });
-
-    dispatch(openModal('REPORT'));
-  };
-};
-
-export function cancelReport() {
-  return {
-    type: REPORT_CANCEL,
-  };
-};
-
-export function toggleStatusReport(statusId, checked) {
-  return {
-    type: REPORT_STATUS_TOGGLE,
-    statusId,
-    checked,
-  };
-};
-
-export function submitReport() {
-  return (dispatch, getState) => {
-    dispatch(submitReportRequest());
-
-    api(getState).post('/api/v1/reports', {
-      account_id: getState().getIn(['reports', 'new', 'account_id']),
-      status_ids: getState().getIn(['reports', 'new', 'status_ids']),
-      comment: getState().getIn(['reports', 'new', 'comment']),
-      forward: getState().getIn(['reports', 'new', 'forward']),
-    }).then(response => {
-      dispatch(closeModal());
-      dispatch(submitReportSuccess(response.data));
-    }).catch(error => dispatch(submitReportFail(error)));
-  };
-};
-
-export function submitReportRequest() {
-  return {
-    type: REPORT_SUBMIT_REQUEST,
-  };
-};
-
-export function submitReportSuccess(report) {
-  return {
-    type: REPORT_SUBMIT_SUCCESS,
-    report,
-  };
-};
-
-export function submitReportFail(error) {
-  return {
-    type: REPORT_SUBMIT_FAIL,
-    error,
-  };
-};
-
-export function changeReportComment(comment) {
-  return {
-    type: REPORT_COMMENT_CHANGE,
-    comment,
-  };
-};
-
-export function changeReportForward(forward) {
-  return {
-    type: REPORT_FORWARD_CHANGE,
-    forward,
-  };
-};
+export const initReport = (account, status) => dispatch =>
+  dispatch(openModal('REPORT', {
+    accountId: account.get('id'),
+    statusId: status?.get('id'),
+  }));
+
+export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
+  dispatch(submitReportRequest());
+
+  api(getState).post('/api/v1/reports', params).then(response => {
+    dispatch(submitReportSuccess(response.data));
+    if (onSuccess) onSuccess();
+  }).catch(error => {
+    dispatch(submitReportFail(error));
+    if (onFail) onFail();
+  });
+};
+
+export const submitReportRequest = () => ({
+  type: REPORT_SUBMIT_REQUEST,
+});
+
+export const submitReportSuccess = report => ({
+  type: REPORT_SUBMIT_SUCCESS,
+  report,
+});
+
+export const submitReportFail = error => ({
+  type: REPORT_SUBMIT_FAIL,
+  error,
+});
diff --git a/app/javascript/mastodon/actions/rules.js b/app/javascript/mastodon/actions/rules.js
new file mode 100644
index 000000000..34e60a121
--- /dev/null
+++ b/app/javascript/mastodon/actions/rules.js
@@ -0,0 +1,27 @@
+import api from '../api';
+
+export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
+export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
+export const RULES_FETCH_FAIL    = 'RULES_FETCH_FAIL';
+
+export const fetchRules = () => (dispatch, getState) => {
+  dispatch(fetchRulesRequest());
+
+  api(getState)
+    .get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules)))
+    .catch(err => dispatch(fetchRulesFail(err)));
+};
+
+const fetchRulesRequest = () => ({
+  type: RULES_FETCH_REQUEST,
+});
+
+const fetchRulesSuccess = rules => ({
+  type: RULES_FETCH_SUCCESS,
+  rules,
+});
+
+const fetchRulesFail = error => ({
+  type: RULES_FETCH_FAIL,
+  error,
+});
diff --git a/app/javascript/mastodon/components/check.js b/app/javascript/mastodon/components/check.js
new file mode 100644
index 000000000..ee2ef1595
--- /dev/null
+++ b/app/javascript/mastodon/components/check.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const Check = () => (
+  <svg width='14' height='11' viewBox='0 0 14 11'>
+    <path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
+  </svg>
+);
+
+export default Check;
diff --git a/app/javascript/mastodon/components/loading_indicator.js b/app/javascript/mastodon/components/loading_indicator.js
index 59f721c50..33c59d94c 100644
--- a/app/javascript/mastodon/components/loading_indicator.js
+++ b/app/javascript/mastodon/components/loading_indicator.js
@@ -6,7 +6,7 @@ export const CircularProgress = ({ size, strokeWidth }) => {
   const radius  = (size - strokeWidth) / 2;
 
   return (
-    <svg width={size} heigh={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
+    <svg width={size} height={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
       <circle
         fill='none'
         cx={size / 2}
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index 4a87714e6..f433e4de9 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -170,7 +170,7 @@ class EmojiPickerMenu extends React.PureComponent {
 
   state = {
     modifierOpen: false,
-    placement: null,
+    readyToFocus: false,
   };
 
   handleDocumentClick = e => {
@@ -182,6 +182,16 @@ class EmojiPickerMenu extends React.PureComponent {
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
+
+    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
+    // to wait for a frame before focusing
+    requestAnimationFrame(() => {
+      this.setState({ readyToFocus: true });
+      if (this.node) {
+        const element = this.node.querySelector('input[type="search"]');
+        if (element) element.focus();
+      }
+    });
   }
 
   componentWillUnmount () {
@@ -281,7 +291,7 @@ class EmojiPickerMenu extends React.PureComponent {
           showSkinTones={false}
           backgroundImageFn={backgroundImageFn}
           notFound={notFoundFn}
-          autoFocus
+          autoFocus={this.state.readyToFocus}
           emojiTooltip
         />
 
@@ -314,6 +324,7 @@ class EmojiPickerDropdown extends React.PureComponent {
   state = {
     active: false,
     loading: false,
+    placement: null,
   };
 
   setRef = (c) => {
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js
index 74b53ce5c..6a402f2d4 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.js
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js
@@ -90,7 +90,7 @@ Object.keys(emojiIndex.emojis).forEach(key => {
   let { short_names, search, unified } = emojiMartData.emojis[key];
 
   if (short_names[0] !== key) {
-    throw new Error('The compresser expects the first short_code to be the ' +
+    throw new Error('The compressor expects the first short_code to be the ' +
       'key. It may need to be rewritten if the emoji change such that this ' +
       'is no longer the case.');
   }
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index ada8b6e4a..84db04430 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
 import ClearColumnButton from './clear_column_button';
 import GrantPermissionButton from './grant_permission_button';
 import SettingToggle from './setting_toggle';
+import { isStaff } from 'mastodon/initial_state';
 
 export default class ColumnSettings extends React.PureComponent {
 
@@ -155,7 +156,7 @@ export default class ColumnSettings extends React.PureComponent {
         </div>
 
         <div role='group' aria-labelledby='notifications-update'>
-          <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
+          <span id='notifications-update' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
 
           <div className='column-settings__row'>
             <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
@@ -164,6 +165,19 @@ export default class ColumnSettings extends React.PureComponent {
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
           </div>
         </div>
+
+        {isStaff && (
+          <div role='group' aria-labelledby='notifications-admin-sign-up'>
+            <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
+
+            <div className='column-settings__row'>
+              <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'admin.sign_up']} onChange={onChange} label={alertStr} />
+              {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'admin.sign_up']} onChange={this.onPushChange} label={pushStr} />}
+              <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'admin.sign_up']} onChange={onChange} label={showStr} />
+              <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'admin.sign_up']} onChange={onChange} label={soundStr} />
+            </div>
+          </div>
+        )}
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index cd471852b..9198e9c9d 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -20,6 +20,7 @@ const messages = defineMessages({
   reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
   status: { id: 'notification.status', defaultMessage: '{name} just posted' },
   update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
+  adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
 });
 
 const notificationForScreenReader = (intl, message, timestamp) => {
@@ -344,6 +345,28 @@ class Notification extends ImmutablePureComponent {
     );
   }
 
+  renderAdminSignUp (notification, account, link) {
+    const { intl, unread } = this.props;
+
+    return (
+      <HotKeys handlers={this.getHandlers()}>
+        <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
+          <div className='notification__message'>
+            <div className='notification__favourite-icon-wrapper'>
+              <Icon id='user-plus' fixedWidth />
+            </div>
+
+            <span title={notification.get('created_at')}>
+              <FormattedMessage id='notification.admin.sign_up' defaultMessage='{name} signed up' values={{ name: link }} />
+            </span>
+          </div>
+
+          <AccountContainer id={account.get('id')} hidden={this.props.hidden} />
+        </div>
+      </HotKeys>
+    );
+  }
+
   render () {
     const { notification } = this.props;
     const account          = notification.get('account');
@@ -367,6 +390,8 @@ class Notification extends ImmutablePureComponent {
       return this.renderUpdate(notification, link);
     case 'poll':
       return this.renderPoll(notification, account);
+    case 'admin.sign_up':
+      return this.renderAdminSignUp(notification, account, link);
     }
 
     return null;
diff --git a/app/javascript/mastodon/features/report/category.js b/app/javascript/mastodon/features/report/category.js
new file mode 100644
index 000000000..122b51c7c
--- /dev/null
+++ b/app/javascript/mastodon/features/report/category.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Button from 'mastodon/components/button';
+import Option from './components/option';
+
+const messages = defineMessages({
+  dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
+  dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
+  spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
+  spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
+  violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
+  violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
+  other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
+  other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' },
+  status: { id: 'report.category.title_status', defaultMessage: 'post' },
+  account: { id: 'report.category.title_account', defaultMessage: 'profile' },
+});
+
+export default @injectIntl
+class Category extends React.PureComponent {
+
+  static propTypes = {
+    onNextStep: PropTypes.func.isRequired,
+    category: PropTypes.string,
+    onChangeCategory: PropTypes.func.isRequired,
+    startedFrom: PropTypes.oneOf(['status', 'account']),
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleNextClick = () => {
+    const { onNextStep, category } = this.props;
+
+    switch(category) {
+    case 'dislike':
+      onNextStep('thanks');
+      break;
+    case 'violation':
+      onNextStep('rules');
+      break;
+    default:
+      onNextStep('statuses');
+      break;
+    }
+  };
+
+  handleCategoryToggle = (value, checked) => {
+    const { onChangeCategory } = this.props;
+
+    if (checked) {
+      onChangeCategory(value);
+    }
+  };
+
+  render () {
+    const { category, startedFrom, intl } = this.props;
+
+    const options = [
+      'dislike',
+      'spam',
+      'violation',
+      'other',
+    ];
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p>
+
+        <div>
+          {options.map(item => (
+            <Option
+              key={item}
+              name='category'
+              value={item}
+              checked={category === item}
+              onToggle={this.handleCategoryToggle}
+              label={intl.formatMessage(messages[item])}
+              description={intl.formatMessage(messages[`${item}_description`])}
+            />
+          ))}
+        </div>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleNextClick} disabled={category === null}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/report/comment.js b/app/javascript/mastodon/features/report/comment.js
new file mode 100644
index 000000000..8d1a4e21f
--- /dev/null
+++ b/app/javascript/mastodon/features/report/comment.js
@@ -0,0 +1,83 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+import Button from 'mastodon/components/button';
+import Toggle from 'react-toggle';
+
+const messages = defineMessages({
+  placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
+});
+
+export default @injectIntl
+class Comment extends React.PureComponent {
+
+  static propTypes = {
+    onSubmit: PropTypes.func.isRequired,
+    comment: PropTypes.string.isRequired,
+    onChangeComment: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    isSubmitting: PropTypes.bool,
+    forward: PropTypes.bool,
+    isRemote: PropTypes.bool,
+    domain: PropTypes.string,
+    onChangeForward: PropTypes.func.isRequired,
+  };
+
+  handleClick = () => {
+    const { onSubmit } = this.props;
+    onSubmit();
+  };
+
+  handleChange = e => {
+    const { onChangeComment } = this.props;
+    onChangeComment(e.target.value);
+  };
+
+  handleKeyDown = e => {
+    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+      this.handleClick();
+    }
+  };
+
+  handleForwardChange = e => {
+    const { onChangeForward } = this.props;
+    onChangeForward(e.target.checked);
+  };
+
+  render () {
+    const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>
+
+        <textarea
+          className='report-dialog-modal__textarea'
+          placeholder={intl.formatMessage(messages.placeholder)}
+          value={comment}
+          onChange={this.handleChange}
+          onKeyDown={this.handleKeyDown}
+          disabled={isSubmitting}
+        />
+
+        {isRemote && (
+          <React.Fragment>
+            <p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
+
+            <label className='report-dialog-modal__toggle'>
+              <Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
+              <FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} />
+            </label>
+          </React.Fragment>
+        )}
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/report/components/option.js b/app/javascript/mastodon/features/report/components/option.js
new file mode 100644
index 000000000..744d85268
--- /dev/null
+++ b/app/javascript/mastodon/features/report/components/option.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import Check from 'mastodon/components/check';
+
+export default class Option extends React.PureComponent {
+
+  static propTypes = {
+    name: PropTypes.string.isRequired,
+    value: PropTypes.string.isRequired,
+    checked: PropTypes.bool,
+    label: PropTypes.node,
+    description: PropTypes.node,
+    onToggle: PropTypes.func,
+    multiple: PropTypes.bool,
+    labelComponent: PropTypes.node,
+  };
+
+  handleKeyPress = e => {
+    const { value, checked, onToggle } = this.props;
+
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.stopPropagation();
+      e.preventDefault();
+      onToggle(value, !checked);
+    }
+  }
+
+  handleChange = e => {
+    const { value, onToggle } = this.props;
+    onToggle(value, e.target.checked);
+  }
+
+  render () {
+    const { name, value, checked, label, labelComponent, description, multiple } = this.props;
+
+    return (
+      <label className='dialog-option poll__option selectable'>
+        <input type={multiple ? 'checkbox' : 'radio'} name={name} value={value} checked={checked} onChange={this.handleChange} />
+
+        <span
+          className={classNames('poll__input', { active: checked, checkbox: multiple })}
+          tabIndex='0'
+          role='radio'
+          onKeyPress={this.handleKeyPress}
+          aria-checked={checked}
+          aria-label={label}
+        >{checked && <Check />}</span>
+
+        {labelComponent ? labelComponent : (
+          <span className='poll__option__text'>
+            <strong>{label}</strong>
+            {description}
+          </span>
+        )}
+      </label>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/report/components/status_check_box.js b/app/javascript/mastodon/features/report/components/status_check_box.js
index c29e517da..a2eb3d6f5 100644
--- a/app/javascript/mastodon/features/report/components/status_check_box.js
+++ b/app/javascript/mastodon/features/report/components/status_check_box.js
@@ -1,23 +1,32 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import Toggle from 'react-toggle';
 import noop from 'lodash/noop';
-import StatusContent from '../../../components/status_content';
-import { MediaGallery, Video } from '../../ui/util/async-components';
-import Bundle from '../../ui/components/bundle';
+import StatusContent from 'mastodon/components/status_content';
+import { MediaGallery, Video } from 'mastodon/features/ui/util/async-components';
+import Bundle from 'mastodon/features/ui/components/bundle';
+import Avatar from 'mastodon/components/avatar';
+import DisplayName from 'mastodon/components/display_name';
+import RelativeTimestamp from 'mastodon/components/relative_timestamp';
+import Option from './option';
 
 export default class StatusCheckBox extends React.PureComponent {
 
   static propTypes = {
+    id: PropTypes.string.isRequired,
     status: ImmutablePropTypes.map.isRequired,
     checked: PropTypes.bool,
     onToggle: PropTypes.func.isRequired,
-    disabled: PropTypes.bool,
+  };
+
+  handleStatusesToggle = (value, checked) => {
+    const { onToggle } = this.props;
+    onToggle(value, checked);
   };
 
   render () {
-    const { status, checked, onToggle, disabled } = this.props;
+    const { status, checked } = this.props;
+
     let media = null;
 
     if (status.get('reblog')) {
@@ -50,24 +59,46 @@ export default class StatusCheckBox extends React.PureComponent {
       } else {
         media = (
           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
-            {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
+            {Component => (
+              <Component
+                media={status.get('media_attachments')}
+                sensitive={status.get('sensitive')}
+                height={110}
+                onOpenMedia={noop}
+              />
+            )}
           </Bundle>
         );
       }
     }
 
-    return (
-      <div className='status-check-box'>
-        <div className='status-check-box__status'>
-          <StatusContent status={status} />
-          {media}
-        </div>
+    const labelComponent = (
+      <div className='status-check-box__status poll__option__text'>
+        <div className='detailed-status__display-name'>
+          <div className='detailed-status__display-avatar'>
+            <Avatar account={status.get('account')} size={46} />
+          </div>
 
-        <div className='status-check-box-toggle'>
-          <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
+          <div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
         </div>
+
+        <StatusContent status={status} />
+
+        {media}
       </div>
     );
+
+    return (
+      <Option
+        name='status_ids'
+        value={status.get('id')}
+        checked={checked}
+        onToggle={this.handleStatusesToggle}
+        label={status.get('search_index')}
+        labelComponent={labelComponent}
+        multiple
+      />
+    );
   }
 
 }
diff --git a/app/javascript/mastodon/features/report/containers/status_check_box_container.js b/app/javascript/mastodon/features/report/containers/status_check_box_container.js
index 48cd0319b..65a7c11fd 100644
--- a/app/javascript/mastodon/features/report/containers/status_check_box_container.js
+++ b/app/javascript/mastodon/features/report/containers/status_check_box_container.js
@@ -1,19 +1,15 @@
 import { connect } from 'react-redux';
 import StatusCheckBox from '../components/status_check_box';
-import { toggleStatusReport } from '../../../actions/reports';
-import { Set as ImmutableSet } from 'immutable';
+import { makeGetStatus } from 'mastodon/selectors';
 
-const mapStateToProps = (state, { id }) => ({
-  status: state.getIn(['statuses', id]),
-  checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id),
-});
+const makeMapStateToProps = () => {
+  const getStatus = makeGetStatus();
 
-const mapDispatchToProps = (dispatch, { id }) => ({
+  const mapStateToProps = (state, { id }) => ({
+    status: getStatus(state, { id }),
+  });
 
-  onToggle (e) {
-    dispatch(toggleStatusReport(id, e.target.checked));
-  },
+  return mapStateToProps;
+};
 
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox);
+export default connect(makeMapStateToProps)(StatusCheckBox);
diff --git a/app/javascript/mastodon/features/report/rules.js b/app/javascript/mastodon/features/report/rules.js
new file mode 100644
index 000000000..f2db0d9e4
--- /dev/null
+++ b/app/javascript/mastodon/features/report/rules.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+import { FormattedMessage } from 'react-intl';
+import Button from 'mastodon/components/button';
+import Option from './components/option';
+
+const mapStateToProps = state => ({
+  rules: state.get('rules'),
+});
+
+export default @connect(mapStateToProps)
+class Rules extends React.PureComponent {
+
+  static propTypes = {
+    onNextStep: PropTypes.func.isRequired,
+    rules: ImmutablePropTypes.list,
+    selectedRuleIds: ImmutablePropTypes.set.isRequired,
+    onToggle: PropTypes.func.isRequired,
+  };
+
+  handleNextClick = () => {
+    const { onNextStep } = this.props;
+    onNextStep('statuses');
+  };
+
+  handleRulesToggle = (value, checked) => {
+    const { onToggle } = this.props;
+    onToggle(value, checked);
+  };
+
+  render () {
+    const { rules, selectedRuleIds } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p>
+
+        <div>
+          {rules.map(item => (
+            <Option
+              key={item.get('id')}
+              name='rule_ids'
+              value={item.get('id')}
+              checked={selectedRuleIds.includes(item.get('id'))}
+              onToggle={this.handleRulesToggle}
+              label={item.get('text')}
+              multiple
+            />
+          ))}
+        </div>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleNextClick} disabled={selectedRuleIds.size < 1}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/report/statuses.js b/app/javascript/mastodon/features/report/statuses.js
new file mode 100644
index 000000000..5999a0e06
--- /dev/null
+++ b/app/javascript/mastodon/features/report/statuses.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';
+import { OrderedSet } from 'immutable';
+import { FormattedMessage } from 'react-intl';
+import Button from 'mastodon/components/button';
+
+const mapStateToProps = (state, { accountId }) => ({
+  availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
+});
+
+export default @connect(mapStateToProps)
+class Statuses extends React.PureComponent {
+
+  static propTypes = {
+    onNextStep: PropTypes.func.isRequired,
+    accountId: PropTypes.string.isRequired,
+    availableStatusIds: ImmutablePropTypes.set.isRequired,
+    selectedStatusIds: ImmutablePropTypes.set.isRequired,
+    onToggle: PropTypes.func.isRequired,
+  };
+
+  handleNextClick = () => {
+    const { onNextStep } = this.props;
+    onNextStep('comment');
+  };
+
+  render () {
+    const { availableStatusIds, selectedStatusIds, onToggle } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
+
+        <div className='report-dialog-modal__statuses'>
+          {availableStatusIds.union(selectedStatusIds).map(statusId => (
+            <StatusCheckBox
+              id={statusId}
+              key={statusId}
+              checked={selectedStatusIds.includes(statusId)}
+              onToggle={onToggle}
+            />
+          ))}
+        </div>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleNextClick}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/report/thanks.js b/app/javascript/mastodon/features/report/thanks.js
new file mode 100644
index 000000000..d169b1e32
--- /dev/null
+++ b/app/javascript/mastodon/features/report/thanks.js
@@ -0,0 +1,84 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+import Button from 'mastodon/components/button';
+import { connect } from 'react-redux';
+import {
+  unfollowAccount,
+  muteAccount,
+  blockAccount,
+} from 'mastodon/actions/accounts';
+
+const mapStateToProps = () => ({});
+
+export default @connect(mapStateToProps)
+class Thanks extends React.PureComponent {
+
+  static propTypes = {
+    submitted: PropTypes.bool,
+    onClose: PropTypes.func.isRequired,
+    account: ImmutablePropTypes.map.isRequired,
+    dispatch: PropTypes.func.isRequired,
+  };
+
+  handleCloseClick = () => {
+    const { onClose } = this.props;
+    onClose();
+  };
+
+  handleUnfollowClick = () => {
+    const { dispatch, account, onClose } = this.props;
+    dispatch(unfollowAccount(account.get('id')));
+    onClose();
+  };
+
+  handleMuteClick = () => {
+    const { dispatch, account, onClose } = this.props;
+    dispatch(muteAccount(account.get('id')));
+    onClose();
+  };
+
+  handleBlockClick = () => {
+    const { dispatch, account, onClose } = this.props;
+    dispatch(blockAccount(account.get('id')));
+    onClose();
+  };
+
+  render () {
+    const { account, submitted } = this.props;
+
+    return (
+      <React.Fragment>
+        <h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3>
+        <p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p>
+
+        {account.getIn(['relationship', 'following']) && (
+          <React.Fragment>
+            <h4 className='report-dialog-modal__subtitle'><FormattedMessage id='report.unfollow' defaultMessage='Unfollow @{name}' values={{ name: account.get('username') }} /></h4>
+            <p className='report-dialog-modal__lead'><FormattedMessage id='report.unfollow_explanation' defaultMessage='You are following this account. To not see their posts in your home feed anymore, unfollow them.' /></p>
+            <Button secondary onClick={this.handleUnfollowClick}><FormattedMessage id='account.unfollow' defaultMessage='Unfollow' /></Button>
+            <hr />
+          </React.Fragment>
+        )}
+
+        <h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.mute' defaultMessage='Mute @{name}' values={{ name: account.get('username') }} /></h4>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.mute_explanation' defaultMessage='You will not see their posts. They can still follow you and see your posts and will not know that they are muted.' /></p>
+        <Button secondary onClick={this.handleMuteClick}>{!account.getIn(['relationship', 'muting']) ? <FormattedMessage id='report.mute' defaultMessage='Mute' /> : <FormattedMessage id='account.muted' defaultMessage='Muted' />}</Button>
+
+        <hr />
+
+        <h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.block' defaultMessage='Block @{name}' values={{ name: account.get('username') }} /></h4>
+        <p className='report-dialog-modal__lead'><FormattedMessage id='report.block_explanation' defaultMessage='You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.' /></p>
+        <Button secondary onClick={this.handleBlockClick}>{!account.getIn(['relationship', 'blocking']) ? <FormattedMessage id='report.block' defaultMessage='Block' /> : <FormattedMessage id='account.blocked' defaultMessage='Blocked' />}</Button>
+
+        <div className='flex-spacer' />
+
+        <div className='report-dialog-modal__actions'>
+          <Button onClick={this.handleCloseClick}><FormattedMessage id='report.close' defaultMessage='Done' /></Button>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js
index f4f0a3884..744dd248b 100644
--- a/app/javascript/mastodon/features/ui/components/report_modal.js
+++ b/app/javascript/mastodon/features/ui/components/report_modal.js
@@ -1,38 +1,31 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
-import { expandAccountTimeline } from '../../../actions/timelines';
+import { submitReport } from 'mastodon/actions/reports';
+import { expandAccountTimeline } from 'mastodon/actions/timelines';
+import { fetchRules } from 'mastodon/actions/rules';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { makeGetAccount } from '../../../selectors';
+import { makeGetAccount } from 'mastodon/selectors';
 import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import StatusCheckBox from '../../report/containers/status_check_box_container';
 import { OrderedSet } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import Button from '../../../components/button';
-import Toggle from 'react-toggle';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'mastodon/components/icon_button';
+import Category from 'mastodon/features/report/category';
+import Statuses from 'mastodon/features/report/statuses';
+import Rules from 'mastodon/features/report/rules';
+import Comment from 'mastodon/features/report/comment';
+import Thanks from 'mastodon/features/report/thanks';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
-  placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
-  submit: { id: 'report.submit', defaultMessage: 'Submit' },
 });
 
 const makeMapStateToProps = () => {
   const getAccount = makeGetAccount();
 
-  const mapStateToProps = state => {
-    const accountId = state.getIn(['reports', 'new', 'account_id']);
-
-    return {
-      isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
-      account: getAccount(state, accountId),
-      comment: state.getIn(['reports', 'new', 'comment']),
-      forward: state.getIn(['reports', 'new', 'forward']),
-      statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
-    };
-  };
+  const mapStateToProps = (state, { accountId }) => ({
+    account: getAccount(state, accountId),
+  });
 
   return mapStateToProps;
 };
@@ -42,92 +35,182 @@ export default @connect(makeMapStateToProps)
 class ReportModal extends ImmutablePureComponent {
 
   static propTypes = {
-    isSubmitting: PropTypes.bool,
-    account: ImmutablePropTypes.map,
-    statusIds: ImmutablePropTypes.orderedSet.isRequired,
-    comment: PropTypes.string.isRequired,
-    forward: PropTypes.bool,
+    accountId: PropTypes.string.isRequired,
+    statusId: PropTypes.string,
     dispatch: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
+    account: ImmutablePropTypes.map.isRequired,
   };
 
-  handleCommentChange = e => {
-    this.props.dispatch(changeReportComment(e.target.value));
-  }
-
-  handleForwardChange = e => {
-    this.props.dispatch(changeReportForward(e.target.checked));
-  }
+  state = {
+    step: 'category',
+    selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
+    comment: '',
+    category: null,
+    selectedRuleIds: OrderedSet(),
+    forward: true,
+    isSubmitting: false,
+    isSubmitted: false,
+  };
 
   handleSubmit = () => {
-    this.props.dispatch(submitReport());
-  }
+    const { dispatch, accountId } = this.props;
+    const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
+
+    this.setState({ isSubmitting: true });
+
+    dispatch(submitReport({
+      account_id: accountId,
+      status_ids: selectedStatusIds.toArray(),
+      comment,
+      forward,
+      category,
+      rule_ids: selectedRuleIds.toArray(),
+    }, this.handleSuccess, this.handleFail));
+  };
+
+  handleSuccess = () => {
+    this.setState({ isSubmitting: false, isSubmitted: true, step: 'thanks' });
+  };
+
+  handleFail = () => {
+    this.setState({ isSubmitting: false });
+  };
+
+  handleStatusToggle = (statusId, checked) => {
+    const { selectedStatusIds } = this.state;
+
+    if (checked) {
+      this.setState({ selectedStatusIds: selectedStatusIds.add(statusId) });
+    } else {
+      this.setState({ selectedStatusIds: selectedStatusIds.remove(statusId) });
+    }
+  };
 
-  handleKeyDown = e => {
-    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
-      this.handleSubmit();
+  handleRuleToggle = (ruleId, checked) => {
+    const { selectedRuleIds } = this.state;
+
+    if (checked) {
+      this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
+    } else {
+      this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
     }
   }
 
+  handleChangeCategory = category => {
+    this.setState({ category });
+  };
+
+  handleChangeComment = comment => {
+    this.setState({ comment });
+  };
+
+  handleChangeForward = forward => {
+    this.setState({ forward });
+  };
+
+  handleNextStep = step => {
+    this.setState({ step });
+  };
+
   componentDidMount () {
-    this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true }));
-  }
+    const { dispatch, accountId } = this.props;
 
-  componentWillReceiveProps (nextProps) {
-    if (this.props.account !== nextProps.account && nextProps.account) {
-      this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true }));
-    }
+    dispatch(expandAccountTimeline(accountId, { withReplies: true }));
+    dispatch(fetchRules());
   }
 
   render () {
-    const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
+    const {
+      accountId,
+      account,
+      intl,
+      onClose,
+    } = this.props;
 
     if (!account) {
       return null;
     }
 
-    const domain = account.get('acct').split('@')[1];
+    const {
+      step,
+      selectedStatusIds,
+      selectedRuleIds,
+      comment,
+      forward,
+      category,
+      isSubmitting,
+      isSubmitted,
+    } = this.state;
+
+    const domain   = account.get('acct').split('@')[1];
+    const isRemote = !!domain;
+
+    let stepComponent;
+
+    switch(step) {
+    case 'category':
+      stepComponent = (
+        <Category
+          onNextStep={this.handleNextStep}
+          startedFrom={this.props.statusId ? 'status' : 'account'}
+          category={category}
+          onChangeCategory={this.handleChangeCategory}
+        />
+      );
+      break;
+    case 'rules':
+      stepComponent = (
+        <Rules
+          onNextStep={this.handleNextStep}
+          selectedRuleIds={selectedRuleIds}
+          onToggle={this.handleRuleToggle}
+        />
+      );
+      break;
+    case 'statuses':
+      stepComponent = (
+        <Statuses
+          onNextStep={this.handleNextStep}
+          accountId={accountId}
+          selectedStatusIds={selectedStatusIds}
+          onToggle={this.handleStatusToggle}
+        />
+      );
+      break;
+    case 'comment':
+      stepComponent = (
+        <Comment
+          onSubmit={this.handleSubmit}
+          isSubmitting={isSubmitting}
+          isRemote={isRemote}
+          comment={comment}
+          forward={forward}
+          domain={domain}
+          onChangeComment={this.handleChangeComment}
+          onChangeForward={this.handleChangeForward}
+        />
+      );
+      break;
+    case 'thanks':
+      stepComponent = (
+        <Thanks
+          submitted={isSubmitted}
+          account={account}
+          onClose={onClose}
+        />
+      );
+    }
 
     return (
-      <div className='modal-root__modal report-modal'>
+      <div className='modal-root__modal report-dialog-modal'>
         <div className='report-modal__target'>
           <IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
           <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
         </div>
 
-        <div className='report-modal__container'>
-          <div className='report-modal__comment'>
-            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
-
-            <textarea
-              className='setting-text light'
-              placeholder={intl.formatMessage(messages.placeholder)}
-              value={comment}
-              onChange={this.handleCommentChange}
-              onKeyDown={this.handleKeyDown}
-              disabled={isSubmitting}
-              autoFocus
-            />
-
-            {domain && (
-              <div>
-                <p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
-
-                <div className='setting-toggle'>
-                  <Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
-                  <label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
-                </div>
-              </div>
-            )}
-
-            <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
-          </div>
-
-          <div className='report-modal__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
+        <div className='report-dialog-modal__container'>
+          {stepComponent}
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json
index 2675da68c..5456eb88e 100644
--- a/app/javascript/mastodon/locales/af.json
+++ b/app/javascript/mastodon/locales/af.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index d244ab5fc..33a78afa3 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -21,7 +21,7 @@
   "account.following_counter": "{count, plural, zero{لا يُتابِع} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}",
   "account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.",
   "account.follows_you": "يُتابِعُك",
-  "account.hide_reblogs": "إخفاء تعزيزات @{name}",
+  "account.hide_reblogs": "إخفاء مشاركات @{name}",
   "account.joined": "انضم في {date}",
   "account.last_status": "آخر نشاط",
   "account.link_verified_on": "تمَّ التَّحقق مِن مِلْكيّة هذا الرابط بتاريخ {date}",
@@ -38,7 +38,7 @@
   "account.report": "الإبلاغ عن @{name}",
   "account.requested": "في اِنتظر القُبول. اِنقُر لإلغاء طلب المُتابعة",
   "account.share": "مُشاركة الملف الشخصي لـ @{name}",
-  "account.show_reblogs": "عرض تعزيزات @{name}",
+  "account.show_reblogs": "عرض مشاركات @{name}",
   "account.statuses_counter": "{count, plural, zero {لَا تَبويقات} one {تَبويقةٌ واحدة} two {تَبويقَتانِ اِثنتان} few {{counter} تَبويقات} many {{counter} تَبويقتًا} other {{counter} تَبويقة}}",
   "account.unblock": "إلغاء الحَظر عن @{name}",
   "account.unblock_domain": "إلغاء الحَظر عن النِّطاق {domain}",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "تغيِير الاستطلاع للسماح باِخيار واحد فقط",
   "compose_form.publish": "تبويق",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "احفظ التعديلات",
   "compose_form.sensitive.hide": "{count, plural, one {الإشارة إلى الوَسط كمُحتوى حسّاس} two{الإشارة إلى الوسطان كمُحتويان حسّاسان} other {الإشارة إلى الوسائط كمُحتويات حسّاسة}}",
   "compose_form.sensitive.marked": "{count, plural, one {تمَّ الإشارة إلى الوسط كمُحتوى حسّاس} two{تمَّ الإشارة إلى الوسطان كمُحتويان حسّاسان} other {تمَّ الإشارة إلى الوسائط كمُحتويات حسّاسة}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {لم تَتِمّ الإشارة إلى الوسط كمُحتوى حسّاس} two{لم تَتِمّ الإشارة إلى الوسطان كمُحتويان حسّاسان} other {لم تَتِمّ الإشارة إلى الوسائط كمُحتويات حسّاسة}}",
@@ -248,7 +248,7 @@
   "keyboard_shortcuts.start": "لفتح عمود \"هيا نبدأ\"",
   "keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير",
   "keyboard_shortcuts.toggle_sensitivity": "لعرض/إخفاء الوسائط",
-  "keyboard_shortcuts.toot": "لتحرير تبويق جديد",
+  "keyboard_shortcuts.toot": "للشروع في تحرير منشور جديد",
   "keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث",
   "keyboard_shortcuts.up": "للانتقال إلى أعلى القائمة",
   "lightbox.close": "إغلاق",
@@ -281,7 +281,7 @@
   "navigation_bar.blocks": "الحسابات المحجوبة",
   "navigation_bar.bookmarks": "الفواصل المرجعية",
   "navigation_bar.community_timeline": "الخيط العام المحلي",
-  "navigation_bar.compose": "تحرير تبويق جديد",
+  "navigation_bar.compose": "لتحرير منشور جديد",
   "navigation_bar.direct": "الرسائل المباشِرة",
   "navigation_bar.discover": "اكتشف",
   "navigation_bar.domain_blocks": "النطاقات المخفية",
@@ -306,8 +306,9 @@
   "notification.mention": "{name} ذكرك",
   "notification.own_poll": "انتهى استطلاعك للرأي",
   "notification.poll": "لقد إنتها تصويت شاركت فيه",
-  "notification.reblog": "{name} قام بترقية تبويقك",
+  "notification.reblog": "قام {name} بمشاركة منشورك",
   "notification.status": "{name} نشر للتو",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "امسح الإخطارات",
   "notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
   "notifications.column_settings.alert": "إشعارات سطح المكتب",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "منشورات جديدة:",
   "notifications.column_settings.unread_notifications.category": "إشعارات غير مقروءة",
   "notifications.column_settings.unread_notifications.highlight": "علّم الإشعارات غير المقرؤة",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "الكل",
   "notifications.filter.boosts": "الترقيات",
   "notifications.filter.favourites": "المفضلة",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "جارٍ التحميل…",
   "regeneration_indicator.sublabel": "جارٍ تجهيز تغذية صفحتك الرئيسية!",
   "relative_time.days": "{number}ي",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "منذ {number, plural, zero {} one {# يوم} two {# يومين} few {# أيام} many {# أيام} other {# يوم}}",
+  "relative_time.full.hours": "منذ {number, plural, zero {} one {# ساعة واحدة} two {# ساعتين} few {# ساعات} many {# ساعات} other {# ساعة}}",
+  "relative_time.full.just_now": "الآن",
+  "relative_time.full.minutes": "منذ {number, plural, zero {} one {# دقيقة واحدة} two {# دقيقتين} few {# دقائق} many {# دقيقة} other {# دقائق}}",
+  "relative_time.full.seconds": "منذ {number, plural, zero {} one {# ثانية واحدة} two {# ثانيتين} few {# ثوانٍ} many {# ثوانٍ} other {# ثانية}}",
   "relative_time.hours": "{number}سا",
   "relative_time.just_now": "الآن",
   "relative_time.minutes": "{number}د",
   "relative_time.seconds": "{number}ثا",
   "relative_time.today": "اليوم",
   "reply_indicator.cancel": "إلغاء",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "أخرى",
+  "report.categories.spam": "مزعج",
+  "report.categories.violation": "المحتوى ينتهك شرطا أو عدة شروط استخدام للخادم",
   "report.forward": "التحويل إلى {target}",
   "report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا؟",
   "report.hint": "سوف يتم إرسال التقرير إلى المُشرِفين على خادومكم. بإمكانكم الإدلاء بشرح عن سبب الإبلاغ عن الحساب أسفله:",
@@ -407,9 +409,9 @@
   "status.delete": "احذف",
   "status.detailed_status": "تفاصيل المحادثة",
   "status.direct": "رسالة خاصة إلى @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "تعديل",
+  "status.edited": "عُدّل في {date}",
+  "status.edited_x_times": "عُدّل {count, plural, zero {} one {{count} مرة} two {{count} مرتين} few {{count} مرات} many {{count} مرات} other {{count} مرة}}",
   "status.embed": "إدماج",
   "status.favourite": "أضف إلى المفضلة",
   "status.filtered": "مُصفّى",
@@ -423,12 +425,12 @@
   "status.mute_conversation": "كتم المحادثة",
   "status.open": "وسع هذه المشاركة",
   "status.pin": "دبّسه على الصفحة التعريفية",
-  "status.pinned": "تبويق مثبَّت",
+  "status.pinned": "منشور مثبَّت",
   "status.read_more": "اقرأ المزيد",
   "status.reblog": "رَقِّي",
   "status.reblog_private": "القيام بالترقية إلى الجمهور الأصلي",
   "status.reblogged_by": "رقّاه {name}",
-  "status.reblogs.empty": "لم يقم أي أحد بترقية هذا التبويق بعد. عندما يقوم أحدهم بذلك سوف تظهر هنا.",
+  "status.reblogs.empty": "لم يقم أي أحد بمشاركة هذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
   "status.redraft": "إزالة و إعادة الصياغة",
   "status.remove_bookmark": "احذفه مِن الفواصل المرجعية",
   "status.reply": "ردّ",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index d4c4f84b0..526c0f0f0 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -308,6 +308,7 @@
   "notification.poll": "Finó una encuesta na que votesti",
   "notification.reblog": "{name} compartió'l to estáu",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Llimpiar avisos",
   "notifications.clear_confirmation": "¿De xuru que quies llimpiar dafechu tolos avisos?",
   "notifications.column_settings.alert": "Avisos d'escritoriu",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Too",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 02e37c4c7..14cbddaf3 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -308,6 +308,7 @@
   "notification.poll": "Анкета, в която сте гласували, приключи",
   "notification.reblog": "{name} сподели твоята публикация",
   "notification.status": "{name} току-що публикува",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Изчистване на известия",
   "notifications.clear_confirmation": "Сигурни ли сте, че искате да изчистите окончателно всичките си известия?",
   "notifications.column_settings.alert": "Десктоп известия",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Нови публикации:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Всичко",
   "notifications.filter.boosts": "Споделяния",
   "notifications.filter.favourites": "Любими",
diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json
index ec034b002..0c0a1ca1b 100644
--- a/app/javascript/mastodon/locales/bn.json
+++ b/app/javascript/mastodon/locales/bn.json
@@ -308,6 +308,7 @@
   "notification.poll": "আপনি ভোট দিয়েছিলেন এমন এক  নির্বাচনের ভোটের সময় শেষ হয়েছে",
   "notification.reblog": "{name} আপনার কার্যক্রমে সমর্থন দেখিয়েছেন",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "প্রজ্ঞাপনগুলো মুছে ফেলতে",
   "notifications.clear_confirmation": "আপনি কি নির্চিত প্রজ্ঞাপনগুলো মুছে ফেলতে চান ?",
   "notifications.column_settings.alert": "কম্পিউটারে প্রজ্ঞাপনগুলি",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "সব",
   "notifications.filter.boosts": "সমর্থনগুলো",
   "notifications.filter.favourites": "পছন্দের গুলো",
diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json
index f13b7dfd7..daea18086 100644
--- a/app/javascript/mastodon/locales/br.json
+++ b/app/javascript/mastodon/locales/br.json
@@ -308,6 +308,7 @@
   "notification.poll": "Ur sontadeg ho deus mouezhet warnañ a zo echuet",
   "notification.reblog": "{name} skignet ho toud",
   "notification.status": "{name} en/he deus toudet",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Skarzhañ ar c'hemennoù",
   "notifications.clear_confirmation": "Ha sur oc'h e fell deoc'h skarzhañ ho kemennoù penn-da-benn?",
   "notifications.column_settings.alert": "Kemennoù war ar burev",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Toudoù nevez:",
   "notifications.column_settings.unread_notifications.category": "Kemennoù n'int ket lennet",
   "notifications.column_settings.unread_notifications.highlight": "Usskediñ kemennoù nevez",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Pep tra",
   "notifications.filter.boosts": "Skignadennoù",
   "notifications.filter.favourites": "Muiañ-karet",
@@ -367,7 +369,7 @@
   "relative_time.days": "{number}d",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
+  "relative_time.full.just_now": "bremañ",
   "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
   "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
   "relative_time.hours": "{number}e",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 0ab88e2cd..15036eef9 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -47,8 +47,8 @@
   "account.unmute": "Treure silenci de @{name}",
   "account.unmute_notifications": "Activar notificacions de @{name}",
   "account_note.placeholder": "Fes clic per afegir una nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Ràtio de retenció per dia després del registre",
+  "admin.dashboard.monthly_retention": "Ràtio de retenció per mes després del registre",
   "admin.dashboard.retention.average": "Mitjana",
   "admin.dashboard.retention.cohort": "Registres mes",
   "admin.dashboard.retention.cohort_size": "Nous usuaris",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció",
   "compose_form.publish": "Publicar",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Desa els canvis",
   "compose_form.sensitive.hide": "Marcar mèdia com a sensible",
   "compose_form.sensitive.marked": "Mèdia marcat com a sensible",
   "compose_form.sensitive.unmarked": "Mèdia no està marcat com a sensible",
@@ -308,6 +308,7 @@
   "notification.poll": "Ha finalitzat una enquesta en la que has votat",
   "notification.reblog": "{name} ha impulsat el teu estat",
   "notification.status": "ha publicat {name}",
+  "notification.update": "{name} ha editat una publicació",
   "notifications.clear": "Netejar notificacions",
   "notifications.clear_confirmation": "Estàs segur que vols esborrar permanentment totes les teves notificacions?",
   "notifications.column_settings.alert": "Notificacions d'escriptori",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nous tuts:",
   "notifications.column_settings.unread_notifications.category": "Notificacions no llegides",
   "notifications.column_settings.unread_notifications.highlight": "Destaca notificacions no llegides",
+  "notifications.column_settings.update": "Edicions:",
   "notifications.filter.all": "Tots",
   "notifications.filter.boosts": "Impulsos",
   "notifications.filter.favourites": "Favorits",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Carregant…",
   "regeneration_indicator.sublabel": "S'està preparant la línia de temps Inici!",
   "relative_time.days": "fa {number} dies",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "fa {number, plural, one {# dia} other {# dies}}",
+  "relative_time.full.hours": "fa {number, plural, one {# hora} other {# hores}}",
+  "relative_time.full.just_now": "ara mateix",
+  "relative_time.full.minutes": "fa {number, plural, one {# minut} other {# minuts}}",
+  "relative_time.full.seconds": "fa {number, plural, one {# segon} other {# segons}}",
   "relative_time.hours": "fa {number} hores",
   "relative_time.just_now": "ara",
   "relative_time.minutes": "fa {number} minuts",
   "relative_time.seconds": "fa {number} segons",
   "relative_time.today": "avui",
   "reply_indicator.cancel": "Cancel·lar",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Altres",
+  "report.categories.spam": "Contingut brossa",
+  "report.categories.violation": "El contingut viola una o més regles del servidor",
   "report.forward": "Reenvia a {target}",
   "report.forward_hint": "Aquest compte és d'un altre servidor. Enviar-hi també una copia anònima del informe?",
   "report.hint": "El informe s'enviarà als moderadors del teu servidor. Pots explicar perquè vols informar d'aquest compte aquí:",
@@ -407,14 +409,14 @@
   "status.delete": "Esborrar",
   "status.detailed_status": "Visualització detallada de la conversa",
   "status.direct": "Missatge directe @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Edita",
+  "status.edited": "Editat {date}",
+  "status.edited_x_times": "Editat {count, plural, one {{count} vegada} other {{count} vegades}}",
   "status.embed": "Incrustar",
   "status.favourite": "Favorit",
   "status.filtered": "Filtrat",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} ha creat {date}",
+  "status.history.edited": "{name} ha editat {date}",
   "status.load_more": "Carrega més",
   "status.media_hidden": "Multimèdia amagat",
   "status.mention": "Esmentar @{name}",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index d3fade546..8956cd678 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -308,6 +308,7 @@
   "notification.poll": "Un scandagliu induve avete vutatu hè finitu",
   "notification.reblog": "{name} hà spartutu u vostru statutu",
   "notification.status": "{name} hà appena pubblicatu",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Purgà e nutificazione",
   "notifications.clear_confirmation": "Site sicuru·a che vulete toglie tutte ste nutificazione?",
   "notifications.column_settings.alert": "Nutificazione nant'à l'urdinatore",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Statuti novi:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Tuttu",
   "notifications.filter.boosts": "Spartere",
   "notifications.filter.favourites": "Favuriti",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 2b31d32c3..2c5bf8a85 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -308,6 +308,7 @@
   "notification.poll": "Anketa, ve které jste hlasovali, skončila",
   "notification.reblog": "Uživatel {name} boostnul váš příspěvek",
   "notification.status": "Nový příspěvek od {name}",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Smazat oznámení",
   "notifications.clear_confirmation": "Opravdu chcete trvale smazat všechna vaše oznámení?",
   "notifications.column_settings.alert": "Oznámení na počítači",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nové příspěvky:",
   "notifications.column_settings.unread_notifications.category": "Nepřečtená oznámení",
   "notifications.column_settings.unread_notifications.highlight": "Zvýraznit nepřečtená oznámení",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Vše",
   "notifications.filter.boosts": "Boosty",
   "notifications.filter.favourites": "Oblíbení",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index a6379568c..387f33782 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -308,6 +308,7 @@
   "notification.poll": "Mae pleidlais rydych wedi pleidleisio ynddi wedi dod i ben",
   "notification.reblog": "Hysbysebodd {name} eich tŵt",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clirio hysbysiadau",
   "notifications.clear_confirmation": "Ydych chi'n sicr eich bod am glirio'ch holl hysbysiadau am byth?",
   "notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Pob",
   "notifications.filter.boosts": "Hybiadau",
   "notifications.filter.favourites": "Ffefrynnau",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index 56c1daa13..4e1832fd3 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -47,7 +47,7 @@
   "account.unmute": "Fjern tavsgjort for @{name}",
   "account.unmute_notifications": "Slå notifikationer om @{name} til igen",
   "account_note.placeholder": "Klik for at tilføje notat",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
+  "admin.dashboard.daily_retention": "Brugerfastholdelsesrate efter dag efter tilmelding",
   "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
   "admin.dashboard.retention.average": "Gennemsnitlig",
   "admin.dashboard.retention.cohort": "Tilmeldingsmåned",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
   "compose_form.publish": "Udgiv",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Gem ændringer",
   "compose_form.sensitive.hide": "{count, plural, one {Markér medie som følsomt} other {Markér medier som følsomme}}",
   "compose_form.sensitive.marked": "{count, plural, one {Medie er markeret som sensitivt} other {Medier er markerede som sensitive}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Medie er ikke market som sensitivt} other {Medier er ikke markerede som sensitive}}",
@@ -308,6 +308,7 @@
   "notification.poll": "En afstemning, du deltog i, er færdig",
   "notification.reblog": "{name} fremhævede dit indlæg",
   "notification.status": "{name} har netop udgivet",
+  "notification.update": "{name} redigerede et indlæg",
   "notifications.clear": "Ryd notifikationer",
   "notifications.clear_confirmation": "Er du sikker på, du vil rydde alle dine notifikationer permanent?",
   "notifications.column_settings.alert": "Skrivebordsnotifikationer",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nye indlæg:",
   "notifications.column_settings.unread_notifications.category": "Ulæste notifikationer",
   "notifications.column_settings.unread_notifications.highlight": "Fremhæv ulæste notifikationer",
+  "notifications.column_settings.update": "Redigeringer:",
   "notifications.filter.all": "Alle",
   "notifications.filter.boosts": "Fremhævelser",
   "notifications.filter.favourites": "Favoritter",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Indlæser…",
   "regeneration_indicator.sublabel": "Din hjemmetidslinje klargøres!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# dag} other {# dage}} diden",
+  "relative_time.full.hours": "{number, plural, one {# time} other {# timer}} siden",
+  "relative_time.full.just_now": "netop nu",
+  "relative_time.full.minutes": "{number, plural, one {# minut} other {# minutter}} siden",
+  "relative_time.full.seconds": "{number, plural, one {# sekund} other {# sekunder}} siden",
   "relative_time.hours": "{number}t",
   "relative_time.just_now": "nu",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "i dag",
   "reply_indicator.cancel": "Afbryd",
-  "report.categories.other": "Other",
+  "report.categories.other": "Andre",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Indhold overtræder en eller flere serverregler",
   "report.forward": "Videresend til {target}",
   "report.forward_hint": "Kontoen er fra en anden server. Send en anonymiseret kopi af anmeldelsen dertil også?",
   "report.hint": "Anmeldelsen sendes til din serverordstyrer. Du kan oplyse nærmere om kontoanmeldelsen nedenfor:",
@@ -407,14 +409,14 @@
   "status.delete": "Slet",
   "status.detailed_status": "Detaljeret samtalevisning",
   "status.direct": "Direkte besked til @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Redigér",
+  "status.edited": "Redigeret {date}",
+  "status.edited_x_times": "Redigeret {count, plural, one {{count} gang} other {{count} gange}}",
   "status.embed": "Indlejr",
   "status.favourite": "Favorit",
   "status.filtered": "Filtreret",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} oprettet {date}",
+  "status.history.edited": "{name} redigeret {date}",
   "status.load_more": "Indlæs mere",
   "status.media_hidden": "Medie skjult",
   "status.mention": "Nævn @{name}",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 12ac0b253..89faee676 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -47,8 +47,8 @@
   "account.unmute": "@{name} nicht mehr stummschalten",
   "account.unmute_notifications": "Benachrichtigungen von @{name} einschalten",
   "account_note.placeholder": "Notiz durch Klicken hinzufügen",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Benutzerverbleibrate nach Tag nach Anmeldung",
+  "admin.dashboard.monthly_retention": "Benutzerverbleibrate nach Monat nach Anmeldung",
   "admin.dashboard.retention.average": "Durchschnitt",
   "admin.dashboard.retention.cohort": "Anmeldemonat",
   "admin.dashboard.retention.cohort_size": "Neue Benutzer",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Umfrage ändern, um eine einzige Wahl zu erlauben",
   "compose_form.publish": "Tröt",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Änderungen speichern",
   "compose_form.sensitive.hide": "Medien als NSFW markieren",
   "compose_form.sensitive.marked": "Medien sind als NSFW markiert",
   "compose_form.sensitive.unmarked": "Medien sind nicht als NSFW markiert",
@@ -308,6 +308,7 @@
   "notification.poll": "Eine Umfrage in der du abgestimmt hast ist vorbei",
   "notification.reblog": "{name} hat deinen Beitrag geteilt",
   "notification.status": "{name} hat gerade etwas gepostet",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Mitteilungen löschen",
   "notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchtest?",
   "notifications.column_settings.alert": "Desktop-Benachrichtigungen",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Neue Beiträge:",
   "notifications.column_settings.unread_notifications.category": "Ungelesene Benachrichtigungen",
   "notifications.column_settings.unread_notifications.highlight": "Ungelesene Benachrichtigungen hervorheben",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Alle",
   "notifications.filter.boosts": "Geteilte Beiträge",
   "notifications.filter.favourites": "Favorisierungen",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Laden…",
   "regeneration_indicator.sublabel": "Deine Startseite wird gerade vorbereitet!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "vor {number, plural, one {# Tag} other {# Tagen}}",
+  "relative_time.full.hours": "vor {number, plural, one {# Stunde} other {# Stunden}}",
+  "relative_time.full.just_now": "gerade eben",
+  "relative_time.full.minutes": "vor {number, plural, one {# Minute} other {# Minuten}}",
+  "relative_time.full.seconds": "vor {number, plural, one {1 Sekunde} other {# Sekunden}}",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "jetzt",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "heute",
   "reply_indicator.cancel": "Abbrechen",
-  "report.categories.other": "Other",
+  "report.categories.other": "Andere",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Inhalt verletzt ein oder mehrere Server-Regeln",
   "report.forward": "An {target} weiterleiten",
   "report.forward_hint": "Dieses Konto ist von einem anderen Server. Soll eine anonymisierte Kopie des Berichts auch dorthin geschickt werden?",
   "report.hint": "Der Bericht wird an die Moderatoren des Servers geschickt. Du kannst hier eine Erklärung angeben, warum du dieses Konto meldest:",
@@ -407,14 +409,14 @@
   "status.delete": "Löschen",
   "status.detailed_status": "Detaillierte Ansicht der Konversation",
   "status.direct": "Direktnachricht @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Bearbeiten",
+  "status.edited": "Bearbeitet {date}",
+  "status.edited_x_times": "{count, plural, one {{count} mal} other {{count} mal}} bearbeitet",
   "status.embed": "Einbetten",
   "status.favourite": "Favorisieren",
   "status.filtered": "Gefiltert",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} erstellte {date}",
+  "status.history.edited": "{name} bearbeitete {date}",
   "status.load_more": "Weitere laden",
   "status.media_hidden": "Medien versteckt",
   "status.mention": "@{name} erwähnen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 1f6477e44..5be55db82 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -2408,6 +2408,10 @@
       {
         "defaultMessage": "New toots:",
         "id": "notifications.column_settings.status"
+      },
+      {
+        "defaultMessage": "Edits:",
+        "id": "notifications.column_settings.update"
       }
     ],
     "path": "app/javascript/mastodon/features/notifications/components/column_settings.json"
@@ -2494,6 +2498,10 @@
         "id": "notification.status"
       },
       {
+        "defaultMessage": "{name} edited a post",
+        "id": "notification.update"
+      },
+      {
         "defaultMessage": "{name} has requested to follow you",
         "id": "notification.follow_request"
       }
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 78d071f66..450b3aff8 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Ενημέρωση δημοσκόπησης με μοναδική επιλογή",
   "compose_form.publish": "Τουτ",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Αποθήκευση αλλαγών",
   "compose_form.sensitive.hide": "Σημείωσε τα πολυμέσα ως ευαίσθητα",
   "compose_form.sensitive.marked": "Το πολυμέσο έχει σημειωθεί ως ευαίσθητο",
   "compose_form.sensitive.unmarked": "Το πολυμέσο δεν έχει σημειωθεί ως ευαίσθητο",
@@ -308,6 +308,7 @@
   "notification.poll": "Τελείωσε μια από τις ψηφοφορίες που συμμετείχες",
   "notification.reblog": "Ο/Η {name} προώθησε την κατάστασή σου",
   "notification.status": "Ο/Η {name} μόλις έγραψε κάτι",
+  "notification.update": "{name} επεξεργάστηκε μια δημοσίευση",
   "notifications.clear": "Καθαρισμός ειδοποιήσεων",
   "notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;",
   "notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Νέα τουτ:",
   "notifications.column_settings.unread_notifications.category": "Μη αναγνωσμένες ειδοποιήσεις",
   "notifications.column_settings.unread_notifications.highlight": "Επισήμανση μη αναγνωσμένων ειδοποιήσεων",
+  "notifications.column_settings.update": "Επεξεργασίες:",
   "notifications.filter.all": "Όλες",
   "notifications.filter.boosts": "Προωθήσεις",
   "notifications.filter.favourites": "Αγαπημένα",
@@ -367,7 +369,7 @@
   "relative_time.days": "{number}η",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
+  "relative_time.full.just_now": "μόλις τώρα",
   "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
   "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
   "relative_time.hours": "{number}ω",
@@ -376,9 +378,9 @@
   "relative_time.seconds": "{number}δ",
   "relative_time.today": "σήμερα",
   "reply_indicator.cancel": "Άκυρο",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Άλλες",
+  "report.categories.spam": "Ανεπιθύμητα",
+  "report.categories.violation": "Το περιεχόμενο παραβιάζει έναν ή περισσότερους κανόνες διακομιστή",
   "report.forward": "Προώθηση προς {target}",
   "report.forward_hint": "Ο λογαριασμός είναι από διαφορετικό διακομιστή. Να σταλεί ανώνυμο αντίγραφο της καταγγελίας κι εκεί;",
   "report.hint": "Η καταγγελία θα σταλεί στους διαχειριστές του κόμβου σου. Μπορείς να περιγράψεις γιατί καταγγέλεις αυτόν το λογαριασμό παρακάτω:",
@@ -407,8 +409,8 @@
   "status.delete": "Διαγραφή",
   "status.detailed_status": "Προβολή λεπτομερειών συζήτησης",
   "status.direct": "Προσωπικό μήνυμα προς @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
+  "status.edit": "Επεξεργασία",
+  "status.edited": "Επεξεργάστηκε στις {date}",
   "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
   "status.embed": "Ενσωμάτωσε",
   "status.favourite": "Σημείωσε ως αγαπημένο",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 9b5676ee3..ad3508f8d 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -313,6 +313,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your post",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -331,6 +332,7 @@
   "notifications.column_settings.status": "New posts:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index c3f7e3acd..1039bb695 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -308,6 +308,7 @@
   "notification.poll": "Partoprenita balotenketo finiĝis",
   "notification.reblog": "{name} diskonigis vian mesaĝon",
   "notification.status": "{name} ĵus afiŝita",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Forviŝi sciigojn",
   "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?",
   "notifications.column_settings.alert": "Retumilaj sciigoj",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Novaj mesaĝoj:",
   "notifications.column_settings.unread_notifications.category": "Nelegitaj sciigoj",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Ĉiuj",
   "notifications.filter.boosts": "Diskonigoj",
   "notifications.filter.favourites": "Stelumoj",
diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json
index d57be2717..24902a058 100644
--- a/app/javascript/mastodon/locales/es-AR.json
+++ b/app/javascript/mastodon/locales/es-AR.json
@@ -47,8 +47,8 @@
   "account.unmute": "Dejar de silenciar a @{name}",
   "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
   "account_note.placeholder": "Hacé clic par agregar una nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día, después del registro",
+  "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes, después del registro",
   "admin.dashboard.retention.average": "Promedio",
   "admin.dashboard.retention.cohort": "Mes de registro",
   "admin.dashboard.retention.cohort_size": "Nuevos usuarios",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Cambiar encuesta para permitir una sola opción",
   "compose_form.publish": "Enviar",
   "compose_form.publish_loud": "¡{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Guardar cambios",
   "compose_form.sensitive.hide": "Marcar medio como sensible",
   "compose_form.sensitive.marked": "{count, plural, one {El medio está marcado como sensible} other {Los medios están marcados como sensibles}}",
   "compose_form.sensitive.unmarked": "El medio no está marcado como sensible",
@@ -308,6 +308,7 @@
   "notification.poll": "Finalizó una encuesta en la que votaste",
   "notification.reblog": "{name} adhirió a tu mensaje",
   "notification.status": "{name} acaba de enviar un mensaje",
+  "notification.update": "{name} editó un mensaje",
   "notifications.clear": "Limpiar notificaciones",
   "notifications.clear_confirmation": "¿Estás seguro que querés limpiar todas tus notificaciones permanentemente?",
   "notifications.column_settings.alert": "Notificaciones de escritorio",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nuevos mensajes:",
   "notifications.column_settings.unread_notifications.category": "Notificaciones sin leer",
   "notifications.column_settings.unread_notifications.highlight": "Resaltar notificaciones no leídas",
+  "notifications.column_settings.update": "Ediciones:",
   "notifications.filter.all": "Todas",
   "notifications.filter.boosts": "Adhesiones",
   "notifications.filter.favourites": "Favoritos",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Cargando…",
   "regeneration_indicator.sublabel": "¡Se está preparando tu línea temporal principal!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural,one {hace # día} other {hace # días}}",
+  "relative_time.full.hours": "{number, plural,one {hace # hora} other {hace # horas}}",
+  "relative_time.full.just_now": "recién",
+  "relative_time.full.minutes": "{number, plural,one {hace # minuto} other {hace # minutos}}",
+  "relative_time.full.seconds": "{number, plural,one {hace # segundo} other {hace # segundos}}",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "ahora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "hoy",
   "reply_indicator.cancel": "Cancelar",
-  "report.categories.other": "Other",
+  "report.categories.other": "Otra",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "El contenido viola una o más reglas del servidor",
   "report.forward": "Reenviar a {target}",
   "report.forward_hint": "La cuenta es de otro servidor. ¿Querés enviar una copia anonimizada del informe también ahí?",
   "report.hint": "La denuncia se enviará a los moderadores de tu servidor. A continuación, podés proporcionar una explicación de por qué estás denunciando esta cuenta:",
@@ -407,14 +409,14 @@
   "status.delete": "Eliminar",
   "status.detailed_status": "Vista de conversación detallada",
   "status.direct": "Mensaje directo para @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Editar",
+  "status.edited": "Editado {date}",
+  "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
   "status.embed": "Insertar",
   "status.favourite": "Marcar como favorito",
   "status.filtered": "Filtrado",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "Creado por {name} el {date}",
+  "status.history.edited": "Editado por {name} el {date}",
   "status.load_more": "Cargar más",
   "status.media_hidden": "Medios ocultos",
   "status.mention": "Mencionar a @{name}",
diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json
index ba56241ff..ad967f4e9 100644
--- a/app/javascript/mastodon/locales/es-MX.json
+++ b/app/javascript/mastodon/locales/es-MX.json
@@ -47,8 +47,8 @@
   "account.unmute": "Dejar de silenciar a @{name}",
   "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
   "account_note.placeholder": "Clic para añadir nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro",
+  "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro",
   "admin.dashboard.retention.average": "Promedio",
   "admin.dashboard.retention.cohort": "Mes de registro",
   "admin.dashboard.retention.cohort_size": "Nuevos usuarios",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
   "compose_form.publish": "Tootear",
   "compose_form.publish_loud": "¡{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Guardar cambios",
   "compose_form.sensitive.hide": "Marcar multimedia como sensible",
   "compose_form.sensitive.marked": "Material marcado como sensible",
   "compose_form.sensitive.unmarked": "Material no marcado como sensible",
@@ -308,6 +308,7 @@
   "notification.poll": "Una encuesta en la que has votado ha terminado",
   "notification.reblog": "{name} ha retooteado tu estado",
   "notification.status": "{name} acaba de publicar",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Limpiar notificaciones",
   "notifications.clear_confirmation": "¿Seguro que quieres limpiar permanentemente todas tus notificaciones?",
   "notifications.column_settings.alert": "Notificaciones de escritorio",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nuevos toots:",
   "notifications.column_settings.unread_notifications.category": "Notificaciones sin leer",
   "notifications.column_settings.unread_notifications.highlight": "Destacar notificaciones no leídas",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Todos",
   "notifications.filter.boosts": "Retoots",
   "notifications.filter.favourites": "Favoritos",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Cargando…",
   "regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
   "relative_time.days": "{number} d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "hace {number, plural, one {# día} other {# días}}",
+  "relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}",
+  "relative_time.full.just_now": "justo ahora",
+  "relative_time.full.minutes": "hace {number, plural, one {# minuto} other {# minutos}}",
+  "relative_time.full.seconds": "hace {number, plural, one {# segundo} other {# segundos}}",
   "relative_time.hours": "{number} h",
   "relative_time.just_now": "ahora",
   "relative_time.minutes": "{number} m",
   "relative_time.seconds": "{number} s",
   "relative_time.today": "hoy",
   "reply_indicator.cancel": "Cancelar",
-  "report.categories.other": "Other",
+  "report.categories.other": "Otros",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "El contenido viola una o más reglas del servidor",
   "report.forward": "Reenviar a {target}",
   "report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?",
   "report.hint": "El informe se enviará a los moderadores de tu instancia. Puedes proporcionar una explicación de por qué informas sobre esta cuenta a continuación:",
@@ -407,14 +409,14 @@
   "status.delete": "Borrar",
   "status.detailed_status": "Vista de conversación detallada",
   "status.direct": "Mensaje directo a @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Editar",
+  "status.edited": "Editado {date}",
+  "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
   "status.embed": "Incrustado",
   "status.favourite": "Favorito",
   "status.filtered": "Filtrado",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} creó {date}",
+  "status.history.edited": "{name} editó {date}",
   "status.load_more": "Cargar más",
   "status.media_hidden": "Contenido multimedia oculto",
   "status.mention": "Mencionar",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 0b91f0809..3ac5a9c05 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -47,8 +47,8 @@
   "account.unmute": "Dejar de silenciar a @{name}",
   "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
   "account_note.placeholder": "Clic para añadir nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro",
+  "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro",
   "admin.dashboard.retention.average": "Media",
   "admin.dashboard.retention.cohort": "Mes de registro",
   "admin.dashboard.retention.cohort_size": "Nuevos usuarios",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
   "compose_form.publish": "Tootear",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Guardar cambios",
   "compose_form.sensitive.hide": "{count, plural, one {Marcar material como sensible} other {Marcar material como sensible}}",
   "compose_form.sensitive.marked": "{count, plural, one {Material marcado como sensible} other {Material marcado como sensible}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Material no marcado como sensible} other {Material no marcado como sensible}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Una encuesta en la que has votado ha terminado",
   "notification.reblog": "{name} ha retooteado tu publicación",
   "notification.status": "{name} acaba de publicar",
+  "notification.update": "{name} editó una publicación",
   "notifications.clear": "Limpiar notificaciones",
   "notifications.clear_confirmation": "¿Seguro que quieres limpiar permanentemente todas tus notificaciones?",
   "notifications.column_settings.alert": "Notificaciones de escritorio",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nuevas publicaciones:",
   "notifications.column_settings.unread_notifications.category": "Notificaciones sin leer",
   "notifications.column_settings.unread_notifications.highlight": "Destacar notificaciones no leídas",
+  "notifications.column_settings.update": "Ediciones:",
   "notifications.filter.all": "Todos",
   "notifications.filter.boosts": "Retoots",
   "notifications.filter.favourites": "Favoritos",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Cargando…",
   "regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
   "relative_time.days": "{number} d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "hace {number, plural, one {# día} other {# días}}",
+  "relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}",
+  "relative_time.full.just_now": "justo ahora",
+  "relative_time.full.minutes": "hace {number, plural, one {# minuto} other {# minutos}}",
+  "relative_time.full.seconds": "hace {number, plural, one {# segundo} other {# segundos}}",
   "relative_time.hours": "{number} h",
   "relative_time.just_now": "ahora",
   "relative_time.minutes": "{number} m",
   "relative_time.seconds": "{number} s",
   "relative_time.today": "hoy",
   "reply_indicator.cancel": "Cancelar",
-  "report.categories.other": "Other",
+  "report.categories.other": "Otros",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "El contenido viola una o más reglas del servidor",
   "report.forward": "Reenviar a {target}",
   "report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?",
   "report.hint": "El informe se enviará a los moderadores de tu instancia. Puedes proporcionar una explicación de por qué informas sobre esta cuenta a continuación:",
@@ -407,14 +409,14 @@
   "status.delete": "Borrar",
   "status.detailed_status": "Vista de conversación detallada",
   "status.direct": "Mensaje directo a @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Editar",
+  "status.edited": "Editado {date}",
+  "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
   "status.embed": "Incrustado",
   "status.favourite": "Favorito",
   "status.filtered": "Filtrado",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} creó {date}",
+  "status.history.edited": "{name} editó {date}",
   "status.load_more": "Cargar más",
   "status.media_hidden": "Contenido multimedia oculto",
   "status.mention": "Mencionar",
diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json
index c1c71a91a..8f180c411 100644
--- a/app/javascript/mastodon/locales/et.json
+++ b/app/javascript/mastodon/locales/et.json
@@ -308,6 +308,7 @@
   "notification.poll": "Küsitlus, milles osalesite, on lõppenud",
   "notification.reblog": "{name} upitas Teie staatust",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Puhasta teated",
   "notifications.clear_confirmation": "Olete kindel, et soovite püsivalt kõik oma teated eemaldada?",
   "notifications.column_settings.alert": "Töölauateated",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Kõik",
   "notifications.filter.boosts": "Upitused",
   "notifications.filter.favourites": "Lemmikud",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 4581dfe95..da3d66a9a 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -47,8 +47,8 @@
   "account.unmute": "Desmututu @{name}",
   "account.unmute_notifications": "Desmututu @{name}(r)en jakinarazpenak",
   "account_note.placeholder": "Click to add a note",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Erabiltzaile atxikitze-tasa izena eman ondorengo eguneko",
+  "admin.dashboard.monthly_retention": "Erabiltzaile atxikitze-tasa izena eman ondorengo hilabeteko",
   "admin.dashboard.retention.average": "Batezbestekoa",
   "admin.dashboard.retention.cohort": "Izen emate hilean",
   "admin.dashboard.retention.cohort_size": "Erabiltzaile berriak",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Aldatu inkesta aukera bakarra onartzeko",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Gorde aldaketak",
   "compose_form.sensitive.hide": "Markatu multimedia hunkigarri gisa",
   "compose_form.sensitive.marked": "Multimedia edukia hunkigarri gisa markatu da",
   "compose_form.sensitive.unmarked": "Multimedia edukia ez da hunkigarri gisa markatu",
@@ -308,6 +308,7 @@
   "notification.poll": "Zuk erantzun duzun inkesta bat bukatu da",
   "notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari",
   "notification.status": "{name} erabiltzaileak bidalketa egin berri du",
+  "notification.update": "{name} erabiltzaileak bidalketa bat editatu du",
   "notifications.clear": "Garbitu jakinarazpenak",
   "notifications.clear_confirmation": "Ziur zure jakinarazpen guztiak behin betirako garbitu nahi dituzula?",
   "notifications.column_settings.alert": "Mahaigaineko jakinarazpenak",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Bidalketa berriak:",
   "notifications.column_settings.unread_notifications.category": "Irakurri gabeko jakinarazpenak",
   "notifications.column_settings.unread_notifications.highlight": "Nabarmendu irakurri gabeko jakinarazpenak",
+  "notifications.column_settings.update": "Edizioak:",
   "notifications.filter.all": "Denak",
   "notifications.filter.boosts": "Bultzadak",
   "notifications.filter.favourites": "Gogokoak",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Kargatzen…",
   "regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!",
   "relative_time.days": "{number}e",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "Duela {number, plural, one {egun #} other {# egun}}",
+  "relative_time.full.hours": "Duela {number, plural, one {ordu #} other {# ordu}}",
+  "relative_time.full.just_now": "oraintxe",
+  "relative_time.full.minutes": "Duela {number, plural, one {minutu #} other {# minutu}}",
+  "relative_time.full.seconds": "Duela {number, plural, one {segundo #} other {# segundo}}",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "orain",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "gaur",
   "reply_indicator.cancel": "Utzi",
-  "report.categories.other": "Other",
+  "report.categories.other": "Bestelakoak",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Edukiak zerbitzariko arau bat edo gehiago urratzen ditu",
   "report.forward": "Birbidali hona: {target}",
   "report.forward_hint": "Kontu hau beste zerbitzari batekoa da. Bidali txostenaren kopia anonimo hara ere?",
   "report.hint": "Txostena zure zerbitzariaren moderatzaileei bidaliko zaie. Kontu hau zergatik salatzen duzun behean azaldu dezakezu:",
@@ -407,14 +409,14 @@
   "status.delete": "Ezabatu",
   "status.detailed_status": "Elkarrizketaren ikuspegi xehetsua",
   "status.direct": "Mezu zuzena @{name}(r)i",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Editatu",
+  "status.edited": "Editatua {date}",
+  "status.edited_x_times": "{count, plural, one {behin} other {{count} aldiz}} editatua",
   "status.embed": "Txertatu",
   "status.favourite": "Gogokoa",
   "status.filtered": "Iragazita",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} erabiltzaileak sortua {date}",
+  "status.history.edited": "{name} erabiltzaileak editatua {date}",
   "status.load_more": "Kargatu gehiago",
   "status.media_hidden": "Multimedia ezkutatua",
   "status.mention": "Aipatu @{name}",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index f36850dc2..76dd3fdc0 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -308,6 +308,7 @@
   "notification.poll": "نظرسنجی‌ای که در آن رأی دادید به پایان رسیده است",
   "notification.reblog": "‫{name}‬ فرسته‌تان را تقویت کرد",
   "notification.status": "{name} چیزی فرستاد",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "پاک‌سازی آگاهی‌ها",
   "notifications.clear_confirmation": "مطمئنید می‌خواهید همهٔ آگاهی‌هایتان را برای همیشه پاک کنید؟",
   "notifications.column_settings.alert": "آگاهی‌های میزکار",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "فرسته‌های جدید:",
   "notifications.column_settings.unread_notifications.category": "آگاهی‌های خوانده نشده",
   "notifications.column_settings.unread_notifications.highlight": "پررنگ کردن آگاهی‌های خوانده نشده",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "همه",
   "notifications.filter.boosts": "تقویت‌ها",
   "notifications.filter.favourites": "پسندها",
@@ -412,7 +414,7 @@
   "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
   "status.embed": "جاگذاری",
   "status.favourite": "پسندیدن",
-  "status.filtered": "پالایش‌شده",
+  "status.filtered": "پالوده",
   "status.history.created": "{name} created {date}",
   "status.history.edited": "{name} edited {date}",
   "status.load_more": "بار کردن بیش‌تر",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 58e65bed1..032344abe 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -47,8 +47,8 @@
   "account.unmute": "Poista käyttäjän @{name} mykistys",
   "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta",
   "account_note.placeholder": "Lisää muistiinpano napsauttamalla",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Käyttäjän säilyminen rekisteröitymisen jälkeiseen päivään mennessä",
+  "admin.dashboard.monthly_retention": "Käyttäjän säilyminen rekisteröitymisen jälkeiseen kuukauteen mennessä",
   "admin.dashboard.retention.average": "Keskimäärin",
   "admin.dashboard.retention.cohort": "Kirjautumiset",
   "admin.dashboard.retention.cohort_size": "Uudet käyttäjät",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Muuta kysely sallimaan vain yksi valinta",
   "compose_form.publish": "Lähetä viesti",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Tallenna muutokset",
   "compose_form.sensitive.hide": "{count, plural, one {Merkitse media arkaluontoiseksi} other {Merkitse media arkaluontoiseksi}}",
   "compose_form.sensitive.marked": "{count, plural, one {Media on merkitty arkaluontoiseksi} other {Media on merkitty arkaluontoiseksi}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Mediaa ei ole merkitty arkaluontoiseksi} other {Mediaa ei ole merkitty arkaluontoiseksi}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Kysely, johon osallistuit, on päättynyt",
   "notification.reblog": "{name} buustasi julkaisusi",
   "notification.status": "{name} julkaisi juuri",
+  "notification.update": "{name} muokkasi viestiä",
   "notifications.clear": "Tyhjennä ilmoitukset",
   "notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?",
   "notifications.column_settings.alert": "Työpöytäilmoitukset",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Uudet julkaisut:",
   "notifications.column_settings.unread_notifications.category": "Lukemattomat ilmoitukset",
   "notifications.column_settings.unread_notifications.highlight": "Korosta lukemattomat ilmoitukset",
+  "notifications.column_settings.update": "Muokkaukset:",
   "notifications.filter.all": "Kaikki",
   "notifications.filter.boosts": "Buustit",
   "notifications.filter.favourites": "Suosikit",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Ladataan…",
   "regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!",
   "relative_time.days": "{number} pv",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# päivä} other {# päivää}} sitten",
+  "relative_time.full.hours": "{number, plural, one {# tunti} other {# tuntia}} sitten",
+  "relative_time.full.just_now": "juuri nyt",
+  "relative_time.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} sitten",
+  "relative_time.full.seconds": "{number, plural, one {# sekunti} other {# sekuntia}} sitten",
   "relative_time.hours": "{number} tuntia",
   "relative_time.just_now": "nyt",
   "relative_time.minutes": "{number} min",
   "relative_time.seconds": "{number} sek",
   "relative_time.today": "tänään",
   "reply_indicator.cancel": "Peruuta",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Muu",
+  "report.categories.spam": "Roskaposti",
+  "report.categories.violation": "Sisältö rikkoo yhden tai useamman palvelimen sääntöjä",
   "report.forward": "Välitä kohteeseen {target}",
   "report.forward_hint": "Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne?",
   "report.hint": "Raportti lähetetään oman palvelimesi moderaattoreille. Voit kertoa alla, miksi raportoit tästä tilistä:",
@@ -407,14 +409,14 @@
   "status.delete": "Poista",
   "status.detailed_status": "Yksityiskohtainen keskustelunäkymä",
   "status.direct": "Pikaviesti käyttäjälle @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Muokkaa",
+  "status.edited": "Muokattu {date}",
+  "status.edited_x_times": "Muokattu {count, plural, one {{count} aika} other {{count} kertaa}}",
   "status.embed": "Upota",
   "status.favourite": "Tykkää",
   "status.filtered": "Suodatettu",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} luotu {date}",
+  "status.history.edited": "{name} muokkasi {date}",
   "status.load_more": "Lataa lisää",
   "status.media_hidden": "Media piilotettu",
   "status.mention": "Mainitse @{name}",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 8c2b8f3e6..c12eb7e6c 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -13,7 +13,7 @@
   "account.domain_blocked": "Domaine bloqué",
   "account.edit_profile": "Modifier le profil",
   "account.enable_notifications": "Me notifier quand @{name} publie",
-  "account.endorse": "Recommander sur le profil",
+  "account.endorse": "Recommander sur votre profil",
   "account.follow": "Suivre",
   "account.followers": "Abonnés",
   "account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.",
@@ -34,7 +34,7 @@
   "account.muted": "Masqué·e",
   "account.never_active": "Jamais",
   "account.posts": "Messages",
-  "account.posts_with_replies": "Partages et réponses",
+  "account.posts_with_replies": "Messages et réponses",
   "account.report": "Signaler @{name}",
   "account.requested": "En attente d’approbation. Cliquez pour annuler la requête",
   "account.share": "Partager le profil de @{name}",
@@ -47,8 +47,8 @@
   "account.unmute": "Ne plus masquer @{name}",
   "account.unmute_notifications": "Ne plus masquer les notifications de @{name}",
   "account_note.placeholder": "Cliquez pour ajouter une note",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Taux de maintien des utilisateur·rice·s par jour après inscription",
+  "admin.dashboard.monthly_retention": "Brugerfastholdelsesrate efter måned efter tilmelding",
   "admin.dashboard.retention.average": "Moyenne",
   "admin.dashboard.retention.cohort": "Mois d'inscription",
   "admin.dashboard.retention.cohort_size": "Nouveaux utilisateurs",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Changer le sondage pour autoriser qu'un seul choix",
   "compose_form.publish": "Pouet",
   "compose_form.publish_loud": "{publish} !",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Enregistrer les modifications",
   "compose_form.sensitive.hide": "Marquer le média comme sensible",
   "compose_form.sensitive.marked": "{count, plural, one {Le média est marqué comme sensible} other {Les médias sont marqués comme sensibles}}",
   "compose_form.sensitive.unmarked": "Le média n’est pas marqué comme sensible",
@@ -134,7 +134,7 @@
   "confirmations.reply.confirm": "Répondre",
   "confirmations.reply.message": "Répondre maintenant écrasera le message que vous rédigez actuellement. Voulez-vous vraiment continuer ?",
   "confirmations.unfollow.confirm": "Ne plus suivre",
-  "confirmations.unfollow.message": "Voulez-vous vraiment arrêter de suivre {name} ?",
+  "confirmations.unfollow.message": "Voulez-vous vraiment vous désabonner de {name} ?",
   "conversation.delete": "Supprimer la conversation",
   "conversation.mark_as_read": "Marquer comme lu",
   "conversation.open": "Afficher la conversation",
@@ -288,7 +288,7 @@
   "navigation_bar.edit_profile": "Modifier le profil",
   "navigation_bar.favourites": "Favoris",
   "navigation_bar.filters": "Mots masqués",
-  "navigation_bar.follow_requests": "Demandes de suivi",
+  "navigation_bar.follow_requests": "Demandes d’abonnement",
   "navigation_bar.follows_and_followers": "Abonnements et abonné⋅e·s",
   "navigation_bar.info": "À propos de ce serveur",
   "navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
@@ -308,6 +308,7 @@
   "notification.poll": "Un sondage auquel vous avez participé vient de se terminer",
   "notification.reblog": "{name} a partagé votre message",
   "notification.status": "{name} vient de publier",
+  "notification.update": "{name} a modifié un message",
   "notifications.clear": "Effacer les notifications",
   "notifications.clear_confirmation": "Voulez-vous vraiment effacer toutes vos notifications ?",
   "notifications.column_settings.alert": "Notifications du navigateur",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nouveaux messages :",
   "notifications.column_settings.unread_notifications.category": "Notifications non lues",
   "notifications.column_settings.unread_notifications.highlight": "Surligner les notifications non lues",
+  "notifications.column_settings.update": "Modifications :",
   "notifications.filter.all": "Tout",
   "notifications.filter.boosts": "Partages",
   "notifications.filter.favourites": "Favoris",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Chargement…",
   "regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation !",
   "relative_time.days": "{number} j",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}",
+  "relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}",
+  "relative_time.full.just_now": "à l’instant",
+  "relative_time.full.minutes": "il y a {number, plural, one {# minute} other {# minutes}}",
+  "relative_time.full.seconds": "il y a {number, plural, one {# second} other {# seconds}}",
   "relative_time.hours": "{number} h",
   "relative_time.just_now": "à l’instant",
   "relative_time.minutes": "{number} min",
   "relative_time.seconds": "{number} s",
   "relative_time.today": "aujourd’hui",
   "reply_indicator.cancel": "Annuler",
-  "report.categories.other": "Other",
+  "report.categories.other": "Autre",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Le contenu enfreint une ou plusieurs règles du serveur",
   "report.forward": "Transférer à {target}",
   "report.forward_hint": "Le compte provient d’un autre serveur. Envoyer également une copie anonyme du rapport ?",
   "report.hint": "Le rapport sera envoyé aux modérateur·rice·s de votre serveur. Vous pouvez expliquer pourquoi vous signalez le compte ci-dessous :",
@@ -389,7 +391,7 @@
   "search_popout.search_format": "Recherche avancée",
   "search_popout.tips.full_text": "Un texte normal retourne les messages que vous avez écrits, ajoutés à vos favoris, partagés, ou vous mentionnant, ainsi que les identifiants, les noms affichés, et les hashtags des personnes et messages correspondants.",
   "search_popout.tips.hashtag": "hashtag",
-  "search_popout.tips.status": "statuts",
+  "search_popout.tips.status": "message",
   "search_popout.tips.text": "Un texte simple renvoie les noms affichés, les identifiants et les hashtags correspondants",
   "search_popout.tips.user": "utilisateur·ice",
   "search_results.accounts": "Comptes",
@@ -407,14 +409,14 @@
   "status.delete": "Supprimer",
   "status.detailed_status": "Vue détaillée de la conversation",
   "status.direct": "Envoyer un message direct à @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Éditer",
+  "status.edited": "Édité le {date}",
+  "status.edited_x_times": "Edité {count, plural, one {{count} fois} other {{count} fois}}",
   "status.embed": "Intégrer",
   "status.favourite": "Ajouter aux favoris",
   "status.filtered": "Filtré",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "créé par {name} {date}",
+  "status.history.edited": "édité par {name} {date}",
   "status.load_more": "Charger plus",
   "status.media_hidden": "Média caché",
   "status.mention": "Mentionner @{name}",
diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json
index c9f2cd414..60e6441c3 100644
--- a/app/javascript/mastodon/locales/ga.json
+++ b/app/javascript/mastodon/locales/ga.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json
index 750674ca2..a7ffbe6b2 100644
--- a/app/javascript/mastodon/locales/gd.json
+++ b/app/javascript/mastodon/locales/gd.json
@@ -308,6 +308,7 @@
   "notification.poll": "Thàinig cunntas-bheachd sa bhòt thu gu crìoch",
   "notification.reblog": "Bhrosnaich {name} am post agad",
   "notification.status": "Tha {name} air rud a phostadh",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Falamhaich na brathan",
   "notifications.clear_confirmation": "A bheil thu cinnteach gu bheil thu airson na brathan uile agad fhalamhachadh gu buan?",
   "notifications.column_settings.alert": "Brathan deasga",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Postaichean ùra:",
   "notifications.column_settings.unread_notifications.category": "Brathan nach deach a leughadh",
   "notifications.column_settings.unread_notifications.highlight": "Soillsich na brathan nach deach a leughadh",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Na h-uile",
   "notifications.filter.boosts": "Brosnachaidhean",
   "notifications.filter.favourites": "Na h-annsachdan",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index e926239bb..39f121065 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -47,8 +47,8 @@
   "account.unmute": "Deixar de silenciar a @{name}",
   "account.unmute_notifications": "Deixar de silenciar as notificacións de @{name}",
   "account_note.placeholder": "Preme para engadir nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Ratio de retención de usuarias após rexistrarse",
+  "admin.dashboard.monthly_retention": "Ratio de retención de usuarias após un mes do rexistro",
   "admin.dashboard.retention.average": "Media",
   "admin.dashboard.retention.cohort": "Mes de rexistro",
   "admin.dashboard.retention.cohort_size": "Novas usuarias",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Mudar a enquisa para permitir unha soa escolla",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Gardar cambios",
   "compose_form.sensitive.hide": "{count, plural, one {Marca multimedia como sensible} other {Marca multimedia como sensibles}}",
   "compose_form.sensitive.marked": "{count, plural, one {Multimedia marcado como sensible} other {Multimedia marcados como sensibles}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Multimedia non marcado como sensible} other {Multimedia non marcado como sensible}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Unha enquisa na que votaches rematou",
   "notification.reblog": "{name} compartiu a túa publicación",
   "notification.status": "{name} publicou",
+  "notification.update": "{name} editou unha publicación",
   "notifications.clear": "Limpar notificacións",
   "notifications.clear_confirmation": "Tes a certeza de querer limpar de xeito permanente todas as túas notificacións?",
   "notifications.column_settings.alert": "Notificacións de escritorio",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Novas publicacións:",
   "notifications.column_settings.unread_notifications.category": "Notificacións non lidas",
   "notifications.column_settings.unread_notifications.highlight": "Resaltar notificacións non lidas",
+  "notifications.column_settings.update": "Edicións:",
   "notifications.filter.all": "Todo",
   "notifications.filter.boosts": "Compartidos",
   "notifications.filter.favourites": "Favoritos",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Estase a cargar…",
   "regeneration_indicator.sublabel": "Estase a preparar a túa cronoloxía de inicio!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "fai {number, plural, one {# día} other {# días}}",
+  "relative_time.full.hours": "fai {number, plural, one {# hora} other {# horas}} ago",
+  "relative_time.full.just_now": "xusto agora",
+  "relative_time.full.minutes": "fai {number, plural, one {# minuto} other {# minutos}}",
+  "relative_time.full.seconds": "fai {number, plural, one {# segundo} other {# segundos}}",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "agora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "hoxe",
   "reply_indicator.cancel": "Desbotar",
-  "report.categories.other": "Other",
+  "report.categories.other": "Outro",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "O contido viola unha ou máis regras do servidor",
   "report.forward": "Reenviar a {target}",
   "report.forward_hint": "A conta é doutro servidor. Enviar unha copia anónima da denuncia aló tamén?",
   "report.hint": "A denuncia enviarase á moderación do teu servidor. Abaixo podes explicar a razón pola que estás a denunciar:",
@@ -407,14 +409,14 @@
   "status.delete": "Eliminar",
   "status.detailed_status": "Vista detallada da conversa",
   "status.direct": "Mensaxe directa a @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Editar",
+  "status.edited": "Editado {date}",
+  "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
   "status.embed": "Incrustar",
   "status.favourite": "Favorito",
   "status.filtered": "Filtrado",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} creado o {date}",
+  "status.history.edited": "{name} editado o {date}",
   "status.load_more": "Cargar máis",
   "status.media_hidden": "Contido multimedia agochado",
   "status.mention": "Mencionar @{name}",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 1447ff711..c163812ac 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "חצרוצך הודהד על ידי {name}",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} ערכו פוסט",
   "notifications.clear": "הסרת התראות",
   "notifications.clear_confirmation": "להסיר את כל ההתראות? בטוח?",
   "notifications.column_settings.alert": "התראות לשולחן העבודה",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "שינויים:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
index d3221c468..fef87bda2 100644
--- a/app/javascript/mastodon/locales/hi.json
+++ b/app/javascript/mastodon/locales/hi.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "सभी",
   "notifications.filter.boosts": "बूस्ट",
   "notifications.filter.favourites": "पसंदीदा",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index ecdfab67a..32deff205 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -308,6 +308,7 @@
   "notification.poll": "Anketa u kojoj ste glasali je završila",
   "notification.reblog": "{name} je boostao/la Vaš status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Očisti obavijesti",
   "notifications.clear_confirmation": "Želite li zaista trajno očistiti sve Vaše obavijesti?",
   "notifications.column_settings.alert": "Obavijesti radne površine",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Sve",
   "notifications.filter.boosts": "Boostovi",
   "notifications.filter.favourites": "Favoriti",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 4a55c85d9..97b985d95 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -17,7 +17,7 @@
   "account.follow": "Követés",
   "account.followers": "Követő",
   "account.followers.empty": "Ezt a felhasználót még senki sem követi.",
-  "account.followers_counter": "{count, plural, one {{counter} követő} other {{counter} követő}}",
+  "account.followers_counter": "{count, plural, one {{counter} Követő} other {{counter} Követő}}",
   "account.following_counter": "{count, plural, other {{counter} Követett}}",
   "account.follows.empty": "Ez a felhasználó még senkit sem követ.",
   "account.follows_you": "Követ téged",
@@ -47,8 +47,8 @@
   "account.unmute": "@{name} némítás feloldása",
   "account.unmute_notifications": "@{name} némított értesítéseinek feloldása",
   "account_note.placeholder": "Klikk a feljegyzéshez",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Napi regisztráció utáni felhasználómegtartási arány",
+  "admin.dashboard.monthly_retention": "Havi regisztráció utáni felhasználómegtartási arány",
   "admin.dashboard.retention.average": "Átlag",
   "admin.dashboard.retention.cohort": "Regisztráció hónapja",
   "admin.dashboard.retention.cohort_size": "Új felhasználó",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Szavazás megváltoztatása egyetlen választásosra",
   "compose_form.publish": "Tülk",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Módosítások mentése",
   "compose_form.sensitive.hide": "{count, plural, one {Média kényesnek jelölése} other {Média kényesnek jelölése}}",
   "compose_form.sensitive.marked": "{count, plural, one {A médiát kényesnek jelölték} other {A médiát kényesnek jelölték}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {A médiát nem jelölték kényesnek} other {A médiát nem jelölték kényesnek}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Egy szavazás, melyben részt vettél, véget ért",
   "notification.reblog": "{name} megtolta a bejegyzésedet",
   "notification.status": "{name} bejegyzést tett közzé",
+  "notification.update": "{name} szerkesztett egy bejegyzést",
   "notifications.clear": "Értesítések törlése",
   "notifications.clear_confirmation": "Biztos, hogy véglegesen törölni akarod az összes értesítésed?",
   "notifications.column_settings.alert": "Asztali értesítések",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Új bejegyzések:",
   "notifications.column_settings.unread_notifications.category": "Olvasatlan értesítések",
   "notifications.column_settings.unread_notifications.highlight": "Olvasatlan értesítések kiemelése",
+  "notifications.column_settings.update": "Szerkesztések:",
   "notifications.filter.all": "Mind",
   "notifications.filter.boosts": "Megtolások",
   "notifications.filter.favourites": "Kedvencnek jelölések",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Töltődik…",
   "regeneration_indicator.sublabel": "A saját idővonalad épp készül!",
   "relative_time.days": "{number}n",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# napja} other {# napja}}",
+  "relative_time.full.hours": "{number, plural, one {# órája} other {# órája}}",
+  "relative_time.full.just_now": "épp most",
+  "relative_time.full.minutes": "{number, plural, one {# perce} other {# perce}}",
+  "relative_time.full.seconds": "{number, plural, one {# másodperce} other {# másodperce}}",
   "relative_time.hours": "{number}ó",
   "relative_time.just_now": "most",
   "relative_time.minutes": "{number}p",
   "relative_time.seconds": "{number}mp",
   "relative_time.today": "ma",
   "reply_indicator.cancel": "Mégsem",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Egyéb",
+  "report.categories.spam": "Kéretlen üzenet",
+  "report.categories.violation": "A tartalom a kiszolgáló egy vagy több szabályát sérti",
   "report.forward": "Továbbítás: {target}",
   "report.forward_hint": "Ez a fiók egy másik kiszolgálóról van. Oda is elküldöd a jelentés egy anonimizált másolatát?",
   "report.hint": "A bejelentést a szervered moderátorainak küldjük el. Megmagyarázhatod, miért jelented az alábbi problémát:",
@@ -407,14 +409,14 @@
   "status.delete": "Törlés",
   "status.detailed_status": "Részletes beszélgetési nézet",
   "status.direct": "Közvetlen üzenet @{name} számára",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Szerkesztés",
+  "status.edited": "Szerkesztve: {date}",
+  "status.edited_x_times": "{count, plural, one {{count} alkalommal} other {{count} alkalommal}} szerkesztve",
   "status.embed": "Beágyazás",
   "status.favourite": "Kedvenc",
   "status.filtered": "Megszűrt",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} létrehozta: {date}",
+  "status.history.edited": "{name} szerkesztette: {date}",
   "status.load_more": "Többet",
   "status.media_hidden": "Média elrejtve",
   "status.mention": "@{name} megemlítése",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index dde99078b..24139d132 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -308,6 +308,7 @@
   "notification.poll": "Հարցումը, ուր դու քուէարկել ես, աւարտուեց։",
   "notification.reblog": "{name} տարածեց գրառումդ",
   "notification.status": "{name} հենց նոր գրառում արեց",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Մաքրել ծանուցումները",
   "notifications.clear_confirmation": "Վստա՞հ ես, որ ուզում ես մշտապէս մաքրել քո բոլոր ծանուցումները։",
   "notifications.column_settings.alert": "Աշխատատիրոյթի ծանուցումներ",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Նոր գրառումներ։",
   "notifications.column_settings.unread_notifications.category": "Չկարդացուած ծանուցումներ",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Բոլորը",
   "notifications.filter.boosts": "Տարածածները",
   "notifications.filter.favourites": "Հաւանածները",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 3b43f60d5..e8e81cfde 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -47,8 +47,8 @@
   "account.unmute": "Berhenti membisukan @{name}",
   "account.unmute_notifications": "Berhenti bisukan pemberitahuan dari @{name}",
   "account_note.placeholder": "Klik untuk menambah catatan",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Tingkat retensi pengguna perhari setelah mendaftar",
+  "admin.dashboard.monthly_retention": "Tingkat retensi pengguna perbulan setelah mendaftar",
   "admin.dashboard.retention.average": "Rata-rata",
   "admin.dashboard.retention.cohort": "Bulan pendaftaran",
   "admin.dashboard.retention.cohort_size": "Pengguna baru",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Ubah japat menjadi pilihan tunggal",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Simpan perubahan",
   "compose_form.sensitive.hide": "{count, plural, other {Tandai media sebagai sensitif}}",
   "compose_form.sensitive.marked": "{count, plural, other {Media ini ditandai sebagai sensitif}}",
   "compose_form.sensitive.unmarked": "{count, plural, other {Media ini tidak ditandai sebagai sensitif}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Japat yang Anda ikuti telah berakhir",
   "notification.reblog": "{name} mem-boost status anda",
   "notification.status": "{name} baru saja memposting",
+  "notification.update": "{name} mengedit kiriman",
   "notifications.clear": "Hapus notifikasi",
   "notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?",
   "notifications.column_settings.alert": "Notifikasi desktop",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Toot baru:",
   "notifications.column_settings.unread_notifications.category": "Notifikasi yang belum dibaca",
   "notifications.column_settings.unread_notifications.highlight": "Sorot notifikasi yang belum dibaca",
+  "notifications.column_settings.update": "Edit:",
   "notifications.filter.all": "Semua",
   "notifications.filter.boosts": "Boost",
   "notifications.filter.favourites": "Favorit",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Memuat…",
   "regeneration_indicator.sublabel": "Linimasa anda sedang disiapkan!",
   "relative_time.days": "{number}h",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, other {# hari}} yang lalu",
+  "relative_time.full.hours": "{number, plural, other {# jam}} yang lalu",
+  "relative_time.full.just_now": "baru saja",
+  "relative_time.full.minutes": "{number, plural, other {# menit}} yang lalu",
+  "relative_time.full.seconds": "{number, plural, other {# detik}} yang lalu",
   "relative_time.hours": "{number}j",
   "relative_time.just_now": "sekarang",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}d",
   "relative_time.today": "hari ini",
   "reply_indicator.cancel": "Batal",
-  "report.categories.other": "Other",
+  "report.categories.other": "Lainnya",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Konten melanggar satu atau lebih peraturan server",
   "report.forward": "Teruskan ke {target}",
   "report.forward_hint": "Akun dari server lain. Kirim salinan laporan scr anonim ke sana?",
   "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
@@ -408,13 +410,13 @@
   "status.detailed_status": "Tampilan detail percakapan",
   "status.direct": "Pesan langsung @{name}",
   "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edited": "Diedit {date}",
+  "status.edited_x_times": "Diedit {count, plural, other {{count} kali}}",
   "status.embed": "Tanam",
   "status.favourite": "Difavoritkan",
   "status.filtered": "Disaring",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} membuat pada {date}",
+  "status.history.edited": "{name} mengedit pada {date}",
   "status.load_more": "Tampilkan semua",
   "status.media_hidden": "Media disembunyikan",
   "status.mention": "Balasan @{name}",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index ee8a2b1b9..e4ae1d2ff 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} repetis tua mesajo",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Efacar savigi",
   "notifications.clear_confirmation": "Ka tu esas certa, ke tu volas efacar omna tua savigi?",
   "notifications.column_settings.alert": "Surtabla savigi",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json
index 5020bd068..a5164fa24 100644
--- a/app/javascript/mastodon/locales/is.json
+++ b/app/javascript/mastodon/locales/is.json
@@ -1,16 +1,16 @@
 {
   "account.account_note_header": "Minnispunktur",
   "account.add_or_remove_from_list": "Bæta við eða fjarlægja af listum",
-  "account.badges.bot": "Þjarkur",
+  "account.badges.bot": "Róbót",
   "account.badges.group": "Hópur",
   "account.block": "Loka á @{name}",
-  "account.block_domain": "Fela allt frá {domain}",
-  "account.blocked": "Lokað á",
+  "account.block_domain": "Útiloka lénið {domain}",
+  "account.blocked": "Útilokaður",
   "account.browse_more_on_origin_server": "Skoða nánari upplýsingar á notandasniðinu",
   "account.cancel_follow_request": "Hætta við beiðni um að fylgjas",
   "account.direct": "Bein skilaboð til @{name}",
-  "account.disable_notifications": "Hættu að láta mig vita þegar @{name} þýtur",
-  "account.domain_blocked": "Lén falið",
+  "account.disable_notifications": "Hætta að láta mig vita þegar @{name} sendir inn",
+  "account.domain_blocked": "Lén útilokað",
   "account.edit_profile": "Breyta notandasniði",
   "account.enable_notifications": "Láta mig vita þegar @{name} sendir inn",
   "account.endorse": "Birta á notandasniði",
@@ -33,22 +33,22 @@
   "account.mute_notifications": "Þagga tilkynningar frá @{name}",
   "account.muted": "Þaggað",
   "account.never_active": "Aldrei",
-  "account.posts": "Þyt",
-  "account.posts_with_replies": "Þyt og svör",
+  "account.posts": "Færslur",
+  "account.posts_with_replies": "Færslur og svör",
   "account.report": "Kæra @{name}",
   "account.requested": "Bíður eftir samþykki. Smelltu til að hætta við beiðni um að fylgjast með",
   "account.share": "Deila notandasniði fyrir @{name}",
   "account.show_reblogs": "Sýna endurbirtingar frá @{name}",
-  "account.statuses_counter": "{count, plural, one {{counter} tíst} other {{counter} tíst}}",
+  "account.statuses_counter": "{count, plural, one {{counter} færsla} other {{counter} færslur}}",
   "account.unblock": "Aflétta útilokun af @{name}",
-  "account.unblock_domain": "Hætta að fela {domain}",
+  "account.unblock_domain": "Aflétta útilokun lénsins {domain}",
   "account.unendorse": "Ekki birta á notandasniði",
   "account.unfollow": "Hætta að fylgja",
   "account.unmute": "Hætta að þagga niður í @{name}",
   "account.unmute_notifications": "Hætta að þagga tilkynningar frá @{name}",
-  "account_note.placeholder": "Engin athugasemd gefin",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "account_note.placeholder": "Smelltu til að bæta við minnispunkti",
+  "admin.dashboard.daily_retention": "Hlutfall virkra notenda eftir nýskráningu eftir dögum",
+  "admin.dashboard.monthly_retention": "Hlutfall virkra notenda eftir nýskráningu eftir mánuðum",
   "admin.dashboard.retention.average": "Meðaltal",
   "admin.dashboard.retention.cohort": "Mánuður nýskráninga",
   "admin.dashboard.retention.cohort_size": "Nýir notendur",
@@ -71,14 +71,14 @@
   "column.community": "Staðvær tímalína",
   "column.direct": "Bein skilaboð",
   "column.directory": "Skoða notandasnið",
-  "column.domain_blocks": "Falin lén",
-  "column.favourites": "Fílanir",
-  "column.follow_requests": "Fylgja beiðnum",
+  "column.domain_blocks": "Útilokuð lén",
+  "column.favourites": "Eftirlæti",
+  "column.follow_requests": "Beiðnir um að fylgjast með",
   "column.home": "Heim",
   "column.lists": "Listar",
   "column.mutes": "Þaggaðir notendur",
   "column.notifications": "Tilkynningar",
-  "column.pins": "Föst tíst",
+  "column.pins": "Festar færslur",
   "column.public": "Sameiginleg tímalína",
   "column_back_button.label": "Til baka",
   "column_header.hide_settings": "Fela stillingar",
@@ -91,9 +91,9 @@
   "community.column_settings.local_only": "Einungis staðvært",
   "community.column_settings.media_only": "Einungis myndskrár",
   "community.column_settings.remote_only": "Einungis fjartengt",
-  "compose_form.direct_message_warning": "Þetta tíst verður aðeins sent á notendur sem minnst er á.",
+  "compose_form.direct_message_warning": "Þessi færsla verður aðeins send á notendur sem minnst er á.",
   "compose_form.direct_message_warning_learn_more": "Kanna nánar",
-  "compose_form.hashtag_warning": "Þetta tíst verður ekki talið með undir nokkru myllumerki þar sem það er óskráð. Einungis er hægt að leita að opinberum tístum eftir myllumerkjum.",
+  "compose_form.hashtag_warning": "Þessi færsla verður ekki talin með undir nokkru myllumerki þar sem það er óskráð. Einungis er hægt að leita að opinberum færslum eftir myllumerkjum.",
   "compose_form.lock_disclaimer": "Aðgangurinn þinn er ekki {locked}. Hver sem er getur fylgst með þeim færslum þínum sem einungis eru til fylgjenda þinna.",
   "compose_form.lock_disclaimer.lock": "læst",
   "compose_form.placeholder": "Hvað varstu að hugsa?",
@@ -103,26 +103,26 @@
   "compose_form.poll.remove_option": "Fjarlægja þennan valkost",
   "compose_form.poll.switch_to_multiple": "Breyta könnun svo hægt sé að hafa marga valkosti",
   "compose_form.poll.switch_to_single": "Breyta könnun svo hægt sé að hafa einn stakan valkost",
-  "compose_form.publish": "Þyt",
+  "compose_form.publish": "Tíst",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
-  "compose_form.sensitive.hide": "Merkja myndir sem viðkvæmar",
-  "compose_form.sensitive.marked": "Mynd er merkt sem viðkvæm",
-  "compose_form.sensitive.unmarked": "Mynd er ekki merkt sem viðkvæm",
-  "compose_form.spoiler.marked": "Texti er falinn á bak við aðvörun",
-  "compose_form.spoiler.unmarked": "Texti er ekki falinn",
+  "compose_form.save_changes": "Vista breytingar",
+  "compose_form.sensitive.hide": "{count, plural, one {Merkja mynd sem viðkvæma} other {Merkja myndir sem viðkvæmar}}",
+  "compose_form.sensitive.marked": "{count, plural, one {Mynd er merkt sem viðkvæm} other {Myndir eru merktar sem viðkvæmar}}",
+  "compose_form.sensitive.unmarked": "{count, plural, one {Mynd er ekki merkt sem viðkvæm} other {Myndir eru ekki merktar sem viðkvæmar}}",
+  "compose_form.spoiler.marked": "Fjarlægja aðvörun vegna efnis",
+  "compose_form.spoiler.unmarked": "Bæta við aðvörun vegna efnis",
   "compose_form.spoiler_placeholder": "Skrifaðu aðvörunina þína hér",
   "confirmation_modal.cancel": "Hætta við",
   "confirmations.block.block_and_report": "Útiloka og kæra",
   "confirmations.block.confirm": "Útiloka",
   "confirmations.block.message": "Ertu viss um að þú viljir loka á {name}?",
   "confirmations.delete.confirm": "Eyða",
-  "confirmations.delete.message": "Ertu viss um að þú viljir eyða þessari stöðufærslu?",
+  "confirmations.delete.message": "Ertu viss um að þú viljir eyða þessari færslu?",
   "confirmations.delete_list.confirm": "Eyða",
   "confirmations.delete_list.message": "Ertu viss um að þú viljir eyða þessum lista endanlega?",
   "confirmations.discard_edit_media.confirm": "Henda",
   "confirmations.discard_edit_media.message": "Þú ert með óvistaðar breytingar á lýsingu myndefnis eða forskoðunar, henda þeim samt?",
-  "confirmations.domain_block.confirm": "Fela allt lénið",
+  "confirmations.domain_block.confirm": "Útiloka allt lénið",
   "confirmations.domain_block.message": "Ertu alveg algjörlega viss um að þú viljir loka á allt {domain}? Í flestum tilfellum er vænlegra að nota færri en markvissari útilokanir eða að þagga niður tiltekna aðila. Þú munt ekki sjá efni frá þessu léni í neinum opinberum tímalínum eða í tilkynningunum þínum. Fylgjendur þínir frá þessu léni verða fjarlægðir.",
   "confirmations.logout.confirm": "Skrá út",
   "confirmations.logout.message": "Ertu viss um að þú viljir skrá þig út?",
@@ -130,7 +130,7 @@
   "confirmations.mute.explanation": "Þetta mun fela færslur frá þeim og þær færslur þar sem minnst er á þau, en það mun samt sem áður gera þeim kleift að sjá færslurnar þínar og að fylgjast með þér.",
   "confirmations.mute.message": "Ertu viss um að þú viljir þagga niður í {name}?",
   "confirmations.redraft.confirm": "Eyða og enduvinna drög",
-  "confirmations.redraft.message": "Ertu viss um að þú viljir eyða þessari stöðufærslu og enduvinna drögin? Fílanir og endurbirtingar munu glatast og svör við upprunalegu fæerslunni munu verða munaðarlaus.",
+  "confirmations.redraft.message": "Ertu viss um að þú viljir eyða þessari færslu og enduvinna drögin? Eftirlæti og endurbirtingar munu glatast og svör við upprunalegu færslunni munu verða munaðarlaus.",
   "confirmations.reply.confirm": "Svara",
   "confirmations.reply.message": "Ef þú svarar núna verður skrifað yfir skilaboðin sem þú ert að semja núna. Ertu viss um að þú viljir halda áfram?",
   "confirmations.unfollow.confirm": "Hætta að fylgja",
@@ -151,7 +151,7 @@
   "emoji_button.food": "Matur og drykkur",
   "emoji_button.label": "Setja inn tjáningartákn",
   "emoji_button.nature": "Náttúra",
-  "emoji_button.not_found": "Engin tjáningartákn!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "Engin samsvarandi tjáningartákn fundust",
   "emoji_button.objects": "Hlutir",
   "emoji_button.people": "Fólk",
   "emoji_button.recent": "Oft notað",
@@ -160,21 +160,21 @@
   "emoji_button.symbols": "Tákn",
   "emoji_button.travel": "Ferðalög og staðir",
   "empty_column.account_suspended": "Notandaaðgangur í bið",
-  "empty_column.account_timeline": "Engin þyt hér!",
+  "empty_column.account_timeline": "Engar færslur hér!",
   "empty_column.account_unavailable": "Notandasnið ekki tiltækt",
   "empty_column.blocks": "Þú hefur ekki ennþá útilokað neina notendur.",
-  "empty_column.bookmarked_statuses": "Þú ert ekki ennþá með nein bókamerkt þyt. Þegar þú gefur þyti bókamerki, mun það birtast hér.",
+  "empty_column.bookmarked_statuses": "Þú ert ekki ennþá með neinar bókamerktar færslur. Þegar þú bókamerkir færslu, mun það birtast hér.",
   "empty_column.community": "Staðværa tímalínan er tóm. Skrifaðu eitthvað opinberlega til að láta boltann fara að rúlla!",
   "empty_column.direct": "Þú átt ennþá engin bein skilaboð. Þegar þú sendir eða tekur á móti slíkum skilaboðum, munu þau birtast hér.",
-  "empty_column.domain_blocks": "Það eru engin falin lén ennþá.",
-  "empty_column.favourited_statuses": "Þú hefur ekki fílað nein þyt. Þegar að þú fílar þyt, þá mun það birtast hér.",
-  "empty_column.favourites": "Enginn hefu fílað þetta þyt ennþá. Þegar einhver gerir það, mun sá birtast hér.",
+  "empty_column.domain_blocks": "Það eru ennþá engin útilokuð lén.",
+  "empty_column.favourited_statuses": "Þú ert ekki ennþá með neinar eftirlætisfærslur. Þegar þú setur færslu í eftirlæti, munu þau birtast hér.",
+  "empty_column.favourites": "Enginn hefur ennþá sett þessa færslu í eftirlæti. Þegar einhver gerir það, mun það birtast hér.",
   "empty_column.follow_recommendations": "Það lítur út fyrir að ekki hafi verið hægt að útbúa neinar tillögur fyrir þig. Þú getur reynt að leita að fólki sem þú gætir þekkt eða skoðað myllumerki sem eru í umræðunni.",
   "empty_column.follow_requests": "Þú átt ennþá engar beiðnir um að fylgja þér. Þegar þú færð slíkar beiðnir, munu þær birtast hér.",
   "empty_column.hashtag": "Það er ekkert ennþá undir þessu myllumerki.",
-  "empty_column.home": "Heimatímalínan þín er tóm! Skoðaðu {public} eða notaðu leitina til að komast í gang og finna annað fólk.",
+  "empty_column.home": "Heimatímalínan þín er tóm! Fylgstu með fleira fólki til að fylla hana. {suggestions}",
   "empty_column.home.suggestions": "Skoðaðu nokkrar tillögur",
-  "empty_column.list": "Það er ennþá ekki neitt á þessum lista. Þegar meðlimir á listanum senda inn nýjar stöðufærslur, munu þær birtast hér.",
+  "empty_column.list": "Það er ennþá ekki neitt á þessum lista. Þegar meðlimir á listanum senda inn nýjar færslur, munu þær birtast hér.",
   "empty_column.lists": "Þú ert ennþá ekki með neina lista. Þegar þú byrð til einhvern lista, munu hann birtast hér.",
   "empty_column.mutes": "Þú hefur ekki þaggað niður í neinum notendum ennþá.",
   "empty_column.notifications": "Þú ert ekki ennþá með neinar tilkynningar. Vertu í samskiptum við aðra til að umræður fari af stað.",
@@ -217,40 +217,40 @@
   "intervals.full.days": "{number, plural, one {# dagur} other {# dagar}}",
   "intervals.full.hours": "{number, plural, one {# klukkustund} other {# klukkustundir}}",
   "intervals.full.minutes": "{number, plural, one {# mínúta} other {# mínútur}}",
-  "keyboard_shortcuts.back": "að fara til baka",
-  "keyboard_shortcuts.blocked": "að opna lista yfir útilokaða notendur",
-  "keyboard_shortcuts.boost": "að endurbirta",
-  "keyboard_shortcuts.column": "að setja virkni á stöðufærslu í einum af dálkunum",
-  "keyboard_shortcuts.compose": "að setja virkni á textainnsetningarreit",
+  "keyboard_shortcuts.back": "Fara til baka",
+  "keyboard_shortcuts.blocked": "Opna lista yfir útilokaða notendur",
+  "keyboard_shortcuts.boost": "Endurbirta færslu",
+  "keyboard_shortcuts.column": "Setja virkni í dálk",
+  "keyboard_shortcuts.compose": "Setja virkni á textainnsetningarreit",
   "keyboard_shortcuts.description": "Lýsing",
-  "keyboard_shortcuts.direct": "að opna dálk með beinum skilaboðum",
-  "keyboard_shortcuts.down": "að fara neðar í listanum",
-  "keyboard_shortcuts.enter": "að opna stöðufærslu",
-  "keyboard_shortcuts.favourite": "Fíla þyt",
-  "keyboard_shortcuts.favourites": "Opna fílanir",
-  "keyboard_shortcuts.federated": "að opna sameiginlega tímalínu",
+  "keyboard_shortcuts.direct": "Opna dálk með beinum skilaboðum",
+  "keyboard_shortcuts.down": "Fara neðar í listanum",
+  "keyboard_shortcuts.enter": "Opna færslu",
+  "keyboard_shortcuts.favourite": "Eftirlætisfærsla",
+  "keyboard_shortcuts.favourites": "Opna eftirlætislista",
+  "keyboard_shortcuts.federated": "Opna sameiginlega tímalínu",
   "keyboard_shortcuts.heading": "Flýtileiðir á lyklaborði",
-  "keyboard_shortcuts.home": "að opna heimatímalínu",
+  "keyboard_shortcuts.home": "Opna heimatímalínu",
   "keyboard_shortcuts.hotkey": "Flýtilykill",
-  "keyboard_shortcuts.legend": "að birta þessa skýringu",
-  "keyboard_shortcuts.local": "að opna staðværa tímalínu",
-  "keyboard_shortcuts.mention": "að minnast á höfund",
-  "keyboard_shortcuts.muted": "að opna lista yfir þaggaða notendur",
-  "keyboard_shortcuts.my_profile": "að opna notandasniðið þitt",
-  "keyboard_shortcuts.notifications": "að opna tilkynningadálk",
-  "keyboard_shortcuts.open_media": "til að opna margmiðlunargögn",
-  "keyboard_shortcuts.pinned": "Opna lista yfir föst þyt",
-  "keyboard_shortcuts.profile": "að opna notandasnið höfundar",
-  "keyboard_shortcuts.reply": "að svara",
-  "keyboard_shortcuts.requests": "að opna lista yfir fylgjendabeiðnir",
-  "keyboard_shortcuts.search": "að setja virkni í leit",
-  "keyboard_shortcuts.spoilers": "til að birta/fela reit með aðvörun vegna efnis",
-  "keyboard_shortcuts.start": "að opna \"komast í gang\" dálk",
-  "keyboard_shortcuts.toggle_hidden": "að birta/fela texta á bak við aðvörun vegna efnis",
-  "keyboard_shortcuts.toggle_sensitivity": "að birta/fela myndir",
-  "keyboard_shortcuts.toot": "Hefja glænýtt þyt",
-  "keyboard_shortcuts.unfocus": "að taka virkni úr textainnsetningarreit eða leit",
-  "keyboard_shortcuts.up": "að fara ofar í listanum",
+  "keyboard_shortcuts.legend": "Birta þessa skýringu",
+  "keyboard_shortcuts.local": "Opna staðværa tímalínu",
+  "keyboard_shortcuts.mention": "Minnast á höfund",
+  "keyboard_shortcuts.muted": "Opna lista yfir þaggaða notendur",
+  "keyboard_shortcuts.my_profile": "Opna notandasniðið þitt",
+  "keyboard_shortcuts.notifications": "Opna tilkynningadálk",
+  "keyboard_shortcuts.open_media": "Opna margmiðlunargögn",
+  "keyboard_shortcuts.pinned": "Opna lista yfir festar færslur",
+  "keyboard_shortcuts.profile": "Opna notandasnið höfundar",
+  "keyboard_shortcuts.reply": "Svara færslu",
+  "keyboard_shortcuts.requests": "Opna lista yfir fylgjendabeiðnir",
+  "keyboard_shortcuts.search": "Setja virkni í leitarreit",
+  "keyboard_shortcuts.spoilers": "Birta/fela reit með aðvörun vegna efnis",
+  "keyboard_shortcuts.start": "Opna \"komast í gang\" dálk",
+  "keyboard_shortcuts.toggle_hidden": "Birta/fela texta á bak við aðvörun vegna efnis",
+  "keyboard_shortcuts.toggle_sensitivity": "Birta/fela myndir",
+  "keyboard_shortcuts.toot": "Byrja nýja færslu",
+  "keyboard_shortcuts.unfocus": "Taka virkni úr textainnsetningarreit eða leit",
+  "keyboard_shortcuts.up": "Fara ofar í listanum",
   "lightbox.close": "Loka",
   "lightbox.compress": "Þjappa myndskoðunarreit",
   "lightbox.expand": "Fletta út myndskoðunarreit",
@@ -281,12 +281,12 @@
   "navigation_bar.blocks": "Útilokaðir notendur",
   "navigation_bar.bookmarks": "Bókamerki",
   "navigation_bar.community_timeline": "Staðvær tímalína",
-  "navigation_bar.compose": "Semja nýtt þyt",
+  "navigation_bar.compose": "Semja nýja færslu",
   "navigation_bar.direct": "Bein skilaboð",
   "navigation_bar.discover": "Uppgötva",
-  "navigation_bar.domain_blocks": "Falin lén",
+  "navigation_bar.domain_blocks": "Útilokuð lén",
   "navigation_bar.edit_profile": "Breyta notandasniði",
-  "navigation_bar.favourites": "Fílanir",
+  "navigation_bar.favourites": "Eftirlæti",
   "navigation_bar.filters": "Þögguð orð",
   "navigation_bar.follow_requests": "Beiðnir um að fylgjast með",
   "navigation_bar.follows_and_followers": "Fylgist með og fylgjendur",
@@ -296,22 +296,23 @@
   "navigation_bar.logout": "Útskráning",
   "navigation_bar.mutes": "Þaggaðir notendur",
   "navigation_bar.personal": "Einka",
-  "navigation_bar.pins": "Föst þyt",
+  "navigation_bar.pins": "Festar færslur",
   "navigation_bar.preferences": "Kjörstillingar",
   "navigation_bar.public_timeline": "Sameiginleg tímalína",
   "navigation_bar.security": "Öryggi",
-  "notification.favourite": "{name} filaði stöðufærslu þína",
+  "notification.favourite": "{name} setti færslu þína í eftirlæti",
   "notification.follow": "{name} fylgist með þér",
   "notification.follow_request": "{name} hefur beðið um að fylgjast með þér",
   "notification.mention": "{name} minntist á þig",
   "notification.own_poll": "Könnuninni þinni er lokið",
   "notification.poll": "Könnun sem þú tókst þátt í er lokið",
-  "notification.reblog": "{name} endurbirti stöðufærsluna þína",
+  "notification.reblog": "{name} endurbirti færsluna þína",
   "notification.status": "{name} sendi inn rétt í þessu",
+  "notification.update": "{name} breytti færslu",
   "notifications.clear": "Hreinsa tilkynningar",
   "notifications.clear_confirmation": "Ertu viss um að þú viljir endanlega eyða öllum tilkynningunum þínum?",
   "notifications.column_settings.alert": "Tilkynningar á skjáborði",
-  "notifications.column_settings.favourite": "Fílanir:",
+  "notifications.column_settings.favourite": "Eftirlæti:",
   "notifications.column_settings.filter_bar.advanced": "Birta alla flokka",
   "notifications.column_settings.filter_bar.category": "Skyndisíustika",
   "notifications.column_settings.filter_bar.show_bar": "Birta síustikuna",
@@ -323,12 +324,13 @@
   "notifications.column_settings.reblog": "Endurbirtingar:",
   "notifications.column_settings.show": "Sýna í dálki",
   "notifications.column_settings.sound": "Spila hljóð",
-  "notifications.column_settings.status": "Ný þyt:",
+  "notifications.column_settings.status": "Nýjar færslur:",
   "notifications.column_settings.unread_notifications.category": "Ólesnar tilkynningar",
   "notifications.column_settings.unread_notifications.highlight": "Áherslulita ólesnar tilkynningar",
+  "notifications.column_settings.update": "Breytingar:",
   "notifications.filter.all": "Allt",
   "notifications.filter.boosts": "Endurbirtingar",
-  "notifications.filter.favourites": "Fílanir",
+  "notifications.filter.favourites": "Eftirlæti",
   "notifications.filter.follows": "Fylgist með",
   "notifications.filter.mentions": "Tilvísanir",
   "notifications.filter.polls": "Niðurstöður könnunar",
@@ -352,7 +354,7 @@
   "poll.votes": "{votes, plural, one {# atkvæði} other {# atkvæði}}",
   "poll_button.add_poll": "Bæta við könnun",
   "poll_button.remove_poll": "Fjarlægja könnun",
-  "privacy.change": "Aðlaga gagnaleynd stöðufærslu",
+  "privacy.change": "Aðlaga gagnaleynd færslu",
   "privacy.direct.long": "Senda einungis á notendur sem minnst er á",
   "privacy.direct.short": "Beint",
   "privacy.private.long": "Senda einungis á fylgjendur",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Hleð inn…",
   "regeneration_indicator.sublabel": "Verið er að útbúa heimastreymið þitt!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "Fyrir {number, plural, one {# degi} other {# dögum}} síðan",
+  "relative_time.full.hours": "Fyrir {number, plural, one {# klukkustund} other {# klukkustundum}} síðan",
+  "relative_time.full.just_now": "í þessu",
+  "relative_time.full.minutes": "Fyrir {number, plural, one {# mínútu} other {# mínútum}} síðan",
+  "relative_time.full.seconds": "Fyrir {number, plural, one {# sekúndu} other {# sekúndum}} síðan",
   "relative_time.hours": "{number}kl.",
   "relative_time.just_now": "núna",
   "relative_time.minutes": "{number}mín",
   "relative_time.seconds": "{number}sek",
   "relative_time.today": "í dag",
   "reply_indicator.cancel": "Hætta við",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Annað",
+  "report.categories.spam": "Ruslpóstur",
+  "report.categories.violation": "Efnið brýtur gegn einni eða fleiri reglum netþjónsins",
   "report.forward": "Áframsenda til {target}",
   "report.forward_hint": "Notandaaðgangurinn er af öðrum vefþjóni. Á einnig að senda nafnlaust afrit af kærunni þangað?",
   "report.hint": "Kæran verður send á umsjónarmenn vefþjónsins þíns. Þú getur gefið skýringu hér fyrir neðan á því af hverju þú ert að kæra þennan notandaaðgang:",
@@ -387,48 +389,48 @@
   "report.target": "Kæri {target}",
   "search.placeholder": "Leita",
   "search_popout.search_format": "Snið ítarlegrar leitar",
-  "search_popout.tips.full_text": "Einfaldur texti skilar stöðufærslum sem þú hefur skrifað, fílað, endurbirt eða sem á þig hefur verið minnst í, ásamt samsvarandi birtingarnöfnum, notendanöfnum og myllumerkjum.",
+  "search_popout.tips.full_text": "Einfaldur texti skilar færslum sem þú hefur skrifað, sett í eftirlæti, endurbirt eða verið minnst á þig í, ásamt samsvarandi birtingarnöfnum, notendanöfnum og myllumerkjum.",
   "search_popout.tips.hashtag": "myllumerki",
-  "search_popout.tips.status": "stöðufærsla",
+  "search_popout.tips.status": "færsla",
   "search_popout.tips.text": "Einfaldur texti skilar samsvarandi birtingarnöfnum, notendanöfnum og myllumerkjum",
   "search_popout.tips.user": "notandi",
   "search_results.accounts": "Fólk",
   "search_results.hashtags": "Myllumerki",
-  "search_results.statuses": "Þyt",
-  "search_results.statuses_fts_disabled": "Að leita í efni þyta er ekki virk á þessum Mastodon-þjóni.",
+  "search_results.statuses": "Færslur",
+  "search_results.statuses_fts_disabled": "Að leita í efni færslna er ekki virkt á þessum Mastodon-þjóni.",
   "search_results.total": "{count, number} {count, plural, one {niðurstaða} other {niðurstöður}}",
   "status.admin_account": "Opna umsjónarviðmót fyrir @{name}",
-  "status.admin_status": "Opna þessa stöðufærslu í umsjónarviðmótinu",
+  "status.admin_status": "Opna þessa færslu í umsjónarviðmótinu",
   "status.block": "Útiloka @{name}",
   "status.bookmark": "Bókamerki",
   "status.cancel_reblog_private": "Taka úr endurbirtingu",
   "status.cannot_reblog": "Þessa færslu er ekki hægt að endurbirta",
-  "status.copy": "Afrita tengil í stöðufærslu",
+  "status.copy": "Afrita tengil í færslu",
   "status.delete": "Eyða",
   "status.detailed_status": "Nákvæm spjallþráðasýn",
   "status.direct": "Bein skilaboð @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Breyta",
+  "status.edited": "Breytt {date}",
+  "status.edited_x_times": "Breytt {count, plural, one {{count} sinni} other {{count} sinnum}}",
   "status.embed": "Ívefja",
-  "status.favourite": "Fílanir",
+  "status.favourite": "Eftirlæti",
   "status.filtered": "Síað",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} útbjó {date}",
+  "status.history.edited": "{name} breytti {date}",
   "status.load_more": "Hlaða inn meiru",
   "status.media_hidden": "Mynd er falin",
   "status.mention": "Minnast á @{name}",
   "status.more": "Meira",
   "status.mute": "Þagga niður í @{name}",
   "status.mute_conversation": "Þagga niður í samtali",
-  "status.open": "Útliða þessa stöðu",
+  "status.open": "Útliða þessa færslu",
   "status.pin": "Festa á notandasnið",
-  "status.pinned": "Fast þyt",
+  "status.pinned": "Fest færsla",
   "status.read_more": "Lesa meira",
   "status.reblog": "Endurbirting",
   "status.reblog_private": "Endurbirta til upphaflegra lesenda",
   "status.reblogged_by": "{name} endurbirti",
-  "status.reblogs.empty": "Enginn hefur ennþá endurbirt þetta þyt. Þegar einhver gerir það, mun sá birtast hér.",
+  "status.reblogs.empty": "Enginn hefur ennþá endurbirt þessa færslu. Þegar einhver gerir það, mun það birtast hér.",
   "status.redraft": "Eyða og enduvinna drög",
   "status.remove_bookmark": "Fjarlægja bókamerki",
   "status.reply": "Svara",
@@ -459,7 +461,7 @@
   "timeline_hint.remote_resource_not_displayed": "{resource} frá öðrum netþjónum er ekki birt.",
   "timeline_hint.resources.followers": "Fylgjendur",
   "timeline_hint.resources.follows": "Fylgist með",
-  "timeline_hint.resources.statuses": "Eldri þyt",
+  "timeline_hint.resources.statuses": "Eldri færslur",
   "trends.counter_by_accounts": "{count, plural, one {{counter} aðili} other {{counter} aðilar}} tala",
   "trends.trending_now": "Í umræðunni núna",
   "ui.beforeunload": "Drögin tapast ef þú ferð út úr Mastodon.",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index c02c84f09..a4847b77b 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -47,8 +47,8 @@
   "account.unmute": "Riattiva @{name}",
   "account.unmute_notifications": "Riattiva le notifiche da @{name}",
   "account_note.placeholder": "Clicca per aggiungere una nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Tasso di ritenzione utente per giorno dopo la registrazione",
+  "admin.dashboard.monthly_retention": "Tasso di ritenzione utente per mese dopo la registrazione",
   "admin.dashboard.retention.average": "Media",
   "admin.dashboard.retention.cohort": "Mese di iscrizione",
   "admin.dashboard.retention.cohort_size": "Nuovi utenti",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Modifica sondaggio per consentire una singola scelta",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Salva modifiche",
   "compose_form.sensitive.hide": "Segna media come sensibile",
   "compose_form.sensitive.marked": "Questo media è contrassegnato come sensibile",
   "compose_form.sensitive.unmarked": "Questo media non è contrassegnato come sensibile",
@@ -308,6 +308,7 @@
   "notification.poll": "Un sondaggio in cui hai votato è terminato",
   "notification.reblog": "{name} ha condiviso il tuo post",
   "notification.status": "{name} ha appena pubblicato un post",
+  "notification.update": "{name} ha modificato un post",
   "notifications.clear": "Cancella notifiche",
   "notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
   "notifications.column_settings.alert": "Notifiche desktop",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nuovi post:",
   "notifications.column_settings.unread_notifications.category": "Notifiche non lette",
   "notifications.column_settings.unread_notifications.highlight": "Evidenzia notifiche non lette",
+  "notifications.column_settings.update": "Modifiche:",
   "notifications.filter.all": "Tutti",
   "notifications.filter.boosts": "Condivisioni",
   "notifications.filter.favourites": "Apprezzati",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Caricamento in corso…",
   "regeneration_indicator.sublabel": "Stiamo preparando il tuo home feed!",
   "relative_time.days": "{number}g",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# giorno} other {# giorni}} fa",
+  "relative_time.full.hours": "{number, plural, one {# ora} other {# ore}} fa",
+  "relative_time.full.just_now": "proprio ora",
+  "relative_time.full.minutes": "{number, plural, one {# minuto} other {# minuti}} fa",
+  "relative_time.full.seconds": "{number, plural, one {# secondo} other {# secondi}} fa",
   "relative_time.hours": "{number}o",
   "relative_time.just_now": "ora",
   "relative_time.minutes": "{number} minuti",
   "relative_time.seconds": "{number} secondi",
   "relative_time.today": "oggi",
   "reply_indicator.cancel": "Annulla",
-  "report.categories.other": "Other",
+  "report.categories.other": "Altro",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Il contenuto viola una o più regole del server",
   "report.forward": "Inoltra a {target}",
   "report.forward_hint": "Questo account appartiene a un altro server. Mandare anche là una copia anonima del rapporto?",
   "report.hint": "La segnalazione sarà inviata ai moderatori del tuo server. Di seguito, puoi fornire il motivo per il quale stai segnalando questo account:",
@@ -407,14 +409,14 @@
   "status.delete": "Elimina",
   "status.detailed_status": "Vista conversazione dettagliata",
   "status.direct": "Messaggio privato @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Modifica",
+  "status.edited": "Modificato il {date}",
+  "status.edited_x_times": "Modificato {count, plural, one {{count} volta} other {{count} volte}}",
   "status.embed": "Incorpora",
   "status.favourite": "Apprezzato",
   "status.filtered": "Filtrato",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} ha creato {date}",
+  "status.history.edited": "{name} ha modificato {date}",
   "status.load_more": "Mostra di più",
   "status.media_hidden": "Allegato nascosto",
   "status.mention": "Nomina @{name}",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index a54922ef6..48766c80b 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -47,8 +47,8 @@
   "account.unmute": "@{name}さんのミュートを解除",
   "account.unmute_notifications": "@{name}さんからの通知を受け取るようにする",
   "account_note.placeholder": "クリックしてメモを追加",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "サインアップ後の日ごとのユーザー継続率",
+  "admin.dashboard.monthly_retention": "サインアップ後の月ごとのユーザー継続率",
   "admin.dashboard.retention.average": "平均",
   "admin.dashboard.retention.cohort": "サインアップ月",
   "admin.dashboard.retention.cohort_size": "新しいユーザー",
@@ -109,7 +109,7 @@
   "compose_form.poll.switch_to_single": "単一選択に変更",
   "compose_form.publish": "トゥート",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "変更を保存",
   "compose_form.sensitive.hide": "メディアを閲覧注意にする",
   "compose_form.sensitive.marked": "メディアに閲覧注意が設定されています",
   "compose_form.sensitive.unmarked": "メディアに閲覧注意が設定されていません",
@@ -313,6 +313,7 @@
   "notification.poll": "アンケートが終了しました",
   "notification.reblog": "{name}さんがあなたの投稿をブーストしました",
   "notification.status": "{name}さんが投稿しました",
+  "notification.update": "{name} が投稿を編集しました",
   "notifications.clear": "通知を消去",
   "notifications.clear_confirmation": "本当に通知を消去しますか?",
   "notifications.column_settings.alert": "デスクトップ通知",
@@ -331,6 +332,7 @@
   "notifications.column_settings.status": "新しい投稿:",
   "notifications.column_settings.unread_notifications.category": "未読の通知:",
   "notifications.column_settings.unread_notifications.highlight": "未読の通知を強調表示",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "すべて",
   "notifications.filter.boosts": "ブースト",
   "notifications.filter.favourites": "お気に入り",
@@ -370,20 +372,20 @@
   "regeneration_indicator.label": "読み込み中…",
   "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
   "relative_time.days": "{number}日前",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number} 日前",
+  "relative_time.full.hours": "{number} 時間前",
+  "relative_time.full.just_now": "今",
+  "relative_time.full.minutes": "{number} 分前",
+  "relative_time.full.seconds": "{number} 秒前",
   "relative_time.hours": "{number}時間前",
   "relative_time.just_now": "今",
   "relative_time.minutes": "{number}分前",
   "relative_time.seconds": "{number}秒前",
   "relative_time.today": "今日",
   "reply_indicator.cancel": "キャンセル",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "その他",
+  "report.categories.spam": "スパム",
+  "report.categories.violation": "サーバーのルールに違反",
   "report.forward": "{target} に転送する",
   "report.forward_hint": "このアカウントは別のサーバーに所属しています。通報内容を匿名で転送しますか?",
   "report.hint": "通報内容はあなたのサーバーのモデレーターへ送信されます。通報理由を入力してください。:",
@@ -412,14 +414,14 @@
   "status.delete": "削除",
   "status.detailed_status": "詳細な会話ビュー",
   "status.direct": "@{name}さんにダイレクトメッセージ",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "編集",
+  "status.edited": "{date} 編集済み",
+  "status.edited_x_times": "{count} 回編集",
   "status.embed": "埋め込み",
   "status.favourite": "お気に入り",
   "status.filtered": "フィルターされました",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name}さんが {date} に作成",
+  "status.history.edited": "{name}さんが {date} に編集",
   "status.load_more": "もっと見る",
   "status.media_hidden": "非表示のメディア",
   "status.mention": "@{name}さんに投稿",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index 12ff62dba..c6c177d58 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name}-მა დაბუსტა თქვენი სტატუსი",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "შეტყობინებების გასუფთავება",
   "notifications.clear_confirmation": "დარწმუნებული ხართ, გსურთ სამუდამოდ წაშალოთ ყველა თქვენი შეტყობინება?",
   "notifications.column_settings.alert": "დესკტოპ შეტყობინებები",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index f420ac45b..b575db516 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
   "compose_form.publish": "Jewweq",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Sekles ibeddilen",
   "compose_form.sensitive.hide": "Creḍ allal n teywalt d anafri",
   "compose_form.sensitive.marked": "Allal n teywalt yettwacreḍ d anafri",
   "compose_form.sensitive.unmarked": "Allal n teywalt ur yettwacreḍ ara d anafri",
@@ -308,6 +308,7 @@
   "notification.poll": "Tfukk tefrant ideg tettekkaḍ",
   "notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen",
   "notification.status": "{name} akken i d-yessufeɣ",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Sfeḍ tilɣa",
   "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-inek·em i lebda?",
   "notifications.column_settings.alert": "Tilɣa n tnarit",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Tiẓenẓunin timaynutin:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Akk",
   "notifications.filter.boosts": "Seǧhed",
   "notifications.filter.favourites": "Ismenyifen",
@@ -367,7 +369,7 @@
   "relative_time.days": "{number}u",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
+  "relative_time.full.just_now": "tura kan",
   "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
   "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
   "relative_time.hours": "{number}isr",
@@ -377,7 +379,7 @@
   "relative_time.today": "assa",
   "reply_indicator.cancel": "Sefsex",
   "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
+  "report.categories.spam": "Aspam",
   "report.categories.violation": "Content violates one or more server rules",
   "report.forward": "Bren-it ɣeṛ {target}",
   "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
@@ -407,8 +409,8 @@
   "status.delete": "Kkes",
   "status.detailed_status": "Detailed conversation view",
   "status.direct": "Izen usrid i @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
+  "status.edit": "Ẓreg",
+  "status.edited": "Tettwaẓreg deg {date}",
   "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
   "status.embed": "Seddu",
   "status.favourite": "Rnu ɣer yismenyifen",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index 074ae5844..4fb5cb3d1 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -308,6 +308,7 @@
   "notification.poll": "Бұл сауалнаманың мерзімі аяқталыпты",
   "notification.reblog": "{name} жазбаңызды бөлісті",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Ескертпелерді тазарт",
   "notifications.clear_confirmation": "Шынымен барлық ескертпелерді өшіресіз бе?",
   "notifications.column_settings.alert": "Үстел ескертпелері",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Барлығы",
   "notifications.filter.boosts": "Бөлісулер",
   "notifications.filter.favourites": "Таңдаулылар",
diff --git a/app/javascript/mastodon/locales/kmr.json b/app/javascript/mastodon/locales/kmr.json
index f45e305cd..2ae288581 100644
--- a/app/javascript/mastodon/locales/kmr.json
+++ b/app/javascript/mastodon/locales/kmr.json
@@ -47,8 +47,8 @@
   "account.unmute": "@{name} Bêdeng bike",
   "account.unmute_notifications": "Agahdariyan ji @{name} bêdeng bike",
   "account_note.placeholder": "Bitikîne bo nîşeyekê tevlî bikî",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Rêjeya ragirtina bikarhêner bi roj piştî tomarkirinê",
+  "admin.dashboard.monthly_retention": "Rêjeya ragirtina bikarhêner bi meh piştî tomarkirinê",
   "admin.dashboard.retention.average": "Navîn",
   "admin.dashboard.retention.cohort": "Meha tomarkirinê",
   "admin.dashboard.retention.cohort_size": "Bikarhênerên nû",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Rapirsîyê biguherîne da ku mafê bidî tenê vebijêrkek",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Guhertinan tomar bike",
   "compose_form.sensitive.hide": "{count, plural, one {Medya wekî hestiyar nîşan bide} other {Medya wekî hestiyar nîşan bide}}",
   "compose_form.sensitive.marked": "{count, plural, one {Medya wekî hestiyar hate nîşan} other {Medya wekî hestiyar nîşan}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Medya wekî hestiyar nehatiye nîşan} other {Medya wekî hestiyar nehatiye nîşan}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Rapirsiyeke ku te deng daye qediya",
   "notification.reblog": "{name} şandiya te bilind kir",
   "notification.status": "{name} niha şand",
+  "notification.update": "{name} şandiyek serrast kir",
   "notifications.clear": "Agahdariyan pak bike",
   "notifications.clear_confirmation": "Bi rastî tu dixwazî bi awayekî dawî hemû agahdariyên xwe pak bikî?",
   "notifications.column_settings.alert": "Agahdariyên sermaseyê",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Şandiyên nû:",
   "notifications.column_settings.unread_notifications.category": "Agahdariyên nexwendî",
   "notifications.column_settings.unread_notifications.highlight": "Agahiyên nexwendî nîşan bike",
+  "notifications.column_settings.update": "Serrastkirin:",
   "notifications.filter.all": "Hemû",
   "notifications.filter.boosts": "Bilindkirî",
   "notifications.filter.favourites": "Bijarte",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Tê barkirin…",
   "regeneration_indicator.sublabel": "Mala te da tê amedekirin!",
   "relative_time.days": "{number}r",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# roj} other {# roj}} berê",
+  "relative_time.full.hours": "{number, plural, one {# demjimêr} other {# demjimêr}} berê",
+  "relative_time.full.just_now": "hema niha",
+  "relative_time.full.minutes": "{number, plural, one {# xulek} other {# xulek}} berê",
+  "relative_time.full.seconds": "{number, plural, one {# çirke} other {# çirke}} xulek",
   "relative_time.hours": "{number}d",
   "relative_time.just_now": "niha",
   "relative_time.minutes": "{number}x",
   "relative_time.seconds": "{number}ç",
   "relative_time.today": "îro",
   "reply_indicator.cancel": "Dev jê berde",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Yên din",
+  "report.categories.spam": "Nexwestî (Spam)",
+  "report.categories.violation": "Naverok yek an çend rêbazên rajekar binpê dike",
   "report.forward": "Biçe bo {target}",
   "report.forward_hint": "Ajimêr ji rajekarek din da ne. Tu kopîyeka anonîm ya raporê bişînî li wur?",
   "report.hint": "Ev rapor yê rajekarê lihevkarên te ra were şandin. Tu dikarî şiroveyekê pêşkêş bikî bê ka tu çima vê ajimêrê jor radigîhînî:",
@@ -407,14 +409,14 @@
   "status.delete": "Jê bibe",
   "status.detailed_status": "Dîtina axaftina berfireh",
   "status.direct": "Peyama rasterast @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Serrast bike",
+  "status.edited": "Di {date} de hate serrastkirin",
+  "status.edited_x_times": "{count, plural, one {{count} car} other {{count} car}} hate serrastkirin",
   "status.embed": "Hedimandî",
   "status.favourite": "Bijarte",
   "status.filtered": "Parzûnkirî",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} {date} afirand",
+  "status.history.edited": "{name} {date} serrast kir",
   "status.load_more": "Bêtir bar bike",
   "status.media_hidden": "Medya veşartî ye",
   "status.mention": "Qal @{name} bike",
@@ -451,9 +453,9 @@
   "tabs_bar.local_timeline": "Herêmî",
   "tabs_bar.notifications": "Agahdarî",
   "tabs_bar.search": "Bigere",
-  "time_remaining.days": "{number, plural, one {# roj} other {# roj}} mayî",
-  "time_remaining.hours": "{number, plural, one {# demjimêr} other {# demjimêr}} mayî",
-  "time_remaining.minutes": "{number, plural, one {# xulek} other {# xulek}} mayî",
+  "time_remaining.days": "{number, plural, one {# roj} other {# roj}} maye",
+  "time_remaining.hours": "{number, plural, one {# demjimêr} other {# demjimêr}} maye",
+  "time_remaining.minutes": "{number, plural, one {# xulek} other {# xulek}} maye",
   "time_remaining.moments": "Demên mayî",
   "time_remaining.seconds": "{number, plural, one {# çirke} other {# çirke}} maye",
   "timeline_hint.remote_resource_not_displayed": "{resource} Ji rajekerên din nayê dîtin.",
diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json
index cc6c34e4a..e877f8eab 100644
--- a/app/javascript/mastodon/locales/kn.json
+++ b/app/javascript/mastodon/locales/kn.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 613ef7600..b3aa2e6ab 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -47,8 +47,8 @@
   "account.unmute": "@{name} 뮤트 해제",
   "account.unmute_notifications": "@{name}의 알림 뮤트 해제",
   "account_note.placeholder": "클릭해서 노트 추가",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "가입 후 일별 사용자 유지율",
+  "admin.dashboard.monthly_retention": "가입 후 월별 사용자 유지율",
   "admin.dashboard.retention.average": "평균",
   "admin.dashboard.retention.cohort": "가입한 달",
   "admin.dashboard.retention.cohort_size": "새로운 사용자",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "단일 선택 투표로 변경",
   "compose_form.publish": "뿌우",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "변경사항 저장",
   "compose_form.sensitive.hide": "미디어를 민감함으로 설정하기",
   "compose_form.sensitive.marked": "미디어가 열람주의로 설정되어 있습니다",
   "compose_form.sensitive.unmarked": "미디어가 열람주의로 설정 되어 있지 않습니다",
@@ -308,6 +308,7 @@
   "notification.poll": "당신이 참여 한 투표가 종료되었습니다",
   "notification.reblog": "{name} 님이 부스트 했습니다",
   "notification.status": "{name} 님이 방금 게시물을 올렸습니다",
+  "notification.update": "{name} 님이 게시물을 수정했습니다",
   "notifications.clear": "알림 지우기",
   "notifications.clear_confirmation": "정말로 알림을 삭제하시겠습니까?",
   "notifications.column_settings.alert": "데스크탑 알림",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "새 게시물:",
   "notifications.column_settings.unread_notifications.category": "읽지 않은 알림",
   "notifications.column_settings.unread_notifications.highlight": "읽지 않은 알림 강조",
+  "notifications.column_settings.update": "수정내역:",
   "notifications.filter.all": "모두",
   "notifications.filter.boosts": "부스트",
   "notifications.filter.favourites": "즐겨찾기",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "불러오는 중…",
   "regeneration_indicator.sublabel": "당신의 홈 피드가 준비되는 중입니다!",
   "relative_time.days": "{number}일 전",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number} 일 전",
+  "relative_time.full.hours": "{number} 시간 전",
+  "relative_time.full.just_now": "방금 전",
+  "relative_time.full.minutes": "{number} 분 전",
+  "relative_time.full.seconds": "{number} 초 전",
   "relative_time.hours": "{number}시간 전",
   "relative_time.just_now": "방금",
   "relative_time.minutes": "{number}분 전",
   "relative_time.seconds": "{number}초 전",
   "relative_time.today": "오늘",
   "reply_indicator.cancel": "취소",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "기타",
+  "report.categories.spam": "스팸",
+  "report.categories.violation": "컨텐츠가 한 개 이상의 서버 규칙을 위반합니다",
   "report.forward": "{target}에 포워드 됨",
   "report.forward_hint": "이 계정은 다른 서버에 있습니다. 익명화 된 사본을 해당 서버에도 전송할까요?",
   "report.hint": "신고는 당신의 서버 스태프에게 전송 됩니다. 왜 이 계정을 신고하는 지에 대한 설명을 아래에 작성할 수 있습니다:",
@@ -407,14 +409,14 @@
   "status.delete": "삭제",
   "status.detailed_status": "대화 자세히 보기",
   "status.direct": "@{name}에게 다이렉트 메시지",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "수정",
+  "status.edited": "{date}에 편집됨",
+  "status.edited_x_times": "{count}번 수정됨",
   "status.embed": "공유하기",
   "status.favourite": "즐겨찾기",
   "status.filtered": "필터로 걸러짐",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} 님이 {date}에 생성함",
+  "status.history.edited": "{name} 님이 {date}에 수정함",
   "status.load_more": "더 보기",
   "status.media_hidden": "미디어 숨겨짐",
   "status.mention": "@{name}에게 글쓰기",
diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json
index 36b2ffef1..331ab57f0 100644
--- a/app/javascript/mastodon/locales/ku.json
+++ b/app/javascript/mastodon/locales/ku.json
@@ -308,6 +308,7 @@
   "notification.poll": "ڕاپرسییەک کە دەنگی پێداویت کۆتایی هات",
   "notification.reblog": "{name} نووسراوەکەتی دووبارە توتاند",
   "notification.status": "{name} تازە بڵاوکرایەوە",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "ئاگانامەکان بسڕیەوە",
   "notifications.clear_confirmation": "ئایا دڵنیایت لەوەی دەتەوێت بە هەمیشەیی هەموو ئاگانامەکانت بسڕیتەوە?",
   "notifications.column_settings.alert": "ئاگانامەکانی پیشانگەرر ڕومێزی",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "توتەکانی نوێ:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "هەموو",
   "notifications.filter.boosts": "دووبارەتوتەکان",
   "notifications.filter.favourites": "دڵخوازەکان",
diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json
index c8638e56a..9a8c9685a 100644
--- a/app/javascript/mastodon/locales/kw.json
+++ b/app/javascript/mastodon/locales/kw.json
@@ -308,6 +308,7 @@
   "notification.poll": "An sondyans may hwrussowgh ragleva a worfennas",
   "notification.reblog": "{name} a generthas agas post",
   "notification.status": "{name} a wrug nowydh postya",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Dilea gwarnyansow",
   "notifications.clear_confirmation": "Owgh hwi sur a vynnes dilea agas gwarnyansow oll yn fast?",
   "notifications.column_settings.alert": "Gwarnyansow pennskrin",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Postow nowydh:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Oll",
   "notifications.filter.boosts": "Kenerthow",
   "notifications.filter.favourites": "Re drudh",
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index 1ed083f0c..c9f8f32d2 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index f8f61532a..955d2afa3 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -47,8 +47,8 @@
   "account.unmute": "Noņemt apklusinājumu @{name}",
   "account.unmute_notifications": "Rādīt paziņojumus no lietotāja @{name}",
   "account_note.placeholder": "Noklikšķiniet, lai pievienotu piezīmi",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Lietotāju saglabāšanas rādītājs dienā pēc reģistrēšanās",
+  "admin.dashboard.monthly_retention": "Lietotāju saglabāšanas rādītājs mēnesī pēc reģistrēšanās",
   "admin.dashboard.retention.average": "Vidēji",
   "admin.dashboard.retention.cohort": "Reģistrēšanās mēnesis",
   "admin.dashboard.retention.cohort_size": "Jauni lietotāji",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Maini aptaujas veidu, lai atļautu vienu izvēli",
   "compose_form.publish": "Taurēt",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Saglabāt izmaiņas",
   "compose_form.sensitive.hide": "{count, plural, one {Atzīmēt mediju kā sensitīvu} other {Atzīmēt medijus kā sensitīvus}}",
   "compose_form.sensitive.marked": "{count, plural, one {Medijs ir atzīmēts kā sensitīvs} other {Mediji ir atzīmēti kā sensitīvi}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Medijs nav atzīmēts kā sensitīvs} other {Mediji nav atzīmēti kā sensitīvi}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Aprauja, kurā tu piedalījies, ir pabeigta",
   "notification.reblog": "{name} paaugstināja tavu ziņu",
   "notification.status": "{name} tikko publicēja",
+  "notification.update": "{name} ir rediģējis rakstu",
   "notifications.clear": "Notīrīt paziņojumus",
   "notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?",
   "notifications.column_settings.alert": "Darbvirsmas paziņojumi",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Jaunas ziņas:",
   "notifications.column_settings.unread_notifications.category": "Nelasītie paziņojumi",
   "notifications.column_settings.unread_notifications.highlight": "Iezīmēt nelasītos paziņojumus",
+  "notifications.column_settings.update": "Labojumi:",
   "notifications.filter.all": "Visi",
   "notifications.filter.boosts": "Palielinājumi",
   "notifications.filter.favourites": "Izlases",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Ielādē…",
   "regeneration_indicator.sublabel": "Tiek gatavota tava plūsma!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# diena} other {# dienas}} atpakaļ",
+  "relative_time.full.hours": "{number, plural, one {# stunda} other {# stundas}} atpakaļ",
+  "relative_time.full.just_now": "tikko",
+  "relative_time.full.minutes": "{number, plural, one {# minūte} other {# minūtes}} atpakaļ",
+  "relative_time.full.seconds": "{number, plural, one {# sekunde} other {# sekundes}} atpakaļ",
   "relative_time.hours": "{number}st",
   "relative_time.just_now": "tagad",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "šodien",
   "reply_indicator.cancel": "Atcelt",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Citi",
+  "report.categories.spam": "Spams",
+  "report.categories.violation": "Saturs pārkāpj vienu vai vairākus servera noteikumus",
   "report.forward": "Pārsūtīt {target}",
   "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu ziņojuma kopiju arī tam?",
   "report.hint": "Pārskats tiks nosūtīts tava servera moderatoriem. Tu vari pievienot paskaidrojumu, kādēļ tu ziņo par kontu:",
@@ -407,14 +409,14 @@
   "status.delete": "Dzēst",
   "status.detailed_status": "Detalizēts sarunas skats",
   "status.direct": "Privāta ziņa @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Rediģēt",
+  "status.edited": "Rediģēts {date}",
+  "status.edited_x_times": "Rediģēts {count, plural, one {{count} reize} other {{count} reizes}}",
   "status.embed": "Iestrādāt",
   "status.favourite": "Iecienītā",
   "status.filtered": "Filtrēts",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} izveidots {date}",
+  "status.history.edited": "{name} rediģēts {date}",
   "status.load_more": "Ielādēt vairāk",
   "status.media_hidden": "Medijs ir paslēpts",
   "status.mention": "Pieminēt @{name}",
diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json
index ae925b391..609232e7a 100644
--- a/app/javascript/mastodon/locales/mk.json
+++ b/app/javascript/mastodon/locales/mk.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Сите",
   "notifications.filter.boosts": "Бустови",
   "notifications.filter.favourites": "Омилени",
diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json
index b17642b3c..e85a5ff75 100644
--- a/app/javascript/mastodon/locales/ml.json
+++ b/app/javascript/mastodon/locales/ml.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} നിങ്ങളുടെ പോസ്റ്റ് ബൂസ്റ്റ് ചെയ്തു",
   "notification.status": "{name} ഇപ്പോൾ പോസ്റ്റുചെയ്‌തു",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "അറിയിപ്പ് മായ്ക്കുക",
   "notifications.clear_confirmation": "നിങ്ങളുടെ എല്ലാ അറിയിപ്പുകളും ശാശ്വതമായി മായ്‌ക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?",
   "notifications.column_settings.alert": "ഡെസ്ക്ടോപ്പ് അറിയിപ്പുകൾ",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "പുതിയ ടൂട്ടുകൾ:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "എല്ലാം",
   "notifications.filter.boosts": "ബൂസ്റ്റുകൾ",
   "notifications.filter.favourites": "പ്രിയപ്പെട്ടവ",
diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json
index 14e5d2de9..ee3e21bf8 100644
--- a/app/javascript/mastodon/locales/mr.json
+++ b/app/javascript/mastodon/locales/mr.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index 5db089d5a..c50e442c1 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -308,6 +308,7 @@
   "notification.poll": "Sebuah undian yang anda undi telah tamat",
   "notification.reblog": "{name} menggalak hantaran anda",
   "notification.status": "{name} baru sahaja mengirim hantaran",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Buang pemberitahuan",
   "notifications.clear_confirmation": "Adakah anda pasti anda ingin membuang semua pemberitahuan anda secara kekal?",
   "notifications.column_settings.alert": "Pemberitahuan atas meja",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Hantaran baharu:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Semua",
   "notifications.filter.boosts": "Galakan",
   "notifications.filter.favourites": "Kegemaran",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index ec4e4aabd..f636d4d24 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -308,6 +308,7 @@
   "notification.poll": "Een poll waaraan jij hebt meegedaan is beëindigd",
   "notification.reblog": "{name} boostte jouw toot",
   "notification.status": "{name} heeft zojuist een toot geplaatst",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Meldingen verwijderen",
   "notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
   "notifications.column_settings.alert": "Desktopmeldingen",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nieuwe toots:",
   "notifications.column_settings.unread_notifications.category": "Ongelezen meldingen",
   "notifications.column_settings.unread_notifications.highlight": "Ongelezen meldingen markeren",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Alles",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favorieten",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index 17e1a4d25..d85cb3e20 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -308,6 +308,7 @@
   "notification.poll": "Ei rundspørjing du har røysta i er ferdig",
   "notification.reblog": "{name} framheva statusen din",
   "notification.status": "{name} la nettopp ut",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Tøm varsel",
   "notifications.clear_confirmation": "Er du sikker på at du vil fjerna alle varsla dine for alltid?",
   "notifications.column_settings.alert": "Skrivebordsvarsel",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nye tuter:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Alle",
   "notifications.filter.boosts": "Framhevingar",
   "notifications.filter.favourites": "Favorittar",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 0ede09267..2948a0586 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -308,6 +308,7 @@
   "notification.poll": "En avstemning du har stemt på har avsluttet",
   "notification.reblog": "{name} fremhevde din status",
   "notification.status": "{name} la nettopp ut",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Fjern varsler",
   "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler permanent?",
   "notifications.column_settings.alert": "Skrivebordsvarslinger",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nye tuter:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Alle",
   "notifications.filter.boosts": "Fremhevinger",
   "notifications.filter.favourites": "Favoritter",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 7e7b68f77..65ba8aeb6 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -308,6 +308,7 @@
   "notification.poll": "Avètz participat a un sondatge que ven de s’acabar",
   "notification.reblog": "{name} a partejat vòstre estatut",
   "notification.status": "{name} ven de publicar",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Escafar",
   "notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?",
   "notifications.column_settings.alert": "Notificacions localas",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Tuts novèls :",
   "notifications.column_settings.unread_notifications.category": "Notificacions pas legidas",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Totas",
   "notifications.filter.boosts": "Partages",
   "notifications.filter.favourites": "Favorits",
diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json
index 2675da68c..5456eb88e 100644
--- a/app/javascript/mastodon/locales/pa.json
+++ b/app/javascript/mastodon/locales/pa.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 07754475f..c33275866 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -109,7 +109,7 @@
   "compose_form.poll.switch_to_single": "Pozwól na wybranie tylko jednej opcji",
   "compose_form.publish": "Wyślij",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Zapisz zmiany",
   "compose_form.sensitive.hide": "Oznacz multimedia jako wrażliwe",
   "compose_form.sensitive.marked": "Zawartość multimedia jest oznaczona jako wrażliwa",
   "compose_form.sensitive.unmarked": "Zawartość multimedialna nie jest oznaczona jako wrażliwa",
@@ -313,6 +313,7 @@
   "notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyło się",
   "notification.reblog": "{name} podbił(a) Twój wpis",
   "notification.status": "{name} właśnie utworzył(a) wpis",
+  "notification.update": "{name} edytował post",
   "notifications.clear": "Wyczyść powiadomienia",
   "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
   "notifications.column_settings.alert": "Powiadomienia na pulpicie",
@@ -331,6 +332,7 @@
   "notifications.column_settings.status": "Nowe wpisy:",
   "notifications.column_settings.unread_notifications.category": "Nieprzeczytane powiadomienia",
   "notifications.column_settings.unread_notifications.highlight": "Podświetl nieprzeczytane powiadomienia",
+  "notifications.column_settings.update": "Edycje:",
   "notifications.filter.all": "Wszystkie",
   "notifications.filter.boosts": "Podbicia",
   "notifications.filter.favourites": "Ulubione",
@@ -370,20 +372,20 @@
   "regeneration_indicator.label": "Ładuję…",
   "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
   "relative_time.days": "{number} dni",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}} temu",
+  "relative_time.full.hours": "{number, plural, one {# godzinę} few {# godziny} many {# godzin} other {# godzin}} temu",
+  "relative_time.full.just_now": "przed chwilą",
+  "relative_time.full.minutes": "{number, plural, one {# minutę} few {# minuty} many {# minut} other {# minut}} temu",
+  "relative_time.full.seconds": "{number, plural, one {# sekundę} few {# sekundy} many {# sekund} other {# sekund}} temu",
   "relative_time.hours": "{number} godz.",
   "relative_time.just_now": "teraz",
   "relative_time.minutes": "{number} min.",
   "relative_time.seconds": "{number} s.",
   "relative_time.today": "dzisiaj",
   "reply_indicator.cancel": "Anuluj",
-  "report.categories.other": "Other",
+  "report.categories.other": "Inne",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Zawartość narusza co najmniej jedną zasadę serwera",
   "report.forward": "Przekaż na {target}",
   "report.forward_hint": "To konto znajduje się na innej instancji. Czy chcesz wysłać anonimową kopię zgłoszenia rnież na nią?",
   "report.hint": "Zgłoszenie zostanie wysłane moderatorom Twojego serwera. Poniżej możesz też umieścić wyjaśnienie dlaczego zgłaszasz to konto:",
@@ -412,8 +414,8 @@
   "status.delete": "Usuń",
   "status.detailed_status": "Szczegółowy widok konwersacji",
   "status.direct": "Wyślij wiadomość bezpośrednią do @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
+  "status.edit": "Edytuj",
+  "status.edited": "Edytowano {date}",
   "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
   "status.embed": "Osadź",
   "status.favourite": "Dodaj do ulubionych",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index e61c3ef5a..be49f2b4e 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -308,6 +308,7 @@
   "notification.poll": "Uma enquete que você votou terminou",
   "notification.reblog": "{name} deu boost no teu toot",
   "notification.status": "{name} acabou de tootar",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Limpar notificações",
   "notifications.clear_confirmation": "Você tem certeza de que deseja limpar todas as suas notificações?",
   "notifications.column_settings.alert": "Notificações no computador",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Novos toots:",
   "notifications.column_settings.unread_notifications.category": "Notificações não lidas",
   "notifications.column_settings.unread_notifications.highlight": "Destacar notificações não lidas",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Tudo",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favoritos",
diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json
index 988ecef3a..531265706 100644
--- a/app/javascript/mastodon/locales/pt-PT.json
+++ b/app/javascript/mastodon/locales/pt-PT.json
@@ -47,8 +47,8 @@
   "account.unmute": "Não silenciar @{name}",
   "account.unmute_notifications": "Deixar de silenciar @{name}",
   "account_note.placeholder": "Clique para adicionar nota",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição",
+  "admin.dashboard.monthly_retention": "Taxa de retenção de utilizadores por mês após a inscrição",
   "admin.dashboard.retention.average": "Média",
   "admin.dashboard.retention.cohort": "Mês de inscrição",
   "admin.dashboard.retention.cohort_size": "Novos utilizadores",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Alterar a votação para permitir uma única escolha",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Guardar alterações",
   "compose_form.sensitive.hide": "Marcar media como sensível",
   "compose_form.sensitive.marked": "Media marcada como sensível",
   "compose_form.sensitive.unmarked": "Media não está marcada como sensível",
@@ -308,6 +308,7 @@
   "notification.poll": "Uma votação em que participaste chegou ao fim",
   "notification.reblog": "{name} partilhou a tua publicação",
   "notification.status": "{name} acabou de publicar",
+  "notification.update": "{name} editou uma publicação",
   "notifications.clear": "Limpar notificações",
   "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
   "notifications.column_settings.alert": "Notificações no ambiente de trabalho",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Novos toots:",
   "notifications.column_settings.unread_notifications.category": "Notificações não lidas",
   "notifications.column_settings.unread_notifications.highlight": "Destacar notificações não lidas",
+  "notifications.column_settings.update": "Edições:",
   "notifications.filter.all": "Todas",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favoritos",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "A carregar…",
   "regeneration_indicator.sublabel": "A tua página inicial está a ser preparada!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural,one {# dia} other {# dias}} atrás",
+  "relative_time.full.hours": "{number, plural,one {# hora}other {# horas}} atrás",
+  "relative_time.full.just_now": "agora mesmo",
+  "relative_time.full.minutes": "{number, plural,one {# minuto}other {# minutos}} atrás",
+  "relative_time.full.seconds": "{number, plural,one {# segundo} other {# segundos}} atrás",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "agora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "hoje",
   "reply_indicator.cancel": "Cancelar",
-  "report.categories.other": "Other",
+  "report.categories.other": "Outro",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "O conteúdo viola uma ou mais regras do servidor",
   "report.forward": "Reenviar para {target}",
   "report.forward_hint": "A conta é de outro servidor. Enviar uma cópia anónima da denúncia para lá também?",
   "report.hint": "A denúncia será enviada para os moderadores do seu servidor. Pode fornecer, em baixo, uma explicação do motivo pelo qual está a denunciar esta conta:",
@@ -407,14 +409,14 @@
   "status.delete": "Eliminar",
   "status.detailed_status": "Vista de conversação detalhada",
   "status.direct": "Mensagem direta @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Editar",
+  "status.edited": "Editado em {date}",
+  "status.edited_x_times": "Editado {count, plural,one {{count} vez} other {{count} vezes}}",
   "status.embed": "Incorporar",
   "status.favourite": "Adicionar aos favoritos",
   "status.filtered": "Filtrada",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} criado em {date}",
+  "status.history.edited": "{name} editado em {date}",
   "status.load_more": "Carregar mais",
   "status.media_hidden": "Media escondida",
   "status.mention": "Mencionar @{name}",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index 15baeef12..1a6e19713 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -308,6 +308,7 @@
   "notification.poll": "Un sondaj pentru care ai votat s-a încheiat",
   "notification.reblog": "{name} ți-a distribuit postarea",
   "notification.status": "{name} tocmai a postat",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Șterge notificările",
   "notifications.clear_confirmation": "Ești sigur că vrei să ștergi permanent toate notificările?",
   "notifications.column_settings.alert": "Notificări pe desktop",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Postări noi:",
   "notifications.column_settings.unread_notifications.category": "Notificări necitite",
   "notifications.column_settings.unread_notifications.highlight": "Evidențiază notificările necitite",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Toate",
   "notifications.filter.boosts": "Distribuiri",
   "notifications.filter.favourites": "Favorite",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index c414f9e7c..a0438ceb5 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -35,7 +35,7 @@
   "account.never_active": "Никогда",
   "account.posts": "Посты",
   "account.posts_with_replies": "Посты и ответы",
-  "account.report": "Жалоба №{name}",
+  "account.report": "Пожаловаться на @{name}",
   "account.requested": "Ожидает подтверждения. Нажмите для отмены запроса",
   "account.share": "Поделиться профилем @{name}",
   "account.show_reblogs": "Показывать продвижения от @{name}",
@@ -47,8 +47,8 @@
   "account.unmute": "Убрать {name} из игнорируемых",
   "account.unmute_notifications": "Показывать уведомления от @{name}",
   "account_note.placeholder": "Текст заметки",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Уровень удержания пользователей после регистрации, в днях",
+  "admin.dashboard.monthly_retention": "Уровень удержания пользователей после регистрации, в месяцах",
   "admin.dashboard.retention.average": "Среднее",
   "admin.dashboard.retention.cohort": "Месяц регистрации",
   "admin.dashboard.retention.cohort_size": "Новые пользователи",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Переключить в режим выбора одного ответа",
   "compose_form.publish": "Запостить",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Сохранить",
   "compose_form.sensitive.hide": "{count, plural, one {Отметить медифайл как деликатный} other {Отметить медифайлы как деликатные}}",
   "compose_form.sensitive.marked": "Медиа{count, plural, =1 {файл отмечен} other {файлы отмечены}} как «деликатного характера»",
   "compose_form.sensitive.unmarked": "Медиа{count, plural, =1 {файл не отмечен} other {файлы не отмечены}} как «деликатного характера»",
@@ -308,6 +308,7 @@
   "notification.poll": "Опрос, в котором вы приняли участие, завершился",
   "notification.reblog": "{name} продвинул(а) ваш пост",
   "notification.status": "{name} только что запостил",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Очистить уведомления",
   "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?",
   "notifications.column_settings.alert": "Уведомления на рабочем столе",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Новые посты:",
   "notifications.column_settings.unread_notifications.category": "Непрочитанные уведомления",
   "notifications.column_settings.unread_notifications.highlight": "Выделять непрочитанные уведомления",
+  "notifications.column_settings.update": "Изменения:",
   "notifications.filter.all": "Все",
   "notifications.filter.boosts": "Продвижения",
   "notifications.filter.favourites": "Отметки «избранного»",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Загрузка…",
   "regeneration_indicator.sublabel": "Один момент, мы подготавливаем вашу ленту!",
   "relative_time.days": "{number} д",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# день} many {# дней} other {# дня}} назад",
+  "relative_time.full.hours": "{number, plural, one {# час} many {# часов} other {# часа}} назад",
+  "relative_time.full.just_now": "только что",
+  "relative_time.full.minutes": "{number, plural, one {# минуту} many {# минут} other {# минуты}} назад",
+  "relative_time.full.seconds": "{number, plural, one {# секунду} many {# секунд} other {# секунды}} назад",
   "relative_time.hours": "{number} ч",
   "relative_time.just_now": "только что",
   "relative_time.minutes": "{number} мин",
   "relative_time.seconds": "{number} с",
   "relative_time.today": "сегодня",
   "reply_indicator.cancel": "Отмена",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Другое",
+  "report.categories.spam": "Спам",
+  "report.categories.violation": "Содержимое нарушает одно или несколько правил узла",
   "report.forward": "Переслать в {target}",
   "report.forward_hint": "Эта учётная запись расположена на другом узле. Отправить туда анонимную копию вашей жалобы?",
   "report.hint": "Жалоба будет отправлена модераторам вашего узла. Вы также можете указать подробную причину жалобы ниже:",
@@ -407,14 +409,14 @@
   "status.delete": "Удалить",
   "status.detailed_status": "Подробный просмотр обсуждения",
   "status.direct": "Написать @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Изменить",
+  "status.edited": "Последнее изменение: {date}",
+  "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}",
   "status.embed": "Встроить на свой сайт",
   "status.favourite": "В избранное",
   "status.filtered": "Отфильтровано",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} создал {date}",
+  "status.history.edited": "{name} отредактировал {date}",
   "status.load_more": "Загрузить остальное",
   "status.media_hidden": "Файл скрыт",
   "status.mention": "Упомянуть @{name}",
@@ -460,7 +462,7 @@
   "timeline_hint.resources.followers": "подписчиков",
   "timeline_hint.resources.follows": "подписки",
   "timeline_hint.resources.statuses": "прошлые посты",
-  "trends.counter_by_accounts": "{count, plural, one {{counter} человек обсуждает} few {{counter} человека обсуждает} many {{counter} человек обсуждают} other {{counter} обсуждают}} ",
+  "trends.counter_by_accounts": "{count, plural, one {{counter} человек обсуждает} few {{counter} человека обсуждают} many {{counter} человек обсуждают} other {{counter} человека обсуждает}}",
   "trends.trending_now": "Самое актуальное",
   "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.",
   "units.short.billion": "{count} млрд",
diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json
index a2a22e758..357a69187 100644
--- a/app/javascript/mastodon/locales/sa.json
+++ b/app/javascript/mastodon/locales/sa.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json
index 4f8f12357..467857136 100644
--- a/app/javascript/mastodon/locales/sc.json
+++ b/app/javascript/mastodon/locales/sc.json
@@ -308,6 +308,7 @@
   "notification.poll": "Unu sondàgiu in su chi as votadu est acabbadu",
   "notification.reblog": "{name} at cumpartzidu sa publicatzione tua",
   "notification.status": "{name} at publicadu cosa",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Lìmpia notìficas",
   "notifications.clear_confirmation": "Seguru chi boles isboidare in manera permanente totu is notìficas tuas?",
   "notifications.column_settings.alert": "Notìficas de iscrivania",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Publicatziones noas:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Totus",
   "notifications.filter.boosts": "Cumpartziduras",
   "notifications.filter.favourites": "Preferidos",
diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json
index 40939f1b2..c90c785fb 100644
--- a/app/javascript/mastodon/locales/si.json
+++ b/app/javascript/mastodon/locales/si.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "දැනුම්දීම් හිස්කරන්න",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "සියල්ල",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "ප්‍රියතමයන්",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 34c3da043..f0833ff70 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -308,6 +308,7 @@
   "notification.poll": "Anketa v ktorej si hlasoval/a sa skončila",
   "notification.reblog": "{name} zdieľal/a tvoj príspevok",
   "notification.status": "{name} práve uverejnil/a",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Vyčisti oboznámenia",
   "notifications.clear_confirmation": "Naozaj chceš nenávratne prečistiť všetky tvoje oboznámenia?",
   "notifications.column_settings.alert": "Oboznámenia na ploche",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nové príspevky:",
   "notifications.column_settings.unread_notifications.category": "Neprečítané oboznámenia",
   "notifications.column_settings.unread_notifications.highlight": "Zdôrazni neprečítané oboznámenia",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Všetky",
   "notifications.filter.boosts": "Vyzdvihnutia",
   "notifications.filter.favourites": "Obľúbené",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index 212e9256c..f69d986fb 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Spremenite anketo, da omogočite eno izbiro",
   "compose_form.publish": "Tutni",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Shrani spremembe",
   "compose_form.sensitive.hide": "Označi medij kot občutljiv",
   "compose_form.sensitive.marked": "Medij je označen kot občutljiv",
   "compose_form.sensitive.unmarked": "Medij ni označen kot občutljiv",
@@ -308,6 +308,7 @@
   "notification.poll": "Glasovanje, v katerem ste sodelovali, se je končalo",
   "notification.reblog": "{name} je spodbudil/a vaš status",
   "notification.status": "{name} je pravkar objavil/a",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Počisti obvestila",
   "notifications.clear_confirmation": "Ali ste prepričani, da želite trajno izbrisati vsa vaša obvestila?",
   "notifications.column_settings.alert": "Namizna obvestila",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Neprebrana obvestila",
   "notifications.column_settings.unread_notifications.highlight": "Poudari neprebrana obvestila",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Vse",
   "notifications.filter.boosts": "Spodbude",
   "notifications.filter.favourites": "Priljubljeni",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Nalaganje…",
   "regeneration_indicator.sublabel": "Vaš domači vir se pripravlja!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {pred # dnem} two {pred # dnevoma} few {pred # dnevi} other {pred # dnevi}}",
+  "relative_time.full.hours": "{number, plural, one {pred # uro} two {pred # urama} few {pred # urami} other {pred # urami}}",
+  "relative_time.full.just_now": "pravkar",
+  "relative_time.full.minutes": "{number, plural, one {pred # minuto} two {pred # minutama} few {pred # minutami} other {pred # minutami}}",
+  "relative_time.full.seconds": "{number, plural, one {pred # sekundo} two {pred # sekundama} few {pred # sekundami} other {pred # sekundami}}",
   "relative_time.hours": "{number}u",
   "relative_time.just_now": "zdaj",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "danes",
   "reply_indicator.cancel": "Prekliči",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Drugo",
+  "report.categories.spam": "Neželeno",
+  "report.categories.violation": "Vsebina krši eno ali več pravil strežnika",
   "report.forward": "Posreduj do {target}",
   "report.forward_hint": "Račun je iz drugega strežnika. Pošljem anonimno kopijo poročila tudi na drugi strežnik?",
   "report.hint": "Poročilo bo poslano moderatorjem vašega vozlišča. Spodaj lahko navedete, zakaj prijavljate ta račun:",
@@ -407,14 +409,14 @@
   "status.delete": "Izbriši",
   "status.detailed_status": "Podroben pogled pogovora",
   "status.direct": "Neposredno sporočilo @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Uredi",
+  "status.edited": "Urejeno {date}",
+  "status.edited_x_times": "Urejeno {count, plural, one {#-krat} two {#-krat} few {#-krat} other {#-krat}}",
   "status.embed": "Vgradi",
   "status.favourite": "Priljubljen",
   "status.filtered": "Filtrirano",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name}: ustvarjeno {date}",
+  "status.history.edited": "{name}: urejeno {date}",
   "status.load_more": "Naloži več",
   "status.media_hidden": "Mediji so skriti",
   "status.mention": "Omeni @{name}",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index 2a9a12e17..fb6bbac75 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -47,8 +47,8 @@
   "account.unmute": "Ktheji zërin @{name}",
   "account.unmute_notifications": "Hiqua ndalimin e shfaqjes njoftimeve nga @{name}",
   "account_note.placeholder": "Klikoni për të shtuar shënim",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Shkallë mbajtjeje përdoruesi, në ditë, pas regjistrimit",
+  "admin.dashboard.monthly_retention": "Shkallë mbajtjeje përdoruesi, në muaj, pas regjistrimit",
   "admin.dashboard.retention.average": "Mesatare",
   "admin.dashboard.retention.cohort": "Muaj regjistrimi",
   "admin.dashboard.retention.cohort_size": "Përdorues të rinj",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Ndrysho votimin për të lejuar vetëm një zgjedhje",
   "compose_form.publish": "Mesazh",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Ruaji ndryshimet",
   "compose_form.sensitive.hide": "{count, plural, one {Vëri shenjë medias si rezervat} other {Vëru shenjë mediave si rezervat}}",
   "compose_form.sensitive.marked": "{count, plural, one {Medias i është vënë shenjë rezervat} other {Mediave u është vënë shenjë si rezervat}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Media s’ka shenjë si rezervat} other {Mediat s’kanë shenja si rezervat}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Ka përfunduar një pyetësor ku keni votuar",
   "notification.reblog": "{name} përforcoi mesazhin tuaj",
   "notification.status": "{name} sapo postoi",
+  "notification.update": "{name} përpunoi një postim",
   "notifications.clear": "Spastroji njoftimet",
   "notifications.clear_confirmation": "Jeni i sigurt se doni të spastrohen përgjithmonë krejt njoftimet tuaja?",
   "notifications.column_settings.alert": "Njoftime desktopi",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Mesazhe të rinj:",
   "notifications.column_settings.unread_notifications.category": "Njoftime të palexuara",
   "notifications.column_settings.unread_notifications.highlight": "Theksoji njoftimet e palexuara",
+  "notifications.column_settings.update": "Përpunime:",
   "notifications.filter.all": "Krejt",
   "notifications.filter.boosts": "Përforcime",
   "notifications.filter.favourites": "Të parapëlqyer",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Po ngarkohet…",
   "regeneration_indicator.sublabel": "Prurja juaj vetjake po përgatitet!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# ditë} other {# ditë}} më parë",
+  "relative_time.full.hours": "{number, plural, one {# orë} other {# orë}} më parë",
+  "relative_time.full.just_now": "mu tani",
+  "relative_time.full.minutes": "{number, plural, one {# minutë} other {# minuta}} më parë",
+  "relative_time.full.seconds": "{number, plural, one {# sekondë} other {# sekonda}} më parë",
   "relative_time.hours": "{number}o",
   "relative_time.just_now": "tani",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "sot",
   "reply_indicator.cancel": "Anuloje",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Tjetër",
+  "report.categories.spam": "I padëshiruar",
+  "report.categories.violation": "Lënda shkel një ose disa rregulla shërbyesi",
   "report.forward": "Përcillja {target}",
   "report.forward_hint": "Llogaria është nga një shërbyes tjetër. Të dërgohet edhe një kopje e anonimizuar e raportimit?",
   "report.hint": "Raportimi do t’u dërgohet moderatorëve të shërbyesit tuaj. Më poshtë mund të jepni një shpjegim se pse po e raportoni këtë llogari:",
@@ -407,14 +409,14 @@
   "status.delete": "Fshije",
   "status.detailed_status": "Pamje e hollësishme bisede",
   "status.direct": "Mesazh i drejtpërdrejtë për @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Përpunojeni",
+  "status.edited": "Përpunuar më {date}",
+  "status.edited_x_times": "Përpunuar {count, plural, one {{count} herë} other {{count} herë}}",
   "status.embed": "Trupëzim",
   "status.favourite": "I parapëlqyer",
   "status.filtered": "I filtruar",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} u krijua më {date}",
+  "status.history.edited": "{name} u përpunua më {date}",
   "status.load_more": "Ngarko më tepër",
   "status.media_hidden": "Me media të fshehur",
   "status.mention": "Përmendni @{name}",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index c12cad908..37fbd7666 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} je podržao(la) Vaš status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Očisti obaveštenja",
   "notifications.clear_confirmation": "Da li ste sigurno da trajno želite da očistite Vaša obaveštenja?",
   "notifications.column_settings.alert": "Obaveštenja na radnoj površini",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index fb94b0098..429d4c697 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -308,6 +308,7 @@
   "notification.poll": "Завршена је анкета у којој сте гласали",
   "notification.reblog": "{name} је подржао/ла Ваш статус",
   "notification.status": "{name} управо објавио",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Очисти обавештења",
   "notifications.clear_confirmation": "Да ли сте сигурно да трајно желите да очистите Ваша обавештења?",
   "notifications.column_settings.alert": "Обавештења на радној површини",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Нови тутови:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Све",
   "notifications.filter.boosts": "Подршки",
   "notifications.filter.favourites": "Омиљене",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 47d70fb54..ac3179df1 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -47,8 +47,8 @@
   "account.unmute": "Sluta tysta @{name}",
   "account.unmute_notifications": "Återaktivera aviseringar från @{name}",
   "account_note.placeholder": "Klicka för att lägga till anteckning",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Användarlojalitet per dag efter registrering",
+  "admin.dashboard.monthly_retention": "Användarlojalitet per månad efter registrering",
   "admin.dashboard.retention.average": "Genomsnittlig",
   "admin.dashboard.retention.cohort": "Registreringsmånad",
   "admin.dashboard.retention.cohort_size": "Nya användare",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Ändra enkät för att tillåta ett enda val",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Spara ändringar",
   "compose_form.sensitive.hide": "Markera media som känsligt",
   "compose_form.sensitive.marked": "Media har markerats som känsligt",
   "compose_form.sensitive.unmarked": "Media är inte markerat som känsligt",
@@ -308,6 +308,7 @@
   "notification.poll": "En omröstning du röstat i har avslutats",
   "notification.reblog": "{name} knuffade din status",
   "notification.status": "{name} skrev just",
+  "notification.update": "{name} redigerade ett inlägg",
   "notifications.clear": "Rensa aviseringar",
   "notifications.clear_confirmation": "Är du säker på att du vill rensa alla dina aviseringar permanent?",
   "notifications.column_settings.alert": "Skrivbordsaviseringar",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Nya tutor:",
   "notifications.column_settings.unread_notifications.category": "O-lästa aviseringar",
   "notifications.column_settings.unread_notifications.highlight": "Markera o-lästa aviseringar",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Alla",
   "notifications.filter.boosts": "Knuffar",
   "notifications.filter.favourites": "Favoriter",
@@ -365,19 +367,19 @@
   "regeneration_indicator.label": "Laddar…",
   "regeneration_indicator.sublabel": "Ditt hemmaflöde förbereds!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
+  "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan",
+  "relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan",
   "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.minutes": "{number, plural, one {# minut} other {# minuter}} sedan",
+  "relative_time.full.seconds": "{number, plural, one {# sekund} other {# sekunder}} sedan",
   "relative_time.hours": "{number}tim",
   "relative_time.just_now": "nu",
   "relative_time.minutes": "{number}min",
   "relative_time.seconds": "{number}sek",
   "relative_time.today": "idag",
   "reply_indicator.cancel": "Ångra",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
+  "report.categories.other": "Övrigt",
+  "report.categories.spam": "Skräppost",
   "report.categories.violation": "Content violates one or more server rules",
   "report.forward": "Vidarebefordra till {target}",
   "report.forward_hint": "Kontot är från en annan server. Skicka även en anonymiserad kopia av anmälan dit?",
@@ -407,9 +409,9 @@
   "status.delete": "Radera",
   "status.detailed_status": "Detaljerad samtalsvy",
   "status.direct": "Direktmeddela @{name}",
-  "status.edit": "Edit",
+  "status.edit": "Redigera",
   "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edited_x_times": "Redigerad {count, plural, one {{count} gång} other {{count} gånger}}",
   "status.embed": "Bädda in",
   "status.favourite": "Favorit",
   "status.filtered": "Filtrerat",
diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json
index 2675da68c..5456eb88e 100644
--- a/app/javascript/mastodon/locales/szl.json
+++ b/app/javascript/mastodon/locales/szl.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index 2e329f270..d624df36a 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -308,6 +308,7 @@
   "notification.poll": "நீங்கள் வாக்களித்த வாக்கெடுப்பு முடிவடைந்தது",
   "notification.reblog": "{name} உங்கள் நிலை அதிகரித்தது",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "அறிவிப்புகளை அழிக்கவும்",
   "notifications.clear_confirmation": "உங்கள் எல்லா அறிவிப்புகளையும் நிரந்தரமாக அழிக்க விரும்புகிறீர்களா?",
   "notifications.column_settings.alert": "டெஸ்க்டாப் அறிவிப்புகள்",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "எல்லா",
   "notifications.filter.boosts": "மதிப்பை உயர்த்து",
   "notifications.filter.favourites": "விருப்பத்துக்குகந்த",
diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json
index 6ef519315..5fe5cfdea 100644
--- a/app/javascript/mastodon/locales/tai.json
+++ b/app/javascript/mastodon/locales/tai.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index b84ee23ec..2901689d1 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -308,6 +308,7 @@
   "notification.poll": "మీరు పాల్గొనిన ఎన్సిక ముగిసినది",
   "notification.reblog": "{name} మీ స్టేటస్ ను బూస్ట్ చేసారు",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "ప్రకటనలను తుడిచివేయు",
   "notifications.clear_confirmation": "మీరు మీ అన్ని నోటిఫికేషన్లను శాశ్వతంగా తొలగించాలనుకుంటున్నారా?",
   "notifications.column_settings.alert": "డెస్క్టాప్ నోటిఫికేషన్లు",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "అన్నీ",
   "notifications.filter.boosts": "బూస్ట్లు",
   "notifications.filter.favourites": "ఇష్టాలు",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 27bcbc4bb..bef3af46f 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดี่ยว",
   "compose_form.publish": "โพสต์",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "บันทึกการเปลี่ยนแปลง",
   "compose_form.sensitive.hide": "{count, plural, other {ทำเครื่องหมายสื่อว่าละเอียดอ่อน}}",
   "compose_form.sensitive.marked": "{count, plural, other {มีการทำเครื่องหมายสื่อว่าละเอียดอ่อน}}",
   "compose_form.sensitive.unmarked": "{count, plural, other {ไม่มีการทำเครื่องหมายสื่อว่าละเอียดอ่อน}}",
@@ -132,7 +132,7 @@
   "confirmations.redraft.confirm": "ลบแล้วร่างใหม่",
   "confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบโพสต์นี้แล้วร่างโพสต์ใหม่? รายการโปรดและการดันจะหายไป และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน",
   "confirmations.reply.confirm": "ตอบกลับ",
-  "confirmations.reply.message": "การตอบกลับตอนนี้จะเขียนทับข้อความที่คุณกำลังเขียน คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?",
+  "confirmations.reply.message": "การตอบกลับในตอนนี้จะเขียนทับข้อความที่คุณกำลังเขียน คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?",
   "confirmations.unfollow.confirm": "เลิกติดตาม",
   "confirmations.unfollow.message": "คุณแน่ใจหรือไม่ว่าต้องการเลิกติดตาม {name}?",
   "conversation.delete": "ลบการสนทนา",
@@ -308,6 +308,7 @@
   "notification.poll": "การสำรวจความคิดเห็นที่คุณได้ลงคะแนนได้สิ้นสุดแล้ว",
   "notification.reblog": "{name} ได้ดันโพสต์ของคุณ",
   "notification.status": "{name} เพิ่งโพสต์",
+  "notification.update": "{name} ได้แก้ไขโพสต์",
   "notifications.clear": "ล้างการแจ้งเตือน",
   "notifications.clear_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการล้างการแจ้งเตือนทั้งหมดของคุณอย่างถาวร?",
   "notifications.column_settings.alert": "การแจ้งเตือนบนเดสก์ท็อป",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "โพสต์ใหม่:",
   "notifications.column_settings.unread_notifications.category": "การแจ้งเตือนที่ยังไม่ได้อ่าน",
   "notifications.column_settings.unread_notifications.highlight": "เน้นการแจ้งเตือนที่ยังไม่ได้อ่าน",
+  "notifications.column_settings.update": "การแก้ไข:",
   "notifications.filter.all": "ทั้งหมด",
   "notifications.filter.boosts": "การดัน",
   "notifications.filter.favourites": "รายการโปรด",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "กำลังโหลด…",
   "regeneration_indicator.sublabel": "กำลังเตรียมฟีดหน้าแรกของคุณ!",
   "relative_time.days": "{number} วัน",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, other {# วัน}}ที่แล้ว",
+  "relative_time.full.hours": "{number, plural, other {# ชั่วโมง}}ที่แล้ว",
+  "relative_time.full.just_now": "เมื่อกี้นี้",
+  "relative_time.full.minutes": "{number, plural, other {# นาที}}ที่แล้ว",
+  "relative_time.full.seconds": "{number, plural, other {# วินาที}}ที่แล้ว",
   "relative_time.hours": "{number} ชั่วโมง",
   "relative_time.just_now": "ตอนนี้",
   "relative_time.minutes": "{number} นาที",
   "relative_time.seconds": "{number} วินาที",
   "relative_time.today": "วันนี้",
   "reply_indicator.cancel": "ยกเลิก",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "อื่น ๆ",
+  "report.categories.spam": "สแปม",
+  "report.categories.violation": "เนื้อหาละเมิดหนึ่งกฎของเซิร์ฟเวอร์หรือมากกว่า",
   "report.forward": "ส่งต่อไปยัง {target}",
   "report.forward_hint": "บัญชีมาจากเซิร์ฟเวอร์อื่น ส่งสำเนาของรายงานที่ไม่ระบุตัวตนไปที่นั่นด้วย?",
   "report.hint": "จะส่งรายงานไปยังผู้ควบคุมเซิร์ฟเวอร์ของคุณ คุณสามารถให้คำอธิบายเหตุผลที่คุณรายงานบัญชีนี้ได้ด้านล่าง:",
@@ -407,14 +409,14 @@
   "status.delete": "ลบ",
   "status.detailed_status": "มุมมองการสนทนาโดยละเอียด",
   "status.direct": "ส่งข้อความโดยตรงถึง @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "แก้ไข",
+  "status.edited": "แก้ไขเมื่อ {date}",
+  "status.edited_x_times": "แก้ไข {count, plural, other {{count} ครั้ง}}",
   "status.embed": "ฝัง",
   "status.favourite": "ชื่นชอบ",
   "status.filtered": "กรองอยู่",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} ได้สร้างเมื่อ {date}",
+  "status.history.edited": "{name} ได้แก้ไขเมื่อ {date}",
   "status.load_more": "โหลดเพิ่มเติม",
   "status.media_hidden": "ซ่อนสื่ออยู่",
   "status.mention": "กล่าวถึง @{name}",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index a8c602caa..3d67b62e9 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -47,8 +47,8 @@
   "account.unmute": "@{name} adlı kişinin sesini aç",
   "account.unmute_notifications": "@{name} adlı kişinin bildirimlerini aç",
   "account_note.placeholder": "Not eklemek için tıklayın",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Kayıttan sonra günlük kullanıcı saklama oranı",
+  "admin.dashboard.monthly_retention": "Kayıttan sonra aylık kullanıcı saklama oranı",
   "admin.dashboard.retention.average": "Ortalama",
   "admin.dashboard.retention.cohort": "Kayıt ayı",
   "admin.dashboard.retention.cohort_size": "Yeni kullanıcılar",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Tek bir seçeneğe izin vermek için anketi değiştir",
   "compose_form.publish": "Tootla",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Değişiklikleri kaydet",
   "compose_form.sensitive.hide": "{count, plural, one {Medyayı hassas olarak işaretle} other {Medyayı hassas olarak işaretle}}",
   "compose_form.sensitive.marked": "{count, plural, one {Medya hassas olarak işaretlendi} other {Medya hassas olarak işaretlendi}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Medya hassas olarak işaretlenmemiş} other {Medya hassas olarak işaretlenmemiş}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Oy verdiğiniz bir anket sona erdi",
   "notification.reblog": "{name} gönderini teşvik etti",
   "notification.status": "{name} az önce gönderdi",
+  "notification.update": "{name} bir gönderiyi düzenledi",
   "notifications.clear": "Bildirimleri temizle",
   "notifications.clear_confirmation": "Tüm bildirimlerinizi kalıcı olarak temizlemek ister misiniz?",
   "notifications.column_settings.alert": "Masaüstü bildirimleri",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Yeni gönderiler:",
   "notifications.column_settings.unread_notifications.category": "Okunmamış bildirimler",
   "notifications.column_settings.unread_notifications.highlight": "Okunmamış bildirimleri öne çıkar",
+  "notifications.column_settings.update": "Düzenlemeler:",
   "notifications.filter.all": "Tümü",
   "notifications.filter.boosts": "Boostlar",
   "notifications.filter.favourites": "Beğeniler",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Yükleniyor…",
   "regeneration_indicator.sublabel": "Ana akışın hazırlanıyor!",
   "relative_time.days": "{number}g",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# gün} other {# gün}} önce",
+  "relative_time.full.hours": "{number, plural, one {# saat} other {# saat}} önce",
+  "relative_time.full.just_now": "şimdi",
+  "relative_time.full.minutes": "{number, plural, one {# dakika} other {# dakika}} önce",
+  "relative_time.full.seconds": "{number, plural, one {# saniye} other {# saniye}} önce",
   "relative_time.hours": "{number}sa",
   "relative_time.just_now": "şimdi",
   "relative_time.minutes": "{number}dk",
   "relative_time.seconds": "{number}sn",
   "relative_time.today": "bugün",
   "reply_indicator.cancel": "İptal",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Diğer",
+  "report.categories.spam": "İstenmeyen",
+  "report.categories.violation": "İçerik bir veya daha fazla sunucu kuralını ihlal ediyor",
   "report.forward": "{target} ilet",
   "report.forward_hint": "Hesap başka bir sunucudan. Raporun anonim bir kopyası da oraya gönderilsin mi?",
   "report.hint": "Bu rapor sunucu moderatörlerine gönderilecek. Bu hesabı neden bildirdiğiniz hakkında bilgi verebirsiniz:",
@@ -407,14 +409,14 @@
   "status.delete": "Sil",
   "status.detailed_status": "Ayrıntılı sohbet görünümü",
   "status.direct": "@{name} adlı kişiye direkt mesaj",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Düzenle",
+  "status.edited": "{date} tarihinde düzenlenmiş",
+  "status.edited_x_times": "{count, plural, one {{count} kez} other {{count} kez}} düzenlendi",
   "status.embed": "Gömülü",
   "status.favourite": "Beğen",
   "status.filtered": "Filtrelenmiş",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} oluşturdu {date}",
+  "status.history.edited": "{name} düzenledi {date}",
   "status.load_more": "Daha fazlasını yükle",
   "status.media_hidden": "Medya gizli",
   "status.mention": "@{name} kişisinden bahset",
diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json
index e66a7d4e1..464a65370 100644
--- a/app/javascript/mastodon/locales/tt.json
+++ b/app/javascript/mastodon/locales/tt.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "Бөтенесе",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json
index 2675da68c..5456eb88e 100644
--- a/app/javascript/mastodon/locales/ug.json
+++ b/app/javascript/mastodon/locales/ug.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 5adbd832b..4414d6741 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Перемкнути у режим вибору однієї відповіді",
   "compose_form.publish": "Дмухнути",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Зберегти зміни",
   "compose_form.sensitive.hide": "{count, plural, one {Позначити медіа делікатним} other {Позначити медіа делікатними}}",
   "compose_form.sensitive.marked": "{count, plural, one {Медіа позначене делікатним} other {Медіа позначені делікатними}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Медіа не позначене делікатним} other {Медіа не позначені делікатними}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Опитування, у якому ви голосували, закінчилося",
   "notification.reblog": "{name} передмухнув(-ла) Ваш допис",
   "notification.status": "{name} щойно дописує",
+  "notification.update": "{name} змінює допис",
   "notifications.clear": "Очистити сповіщення",
   "notifications.clear_confirmation": "Ви впевнені, що хочете назавжди видалити всі сповіщеня?",
   "notifications.column_settings.alert": "Сповіщення на комп'ютері",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Нові дмухи:",
   "notifications.column_settings.unread_notifications.category": "Непрочитані сповіщення",
   "notifications.column_settings.unread_notifications.highlight": "Виділити непрочитані сповіщення",
+  "notifications.column_settings.update": "Зміни:",
   "notifications.filter.all": "Усі",
   "notifications.filter.boosts": "Передмухи",
   "notifications.filter.favourites": "Улюблені",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Завантаження…",
   "regeneration_indicator.sublabel": "Ваша домашня стрічка готується!",
   "relative_time.days": "{number}д",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# день} few {# дні} other {# днів}} тому",
+  "relative_time.full.hours": "{number, plural, one {# година} few {# години} other {# годин}} тому",
+  "relative_time.full.just_now": "щойно",
+  "relative_time.full.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}} тому",
+  "relative_time.full.seconds": "{number, plural, one {# секунда} few {# секунди} other {# секунд}} тому",
   "relative_time.hours": "{number}г",
   "relative_time.just_now": "щойно",
   "relative_time.minutes": "{number}х",
   "relative_time.seconds": "{number}с",
   "relative_time.today": "сьогодні",
   "reply_indicator.cancel": "Відмінити",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "Інше",
+  "report.categories.spam": "Спам",
+  "report.categories.violation": "Контент порушує одне або кілька правил сервера",
   "report.forward": "Надіслати до {target}",
   "report.forward_hint": "Це акаунт з іншого серверу. Відправити анонімізовану копію скарги і туди?",
   "report.hint": "Скаргу буде відправлено модераторам Вашого сайту. Ви можете надати їм пояснення, чому ви скаржитесь на акаунт нижче:",
@@ -407,14 +409,14 @@
   "status.delete": "Видалити",
   "status.detailed_status": "Детальний вигляд бесіди",
   "status.direct": "Пряме повідомлення до @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Редагувати",
+  "status.edited": "Відредаговано {date}",
+  "status.edited_x_times": "Відредаговано {count, plural, one {{count} раз} few {{count} рази} many {{counter} разів} other {{counter} разів}}",
   "status.embed": "Вбудувати",
   "status.favourite": "Подобається",
   "status.filtered": "Відфільтровано",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} створює {date}",
+  "status.history.edited": "{name} змінює {date}",
   "status.load_more": "Завантажити більше",
   "status.media_hidden": "Медіа приховано",
   "status.mention": "Згадати @{name}",
diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json
index e029f074c..75d99bf4e 100644
--- a/app/javascript/mastodon/locales/ur.json
+++ b/app/javascript/mastodon/locales/ur.json
@@ -308,6 +308,7 @@
   "notification.poll": "آپ کا ووٹ دیا گیا ایک پول ختم ہو گیا ہے",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} نے ابھی ابھی پوسٹ کیا",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "اطلاعات ہٹائیں",
   "notifications.clear_confirmation": "کیا آپ واقعی اپنی تمام اطلاعات کو صاف کرنا چاہتے ہیں؟",
   "notifications.column_settings.alert": "ڈیسک ٹاپ اطلاعات",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "All",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index f5620638a..588721073 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -47,8 +47,8 @@
   "account.unmute": "Bỏ ẩn @{name}",
   "account.unmute_notifications": "Mở lại thông báo từ @{name}",
   "account_note.placeholder": "Nhấn để thêm",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "Tỉ lệ người dùng sau đăng ký ở lại theo ngày",
+  "admin.dashboard.monthly_retention": "Tỉ lệ người dùng sau đăng ký ở lại theo tháng",
   "admin.dashboard.retention.average": "Trung bình",
   "admin.dashboard.retention.cohort": "Đăng ký tháng",
   "admin.dashboard.retention.cohort_size": "Người dùng mới",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "Chỉ cho phép chọn duy nhất một lựa chọn",
   "compose_form.publish": "Đăng tút",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Lưu thay đổi",
   "compose_form.sensitive.hide": "{count, plural, other {Đánh dấu nội dung nhạy cảm}}",
   "compose_form.sensitive.marked": "{count, plural, other {Nội dung này nhạy cảm}}",
   "compose_form.sensitive.unmarked": "{count, plural, other {Nội dung này bình thường}}",
@@ -308,6 +308,7 @@
   "notification.poll": "Cuộc bình chọn đã kết thúc",
   "notification.reblog": "{name} chia sẻ tút của bạn",
   "notification.status": "{name} vừa đăng",
+  "notification.update": "{name} đã viết lại một tút",
   "notifications.clear": "Xóa hết thông báo",
   "notifications.clear_confirmation": "Bạn thật sự muốn xóa vĩnh viễn tất cả thông báo của mình?",
   "notifications.column_settings.alert": "Thông báo trên máy tính",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "Tút mới:",
   "notifications.column_settings.unread_notifications.category": "Thông báo chưa đọc",
   "notifications.column_settings.unread_notifications.highlight": "Nổi bật thông báo chưa đọc",
+  "notifications.column_settings.update": "Lượt sửa:",
   "notifications.filter.all": "Toàn bộ",
   "notifications.filter.boosts": "Chia sẻ",
   "notifications.filter.favourites": "Thích",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "Đang tải…",
   "regeneration_indicator.sublabel": "Bảng tin của bạn đang được cập nhật!",
   "relative_time.days": "{number} ngày",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, other {# ngày}} trước",
+  "relative_time.full.hours": "{number, plural, other {# giờ}} trước",
+  "relative_time.full.just_now": "vừa xong",
+  "relative_time.full.minutes": "{number, plural, other {# phút}} trước",
+  "relative_time.full.seconds": "{number, plural, other {# giây}} trước",
   "relative_time.hours": "{number} giờ",
   "relative_time.just_now": "vừa xong",
   "relative_time.minutes": "{number} phút",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "hôm nay",
   "reply_indicator.cancel": "Hủy bỏ",
-  "report.categories.other": "Other",
+  "report.categories.other": "Khác",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.violation": "Vi phạm quy tắc máy chủ",
   "report.forward": "Chuyển đến {target}",
   "report.forward_hint": "Người này thuộc máy chủ khác. Gửi một báo cáo ẩn danh tới máy chủ đó?",
   "report.hint": "Hãy cho quản trị viên biết lý do vì sao bạn báo cáo người này:",
@@ -407,14 +409,14 @@
   "status.delete": "Xóa",
   "status.detailed_status": "Xem chi tiết thêm",
   "status.direct": "Nhắn tin @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Sửa",
+  "status.edited": "Đã sửa {date}",
+  "status.edited_x_times": "Đã sửa {count, plural, other {{count} lần}}",
   "status.embed": "Nhúng",
   "status.favourite": "Thích",
   "status.filtered": "Bộ lọc",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} tạo lúc {date}",
+  "status.history.edited": "{name} sửa lúc {date}",
   "status.load_more": "Xem thêm",
   "status.media_hidden": "Đã ẩn",
   "status.mention": "Nhắc đến @{name}",
diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json
index bc9b8bc83..f34772abe 100644
--- a/app/javascript/mastodon/locales/zgh.json
+++ b/app/javascript/mastodon/locales/zgh.json
@@ -308,6 +308,7 @@
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "ⵙⴼⴹ ⵜⵉⵏⵖⵎⵉⵙⵉⵏ",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
   "notifications.column_settings.alert": "Desktop notifications",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "New toots:",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "ⴰⴽⴽⵯ",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favourites",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index cb971126e..56dca56a6 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -47,8 +47,8 @@
   "account.unmute": "不再隐藏 @{name}",
   "account.unmute_notifications": "不再隐藏来自 @{name} 的通知",
   "account_note.placeholder": "点击添加备注",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "注册后用户留存率(按日计算)",
+  "admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)",
   "admin.dashboard.retention.average": "平均",
   "admin.dashboard.retention.cohort": "注册月",
   "admin.dashboard.retention.cohort_size": "新用户",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "将投票改为单选",
   "compose_form.publish": "嘟嘟",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "保存更改",
   "compose_form.sensitive.hide": "标记媒体为敏感内容",
   "compose_form.sensitive.marked": "媒体已被标记为敏感内容",
   "compose_form.sensitive.unmarked": "媒体未被标记为敏感内容",
@@ -308,6 +308,7 @@
   "notification.poll": "你参与的一个投票已经结束",
   "notification.reblog": "{name} 转嘟了你的嘟文",
   "notification.status": "{name} 刚刚发嘟",
+  "notification.update": "{name} 编辑了嘟文",
   "notifications.clear": "清空通知列表",
   "notifications.clear_confirmation": "你确定要永久清空通知列表吗?",
   "notifications.column_settings.alert": "桌面通知",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "新嘟文:",
   "notifications.column_settings.unread_notifications.category": "未读通知",
   "notifications.column_settings.unread_notifications.highlight": "高亮显示未读通知",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "全部",
   "notifications.filter.boosts": "转嘟",
   "notifications.filter.favourites": "喜欢",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "加载中……",
   "regeneration_indicator.sublabel": "你的主页动态正在准备中!",
   "relative_time.days": "{number}天",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
+  "relative_time.full.hours": "{number, plural, one {# 小时} other {# 小时}}前",
+  "relative_time.full.just_now": "刚刚",
+  "relative_time.full.minutes": "{number, plural, one {# 分钟} other {# 分钟}}前",
+  "relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}}前",
   "relative_time.hours": "{number}时",
   "relative_time.just_now": "刚刚",
   "relative_time.minutes": "{number}分",
   "relative_time.seconds": "{number}秒",
   "relative_time.today": "今天",
   "reply_indicator.cancel": "取消",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "其他",
+  "report.categories.spam": "垃圾信息",
+  "report.categories.violation": "内容违反一条或多条服务器规则",
   "report.forward": "转发举报至 {target}",
   "report.forward_hint": "这名用户来自另一个服务器。是否要向那个服务器发送一条匿名的举报?",
   "report.hint": "举报将会发送给你所在服务器的监察员。你可以在下面填写举报该用户的理由:",
@@ -407,14 +409,14 @@
   "status.delete": "删除",
   "status.detailed_status": "对话详情",
   "status.direct": "发送私信给 @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "编辑",
+  "status.edited": "编辑于 {date}",
+  "status.edited_x_times": "共编辑 {count, plural, one {{count} 次} other {{count} 次}}",
   "status.embed": "嵌入",
   "status.favourite": "喜欢",
   "status.filtered": "已过滤",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} 创建于 {date}",
+  "status.history.edited": "{name} 编辑于 {date}",
   "status.load_more": "加载更多",
   "status.media_hidden": "已隐藏的媒体内容",
   "status.mention": "提及 @{name}",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index bd6666b15..1f06f7fc6 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -308,6 +308,7 @@
   "notification.poll": "你參與過的一個投票已經結束",
   "notification.reblog": "{name} 轉推你的文章",
   "notification.status": "{name} 剛發表了文章",
+  "notification.update": "{name} edited a post",
   "notifications.clear": "清空通知紀錄",
   "notifications.clear_confirmation": "你確定要清空通知紀錄嗎?",
   "notifications.column_settings.alert": "顯示桌面通知",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "新的文章",
   "notifications.column_settings.unread_notifications.category": "Unread notifications",
   "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
   "notifications.filter.all": "全部",
   "notifications.filter.boosts": "轉推",
   "notifications.filter.favourites": "最愛",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 60f4bc2f4..3f89737d4 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -47,8 +47,8 @@
   "account.unmute": "取消靜音 @{name}",
   "account.unmute_notifications": "重新接收來自 @{name} 的通知",
   "account_note.placeholder": "按此添加備注",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "註冊後使用者存留率(日)",
+  "admin.dashboard.monthly_retention": "註冊後使用者存留率(月)",
   "admin.dashboard.retention.average": "平均",
   "admin.dashboard.retention.cohort": "註冊月份",
   "admin.dashboard.retention.cohort_size": "新使用者",
@@ -105,7 +105,7 @@
   "compose_form.poll.switch_to_single": "變更投票為允許單一選項",
   "compose_form.publish": "嘟出去",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "儲存變更",
   "compose_form.sensitive.hide": "標記媒體為敏感內容",
   "compose_form.sensitive.marked": "此媒體被標記為敏感內容",
   "compose_form.sensitive.unmarked": "此媒體未被標記為敏感內容",
@@ -308,6 +308,7 @@
   "notification.poll": "您曾投過的投票已經結束",
   "notification.reblog": "{name} 轉嘟了您的嘟文",
   "notification.status": "{name} 剛剛嘟文",
+  "notification.update": "{name} 編輯了嘟文",
   "notifications.clear": "清除通知",
   "notifications.clear_confirmation": "確定要永久清除您的通知嗎?",
   "notifications.column_settings.alert": "桌面通知",
@@ -326,6 +327,7 @@
   "notifications.column_settings.status": "新嘟文:",
   "notifications.column_settings.unread_notifications.category": "未讀通知",
   "notifications.column_settings.unread_notifications.highlight": "突顯未讀通知",
+  "notifications.column_settings.update": "編輯:",
   "notifications.filter.all": "全部",
   "notifications.filter.boosts": "轉嘟",
   "notifications.filter.favourites": "最愛",
@@ -365,20 +367,20 @@
   "regeneration_indicator.label": "載入中…",
   "regeneration_indicator.sublabel": "您的主頁時間軸正在準備中!",
   "relative_time.days": "{number} 天",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
+  "relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前",
+  "relative_time.full.just_now": "剛剛",
+  "relative_time.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}前",
+  "relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}}前",
   "relative_time.hours": "{number}小時前",
   "relative_time.just_now": "剛剛",
   "relative_time.minutes": "{number} 分前",
   "relative_time.seconds": "{number} 秒",
   "relative_time.today": "今天",
   "reply_indicator.cancel": "取消",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
+  "report.categories.other": "其他",
+  "report.categories.spam": "垃圾訊息",
+  "report.categories.violation": "內容違反一項或多項伺服器條款",
   "report.forward": "轉寄到 {target}",
   "report.forward_hint": "這個帳戶屬於其他伺服器。要像該伺服器發送匿名的檢舉訊息嗎?",
   "report.hint": "這項訊息會發送到您伺服器的管理員。您可以提供檢舉這個帳戶的理由:",
@@ -407,14 +409,14 @@
   "status.delete": "刪除",
   "status.detailed_status": "詳細的對話內容",
   "status.direct": "發送私訊給 @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "編輯",
+  "status.edited": "編輯於 {date}",
+  "status.edited_x_times": "已編輯 {count, plural, one {{count} 次} other {{count} 次}}",
   "status.embed": "內嵌",
   "status.favourite": "最愛",
   "status.filtered": "已過濾",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} 於 {date} 建立",
+  "status.history.edited": "{name} 於 {date} 修改",
   "status.load_more": "載入更多",
   "status.media_hidden": "隱藏媒體內容",
   "status.mention": "提及 @{name}",
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index af2ef595e..0219d8a5e 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -17,7 +17,7 @@ import status_lists from './status_lists';
 import mutes from './mutes';
 import blocks from './blocks';
 import boosts from './boosts';
-import reports from './reports';
+import rules from './rules';
 import contexts from './contexts';
 import compose from './compose';
 import search from './search';
@@ -61,7 +61,7 @@ const reducers = {
   mutes,
   blocks,
   boosts,
-  reports,
+  rules,
   contexts,
   compose,
   search,
diff --git a/app/javascript/mastodon/reducers/reports.js b/app/javascript/mastodon/reducers/reports.js
deleted file mode 100644
index 21ae6f93f..000000000
--- a/app/javascript/mastodon/reducers/reports.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
-  REPORT_INIT,
-  REPORT_SUBMIT_REQUEST,
-  REPORT_SUBMIT_SUCCESS,
-  REPORT_SUBMIT_FAIL,
-  REPORT_CANCEL,
-  REPORT_STATUS_TOGGLE,
-  REPORT_COMMENT_CHANGE,
-  REPORT_FORWARD_CHANGE,
-} from '../actions/reports';
-import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
-
-const initialState = ImmutableMap({
-  new: ImmutableMap({
-    isSubmitting: false,
-    account_id: null,
-    status_ids: ImmutableSet(),
-    comment: '',
-    forward: false,
-  }),
-});
-
-export default function reports(state = initialState, action) {
-  switch(action.type) {
-  case REPORT_INIT:
-    return state.withMutations(map => {
-      map.setIn(['new', 'isSubmitting'], false);
-      map.setIn(['new', 'account_id'], action.account.get('id'));
-
-      if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
-        map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet());
-        map.setIn(['new', 'comment'], '');
-      } else if (action.status) {
-        map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
-      }
-    });
-  case REPORT_STATUS_TOGGLE:
-    return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => {
-      if (action.checked) {
-        return set.add(action.statusId);
-      }
-
-      return set.remove(action.statusId);
-    });
-  case REPORT_COMMENT_CHANGE:
-    return state.setIn(['new', 'comment'], action.comment);
-  case REPORT_FORWARD_CHANGE:
-    return state.setIn(['new', 'forward'], action.forward);
-  case REPORT_SUBMIT_REQUEST:
-    return state.setIn(['new', 'isSubmitting'], true);
-  case REPORT_SUBMIT_FAIL:
-    return state.setIn(['new', 'isSubmitting'], false);
-  case REPORT_CANCEL:
-  case REPORT_SUBMIT_SUCCESS:
-    return state.withMutations(map => {
-      map.setIn(['new', 'account_id'], null);
-      map.setIn(['new', 'status_ids'], ImmutableSet());
-      map.setIn(['new', 'comment'], '');
-      map.setIn(['new', 'isSubmitting'], false);
-    });
-  default:
-    return state;
-  }
-};
diff --git a/app/javascript/mastodon/reducers/rules.js b/app/javascript/mastodon/reducers/rules.js
new file mode 100644
index 000000000..c1180b520
--- /dev/null
+++ b/app/javascript/mastodon/reducers/rules.js
@@ -0,0 +1,13 @@
+import { RULES_FETCH_SUCCESS } from 'mastodon/actions/rules';
+import { List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableList();
+
+export default function rules(state = initialState, action) {
+  switch (action.type) {
+  case RULES_FETCH_SUCCESS:
+    return fromJS(action.rules);
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 5146abe98..39639f3dc 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -37,6 +37,7 @@ const initialState = ImmutableMap({
       poll: false,
       status: false,
       update: false,
+      'admin.sign_up': false,
     }),
 
     quickFilter: ImmutableMap({
@@ -57,6 +58,7 @@ const initialState = ImmutableMap({
       poll: true,
       status: true,
       update: true,
+      'admin.sign_up': true,
     }),
 
     sounds: ImmutableMap({
@@ -68,6 +70,7 @@ const initialState = ImmutableMap({
       poll: true,
       status: true,
       update: true,
+      'admin.sign_up': true,
     }),
   }),
 
diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js
index 807a1bcb9..7d713cd37 100644
--- a/app/javascript/mastodon/service_worker/web_push_locales.js
+++ b/app/javascript/mastodon/service_worker/web_push_locales.js
@@ -22,6 +22,7 @@ filenames.forEach(filename => {
     'notification.poll': full['notification.poll'] || '',
     'notification.status': full['notification.status'] || '',
     'notification.update': full['notification.update'] || '',
+    'notification.admin.sign_up': full['notification.admin.sign_up'] || '',
 
     'status.show_more': full['status.show_more'] || '',
     'status.reblog': full['status.reblog'] || '',
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 7ebe8b4d0..be467a8e2 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -151,13 +151,7 @@ function main() {
   });
 
   delegate(document, '.sidebar__toggle__icon', 'click', () => {
-    const target = document.querySelector('.sidebar ul');
-
-    if (target.style.display === 'block') {
-      target.style.display = 'none';
-    } else {
-      target.style.display = 'block';
-    }
+    document.querySelector('.sidebar ul').classList.toggle('visible');
   });
 
   // Empty the honeypot fields in JS in case something like an extension
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 66ce92ce2..a1b99636c 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -322,6 +322,10 @@ $content-width: 840px;
 
       & > ul {
         display: none;
+
+        &.visible {
+          display: block;
+        }
       }
 
       ul a,
@@ -594,12 +598,16 @@ body,
 }
 
 .log-entry {
+  display: block;
   line-height: 20px;
   padding: 15px;
   padding-left: 15px * 2 + 40px;
   background: $ui-base-color;
   border-bottom: 1px solid darken($ui-base-color, 8%);
   position: relative;
+  text-decoration: none;
+  color: $darker-text-color;
+  font-size: 14px;
 
   &:first-child {
     border-top-left-radius: 4px;
@@ -612,15 +620,12 @@ body,
     border-bottom: 0;
   }
 
-  &:hover {
+  &:hover,
+  &:focus,
+  &:active {
     background: lighten($ui-base-color, 4%);
   }
 
-  &__header {
-    color: $darker-text-color;
-    font-size: 14px;
-  }
-
   &__avatar {
     position: absolute;
     left: 15px;
@@ -1278,6 +1283,30 @@ a.sparkline {
       background: linear-gradient(to left, $ui-base-color, transparent);
       pointer-events: none;
     }
+
+    a {
+      color: $secondary-text-color;
+      text-decoration: none;
+      unicode-bidi: isolate;
+
+      &:hover {
+        text-decoration: underline;
+
+        .fa {
+          color: lighten($dark-text-color, 7%);
+        }
+      }
+
+      &.mention {
+        &:hover {
+          text-decoration: none;
+
+          span {
+            text-decoration: underline;
+          }
+        }
+      }
+    }
   }
 
   &__actions {
@@ -1467,3 +1496,75 @@ a.sparkline {
     }
   }
 }
+
+.strike-card {
+  padding: 15px;
+  border-radius: 4px;
+  background: $ui-base-color;
+  font-size: 15px;
+  line-height: 20px;
+  word-wrap: break-word;
+  font-weight: 400;
+  color: $primary-text-color;
+
+  p {
+    margin-bottom: 20px;
+    unicode-bidi: plaintext;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    strong {
+      font-weight: 700;
+    }
+  }
+
+  &__rules {
+    list-style: disc;
+    padding-left: 15px;
+    margin-bottom: 20px;
+    color: $darker-text-color;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    &__text {
+      color: $primary-text-color;
+    }
+  }
+
+  &__statuses-list {
+    border-radius: 4px;
+    border: 1px solid darken($ui-base-color, 8%);
+    font-size: 13px;
+    line-height: 18px;
+    overflow: hidden;
+
+    &__item {
+      padding: 16px;
+      background: lighten($ui-base-color, 2%);
+      border-bottom: 1px solid darken($ui-base-color, 8%);
+
+      &:last-child {
+        border-bottom: 0;
+      }
+
+      &__meta {
+        color: $darker-text-color;
+      }
+
+      a {
+        color: inherit;
+        text-decoration: none;
+
+        &:hover,
+        &:focus,
+        &:active {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 5304bec34..108bf68a5 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -50,16 +50,14 @@
   cursor: pointer;
   display: inline-block;
   font-family: inherit;
-  font-size: 14px;
+  font-size: 17px;
   font-weight: 500;
-  height: 36px;
   letter-spacing: 0;
-  line-height: 36px;
+  line-height: 22px;
   overflow: hidden;
-  padding: 0 16px;
+  padding: 7px 18px;
   position: relative;
   text-align: center;
-  text-transform: uppercase;
   text-decoration: none;
   text-overflow: ellipsis;
   transition: all 100ms ease-in;
@@ -100,17 +98,6 @@
     outline: 0 !important;
   }
 
-  &.button-primary,
-  &.button-alternative,
-  &.button-secondary,
-  &.button-alternative-2 {
-    font-size: 16px;
-    line-height: 36px;
-    height: auto;
-    text-transform: none;
-    padding: 4px 16px;
-  }
-
   &.button-alternative {
     color: $inverted-text-color;
     background: $ui-primary-color;
@@ -135,7 +122,7 @@
   &.button-secondary {
     color: $darker-text-color;
     background: transparent;
-    padding: 3px 15px;
+    padding: 6px 17px;
     border: 1px solid $ui-primary-color;
 
     &:active,
@@ -1114,42 +1101,39 @@
   font-size: 15px;
 }
 
-.status-check-box {
-  border-bottom: 1px solid $ui-secondary-color;
-  display: flex;
+.status-check-box__status {
+  display: block;
+  box-sizing: border-box;
+  width: 100%;
+  padding: 0 10px;
 
-  .status-check-box__status {
-    margin: 10px 0 10px 10px;
-    flex: 1;
-    overflow: hidden;
+  .detailed-status__display-name {
+    color: lighten($inverted-text-color, 16%);
 
-    .media-gallery {
-      max-width: 250px;
+    span {
+      display: inline;
     }
 
-    .status__content {
-      padding: 0;
-      white-space: normal;
+    &:hover strong {
+      text-decoration: none;
     }
+  }
 
-    .video-player,
-    .audio-player {
-      margin-top: 8px;
-      max-width: 250px;
-    }
+  .media-gallery,
+  .audio-player,
+  .video-player {
+    margin-top: 8px;
+    max-width: 250px;
+  }
 
-    .media-gallery__item-thumbnail {
-      cursor: default;
-    }
+  .status__content {
+    padding: 0;
+    white-space: normal;
   }
-}
 
-.status-check-box-toggle {
-  align-items: center;
-  display: flex;
-  flex: 0 0 auto;
-  justify-content: center;
-  padding: 10px;
+  .media-gallery__item-thumbnail {
+    cursor: default;
+  }
 }
 
 .status__prepend {
@@ -5103,6 +5087,192 @@ a.status-card.compact:hover {
   max-width: 700px;
 }
 
+.report-dialog-modal {
+  max-width: 90vw;
+  width: 480px;
+  height: 80vh;
+  background: lighten($ui-secondary-color, 8%);
+  color: $inverted-text-color;
+  border-radius: 8px;
+  overflow: hidden;
+  position: relative;
+  flex-direction: column;
+  display: flex;
+
+  &__container {
+    box-sizing: border-box;
+    border-top: 1px solid $ui-secondary-color;
+    padding: 20px;
+    flex-grow: 1;
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+    overflow: auto;
+  }
+
+  &__title {
+    font-size: 28px;
+    line-height: 33px;
+    font-weight: 700;
+    margin-bottom: 15px;
+
+    @media screen and (max-height: 800px) {
+      font-size: 22px;
+    }
+  }
+
+  &__subtitle {
+    font-size: 17px;
+    font-weight: 600;
+    line-height: 22px;
+    margin-bottom: 4px;
+  }
+
+  &__lead {
+    font-size: 17px;
+    line-height: 22px;
+    color: lighten($inverted-text-color, 16%);
+    margin-bottom: 30px;
+  }
+
+  &__actions {
+    margin-top: 30px;
+    display: flex;
+
+    .button {
+      flex: 1 1 auto;
+    }
+  }
+
+  &__statuses {
+    flex-grow: 1;
+    min-height: 0;
+    overflow: auto;
+  }
+
+  .status__content a {
+    color: $highlight-text-color;
+  }
+
+  .status__content,
+  .status__content p {
+    color: $inverted-text-color;
+  }
+
+  .dialog-option .poll__input {
+    border-color: $inverted-text-color;
+    color: $ui-secondary-color;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+
+    svg {
+      width: 8px;
+      height: auto;
+    }
+
+    &:active,
+    &:focus,
+    &:hover {
+      border-color: lighten($inverted-text-color, 15%);
+      border-width: 4px;
+    }
+
+    &.active {
+      border-color: $inverted-text-color;
+      background: $inverted-text-color;
+    }
+  }
+
+  .poll__option.dialog-option {
+    padding: 15px 0;
+    flex: 0 0 auto;
+    border-bottom: 1px solid $ui-secondary-color;
+
+    &:last-child {
+      border-bottom: 0;
+    }
+
+    & > .poll__option__text {
+      font-size: 13px;
+      color: lighten($inverted-text-color, 16%);
+
+      strong {
+        font-size: 17px;
+        font-weight: 500;
+        line-height: 22px;
+        color: $inverted-text-color;
+        display: block;
+        margin-bottom: 4px;
+
+        &:last-child {
+          margin-bottom: 0;
+        }
+      }
+    }
+  }
+
+  .flex-spacer {
+    background: transparent;
+  }
+
+  &__textarea {
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    margin: 0;
+    color: $inverted-text-color;
+    background: $simple-background-color;
+    padding: 10px;
+    font-family: inherit;
+    font-size: 17px;
+    line-height: 22px;
+    resize: vertical;
+    border: 0;
+    outline: 0;
+    border-radius: 4px;
+    margin: 20px 0;
+
+    &::placeholder {
+      color: $dark-text-color;
+    }
+
+    &:focus {
+      outline: 0;
+    }
+  }
+
+  &__toggle {
+    display: flex;
+    align-items: center;
+
+    & > span {
+      font-size: 17px;
+      font-weight: 500;
+      margin-left: 10px;
+    }
+  }
+
+  .button.button-secondary {
+    border-color: $inverted-text-color;
+    color: $inverted-text-color;
+    flex: 0 0 auto;
+
+    &:hover,
+    &:focus,
+    &:active {
+      border-color: lighten($inverted-text-color, 15%);
+      color: lighten($inverted-text-color, 15%);
+    }
+  }
+
+  hr {
+    border: 0;
+    background: transparent;
+    margin: 15px 0;
+  }
+}
+
 .report-modal__container {
   display: flex;
   border-top: 1px solid $ui-secondary-color;
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
index 00d290883..073ebda7e 100644
--- a/app/javascript/styles/mastodon/footer.scss
+++ b/app/javascript/styles/mastodon/footer.scss
@@ -90,6 +90,20 @@
         .column-4 {
           display: none;
         }
+
+        .column-2 h4 {
+          display: none;
+        }
+      }
+    }
+
+    .legal-xs {
+      display: none;
+      text-align: center;
+      padding-top: 20px;
+
+      @media screen and (max-width: $no-gap-breakpoint) {
+        display: block;
       }
     }
 
@@ -105,7 +119,8 @@
       }
     }
 
-    ul a {
+    ul a,
+    .legal-xs a {
       text-decoration: none;
       color: lighten($ui-base-color, 34%);
 
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index 1f9319290..12fad8da4 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -8,6 +8,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
       original_status = status_from_object
 
       return reject_payload! if original_status.nil? || !announceable?(original_status)
+      return if requested_through_relay?
 
       @status = Status.find_by(account: @account, reblog: original_status)
 
diff --git a/app/lib/admin/metrics/dimension/base_dimension.rb b/app/lib/admin/metrics/dimension/base_dimension.rb
index 5872c22cb..bd2e4ecec 100644
--- a/app/lib/admin/metrics/dimension/base_dimension.rb
+++ b/app/lib/admin/metrics/dimension/base_dimension.rb
@@ -1,23 +1,34 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Dimension::BaseDimension
+  CACHE_TTL = 5.minutes.freeze
+
   def self.with_params?
     false
   end
 
+  attr_reader :loaded
+
+  alias loaded? loaded
+
   def initialize(start_at, end_at, limit, params)
     @start_at = start_at&.to_datetime
     @end_at   = end_at&.to_datetime
     @limit    = limit&.to_i
     @params   = params
+    @loaded   = false
   end
 
   def key
     raise NotImplementedError
   end
 
+  def cache_key
+    ["metrics/dimension/#{key}", @start_at, @end_at, @limit, canonicalized_params].join(';')
+  end
+
   def data
-    raise NotImplementedError
+    load
   end
 
   def self.model_name
@@ -30,11 +41,28 @@ class Admin::Metrics::Dimension::BaseDimension
 
   protected
 
+  def load
+    unless loaded?
+      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_query }
+      @loaded = true
+    end
+
+    @values
+  end
+
+  def perform_query
+    raise NotImplementedError
+  end
+
   def time_period
     (@start_at..@end_at)
   end
 
   def params
-    raise NotImplementedError
+    {}
+  end
+
+  def canonicalized_params
+    params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';')
   end
 end
diff --git a/app/lib/admin/metrics/dimension/languages_dimension.rb b/app/lib/admin/metrics/dimension/languages_dimension.rb
index 1cc5f4120..f1cf82cf2 100644
--- a/app/lib/admin/metrics/dimension/languages_dimension.rb
+++ b/app/lib/admin/metrics/dimension/languages_dimension.rb
@@ -7,7 +7,9 @@ class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension:
     'languages'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT locale, count(*) AS value
       FROM users
diff --git a/app/lib/admin/metrics/dimension/servers_dimension.rb b/app/lib/admin/metrics/dimension/servers_dimension.rb
index 3e80b6625..91bcce655 100644
--- a/app/lib/admin/metrics/dimension/servers_dimension.rb
+++ b/app/lib/admin/metrics/dimension/servers_dimension.rb
@@ -5,7 +5,9 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B
     'servers'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT accounts.domain, count(*) AS value
       FROM statuses
diff --git a/app/lib/admin/metrics/dimension/software_versions_dimension.rb b/app/lib/admin/metrics/dimension/software_versions_dimension.rb
index 34917404d..816615f99 100644
--- a/app/lib/admin/metrics/dimension/software_versions_dimension.rb
+++ b/app/lib/admin/metrics/dimension/software_versions_dimension.rb
@@ -7,12 +7,12 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
     'software_versions'
   end
 
-  def data
+  protected
+
+  def perform_query
     [mastodon_version, ruby_version, postgresql_version, redis_version]
   end
 
-  private
-
   def mastodon_version
     value = Mastodon::Version.to_s
 
diff --git a/app/lib/admin/metrics/dimension/sources_dimension.rb b/app/lib/admin/metrics/dimension/sources_dimension.rb
index a9f061809..122807cdc 100644
--- a/app/lib/admin/metrics/dimension/sources_dimension.rb
+++ b/app/lib/admin/metrics/dimension/sources_dimension.rb
@@ -5,7 +5,9 @@ class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::B
     'sources'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT oauth_applications.name, count(*) AS value
       FROM users
diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
index aa00a2e18..5867c5bab 100644
--- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb
+++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
@@ -8,12 +8,12 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension
     'space_usage'
   end
 
-  def data
+  protected
+
+  def perform_query
     [postgresql_size, redis_size, media_size]
   end
 
-  private
-
   def postgresql_size
     value = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
 
diff --git a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb
index afbc8cde8..e1349c229 100644
--- a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb
+++ b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb
@@ -11,7 +11,9 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
     'tag_languages'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value
       FROM statuses
@@ -28,8 +30,6 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
     rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
   end
 
-  private
-
   def params
     @params.permit(:id)
   end
diff --git a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb
index 12c5980d7..7ddf3378c 100644
--- a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb
+++ b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb
@@ -9,7 +9,9 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
     'tag_servers'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT accounts.domain, count(*) AS value
       FROM statuses
@@ -27,8 +29,6 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
     rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
   end
 
-  private
-
   def params
     @params.permit(:id)
   end
diff --git a/app/lib/admin/metrics/measure/active_users_measure.rb b/app/lib/admin/metrics/measure/active_users_measure.rb
index 513189780..e6f09d4bc 100644
--- a/app/lib/admin/metrics/measure/active_users_measure.rb
+++ b/app/lib/admin/metrics/measure/active_users_measure.rb
@@ -5,20 +5,20 @@ class Admin::Metrics::Measure::ActiveUsersMeasure < Admin::Metrics::Measure::Bas
     'active_users'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     activity_tracker.sum(time_period.first, time_period.last)
   end
 
-  def previous_total
+  def perform_previous_total_query
     activity_tracker.sum(previous_time_period.first, previous_time_period.last)
   end
 
-  def data
+  def perform_data_query
     activity_tracker.get(time_period.first, time_period.last).map { |date, value| { date: date.to_time(:utc).iso8601, value: value.to_s } }
   end
 
-  protected
-
   def activity_tracker
     @activity_tracker ||= ActivityTracker.new('activity:logins', :unique)
   end
diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb
index 0107ffd9c..ed1df9c7d 100644
--- a/app/lib/admin/metrics/measure/base_measure.rb
+++ b/app/lib/admin/metrics/measure/base_measure.rb
@@ -1,14 +1,25 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::BaseMeasure
+  CACHE_TTL = 5.minutes.freeze
+
   def self.with_params?
     false
   end
 
+  attr_reader :loaded
+
+  alias loaded? loaded
+
   def initialize(start_at, end_at, params)
     @start_at = start_at&.to_datetime
     @end_at   = end_at&.to_datetime
     @params   = params
+    @loaded   = false
+  end
+
+  def cache_key
+    ["metrics/measure/#{key}", @start_at, @end_at, canonicalized_params].join(';')
   end
 
   def key
@@ -16,15 +27,15 @@ class Admin::Metrics::Measure::BaseMeasure
   end
 
   def total
-    raise NotImplementedError
+    load[:total]
   end
 
   def previous_total
-    raise NotImplementedError
+    load[:previous_total]
   end
 
   def data
-    raise NotImplementedError
+    load[:data]
   end
 
   def self.model_name
@@ -37,6 +48,35 @@ class Admin::Metrics::Measure::BaseMeasure
 
   protected
 
+  def load
+    unless loaded?
+      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_queries }.with_indifferent_access
+      @loaded = true
+    end
+
+    @values
+  end
+
+  def perform_queries
+    {
+      total: perform_total_query,
+      previous_total: perform_previous_total_query,
+      data: perform_data_query,
+    }
+  end
+
+  def perform_total_query
+    raise NotImplementedError
+  end
+
+  def perform_previous_total_query
+    raise NotImplementedError
+  end
+
+  def perform_data_query
+    raise NotImplementedError
+  end
+
   def time_period
     (@start_at..@end_at)
   end
@@ -50,6 +90,10 @@ class Admin::Metrics::Measure::BaseMeasure
   end
 
   def params
-    raise NotImplementedError
+    {}
+  end
+
+  def canonicalized_params
+    params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';')
   end
 end
diff --git a/app/lib/admin/metrics/measure/interactions_measure.rb b/app/lib/admin/metrics/measure/interactions_measure.rb
index b928fdb8f..7a2b7e0fa 100644
--- a/app/lib/admin/metrics/measure/interactions_measure.rb
+++ b/app/lib/admin/metrics/measure/interactions_measure.rb
@@ -5,20 +5,20 @@ class Admin::Metrics::Measure::InteractionsMeasure < Admin::Metrics::Measure::Ba
     'interactions'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     activity_tracker.sum(time_period.first, time_period.last)
   end
 
-  def previous_total
+  def perform_previous_total_query
     activity_tracker.sum(previous_time_period.first, previous_time_period.last)
   end
 
-  def data
+  def perform_data_query
     activity_tracker.get(time_period.first, time_period.last).map { |date, value| { date: date.to_time(:utc).iso8601, value: value.to_s } }
   end
 
-  protected
-
   def activity_tracker
     @activity_tracker ||= ActivityTracker.new('activity:interactions', :basic)
   end
diff --git a/app/lib/admin/metrics/measure/new_users_measure.rb b/app/lib/admin/metrics/measure/new_users_measure.rb
index b31679ad3..71191f1a2 100644
--- a/app/lib/admin/metrics/measure/new_users_measure.rb
+++ b/app/lib/admin/metrics/measure/new_users_measure.rb
@@ -5,15 +5,17 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
     'new_users'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     User.where(created_at: time_period).count
   end
 
-  def previous_total
+  def perform_previous_total_query
     User.where(created_at: previous_time_period).count
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH new_users AS (
diff --git a/app/lib/admin/metrics/measure/opened_reports_measure.rb b/app/lib/admin/metrics/measure/opened_reports_measure.rb
index 9acc2c33d..4b80a0c8c 100644
--- a/app/lib/admin/metrics/measure/opened_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/opened_reports_measure.rb
@@ -5,15 +5,17 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
     'opened_reports'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     Report.where(created_at: time_period).count
   end
 
-  def previous_total
+  def perform_previous_total_query
     Report.where(created_at: previous_time_period).count
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH new_reports AS (
diff --git a/app/lib/admin/metrics/measure/resolved_reports_measure.rb b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
index 00cb24f7e..4ab746c8f 100644
--- a/app/lib/admin/metrics/measure/resolved_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
@@ -5,15 +5,17 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
     'resolved_reports'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     Report.resolved.where(action_taken_at: time_period).count
   end
 
-  def previous_total
+  def perform_previous_total_query
     Report.resolved.where(action_taken_at: previous_time_period).count
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH resolved_reports AS (
diff --git a/app/lib/admin/metrics/measure/tag_accounts_measure.rb b/app/lib/admin/metrics/measure/tag_accounts_measure.rb
index ef773081b..8f4512efe 100644
--- a/app/lib/admin/metrics/measure/tag_accounts_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_accounts_measure.rb
@@ -9,20 +9,20 @@ class Admin::Metrics::Measure::TagAccountsMeasure < Admin::Metrics::Measure::Bas
     'tag_accounts'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     tag.history.aggregate(time_period).accounts
   end
 
-  def previous_total
+  def perform_previous_total_query
     tag.history.aggregate(previous_time_period).accounts
   end
 
-  def data
+  def perform_data_query
     time_period.map { |date| { date: date.to_time(:utc).iso8601, value: tag.history.get(date).accounts.to_s } }
   end
 
-  protected
-
   def tag
     @tag ||= Tag.find(params[:id])
   end
diff --git a/app/lib/admin/metrics/measure/tag_servers_measure.rb b/app/lib/admin/metrics/measure/tag_servers_measure.rb
index cc064f63f..11f229602 100644
--- a/app/lib/admin/metrics/measure/tag_servers_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_servers_measure.rb
@@ -9,15 +9,17 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
     'tag_servers'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at, with_random: false), Mastodon::Snowflake.id_at(@end_at, with_random: false)).joins(:account).count('distinct accounts.domain')
   end
 
-  def previous_total
+  def perform_previous_total_query
     tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at - length_of_period, with_random: false), Mastodon::Snowflake.id_at(@end_at - length_of_period, with_random: false)).joins(:account).count('distinct accounts.domain')
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         SELECT count(distinct accounts.domain) AS value
@@ -38,8 +40,6 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
     rows.map { |row| { date: row['day'], value: row['value'].to_s } }
   end
 
-  protected
-
   def tag
     @tag ||= Tag.find(params[:id])
   end
diff --git a/app/lib/admin/metrics/measure/tag_uses_measure.rb b/app/lib/admin/metrics/measure/tag_uses_measure.rb
index b7667bc6c..bce86b89f 100644
--- a/app/lib/admin/metrics/measure/tag_uses_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_uses_measure.rb
@@ -9,20 +9,20 @@ class Admin::Metrics::Measure::TagUsesMeasure < Admin::Metrics::Measure::BaseMea
     'tag_uses'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     tag.history.aggregate(time_period).uses
   end
 
-  def previous_total
+  def perform_previous_total_query
     tag.history.aggregate(previous_time_period).uses
   end
 
-  def data
+  def perform_data_query
     time_period.map { |date| { date: date.to_time(:utc).iso8601, value: tag.history.get(date).uses.to_s } }
   end
 
-  protected
-
   def tag
     @tag ||= Tag.find(params[:id])
   end
diff --git a/app/lib/admin/metrics/retention.rb b/app/lib/admin/metrics/retention.rb
index 0179a6e28..f6135ac1e 100644
--- a/app/lib/admin/metrics/retention.rb
+++ b/app/lib/admin/metrics/retention.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Retention
+  CACHE_TTL = 5.minutes.freeze
+
   class Cohort < ActiveModelSerializers::Model
     attributes :period, :frequency, :data
   end
@@ -9,13 +11,37 @@ class Admin::Metrics::Retention
     attributes :date, :rate, :value
   end
 
+  attr_reader :loaded
+
+  alias loaded? loaded
+
   def initialize(start_at, end_at, frequency)
     @start_at  = start_at&.to_date
     @end_at    = end_at&.to_date
     @frequency = %w(day month).include?(frequency) ? frequency : 'day'
+    @loaded    = false
+  end
+
+  def cache_key
+    ['metrics/retention', @start_at, @end_at, @frequency].join(';')
   end
 
   def cohorts
+    load
+  end
+
+  protected
+
+  def load
+    unless loaded?
+      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_query }
+      @loaded = true
+    end
+
+    @values
+  end
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH new_users AS (
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 0713aa471..2c16689bd 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -499,7 +499,7 @@ class FeedManager
 
     return false if active_filters.empty?
 
-    combined_regex = active_filters.reduce { |memo, obj| Regexp.union(memo, obj) }
+    combined_regex = Regexp.union(active_filters)
     status         = status.reblog if status.reblog?
 
     combined_text = [
@@ -549,7 +549,7 @@ class FeedManager
       end
     else
       # A reblog may reach earlier than the original status because of the
-      # delay of the worker deliverying the original status, the late addition
+      # delay of the worker delivering the original status, the late addition
       # by merging timelines, and other reasons.
       # If such a reblog already exists, just do not re-insert it into the feed.
       return false unless redis.zscore(reblog_key, status.id).nil?
diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb
index e07ebfffe..c685d7b6f 100644
--- a/app/lib/search_query_transformer.rb
+++ b/app/lib/search_query_transformer.rb
@@ -2,19 +2,21 @@
 
 class SearchQueryTransformer < Parslet::Transform
   class Query
-    attr_reader :should_clauses, :must_not_clauses, :must_clauses
+    attr_reader :should_clauses, :must_not_clauses, :must_clauses, :filter_clauses
 
     def initialize(clauses)
       grouped = clauses.chunk(&:operator).to_h
       @should_clauses = grouped.fetch(:should, [])
       @must_not_clauses = grouped.fetch(:must_not, [])
       @must_clauses = grouped.fetch(:must, [])
+      @filter_clauses = grouped.fetch(:filter, [])
     end
 
     def apply(search)
       should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) }
       must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) }
       must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) }
+      filter_clauses.each { |clause| search = search.filter(**clause_to_filter(clause)) }
       search.query.minimum_should_match(1)
     end
 
@@ -30,6 +32,15 @@ class SearchQueryTransformer < Parslet::Transform
         raise "Unexpected clause type: #{clause}"
       end
     end
+
+    def clause_to_filter(clause)
+      case clause
+      when PrefixClause
+        { term: { clause.filter => clause.term } }
+      else
+        raise "Unexpected clause type: #{clause}"
+      end
+    end
   end
 
   class Operator
@@ -69,11 +80,33 @@ class SearchQueryTransformer < Parslet::Transform
     end
   end
 
+  class PrefixClause
+    attr_reader :filter, :operator, :term
+
+    def initialize(prefix, term)
+      @operator = :filter
+      case prefix
+      when 'from'
+        @filter = :account_id
+        username, domain = term.split('@')
+        account = Account.find_remote(username, domain)
+
+        raise "Account not found: #{term}" unless account
+
+        @term = account.id
+      else
+        raise "Unknown prefix: #{prefix}"
+      end
+    end
+  end
+
   rule(clause: subtree(:clause)) do
     prefix   = clause[:prefix][:term].to_s if clause[:prefix]
     operator = clause[:operator]&.to_s
 
-    if clause[:term]
+    if clause[:prefix]
+      PrefixClause.new(prefix, clause[:term].to_s)
+    elsif clause[:term]
       TermClause.new(prefix, operator, clause[:term].to_s)
     elsif clause[:shortcode]
       TermClause.new(prefix, operator, ":#{clause[:term]}:")
diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb
index 03e40f923..2896620cb 100644
--- a/app/lib/video_metadata_extractor.rb
+++ b/app/lib/video_metadata_extractor.rb
@@ -2,7 +2,7 @@
 
 class VideoMetadataExtractor
   attr_reader :duration, :bitrate, :video_codec, :audio_codec,
-              :colorspace, :width, :height, :frame_rate
+              :colorspace, :width, :height, :frame_rate, :r_frame_rate
 
   def initialize(path)
     @path     = path
@@ -42,6 +42,7 @@ class VideoMetadataExtractor
         @width       = video_stream[:width]
         @height      = video_stream[:height]
         @frame_rate  = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate])
+        @r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate])
       end
 
       if (audio_stream = audio_streams.first)
diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb
index b23bd1296..a9d00c000 100644
--- a/app/mailers/admin_mailer.rb
+++ b/app/mailers/admin_mailer.rb
@@ -15,6 +15,16 @@ class AdminMailer < ApplicationMailer
     end
   end
 
+  def new_appeal(recipient, appeal)
+    @appeal   = appeal
+    @me       = recipient
+    @instance = Rails.configuration.x.local_domain
+
+    locale_for_account(@me) do
+      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_appeal.subject', instance: @instance, username: @appeal.account.username)
+    end
+  end
+
   def new_pending_account(recipient, user)
     @account  = user.account
     @me       = recipient
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 5221a4892..1a823328c 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -169,7 +169,27 @@ class UserMailer < Devise::Mailer
     I18n.with_locale(@resource.locale || I18n.default_locale) do
       mail to: @resource.email,
            subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}"),
-           reply_to: Setting.site_contact_email
+           reply_to: ENV['SMTP_REPLY_TO']
+    end
+  end
+
+  def appeal_approved(user, appeal)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+    @appeal   = appeal
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('user_mailer.appeal_approved.subject', date: l(@appeal.created_at))
+    end
+  end
+
+  def appeal_rejected(user, appeal)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+    @appeal   = appeal
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('user_mailer.appeal_rejected.subject', date: l(@appeal.created_at))
     end
   end
 
@@ -186,7 +206,7 @@ class UserMailer < Devise::Mailer
     I18n.with_locale(@resource.locale || I18n.default_locale) do
       mail to: @resource.email,
            subject: I18n.t('user_mailer.sign_in_token.subject'),
-           reply_to: Setting.site_contact_email
+           reply_to: ENV['SMTP_REPLY_TO']
     end
   end
 end
diff --git a/app/models/account.rb b/app/models/account.rb
index e41fdf003..8f6663e7c 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -274,6 +274,10 @@ class Account < ApplicationRecord
     true
   end
 
+  def previous_strikes_count
+    strikes.where(overruled_at: nil).count
+  end
+
   def keypair
     @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
   end
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index dcb174122..9da1522dd 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -24,6 +24,8 @@ class AccountFilter
     scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil)
 
     params.each do |key, value|
+      next if key.to_s == 'page'
+
       scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
     end
 
@@ -49,7 +51,7 @@ class AccountFilter
     when 'email'
       accounts_with_users.merge(User.matches_email(value))
     when 'ip'
-      valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value)) : Account.none
+      valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group('users.id, accounts.id')) : Account.none
     when 'invited_by'
       invited_by_scope(value)
     when 'order'
diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb
index fc0d988fd..05d01942d 100644
--- a/app/models/account_warning.rb
+++ b/app/models/account_warning.rb
@@ -12,6 +12,7 @@
 #  updated_at        :datetime         not null
 #  report_id         :bigint(8)
 #  status_ids        :string           is an Array
+#  overruled_at      :datetime
 #
 
 class AccountWarning < ApplicationRecord
@@ -28,12 +29,17 @@ class AccountWarning < ApplicationRecord
   belongs_to :target_account, class_name: 'Account', inverse_of: :strikes
   belongs_to :report, optional: true
 
-  has_one :appeal, dependent: :destroy
+  has_one :appeal, dependent: :destroy, inverse_of: :strike
 
   scope :latest, -> { order(id: :desc) }
   scope :custom, -> { where.not(text: '') }
+  scope :active, -> { where(overruled_at: nil).or(where('account_warnings.overruled_at >= ?', 30.days.ago)) }
 
   def statuses
     Status.with_discarded.where(id: status_ids || [])
   end
+
+  def overruled?
+    overruled_at.present?
+  end
 end
diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb
index 12136223b..0f2f712a2 100644
--- a/app/models/admin/action_log_filter.rb
+++ b/app/models/admin/action_log_filter.rb
@@ -8,6 +8,8 @@ class Admin::ActionLogFilter
   ).freeze
 
   ACTION_TYPE_MAP = {
+    approve_appeal: { target_type: 'Appeal', action: 'approve' }.freeze,
+    reject_appeal: { target_type: 'Appeal', action: 'reject' }.freeze,
     assigned_to_self_report: { target_type: 'Report', action: 'assigned_to_self' }.freeze,
     change_email_user: { target_type: 'User', action: 'change_email' }.freeze,
     confirm_user: { target_type: 'User', action: 'confirm' }.freeze,
diff --git a/app/models/admin/appeal_filter.rb b/app/models/admin/appeal_filter.rb
new file mode 100644
index 000000000..b163d2e56
--- /dev/null
+++ b/app/models/admin/appeal_filter.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+class Admin::AppealFilter
+  KEYS = %i(
+    status
+  ).freeze
+
+  attr_reader :params
+
+  def initialize(params)
+    @params = params
+  end
+
+  def results
+    scope = Appeal.order(id: :desc)
+
+    params.each do |key, value|
+      next if %w(page).include?(key.to_s)
+
+      scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
+    end
+
+    scope
+  end
+
+  private
+
+  def scope_for(key, value)
+    case key.to_s
+    when 'status'
+      status_scope(value)
+    else
+      raise "Unknown filter: #{key}"
+    end
+  end
+
+  def status_scope(value)
+    case value
+    when 'approved'
+      Appeal.approved
+    when 'rejected'
+      Appeal.rejected
+    when 'pending'
+      Appeal.pending
+    else
+      raise "Unknown status: #{value}"
+    end
+  end
+end
diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb
index ce5bb5f46..4fba612a6 100644
--- a/app/models/admin/status_filter.rb
+++ b/app/models/admin/status_filter.rb
@@ -31,7 +31,7 @@ class Admin::StatusFilter
   def scope_for(key, value)
     case key.to_s
     when 'media'
-      Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
+      Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id).reorder('statuses.id desc')
     when 'id'
       Status.where(id: value)
     else
diff --git a/app/models/appeal.rb b/app/models/appeal.rb
new file mode 100644
index 000000000..1f32cfa8b
--- /dev/null
+++ b/app/models/appeal.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: appeals
+#
+#  id                     :bigint(8)        not null, primary key
+#  account_id             :bigint(8)        not null
+#  account_warning_id     :bigint(8)        not null
+#  text                   :text             default(""), not null
+#  approved_at            :datetime
+#  approved_by_account_id :bigint(8)
+#  rejected_at            :datetime
+#  rejected_by_account_id :bigint(8)
+#  created_at             :datetime         not null
+#  updated_at             :datetime         not null
+#
+class Appeal < ApplicationRecord
+  MAX_STRIKE_AGE = 20.days
+
+  belongs_to :account
+  belongs_to :strike, class_name: 'AccountWarning', foreign_key: 'account_warning_id'
+  belongs_to :approved_by_account, class_name: 'Account', optional: true
+  belongs_to :rejected_by_account, class_name: 'Account', optional: true
+
+  validates :text, presence: true, length: { maximum: 2_000 }
+  validates :account_warning_id, uniqueness: true
+
+  validate :validate_time_frame, on: :create
+
+  scope :approved, -> { where.not(approved_at: nil) }
+  scope :rejected, -> { where.not(rejected_at: nil) }
+  scope :pending, -> { where(approved_at: nil, rejected_at: nil) }
+
+  def pending?
+    !approved? && !rejected?
+  end
+
+  def approved?
+    approved_at.present?
+  end
+
+  def rejected?
+    rejected_at.present?
+  end
+
+  def approve!(current_account)
+    update!(approved_at: Time.now.utc, approved_by_account: current_account)
+  end
+
+  def reject!(current_account)
+    update!(rejected_at: Time.now.utc, rejected_by_account: current_account)
+  end
+
+  private
+
+  def validate_time_frame
+    errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago
+  end
+end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 9eaacdc03..4b38d729e 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -38,6 +38,12 @@ class MediaAttachment < ApplicationRecord
 
   MAX_DESCRIPTION_LENGTH = 1_500
 
+  IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i
+  VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i
+
+  MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
+  MAX_VIDEO_FRAME_RATE   = 60
+
   IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif).freeze
   VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze
   AUDIO_FILE_EXTENSIONS = %w(.ogg .oga .mp3 .wav .flac .opus .aac .m4a .3gp .wma).freeze
@@ -75,6 +81,7 @@ class MediaAttachment < ApplicationRecord
   VIDEO_FORMAT = {
     format: 'mp4',
     content_type: 'video/mp4',
+    vfr_frame_rate_threshold: MAX_VIDEO_FRAME_RATE,
     convert_options: {
       output: {
         'loglevel' => 'fatal',
@@ -152,12 +159,6 @@ class MediaAttachment < ApplicationRecord
     all: '-quality 90 -strip +set modify-date +set create-date',
   }.freeze
 
-  IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i
-  VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i
-
-  MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
-  MAX_VIDEO_FRAME_RATE   = 60
-
   belongs_to :account,          inverse_of: :media_attachments, optional: true
   belongs_to :status,           inverse_of: :media_attachments, optional: true
   belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
diff --git a/app/models/notification.rb b/app/models/notification.rb
index c14eb8a7e..9bf296386 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -36,6 +36,7 @@ class Notification < ApplicationRecord
     favourite
     poll
     update
+    admin.sign_up
   ).freeze
 
   TARGET_STATUS_INCLUDES_BY_TYPE = {
@@ -63,13 +64,10 @@ class Notification < ApplicationRecord
   scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
 
   scope :browserable, ->(exclude_types = [], account_id = nil) {
-    types = TYPES - exclude_types.map(&:to_sym)
-
-    if account_id.nil?
-      where(type: types)
-    else
-      where(type: types, from_account_id: account_id)
-    end
+    scope = all
+    scope = where(from_account_id: account_id) if account_id.present?
+    scope = scope.where(type: TYPES - exclude_types.map(&:to_sym)) unless exclude_types.empty?
+    scope
   }
 
   def type
@@ -142,6 +140,8 @@ class Notification < ApplicationRecord
       self.from_account_id = activity&.account_id
     when 'Mention'
       self.from_account_id = activity&.status&.account_id
+    when 'Account'
+      self.from_account_id = activity&.id
     end
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index ee20e293e..a21e96ae5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -111,7 +111,7 @@ class User < ApplicationRecord
   scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
   scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) }
   scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
-  scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value) }
+  scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') }
   scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
 
   before_validation :sanitize_languages
@@ -265,6 +265,10 @@ class User < ApplicationRecord
     settings.notification_emails['pending_account']
   end
 
+  def allows_appeal_emails?
+    settings.notification_emails['appeal']
+  end
+
   def allows_trending_tag_emails?
     settings.notification_emails['trending_tag']
   end
diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb
new file mode 100644
index 000000000..65707dfa7
--- /dev/null
+++ b/app/policies/account_warning_policy.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AccountWarningPolicy < ApplicationPolicy
+  def show?
+    target? || staff?
+  end
+
+  def appeal?
+    target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago
+  end
+
+  private
+
+  def target?
+    record.target_account_id == current_account&.id
+  end
+end
diff --git a/app/policies/appeal_policy.rb b/app/policies/appeal_policy.rb
new file mode 100644
index 000000000..a25187172
--- /dev/null
+++ b/app/policies/appeal_policy.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AppealPolicy < ApplicationPolicy
+  def index?
+    staff?
+  end
+
+  def approve?
+    record.pending? && staff?
+  end
+
+  alias reject? approve?
+end
diff --git a/app/services/appeal_service.rb b/app/services/appeal_service.rb
new file mode 100644
index 000000000..1397c50f5
--- /dev/null
+++ b/app/services/appeal_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class AppealService < BaseService
+  def call(strike, text)
+    @strike = strike
+    @text   = text
+
+    create_appeal!
+    notify_staff!
+
+    @appeal
+  end
+
+  private
+
+  def create_appeal!
+    @appeal = @strike.create_appeal!(
+      text: @text,
+      account: @strike.target_account
+    )
+  end
+
+  def notify_staff!
+    User.staff.includes(:account).each do |u|
+      AdminMailer.new_appeal(u.account, @appeal).deliver_later if u.allows_appeal_emails?
+    end
+  end
+end
diff --git a/app/services/approve_appeal_service.rb b/app/services/approve_appeal_service.rb
new file mode 100644
index 000000000..f76bf8943
--- /dev/null
+++ b/app/services/approve_appeal_service.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+class ApproveAppealService < BaseService
+  def call(appeal, current_account)
+    @appeal          = appeal
+    @strike          = appeal.strike
+    @current_account = current_account
+
+    ApplicationRecord.transaction do
+      undo_strike_action!
+      mark_strike_as_appealed!
+    end
+
+    queue_workers!
+    notify_target_account!
+  end
+
+  private
+
+  def target_account
+    @strike.target_account
+  end
+
+  def undo_strike_action!
+    case @strike.action
+    when 'disable'
+      undo_disable!
+    when 'delete_statuses'
+      undo_delete_statuses!
+    when 'sensitive'
+      undo_sensitive!
+    when 'silence'
+      undo_silence!
+    when 'suspend'
+      undo_suspend!
+    end
+  end
+
+  def mark_strike_as_appealed!
+    @appeal.approve!(@current_account)
+    @strike.touch(:overruled_at)
+  end
+
+  def undo_disable!
+    target_account.user.enable!
+  end
+
+  def undo_delete_statuses!
+    # Cannot be undone
+  end
+
+  def undo_sensitive!
+    target_account.unsensitize!
+  end
+
+  def undo_silence!
+    target_account.unsilence!
+  end
+
+  def undo_suspend!
+    target_account.unsuspend!
+  end
+
+  def queue_workers!
+    case @strike.action
+    when 'suspend'
+      Admin::UnsuspensionWorker.perform_async(target_account.id)
+    end
+  end
+
+  def notify_target_account!
+    UserMailer.appeal_approved(target_account.user, @appeal).deliver_later
+  end
+end
diff --git a/app/services/bootstrap_timeline_service.rb b/app/services/bootstrap_timeline_service.rb
index e1a1b98c3..312c163e4 100644
--- a/app/services/bootstrap_timeline_service.rb
+++ b/app/services/bootstrap_timeline_service.rb
@@ -5,6 +5,7 @@ class BootstrapTimelineService < BaseService
     @source_account = source_account
 
     autofollow_inviter!
+    notify_staff!
   end
 
   private
@@ -14,4 +15,10 @@ class BootstrapTimelineService < BaseService
 
     FollowService.new.call(@source_account, @source_account.user.invite.user.account)
   end
+
+  def notify_staff!
+    User.staff.includes(:account).find_each do |user|
+      NotifyService.new.call(user.account, :'admin.sign_up', @source_account)
+    end
+  end
 end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 94dc6389f..239ab9b93 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -2,7 +2,7 @@
 
 class FetchLinkCardService < BaseService
   URL_PATTERN = %r{
-    (#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]})                                                                #   $1 preceeding chars
+    (#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]})                                                                #   $1 preceding chars
     (                                                                                                                           #   $2 URL
       (https?:\/\/)                                                                                                             #   $3 Protocol (required)
       (#{Twitter::TwitterText::Regex[:valid_domain]})                                                                           #   $4 Domain(s)
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 039e007f5..b1f9fd755 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -22,34 +22,6 @@ class NotifyService < BaseService
     FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
   end
 
-  def blocked_status?
-    false
-  end
-
-  def blocked_favourite?
-    false
-  end
-
-  def blocked_follow?
-    false
-  end
-
-  def blocked_reblog?
-    false
-  end
-
-  def blocked_follow_request?
-    false
-  end
-
-  def blocked_poll?
-    false
-  end
-
-  def blocked_update?
-    false
-  end
-
   def following_sender?
     return @following_sender if defined?(@following_sender)
     @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
@@ -71,7 +43,7 @@ class NotifyService < BaseService
     message? && @notification.target_status.direct_visibility?
   end
 
-  # Returns true if the sender has been mentionned by the recipient up the thread
+  # Returns true if the sender has been mentioned by the recipient up the thread
   def response_to_recipient?
     return false if @notification.target_status.in_reply_to_id.nil?
 
@@ -149,15 +121,15 @@ class NotifyService < BaseService
 
     return blocked if message? && from_staff?
 
-    blocked ||= domain_blocking?                                 # Skip for domain blocked accounts
-    blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
+    blocked ||= domain_blocking?
+    blocked ||= @recipient.blocking?(@notification.from_account)
     blocked ||= @recipient.muting_notifications?(@notification.from_account)
-    blocked ||= hellbanned?                                      # Hellban
-    blocked ||= optional_non_follower?                           # Options
-    blocked ||= optional_non_following?                          # Options
-    blocked ||= optional_non_following_and_direct?               # Options
+    blocked ||= hellbanned?
+    blocked ||= optional_non_follower?
+    blocked ||= optional_non_following?
+    blocked ||= optional_non_following_and_direct?
     blocked ||= conversation_muted?
-    blocked ||= send("blocked_#{@notification.type}?")           # Type-dependent filters
+    blocked ||= blocked_mention? if @notification.type == :mention
     blocked
   end
 
diff --git a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml
deleted file mode 100644
index 432fb79a6..000000000
--- a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.speech-bubble
-  .speech-bubble__bubble
-    = simple_format(h(account_moderation_note.content))
-  .speech-bubble__owner
-    = admin_account_link_to account_moderation_note.account
-    %time.formatted{ datetime: account_moderation_note.created_at.iso8601 }= l account_moderation_note.created_at
-    = table_link_to 'trash', t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note)
diff --git a/app/views/admin/account_warnings/_account_warning.html.haml b/app/views/admin/account_warnings/_account_warning.html.haml
index 8c9c9679c..1462e76d0 100644
--- a/app/views/admin/account_warnings/_account_warning.html.haml
+++ b/app/views/admin/account_warnings/_account_warning.html.haml
@@ -1,6 +1,25 @@
-.speech-bubble.warning
-  .speech-bubble__bubble
-    = Formatter.instance.linkify(account_warning.text)
-  .speech-bubble__owner
-    = admin_account_link_to account_warning.account
-    %time.formatted{ datetime: account_warning.created_at.iso8601 }= l account_warning.created_at
+= link_to disputes_strike_path(account_warning), class: 'log-entry' do
+  .log-entry__header
+    .log-entry__avatar
+      .indicator-icon{ class: account_warning.overruled? ? 'success' : 'failure' }
+        = fa_icon 'warning'
+    .log-entry__content
+      .log-entry__title
+        = t(account_warning.action, scope: 'admin.strikes.actions', name: content_tag(:span, account_warning.account.username, class: 'username'), target: content_tag(:span, account_warning.target_account.acct, class: 'target')).html_safe
+      .log-entry__timestamp
+        %time.formatted{ datetime: account_warning.created_at.iso8601 }
+          = l(account_warning.created_at)
+
+        - if account_warning.report_id.present?
+          ·
+          = t('admin.reports.report', id: account_warning.report_id)
+
+        - if account_warning.overruled?
+          ·
+          %span.positive-hint= t('admin.strikes.appeal_approved')
+        - elsif account_warning.appeal&.pending?
+          ·
+          %span.warning-hint= t('admin.strikes.appeal_pending')
+        - elsif account_warning.appeal&.rejected?
+          ·
+          %span.negative-hint= t('admin.strikes.appeal_rejected')
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index f3853d629..9a1f07a06 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -246,18 +246,29 @@
   %hr.spacer/
 
   - unless @warnings.empty?
-    = render @warnings
+
+    %h3= t 'admin.accounts.previous_strikes'
+
+    %p= t('admin.accounts.previous_strikes_description_html', count: @account.previous_strikes_count)
+
+    .account-strikes
+      = render @warnings
 
     %hr.spacer/
 
-  = render @moderation_notes
+  %h3= t 'admin.reports.notes.title'
 
-  = simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f|
-    = render 'shared/error_messages', object: @account_moderation_note
+  %p= t 'admin.reports.notes_description_html'
 
-    = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6
+  .report-notes
+    = render partial: 'admin/report_notes/report_note', collection: @moderation_notes
+
+  = simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f|
     = f.hidden_field :target_account_id
 
+    .field-group
+      = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6
+
     .actions
       = f.button :button, t('admin.account_moderation_notes.create'), type: :submit
 
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 2ee13b9e2..66e0c0251 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -16,10 +16,10 @@
 
 .dashboard
   .dashboard__item
-    = react_admin_component :counter, measure: 'new_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.new_users'), href: admin_accounts_path
+    = react_admin_component :counter, measure: 'new_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.new_users'), href: admin_accounts_path(origin: 'local')
 
   .dashboard__item
-    = react_admin_component :counter, measure: 'active_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.active_users'), href: admin_accounts_path
+    = react_admin_component :counter, measure: 'active_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.active_users'), href: admin_accounts_path(origin: 'local')
 
   .dashboard__item
     = react_admin_component :counter, measure: 'interactions', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.interactions')
@@ -43,6 +43,9 @@
       %span= t('admin.dashboard.pending_tags_html', count: @pending_tags_count)
       = fa_icon 'chevron-right fw'
 
+    = link_to admin_disputes_appeals_path(status: 'pending'), class: 'dashboard__quick-access' do
+      %span= t('admin.dashboard.pending_appeals_html', count: @pending_appeals_count)
+      = fa_icon 'chevron-right fw'
   .dashboard__item
     = react_admin_component :dimension, dimension: 'sources', start_at: @time_period.first, end_at: @time_period.last, limit: 8, label: t('admin.dashboard.sources')
 
diff --git a/app/views/admin/disputes/appeals/_appeal.html.haml b/app/views/admin/disputes/appeals/_appeal.html.haml
new file mode 100644
index 000000000..02b8777e1
--- /dev/null
+++ b/app/views/admin/disputes/appeals/_appeal.html.haml
@@ -0,0 +1,21 @@
+= link_to disputes_strike_path(appeal.strike), class: ['log-entry', appeal.approved? && 'log-entry--inactive'] do
+  .log-entry__header
+    .log-entry__avatar
+      = image_tag appeal.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar'
+    .log-entry__content
+      .log-entry__title
+        = t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.acct, class: 'target')).html_safe
+      .log-entry__timestamp
+        %time.formatted{ datetime: appeal.strike.created_at.iso8601 }
+          = l(appeal.strike.created_at)
+
+        - if appeal.strike.report_id.present?
+          ·
+          = t('admin.reports.title', id: appeal.strike.report_id)
+        ·
+        - if appeal.approved?
+          %span.positive-hint= t('admin.strikes.appeal_approved')
+        - elsif appeal.rejected?
+          %span.negative-hint= t('admin.strikes.appeal_rejected')
+        - else
+          %span.warning-hint= t('admin.strikes.appeal_pending')
diff --git a/app/views/admin/disputes/appeals/index.html.haml b/app/views/admin/disputes/appeals/index.html.haml
new file mode 100644
index 000000000..42e9c4b1d
--- /dev/null
+++ b/app/views/admin/disputes/appeals/index.html.haml
@@ -0,0 +1,19 @@
+- content_for :page_title do
+  = t('admin.disputes.appeals.title')
+
+.filters
+  .filter-subset
+    %strong= t('admin.tags.review')
+    %ul
+      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Appeal.pending.count})"], ' '), status: 'pending'
+      %li= filter_link_to t('admin.trends.approved'), status: 'approved'
+      %li= filter_link_to t('admin.trends.rejected'), status: 'rejected'
+
+- if @appeals.empty?
+  %div.muted-hint.center-text
+    = t 'admin.disputes.appeals.empty'
+- else
+  .announcements-list
+    = render partial: 'appeal', collection: @appeals
+
+= paginate @appeals
diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml
index 428b6cf59..f9d57c2ae 100644
--- a/app/views/admin/report_notes/_report_note.html.haml
+++ b/app/views/admin/report_notes/_report_note.html.haml
@@ -3,7 +3,7 @@
 
   .report-notes__item__header
     %span.username
-      = link_to display_name(report_note.account), admin_account_path(report_note.account_id)
+      = link_to report_note.account.username, admin_account_path(report_note.account_id)
     %time{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) }
       - if report_note.created_at.today?
         = t('admin.report_notes.today_at', time: l(report_note.created_at, format: :time))
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 018a0c54a..abcbec949 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -53,7 +53,7 @@
         .report-header__details__item__header
           %strong= t('admin.accounts.strikes')
         .report-header__details__item__content
-          = @report.target_account.strikes.count
+          = @report.target_account.previous_strikes_count
 
   .report-header__details
     .report-header__details__item
diff --git a/app/views/admin_mailer/new_appeal.text.erb b/app/views/admin_mailer/new_appeal.text.erb
new file mode 100644
index 000000000..db4529eb7
--- /dev/null
+++ b/app/views/admin_mailer/new_appeal.text.erb
@@ -0,0 +1,9 @@
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
+
+<%= raw t('admin_mailer.new_appeal.body', target: @appeal.account.username, action_taken_by: @appeal.strike.account.username, date: l(@appeal.strike.created_at), type: t(@appeal.strike.action, scope: 'admin_mailer.new_appeal.actions')) %>
+
+> <%= raw word_wrap(@appeal.text, break_sequence: "\n> ") %>
+
+<%= raw t('admin_mailer.new_appeal.next_steps') %>
+
+<%= raw t('application_mailer.view')%> <%= disputes_strike_url(@appeal.strike) %>
diff --git a/app/views/admin_mailer/new_trending_tags.text.erb b/app/views/admin_mailer/new_trending_tags.text.erb
index 5051e8a96..9ea31fa7c 100644
--- a/app/views/admin_mailer/new_trending_tags.text.erb
+++ b/app/views/admin_mailer/new_trending_tags.text.erb
@@ -13,4 +13,4 @@
 <%= t('admin_mailer.new_trending_tags.no_approved_tags') %>
 <% end %>
 
-<%= raw t('application_mailer.view')%> <%= admin_trends_tags_url(pending_review: '1') %>
+<%= raw t('application_mailer.view')%> <%= admin_trends_tags_url(status: 'pending_review') %>
diff --git a/app/views/auth/registrations/_account_warning.html.haml b/app/views/auth/registrations/_account_warning.html.haml
new file mode 100644
index 000000000..40e7e1296
--- /dev/null
+++ b/app/views/auth/registrations/_account_warning.html.haml
@@ -0,0 +1,20 @@
+= link_to disputes_strike_path(account_warning), class: 'log-entry' do
+  .log-entry__header
+    .log-entry__avatar
+      .indicator-icon{ class: account_warning.overruled? ? 'success' : 'failure' }
+        = fa_icon 'warning'
+    .log-entry__content
+      .log-entry__title
+        = t('disputes.strikes.title', action: t(account_warning.action, scope: 'disputes.strikes.title_actions'), date: l(account_warning.created_at.to_date))
+      .log-entry__timestamp
+        %time.formatted{ datetime: account_warning.created_at.iso8601 }= l(account_warning.created_at)
+
+        - if account_warning.overruled?
+          ·
+          %span.positive-hint= t('disputes.strikes.your_appeal_approved')
+        - elsif account_warning.appeal&.pending?
+          ·
+          %span.warning-hint= t('disputes.strikes.your_appeal_pending')
+        - elsif account_warning.appeal&.rejected?
+          ·
+          %span.negative-hint= t('disputes.strikes.your_appeal_rejected')
diff --git a/app/views/auth/registrations/_status.html.haml b/app/views/auth/registrations/_status.html.haml
index 47112dae0..3546510b2 100644
--- a/app/views/auth/registrations/_status.html.haml
+++ b/app/views/auth/registrations/_status.html.haml
@@ -1,22 +1,17 @@
+- if !@user.confirmed?
+  .flash-message.warning
+    = t('auth.status.confirming')
+    = link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path
+- elsif !@user.approved?
+  .flash-message.warning
+    = t('auth.status.pending')
+- elsif @user.account.moved_to_account_id.present?
+  .flash-message.warning
+    = t('auth.status.redirecting_to', acct: @user.account.moved_to_account.acct)
+    = link_to t('migrations.cancel'), settings_migration_path
+
 %h3= t('auth.status.account_status')
 
-.simple_form
-  %p.hint
-    - if @user.account.suspended?
-      %span.negative-hint= t('user_mailer.warning.explanation.suspend')
-    - elsif @user.disabled?
-      %span.negative-hint= t('user_mailer.warning.explanation.disable')
-    - elsif @user.account.silenced?
-      %span.warning-hint= t('user_mailer.warning.explanation.silence')
-    - elsif !@user.confirmed?
-      %span.warning-hint= t('auth.status.confirming')
-      = link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path
-    - elsif !@user.approved?
-      %span.warning-hint= t('auth.status.pending')
-    - elsif @user.account.moved_to_account_id.present?
-      %span.positive-hint= t('auth.status.redirecting_to', acct: @user.account.moved_to_account.acct)
-      = link_to t('migrations.cancel'), settings_migration_path
-    - else
-      %span.positive-hint= t('auth.status.functional')
+= render partial: 'account_warning', collection: @strikes
 
 %hr.spacer/
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
new file mode 100644
index 000000000..7248b2574
--- /dev/null
+++ b/app/views/disputes/strikes/show.html.haml
@@ -0,0 +1,128 @@
+- content_for :page_title do
+  = t('disputes.strikes.title', action: t(@strike.action, scope: 'disputes.strikes.title_actions'), date: l(@strike.created_at.to_date))
+
+- content_for :heading_actions do
+  - if @appeal.persisted?
+    = link_to t('admin.accounts.approve'), approve_admin_disputes_appeal_path(@appeal), method: :post, class: 'button' if can?(:approve, @appeal)
+    = link_to t('admin.accounts.reject'), reject_admin_disputes_appeal_path(@appeal), method: :post, class: 'button button--destructive' if can?(:reject, @appeal)
+
+- if @strike.overruled?
+  %p.hint
+    %span.positive-hint
+      = fa_icon 'check'
+      = ' '
+      = t 'disputes.strikes.appeal_approved'
+- elsif @appeal.persisted? && @appeal.rejected?
+  %p.hint
+    %span.negative-hint
+      = fa_icon 'times'
+      = ' '
+      = t 'disputes.strikes.appeal_rejected'
+
+.report-header
+  .report-header__card
+    .strike-card
+      - unless @strike.none_action?
+        %p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain
+
+      - unless @strike.text.blank?
+        = Formatter.instance.linkify(@strike.text)
+
+      - if @strike.report && !@strike.report.other?
+        %p
+          %strong= t('user_mailer.warning.reason')
+          = t("user_mailer.warning.categories.#{@strike.report.category}")
+
+        - if @strike.report.violation? && @strike.report.rule_ids.present?
+          %ul.strike-card__rules
+            - @strike.report.rules.each do |rule|
+              %li
+                %span.strike-card__rules__text= rule.text
+
+      - if @strike.status_ids.present? && !@strike.status_ids.empty?
+        %p
+          %strong= t('user_mailer.warning.statuses')
+
+        .strike-card__statuses-list
+          - status_map = @strike.statuses.includes(:application, :media_attachments).index_by(&:id)
+
+          - @strike.status_ids.each do |status_id|
+            .strike-card__statuses-list__item
+              - if (status = status_map[status_id.to_i])
+                .one-liner
+                  = link_to short_account_status_url(@strike.target_account, status_id), class: 'emojify' do
+                    = one_line_preview(status)
+
+                    - status.media_attachments.each do |media_attachment|
+                      %abbr{ title: media_attachment.description }
+                        = fa_icon 'link'
+                        = media_attachment.file_file_name
+                .strike-card__statuses-list__item__meta
+                  %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
+                  ·
+                  = status.application.name
+              - else
+                .one-liner= t('disputes.strikes.status', id: status_id)
+                .strike-card__statuses-list__item__meta
+                  = t('disputes.strikes.status_removed')
+
+  .report-header__details
+    .report-header__details__item
+      .report-header__details__item__header
+        %strong= t('disputes.strikes.created_at')
+      .report-header__details__item__content
+        %time.formatted{ datetime: @strike.created_at.iso8601, title: l(@strike.created_at) }= l(@strike.created_at)
+    .report-header__details__item
+      .report-header__details__item__header
+        %strong= t('disputes.strikes.recipient')
+      .report-header__details__item__content
+        = link_to @strike.target_account.username, can?(:show, @strike.target_account) ? admin_account_path(@strike.target_account_id) : ActivityPub::TagManager.instance.url_for(@strike.target_account), class: 'table-action-link'
+    .report-header__details__item
+      .report-header__details__item__header
+        %strong= t('disputes.strikes.action_taken')
+      .report-header__details__item__content
+        - if @strike.overruled?
+          %del= t(@strike.action, scope: 'user_mailer.warning.title')
+        - else
+          = t(@strike.action, scope: 'user_mailer.warning.title')
+    - if @strike.report && can?(:show, @strike.report)
+      .report-header__details__item
+        .report-header__details__item__header
+          %strong= t('disputes.strikes.associated_report')
+        .report-header__details__item__content
+          = link_to t('admin.reports.report', id: @strike.report.id), admin_report_path(@strike.report), class: 'table-action-link'
+    - if @appeal.persisted?
+      .report-header__details__item
+        .report-header__details__item__header
+          %strong= t('disputes.strikes.appeal_submitted_at')
+        .report-header__details__item__content
+          %time.formatted{ datetime: @appeal.created_at.iso8601, title: l(@appeal.created_at) }= l(@appeal.created_at)
+%hr.spacer/
+
+- if @appeal.persisted?
+  %h3= t('disputes.strikes.appeal')
+
+  .report-notes
+    .report-notes__item
+      = image_tag @appeal.account.avatar.url, class: 'report-notes__item__avatar'
+
+      .report-notes__item__header
+        %span.username
+          = link_to @appeal.account.username, can?(:show, @appeal.account) ? admin_account_path(@appeal.account_id) : short_account_url(@appeal.account)
+        %time{ datetime: @appeal.created_at.iso8601, title: l(@appeal.created_at) }
+          - if @appeal.created_at.today?
+            = t('admin.report_notes.today_at', time: l(@appeal.created_at, format: :time))
+          - else
+            = l @appeal.created_at.to_date
+
+      .report-notes__item__content
+        = simple_format(h(@appeal.text))
+- elsif can?(:appeal, @strike)
+  %h3= t('disputes.strikes.appeals.submit')
+
+  = simple_form_for(@appeal, url: disputes_strike_appeal_path(@strike)) do |f|
+    .fields-group
+      = f.input :text, wrapper: :with_label, input_html: { maxlength: 500 }
+
+    .actions
+      = f.button :button, t('disputes.strikes.appeals.submit'), type: :submit
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index 61198171d..1a789cef8 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -53,5 +53,9 @@
             %ul
               %li= link_to t('about.source_code'), Mastodon::Version.source_url
               %li= link_to t('about.apps'), 'https://joinmastodon.org/apps'
+        .legal-xs
+          = link_to "v#{Mastodon::Version.to_s}", Mastodon::Version.source_url
+          ·
+          = link_to t('about.privacy_policy'), terms_path
 
 = render template: 'layouts/application'
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index d7cc1ed5d..223e5d740 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -21,6 +21,7 @@
 
       - if current_user.staff?
         = ff.input :report, as: :boolean, wrapper: :with_label
+        = ff.input :appeal, as: :boolean, wrapper: :with_label
         = ff.input :pending_account, as: :boolean, wrapper: :with_label
         = ff.input :trending_tag, as: :boolean, wrapper: :with_label
 
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index cd5ed52af..1922f53ce 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -49,7 +49,7 @@
     %span.detailed-status__visibility-icon
       = visibility_icon status
     ·
-    - if status.application && @account.user&.setting_show_application
+    - if status.application && status.account.user&.setting_show_application
       - if status.application.website.blank?
         %strong.detailed-status__application= status.application.name
       - else
diff --git a/app/views/user_mailer/appeal_approved.html.haml b/app/views/user_mailer/appeal_approved.html.haml
new file mode 100644
index 000000000..962cab2e2
--- /dev/null
+++ b/app/views/user_mailer/appeal_approved.html.haml
@@ -0,0 +1,59 @@
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.hero
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center.padded
+                              %table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                                %tbody
+                                  %tr
+                                    %td
+                                      = image_tag full_pack_url('media/images/mailer/icon_done.png'), alt: ''
+
+                              %h1= t 'user_mailer.appeal_approved.title'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.content-start
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center
+                              %p= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at)
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell
+                  %table.column{ cellspacing: 0, cellpadding: 0 }
+                    %tbody
+                      %tr
+                        %td.column-cell.button-cell
+                          %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                            %tbody
+                              %tr
+                                %td.button-primary
+                                  = link_to root_url do
+                                    %span= t 'user_mailer.appeal_approved.action'
diff --git a/app/views/user_mailer/appeal_approved.text.erb b/app/views/user_mailer/appeal_approved.text.erb
new file mode 100644
index 000000000..290fa24c3
--- /dev/null
+++ b/app/views/user_mailer/appeal_approved.text.erb
@@ -0,0 +1,7 @@
+<%= t 'user_mailer.appeal_approved.title' %>
+
+===
+
+<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %>
+
+=> <%= root_url %>
diff --git a/app/views/user_mailer/appeal_rejected.html.haml b/app/views/user_mailer/appeal_rejected.html.haml
new file mode 100644
index 000000000..75cd9d023
--- /dev/null
+++ b/app/views/user_mailer/appeal_rejected.html.haml
@@ -0,0 +1,59 @@
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.hero
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center.padded
+                              %table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                                %tbody
+                                  %tr
+                                    %td
+                                      = image_tag full_pack_url('media/images/mailer/icon_warning.png'), alt: ''
+
+                              %h1= t 'user_mailer.appeal_rejected.title'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.content-start
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center
+                              %p= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at)
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell
+                  %table.column{ cellspacing: 0, cellpadding: 0 }
+                    %tbody
+                      %tr
+                        %td.column-cell.button-cell
+                          %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                            %tbody
+                              %tr
+                                %td.button-primary
+                                  = link_to root_url do
+                                    %span= t 'user_mailer.appeal_approved.action'
diff --git a/app/views/user_mailer/appeal_rejected.text.erb b/app/views/user_mailer/appeal_rejected.text.erb
new file mode 100644
index 000000000..f47a76818
--- /dev/null
+++ b/app/views/user_mailer/appeal_rejected.text.erb
@@ -0,0 +1,7 @@
+<%= t 'user_mailer.appeal_rejected.title' %>
+
+===
+
+<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %>
+
+=> <%= root_url %>
diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml
index bda1fef6c..b308e18f7 100644
--- a/app/views/user_mailer/warning.html.haml
+++ b/app/views/user_mailer/warning.html.haml
@@ -77,8 +77,8 @@
                             %tbody
                               %tr
                                 %td.button-primary
-                                  = link_to about_more_url do
-                                    %span= t 'user_mailer.warning.review_server_policies'
+                                  = link_to disputes_strike_url(@warning) do
+                                    %span= t 'user_mailer.warning.appeal'
 
 %table.email-table{ cellspacing: 0, cellpadding: 0 }
   %tbody
@@ -95,4 +95,4 @@
                         %tbody
                           %tr
                             %td.column-cell.text-center
-                              %p= t 'user_mailer.warning.get_in_touch', instance: @instance
+                              %p= t 'user_mailer.warning.appeal_description', instance: @instance