about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorReverite <github@reverite.sh>2019-03-21 15:35:55 -0700
committerReverite <github@reverite.sh>2019-03-21 15:35:55 -0700
commit592735fd80acd0aeffb5a5674255ed48d7a8db0b (patch)
tree0eefc67f624a07df0af860edecd68d5dc64c7ee9 /app
parent75eeb003b09c53d3b4e98046d1c20b0ad8a887bb (diff)
parentbde9196b70299405ebe9b16500b7a3f65539b2c3 (diff)
Merge remote-tracking branch 'glitch/master' into production
Diffstat (limited to 'app')
-rw-r--r--app/controllers/accounts_controller.rb8
-rw-r--r--app/controllers/activitypub/inboxes_controller.rb16
-rw-r--r--app/controllers/admin/accounts_controller.rb2
-rw-r--r--app/controllers/api/proofs_controller.rb30
-rw-r--r--app/controllers/api/v1/preferences_controller.rb12
-rw-r--r--app/controllers/api/v1/statuses/reblogs_controller.rb6
-rw-r--r--app/controllers/application_controller.rb5
-rw-r--r--app/controllers/follower_accounts_controller.rb1
-rw-r--r--app/controllers/relationships_controller.rb108
-rw-r--r--app/controllers/settings/follower_domains_controller.rb24
-rw-r--r--app/controllers/settings/identity_proofs_controller.rb45
-rw-r--r--app/controllers/statuses_controller.rb4
-rw-r--r--app/controllers/well_known/keybase_proof_config_controller.rb9
-rw-r--r--app/helpers/admin/action_logs_helper.rb72
-rw-r--r--app/helpers/admin/filter_helper.rb3
-rw-r--r--app/helpers/settings_helper.rb3
-rw-r--r--app/javascript/flavours/glitch/actions/importer/normalizer.js4
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js2
-rw-r--r--app/javascript/flavours/glitch/components/poll.js13
-rw-r--r--app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js2
-rw-r--r--app/javascript/flavours/glitch/features/emoji_picker/index.js51
-rw-r--r--app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js9
-rw-r--r--app/javascript/flavours/glitch/features/standalone/public_timeline/index.js13
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/components/composer.scss5
-rw-r--r--app/javascript/flavours/glitch/styles/components/emoji.scss4
-rw-r--r--app/javascript/flavours/glitch/styles/components/emoji_picker.scss25
-rw-r--r--app/javascript/flavours/glitch/styles/forms.scss55
-rw-r--r--app/javascript/flavours/glitch/styles/tables.scss43
-rw-r--r--app/javascript/flavours/glitch/theme.yml6
-rw-r--r--app/javascript/flavours/glitch/util/emoji/emoji_picker.js4
-rw-r--r--app/javascript/flavours/vanilla/theme.yml6
-rw-r--r--app/javascript/images/logo_transparent_black.svg1
-rw-r--r--app/javascript/images/proof_providers/keybase.pngbin0 -> 12665 bytes
-rw-r--r--app/javascript/mastodon/actions/importer/normalizer.js4
-rw-r--r--app/javascript/mastodon/actions/notifications.js2
-rw-r--r--app/javascript/mastodon/components/error_boundary.js39
-rw-r--r--app/javascript/mastodon/components/poll.js13
-rw-r--r--app/javascript/mastodon/containers/mastodon.js5
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js51
-rw-r--r--app/javascript/mastodon/features/compose/components/poll_form.js2
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_picker.js4
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js2
-rw-r--r--app/javascript/mastodon/features/standalone/hashtag_timeline/index.js9
-rw-r--r--app/javascript/mastodon/features/standalone/public_timeline/index.js14
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle.js5
-rw-r--r--app/javascript/mastodon/locales/ar.json11
-rw-r--r--app/javascript/mastodon/locales/ast.json11
-rw-r--r--app/javascript/mastodon/locales/bg.json11
-rw-r--r--app/javascript/mastodon/locales/ca.json47
-rw-r--r--app/javascript/mastodon/locales/co.json29
-rw-r--r--app/javascript/mastodon/locales/cs.json91
-rw-r--r--app/javascript/mastodon/locales/cy.json11
-rw-r--r--app/javascript/mastodon/locales/da.json11
-rw-r--r--app/javascript/mastodon/locales/de.json11
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json10
-rw-r--r--app/javascript/mastodon/locales/el.json29
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/eo.json27
-rw-r--r--app/javascript/mastodon/locales/es.json11
-rw-r--r--app/javascript/mastodon/locales/eu.json11
-rw-r--r--app/javascript/mastodon/locales/fa.json35
-rw-r--r--app/javascript/mastodon/locales/fi.json11
-rw-r--r--app/javascript/mastodon/locales/fr.json29
-rw-r--r--app/javascript/mastodon/locales/gl.json13
-rw-r--r--app/javascript/mastodon/locales/he.json11
-rw-r--r--app/javascript/mastodon/locales/hr.json11
-rw-r--r--app/javascript/mastodon/locales/hu.json11
-rw-r--r--app/javascript/mastodon/locales/hy.json11
-rw-r--r--app/javascript/mastodon/locales/id.json11
-rw-r--r--app/javascript/mastodon/locales/io.json11
-rw-r--r--app/javascript/mastodon/locales/it.json29
-rw-r--r--app/javascript/mastodon/locales/ja.json19
-rw-r--r--app/javascript/mastodon/locales/ka.json11
-rw-r--r--app/javascript/mastodon/locales/kk.json29
-rw-r--r--app/javascript/mastodon/locales/ko.json29
-rw-r--r--app/javascript/mastodon/locales/lv.json11
-rw-r--r--app/javascript/mastodon/locales/ms.json11
-rw-r--r--app/javascript/mastodon/locales/nl.json29
-rw-r--r--app/javascript/mastodon/locales/no.json11
-rw-r--r--app/javascript/mastodon/locales/oc.json39
-rw-r--r--app/javascript/mastodon/locales/pl.json10
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json13
-rw-r--r--app/javascript/mastodon/locales/pt.json317
-rw-r--r--app/javascript/mastodon/locales/ro.json11
-rw-r--r--app/javascript/mastodon/locales/ru.json11
-rw-r--r--app/javascript/mastodon/locales/sk.json47
-rw-r--r--app/javascript/mastodon/locales/sl.json11
-rw-r--r--app/javascript/mastodon/locales/sq.json11
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json11
-rw-r--r--app/javascript/mastodon/locales/sr.json11
-rw-r--r--app/javascript/mastodon/locales/sv.json11
-rw-r--r--app/javascript/mastodon/locales/ta.json11
-rw-r--r--app/javascript/mastodon/locales/te.json11
-rw-r--r--app/javascript/mastodon/locales/th.json11
-rw-r--r--app/javascript/mastodon/locales/tr.json11
-rw-r--r--app/javascript/mastodon/locales/uk.json11
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json11
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json11
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json11
-rw-r--r--app/javascript/styles/mastodon/about.scss2
-rw-r--r--app/javascript/styles/mastodon/components.scss9
-rw-r--r--app/javascript/styles/mastodon/emoji_picker.scss25
-rw-r--r--app/javascript/styles/mastodon/forms.scss55
-rw-r--r--app/javascript/styles/mastodon/tables.scss43
-rw-r--r--app/lib/activitypub/activity/flag.rb7
-rw-r--r--app/lib/formatter.rb6
-rw-r--r--app/lib/language_detector.rb31
-rw-r--r--app/lib/proof_provider.rb12
-rw-r--r--app/lib/proof_provider/keybase.rb59
-rw-r--r--app/lib/proof_provider/keybase/badge.rb48
-rw-r--r--app/lib/proof_provider/keybase/config_serializer.rb70
-rw-r--r--app/lib/proof_provider/keybase/serializer.rb25
-rw-r--r--app/lib/proof_provider/keybase/verifier.rb62
-rw-r--r--app/lib/proof_provider/keybase/worker.rb33
-rw-r--r--app/models/account_identity_proof.rb46
-rw-r--r--app/models/concerns/account_associations.rb3
-rw-r--r--app/models/form/account_batch.rb60
-rw-r--r--app/models/poll.rb4
-rw-r--r--app/models/report.rb11
-rw-r--r--app/models/status.rb12
-rw-r--r--app/serializers/activitypub/flag_serializer.rb1
-rw-r--r--app/serializers/rest/instance_serializer.rb2
-rw-r--r--app/serializers/rest/poll_serializer.rb1
-rw-r--r--app/serializers/rest/preferences_serializer.rb30
-rw-r--r--app/serializers/rss/account_serializer.rb2
-rw-r--r--app/serializers/rss/tag_serializer.rb2
-rw-r--r--app/services/post_status_service.rb18
-rw-r--r--app/services/reblog_service.rb7
-rw-r--r--app/services/remove_status_service.rb32
-rw-r--r--app/services/report_service.rb3
-rw-r--r--app/services/suspend_account_service.rb2
-rw-r--r--app/views/about/more.html.haml4
-rw-r--r--app/views/about/show.html.haml36
-rw-r--r--app/views/accounts/_bio.html.haml15
-rw-r--r--app/views/admin/invites/_invite.html.haml11
-rw-r--r--app/views/application/_sidebar.html.haml2
-rw-r--r--app/views/home/index.html.haml2
-rw-r--r--app/views/invites/_invite.html.haml10
-rw-r--r--app/views/layouts/admin.html.haml2
-rw-r--r--app/views/layouts/auth.html.haml2
-rw-r--r--app/views/layouts/mailer.html.haml4
-rw-r--r--app/views/layouts/public.html.haml2
-rw-r--r--app/views/notification_mailer/favourite.html.haml2
-rw-r--r--app/views/notification_mailer/follow.html.haml2
-rw-r--r--app/views/notification_mailer/follow_request.html.haml2
-rw-r--r--app/views/notification_mailer/mention.html.haml2
-rw-r--r--app/views/notification_mailer/reblog.html.haml2
-rw-r--r--app/views/relationships/_account.html.haml20
-rw-r--r--app/views/relationships/show.html.haml47
-rw-r--r--app/views/settings/flavours/show.html.haml2
-rw-r--r--app/views/settings/follower_domains/show.html.haml34
-rw-r--r--app/views/settings/identity_proofs/_proof.html.haml20
-rw-r--r--app/views/settings/identity_proofs/index.html.haml17
-rw-r--r--app/views/settings/identity_proofs/new.html.haml31
-rw-r--r--app/views/shared/_og.html.haml2
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml2
-rw-r--r--app/views/stream_entries/_poll.html.haml4
-rw-r--r--app/views/stream_entries/_simple_status.html.haml2
-rw-r--r--app/views/user_mailer/backup_ready.html.haml2
-rw-r--r--app/views/user_mailer/confirmation_instructions.html.haml2
-rw-r--r--app/views/user_mailer/email_changed.html.haml2
-rw-r--r--app/views/user_mailer/password_change.html.haml2
-rw-r--r--app/views/user_mailer/reconfirmation_instructions.html.haml2
-rw-r--r--app/views/user_mailer/reset_password_instructions.html.haml2
-rw-r--r--app/views/user_mailer/warning.html.haml2
-rw-r--r--app/views/user_mailer/welcome.html.haml2
-rw-r--r--app/workers/distribution_worker.rb8
168 files changed, 2374 insertions, 644 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 157ea8569..fcdebb47f 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -11,6 +11,8 @@ class AccountsController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+        mark_cacheable! unless user_signed_in?
+
         @body_classes      = 'with-modals'
         @pinned_statuses   = []
         @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
@@ -31,17 +33,21 @@ class AccountsController < ApplicationController
       end
 
       format.atom do
+        mark_cacheable!
+
         @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
         render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? || entry.status.local_only? }))
       end
 
       format.rss do
+        mark_cacheable!
+
         @statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status)
         render xml: RSS::AccountSerializer.render(@account, @statuses)
       end
 
       format.json do
-        skip_session!
+        mark_cacheable!
 
         render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
           ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index 8f5e1887e..1501b914e 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -2,11 +2,14 @@
 
 class ActivityPub::InboxesController < Api::BaseController
   include SignatureVerification
+  include JsonLdHelper
 
   before_action :set_account
 
   def create
-    if signed_request_account
+    if unknown_deleted_account?
+      head 202
+    elsif signed_request_account
       upgrade_account
       process_payload
       head 202
@@ -17,12 +20,19 @@ class ActivityPub::InboxesController < Api::BaseController
 
   private
 
+  def unknown_deleted_account?
+    json = Oj.load(body, mode: :strict)
+    json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
+  rescue Oj::ParseError
+    false
+  end
+
   def set_account
     @account = Account.find_local!(params[:account_username]) if params[:account_username]
   end
 
   def body
-    @body ||= request.body.read
+    @body ||= request.body.read.force_encoding('UTF-8')
   end
 
   def upgrade_account
@@ -36,6 +46,6 @@ class ActivityPub::InboxesController < Api::BaseController
   end
 
   def process_payload
-    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'), @account&.id)
+    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
   end
 end
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index e160c603a..e7795e95c 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -53,7 +53,7 @@ module Admin
 
     def reject
       authorize @account.user, :reject?
-      SuspendAccountService.new.call(@account, including_user: true, destroy: true)
+      SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
       redirect_to admin_accounts_path(pending: '1')
     end
 
diff --git a/app/controllers/api/proofs_controller.rb b/app/controllers/api/proofs_controller.rb
new file mode 100644
index 000000000..a84ad2014
--- /dev/null
+++ b/app/controllers/api/proofs_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Api::ProofsController < Api::BaseController
+  before_action :set_account
+  before_action :set_provider
+  before_action :check_account_approval
+  before_action :check_account_suspension
+
+  def index
+    render json: @account, serializer: @provider.serializer_class
+  end
+
+  private
+
+  def set_provider
+    @provider = ProofProvider.find(params[:provider]) || raise(ActiveRecord::RecordNotFound)
+  end
+
+  def set_account
+    @account = Account.find_local!(params[:username])
+  end
+
+  def check_account_approval
+    not_found if @account.user_pending?
+  end
+
+  def check_account_suspension
+    gone if @account.suspended?
+  end
+end
diff --git a/app/controllers/api/v1/preferences_controller.rb b/app/controllers/api/v1/preferences_controller.rb
new file mode 100644
index 000000000..077d39f5d
--- /dev/null
+++ b/app/controllers/api/v1/preferences_controller.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class Api::V1::PreferencesController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
+  before_action :require_user!
+
+  respond_to :json
+
+  def index
+    render json: current_account, serializer: REST::PreferencesSerializer
+  end
+end
diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb
index 04847a6b7..ed4f55100 100644
--- a/app/controllers/api/v1/statuses/reblogs_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogs_controller.rb
@@ -9,7 +9,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
   respond_to :json
 
   def create
-    @status = ReblogService.new.call(current_user.account, status_for_reblog)
+    @status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params)
     render json: @status, serializer: REST::StatusSerializer
   end
 
@@ -32,4 +32,8 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
   def status_for_destroy
     current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
   end
+
+  def reblog_params
+    params.permit(:visibility)
+  end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 0209805d0..5401b9d59 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -227,6 +227,11 @@ class ApplicationController < ActionController::Base
     response.headers['Vary'] = 'Accept'
   end
 
+  def mark_cacheable!
+    skip_session!
+    expires_in 0, public: true
+  end
+
   def skip_session!
     request.session_options[:skip] = true
   end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index f985f0eff..213c209ab 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -7,6 +7,7 @@ class FollowerAccountsController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+        mark_cacheable! unless user_signed_in?
 
         next if @account.user_hides_network?
 
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
new file mode 100644
index 000000000..a0b9c77df
--- /dev/null
+++ b/app/controllers/relationships_controller.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+class RelationshipsController < ApplicationController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_accounts, only: :show
+  before_action :set_pack
+  before_action :set_body_classes
+
+  helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
+
+  def show
+    @form = Form::AccountBatch.new
+  end
+
+  def update
+    @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
+    @form.save
+  rescue ActionController::ParameterMissing
+    # Do nothing
+  ensure
+    redirect_to relationships_path(current_params)
+  end
+
+  private
+
+  def set_accounts
+    @accounts = relationships_scope.page(params[:page]).per(40)
+  end
+
+  def relationships_scope
+    scope = begin
+      if following_relationship?
+        current_account.following.joins(:account_stat)
+      else
+        current_account.followers.joins(:account_stat)
+      end
+    end
+
+    scope.merge!(Follow.recent)
+    scope.merge!(mutual_relationship_scope) if mutual_relationship?
+    scope.merge!(moved_account_scope)       if params[:status] == 'moved'
+    scope.merge!(primary_account_scope)     if params[:status] == 'primary'
+    scope.merge!(by_domain_scope)           if params[:by_domain].present?
+    scope.merge!(dormant_account_scope)     if params[:activity] == 'dormant'
+
+    scope
+  end
+
+  def mutual_relationship_scope
+    Account.where(id: current_account.following)
+  end
+
+  def moved_account_scope
+    Account.where.not(moved_to_account_id: nil)
+  end
+
+  def primary_account_scope
+    Account.where(moved_to_account_id: nil)
+  end
+
+  def dormant_account_scope
+    AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
+  end
+
+  def by_domain_scope
+    Account.where(domain: params[:by_domain])
+  end
+
+  def form_account_batch_params
+    params.require(:form_account_batch).permit(:action, account_ids: [])
+  end
+
+  def following_relationship?
+    params[:relationship].blank? || params[:relationship] == 'following'
+  end
+
+  def mutual_relationship?
+    params[:relationship] == 'mutual'
+  end
+
+  def followed_by_relationship?
+    params[:relationship] == 'followed_by'
+  end
+
+  def current_params
+    params.slice(:page, :status, :relationship, :by_domain, :activity).permit(:page, :status, :relationship, :by_domain, :activity)
+  end
+
+  def action_from_button
+    if params[:unfollow]
+      'unfollow'
+    elsif params[:remove_from_followers]
+      'remove_from_followers'
+    elsif params[:block_domains]
+      'block_domains'
+    end
+  end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
+
+  def set_pack
+    use_pack 'admin'
+  end
+end
diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb
deleted file mode 100644
index 8aae379aa..000000000
--- a/app/controllers/settings/follower_domains_controller.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-class Settings::FollowerDomainsController < Settings::BaseController
-  def show
-    @account = current_account
-    @domains = current_account.followers.reorder(Arel.sql('MIN(follows.id) DESC')).group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10)
-  end
-
-  def update
-    domains = bulk_params[:select] || []
-
-    AfterAccountDomainBlockWorker.push_bulk(domains) do |domain|
-      [current_account.id, domain]
-    end
-
-    redirect_to settings_follower_domains_path, notice: I18n.t('followers.success', count: domains.size)
-  end
-
-  private
-
-  def bulk_params
-    params.permit(select: [])
-  end
-end
diff --git a/app/controllers/settings/identity_proofs_controller.rb b/app/controllers/settings/identity_proofs_controller.rb
new file mode 100644
index 000000000..4a3b89a5e
--- /dev/null
+++ b/app/controllers/settings/identity_proofs_controller.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+class Settings::IdentityProofsController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :check_required_params, only: :new
+
+  def index
+    @proofs = AccountIdentityProof.where(account: current_account).order(provider: :asc, provider_username: :asc)
+    @proofs.each(&:refresh!)
+  end
+
+  def new
+    @proof = current_account.identity_proofs.new(
+      token: params[:token],
+      provider: params[:provider],
+      provider_username: params[:provider_username]
+    )
+
+    render layout: 'auth'
+  end
+
+  def create
+    @proof = current_account.identity_proofs.where(provider: resource_params[:provider], provider_username: resource_params[:provider_username]).first_or_initialize(resource_params)
+    @proof.token = resource_params[:token]
+
+    if @proof.save
+      redirect_to @proof.on_success_path(params[:user_agent])
+    else
+      flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
+      redirect_to settings_identity_proofs_path
+    end
+  end
+
+  private
+
+  def check_required_params
+    redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :token].all? { |k| params[k].present? }
+  end
+
+  def resource_params
+    params.require(:account_identity_proof).permit(:provider, :provider_username, :token)
+  end
+end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 6f56a67ba..53cf1c4ca 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -28,6 +28,8 @@ class StatusesController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+        mark_cacheable! unless user_signed_in?
+
         @body_classes = 'with-modals'
 
         set_ancestors
@@ -37,7 +39,7 @@ class StatusesController < ApplicationController
       end
 
       format.json do
-        skip_session! unless @stream_entry.hidden?
+        mark_cacheable! unless @stream_entry.hidden?
 
         render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
           ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
diff --git a/app/controllers/well_known/keybase_proof_config_controller.rb b/app/controllers/well_known/keybase_proof_config_controller.rb
new file mode 100644
index 000000000..eb41e586f
--- /dev/null
+++ b/app/controllers/well_known/keybase_proof_config_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module WellKnown
+  class KeybaseProofConfigController < ActionController::Base
+    def show
+      render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer
+    end
+  end
+end
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 359d60b60..e5fbb1500 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -9,42 +9,6 @@ module Admin::ActionLogsHelper
     end
   end
 
-  def linkable_log_target(record)
-    case record.class.name
-    when 'Account'
-      link_to record.acct, admin_account_path(record.id)
-    when 'User'
-      link_to record.account.acct, admin_account_path(record.account_id)
-    when 'CustomEmoji'
-      record.shortcode
-    when 'Report'
-      link_to "##{record.id}", admin_report_path(record)
-    when 'DomainBlock', 'EmailDomainBlock'
-      link_to record.domain, "https://#{record.domain}"
-    when 'Status'
-      link_to record.account.acct, TagManager.instance.url_for(record)
-    when 'AccountWarning'
-      link_to record.target_account.acct, admin_account_path(record.target_account_id)
-    end
-  end
-
-  def log_target_from_history(type, attributes)
-    case type
-    when 'CustomEmoji'
-      attributes['shortcode']
-    when 'DomainBlock', 'EmailDomainBlock'
-      link_to attributes['domain'], "https://#{attributes['domain']}"
-    when 'Status'
-      tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
-
-      if tmp_status.account
-        link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
-      else
-        I18n.t('admin.action_logs.deleted_status')
-      end
-    end
-  end
-
   def relevant_log_changes(log)
     if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)
       log.recorded_changes.slice('domain')
@@ -111,4 +75,40 @@ module Admin::ActionLogsHelper
   def opposite_verbs?(log)
     %w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type)
   end
+
+  def linkable_log_target(record)
+    case record.class.name
+    when 'Account'
+      link_to record.acct, admin_account_path(record.id)
+    when 'User'
+      link_to record.account.acct, admin_account_path(record.account_id)
+    when 'CustomEmoji'
+      record.shortcode
+    when 'Report'
+      link_to "##{record.id}", admin_report_path(record)
+    when 'DomainBlock', 'EmailDomainBlock'
+      link_to record.domain, "https://#{record.domain}"
+    when 'Status'
+      link_to record.account.acct, TagManager.instance.url_for(record)
+    when 'AccountWarning'
+      link_to record.target_account.acct, admin_account_path(record.target_account_id)
+    end
+  end
+
+  def log_target_from_history(type, attributes)
+    case type
+    when 'CustomEmoji'
+      attributes['shortcode']
+    when 'DomainBlock', 'EmailDomainBlock'
+      link_to attributes['domain'], "https://#{attributes['domain']}"
+    when 'Status'
+      tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
+
+      if tmp_status.account
+        link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
+      else
+        I18n.t('admin.action_logs.deleted_status')
+      end
+    end
+  end
 end
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 8f78bf5f8..4fd36ef42 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -7,8 +7,9 @@ module Admin::FilterHelper
   CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
   TAGS_FILTERS         = %i(hidden).freeze
   INSTANCES_FILTERS    = %i(limited by_domain).freeze
+  FOLLOWERS_FILTERS    = %i(relationship status by_domain activity).freeze
 
-  FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS
+  FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS
 
   def filter_link_to(text, link_to_params, link_class_params = link_to_params)
     new_url = filtered_url_for(link_to_params)
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 241addb83..92bc222ea 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -6,6 +6,7 @@ module SettingsHelper
     ar: 'العربية',
     ast: 'Asturianu',
     bg: 'Български',
+    bn: 'বাংলা',
     ca: 'Català',
     co: 'Corsu',
     cs: 'Čeština',
@@ -19,8 +20,10 @@ module SettingsHelper
     fa: 'فارسی',
     fi: 'Suomi',
     fr: 'Français',
+    ga: 'Gaeilge',
     gl: 'Galego',
     he: 'עברית',
+    hi: 'हिन्दी',
     hr: 'Hrvatski',
     hu: 'Magyar',
     hy: 'Հայերեն',
diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js
index ccd84364e..a8c3fe16a 100644
--- a/app/javascript/flavours/glitch/actions/importer/normalizer.js
+++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js
@@ -69,9 +69,11 @@ export function normalizeStatus(status, normalOldStatus) {
 export function normalizePoll(poll) {
   const normalPoll = { ...poll };
 
+  const emojiMap = makeEmojiMap(normalPoll);
+
   normalPoll.options = poll.options.map(option => ({
     ...option,
-    title_emojified: emojify(escapeTextContentForBrowser(option.title)),
+    title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
   }));
 
   return normalPoll;
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index dd4f5fd44..57fecf63d 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -7,6 +7,7 @@ import {
   importFetchedStatus,
   importFetchedStatuses,
 } from './importer';
+import { saveSettings } from './settings';
 import { defineMessages } from 'react-intl';
 import { List as ImmutableList } from 'immutable';
 import { unescapeHTML } from 'flavours/glitch/util/html';
@@ -286,5 +287,6 @@ export function setFilter (filterType) {
       value: filterType,
     });
     dispatch(expandNotifications());
+    dispatch(saveSettings());
   };
 };
diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js
index a1b297ce7..56331cb29 100644
--- a/app/javascript/flavours/glitch/components/poll.js
+++ b/app/javascript/flavours/glitch/components/poll.js
@@ -44,6 +44,11 @@ const timeRemainingString = (intl, date, now) => {
   return relativeTime;
 };
 
+const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
+  obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
+  return obj;
+}, {});
+
 export default @injectIntl
 class Poll extends ImmutablePureComponent {
 
@@ -99,6 +104,12 @@ class Poll extends ImmutablePureComponent {
     const active             = !!this.state.selected[`${optionIndex}`];
     const showResults        = poll.get('voted') || poll.get('expired');
 
+    let titleEmojified = option.get('title_emojified');
+    if (!titleEmojified) {
+      const emojiMap = makeEmojiMap(poll);
+      titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
+    }
+
     return (
       <li key={option.get('title')}>
         {showResults && (
@@ -122,7 +133,7 @@ class Poll extends ImmutablePureComponent {
           {!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
           {showResults && <span className='poll__number'>{Math.round(percent)}%</span>}
 
-          <span dangerouslySetInnerHTML={{ __html: option.get('title_emojified', emojify(escapeTextContentForBrowser(option.get('title')))) }} />
+          <span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
         </label>
       </li>
     );
diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js b/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
index 7ee28e304..1915b62d5 100644
--- a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
+++ b/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
@@ -51,7 +51,7 @@ class Option extends React.PureComponent {
           <input
             type='text'
             placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
-            maxlength={pollLimits.max_option_chars}
+            maxLength={pollLimits.max_option_chars}
             value={title}
             onChange={this.handleOptionTitleChange}
           />
diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js
index a78117971..d963039dc 100644
--- a/app/javascript/flavours/glitch/features/emoji_picker/index.js
+++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js
@@ -129,6 +129,7 @@ class ModifierPickerMenu extends React.PureComponent {
     active: PropTypes.bool,
     onSelect: PropTypes.func.isRequired,
     onClose: PropTypes.func.isRequired,
+    modifier: PropTypes.number,
   };
 
   handleClick = e => {
@@ -165,20 +166,36 @@ class ModifierPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
+    if (this.node) {
+      this.node.querySelector('li:first-child button').focus(); // focus the first element when opened
+    }
   }
 
   render () {
-    const { active } = this.props;
+    const { active, modifier } = this.props;
 
     return (
-      <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
-        <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
-      </div>
+      <ul
+        className='emoji-picker-dropdown__modifiers__menu'
+        style={{ display: active ? 'block' : 'none' }}
+        role='menuitem'
+        ref={this.setRef}
+      >
+        {[1, 2, 3, 4, 5, 6].map(i => (
+          <li
+            onClick={this.handleClick}
+            role='menuitemradio'
+            aria-checked={i === (modifier || 1)}
+            data-index={i}
+            key={i}
+          >
+            <Emoji
+              emoji='fist' set='twitter' size={22} sheetSize={32} skin={i}
+              backgroundImageFn={backgroundImageFn}
+            />
+          </li>
+        ))}
+      </ul>
     );
   }
 
@@ -210,10 +227,22 @@ class ModifierPicker extends React.PureComponent {
   render () {
     const { active, modifier } = this.props;
 
+    function setRef(ref) {
+      if (!ref) {
+        return;
+      }
+      // TODO: It would be nice if we could pass props directly to emoji-mart's buttons.
+      const button = ref.querySelector('button');
+      button.setAttribute('aria-haspopup', 'true');
+      button.setAttribute('aria-expanded', active);
+    }
+
     return (
       <div className='emoji-picker-dropdown__modifiers'>
-        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
-        <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
+        <div ref={setRef}>
+          <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
+        </div>
+        <ModifierPickerMenu active={active} modifier={modifier} onSelect={this.handleSelect} onClose={this.props.onClose} />
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js
index 17f064713..4fbd504ef 100644
--- a/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines';
-import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
 import Masonry from 'react-masonry-infinite';
 import { List as ImmutableList } from 'immutable';
 import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container';
@@ -31,14 +30,6 @@ class HashtagTimeline extends React.PureComponent {
     const { dispatch, hashtag } = this.props;
 
     dispatch(expandHashtagTimeline(hashtag));
-    this.disconnect = dispatch(connectHashtagStream(hashtag, hashtag));
-  }
-
-  componentWillUnmount () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
   }
 
   handleLoadMore = () => {
diff --git a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js
index 5e2b3fc6d..5f8a369ff 100644
--- a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
-import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming';
 import Masonry from 'react-masonry-infinite';
 import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
 import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container';
@@ -42,24 +41,12 @@ class PublicTimeline extends React.PureComponent {
     }
   }
 
-  componentWillUnmount () {
-    this._disconnect();
-  }
-
   _connect () {
     const { dispatch, local } = this.props;
 
     dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
-    this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream());
   }
  
-  _disconnect () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
-  }
-
   handleLoadMore = () => {
     const { dispatch, statusIds, local } = this.props;
     const maxId = statusIds.last();
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index 7a457600e..d4ead07a1 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -660,7 +660,7 @@ $small-breakpoint: 960px;
     display: flex;
     justify-content: center;
     align-items: center;
-    padding: 100px;
+    padding: 50px;
 
     img {
       height: 52px;
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index e14775e44..f0729bedc 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -147,6 +147,11 @@
       font-size: 14px;
       font-family: inherit;
       resize: none;
+      scrollbar-color: initial;
+
+      &::-webkit-scrollbar {
+        all: unset;
+      }
 
       &:disabled { background: $ui-secondary-color }
       &:focus { outline: 0 }
diff --git a/app/javascript/flavours/glitch/styles/components/emoji.scss b/app/javascript/flavours/glitch/styles/components/emoji.scss
index dd386d698..ccfd42f28 100644
--- a/app/javascript/flavours/glitch/styles/components/emoji.scss
+++ b/app/javascript/flavours/glitch/styles/components/emoji.scss
@@ -44,11 +44,11 @@
   box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
   overflow: hidden;
 
-  button {
+  li {
     display: block;
     cursor: pointer;
     border: 0;
-    padding: 4px 8px;
+    padding: 3px 8px;
     background: transparent;
 
     &:hover,
diff --git a/app/javascript/flavours/glitch/styles/components/emoji_picker.scss b/app/javascript/flavours/glitch/styles/components/emoji_picker.scss
index dcc551c5b..171623352 100644
--- a/app/javascript/flavours/glitch/styles/components/emoji_picker.scss
+++ b/app/javascript/flavours/glitch/styles/components/emoji_picker.scss
@@ -1,3 +1,5 @@
+@import '~emoji-mart/css/emoji-mart.css';
+
 .emoji-mart {
   &,
   * {
@@ -51,6 +53,14 @@
 
   &:hover {
     color: darken($lighter-text-color, 4%);
+
+    svg {
+      fill: darken($lighter-text-color, 4%);
+    }
+  }
+
+  svg {
+    fill: $lighter-text-color;
   }
 }
 
@@ -59,11 +69,19 @@
 
   &:hover {
     color: darken($highlight-text-color, 4%);
+
+    svg {
+      fill: darken($highlight-text-color, 4%);
+    }
   }
 
   .emoji-mart-anchor-bar {
     bottom: 0;
   }
+
+  svg {
+    fill: $highlight-text-color;
+  }
 }
 
 .emoji-mart-anchor-bar {
@@ -83,7 +101,6 @@
   }
 
   svg {
-    fill: currentColor;
     max-height: 18px;
   }
 }
@@ -103,15 +120,14 @@
 }
 
 .emoji-mart-search {
-  padding: 10px;
-  padding-right: 45px;
+  margin: 10px 40px 10px 5px;
   background: $simple-background-color;
 
   input {
     font-size: 14px;
     font-weight: 400;
     padding: 7px 9px;
-    font-family: inherit;
+    font-family: $font-sans-serif;
     display: block;
     width: 100%;
     background: rgba($ui-secondary-color, 0.3);
@@ -166,6 +182,7 @@
     font-weight: 500;
     padding: 5px 6px;
     background: $simple-background-color;
+    font-family: $font-sans-serif;
   }
 }
 
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index 6051c1d00..9ef45e425 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -801,3 +801,58 @@ code {
     }
   }
 }
+
+.connection-prompt {
+  margin-bottom: 25px;
+
+  .fa-link {
+    background-color: darken($ui-base-color, 4%);
+    border-radius: 100%;
+    font-size: 24px;
+    padding: 10px;
+  }
+
+  &__column {
+    align-items: center;
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+    flex-shrink: 1;
+
+    &-sep {
+      flex-grow: 0;
+      overflow: visible;
+      position: relative;
+      z-index: 1;
+    }
+  }
+
+  .account__avatar {
+    margin-bottom: 20px;
+  }
+
+  &__connection {
+    background-color: lighten($ui-base-color, 8%);
+    box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
+    border-radius: 4px;
+    padding: 25px 10px;
+    position: relative;
+    text-align: center;
+
+    &::after {
+      background-color: darken($ui-base-color, 4%);
+      content: '';
+      display: block;
+      height: 100%;
+      left: 50%;
+      position: absolute;
+      width: 1px;
+    }
+  }
+
+  &__row {
+    align-items: center;
+    display: flex;
+    flex-direction: row;
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss
index 296182ff5..154844665 100644
--- a/app/javascript/flavours/glitch/styles/tables.scss
+++ b/app/javascript/flavours/glitch/styles/tables.scss
@@ -140,6 +140,19 @@ a.table-action-link {
       input {
         margin-top: 8px;
       }
+
+      &--aligned {
+        display: flex;
+        align-items: center;
+
+        input {
+          margin-top: 0;
+        }
+      }
+
+      @media screen and (max-width: $no-gap-breakpoint) {
+        display: none;
+      }
     }
 
     &__actions,
@@ -161,6 +174,10 @@ a.table-action-link {
       text-align: right;
       padding-right: 16px - 5px;
     }
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      display: none;
+    }
   }
 
   &__row {
@@ -168,6 +185,12 @@ a.table-action-link {
     border-top: 0;
     background: darken($ui-base-color, 4%);
 
+    @media screen and (max-width: $no-gap-breakpoint) {
+      &:first-child {
+        border-top: 1px solid darken($ui-base-color, 8%);
+      }
+    }
+
     &:hover {
       background: darken($ui-base-color, 2%);
     }
@@ -183,6 +206,10 @@ a.table-action-link {
     &__content {
       padding-top: 12px;
       padding-bottom: 16px;
+
+      &--unpadded {
+        padding: 0;
+      }
     }
   }
 
@@ -193,4 +220,20 @@ a.table-action-link {
       font-weight: 700;
     }
   }
+
+  .nothing-here {
+    border: 1px solid darken($ui-base-color, 8%);
+    border-top: 0;
+    box-shadow: none;
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      border-top: 1px solid darken($ui-base-color, 8%);
+    }
+  }
+
+  @media screen and (max-width: 870px) {
+    .accounts-table tbody td.optional {
+      display: none;
+    }
+  }
 }
diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml
index d8f313381..587cc0f1e 100644
--- a/app/javascript/flavours/glitch/theme.yml
+++ b/app/javascript/flavours/glitch/theme.yml
@@ -28,10 +28,8 @@ pack:
 locales: locales
 
 #  (OPTIONAL) A file to use as the preview screenshot for the flavour,
-#  or an array thereof. These filenames must be unique across all
-#  images (regardless of path), so it's a good idea to namespace them
-#  to your theme. It's up to you to let webpack know to compile them.
-screenshot: glitch-preview.jpg
+#  or an array thereof. These are the full path from `app/javascript/`.
+screenshot: flavours/glitch/images/glitch-preview.jpg
 
 #  (OPTIONAL) The directory which contains the pack files.
 #  Defaults to the theme directory (`app/javascript/themes/[theme]`),
diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_picker.js b/app/javascript/flavours/glitch/util/emoji/emoji_picker.js
index 044d38cb2..73fcaa8c8 100644
--- a/app/javascript/flavours/glitch/util/emoji/emoji_picker.js
+++ b/app/javascript/flavours/glitch/util/emoji/emoji_picker.js
@@ -1,5 +1,5 @@
-import Picker from 'emoji-mart/dist-es/components/picker/picker';
-import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
+import Picker from 'emoji-mart/dist-modern/components/picker/picker';
+import Emoji from 'emoji-mart/dist-modern/components/emoji/emoji';
 
 export {
   Picker,
diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml
index a215b2625..42e26daea 100644
--- a/app/javascript/flavours/vanilla/theme.yml
+++ b/app/javascript/flavours/vanilla/theme.yml
@@ -26,10 +26,8 @@ pack:
 locales: ../../mastodon/locales
 
 #  (OPTIONAL) A file to use as the preview screenshot for the flavour,
-#  or an array thereof. These filenames must be unique across all
-#  images (regardless of path), so it's a good idea to namespace them
-#  to your theme. It's up to you to let webpack know to compile them.
-screenshot: screenshot.jpg
+#  or an array thereof. These are the full path from `app/javascript/`.
+screenshot: images/screenshot.jpg
 
 #  (OPTIONAL) The directory which contains the pack files.
 #  Defaults to this directory (`app/javascript/flavour/[flavour]`),
diff --git a/app/javascript/images/logo_transparent_black.svg b/app/javascript/images/logo_transparent_black.svg
new file mode 100644
index 000000000..e44bcf5e1
--- /dev/null
+++ b/app/javascript/images/logo_transparent_black.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" fill="#000"/></svg>
diff --git a/app/javascript/images/proof_providers/keybase.png b/app/javascript/images/proof_providers/keybase.png
new file mode 100644
index 000000000..7e3ac657f
--- /dev/null
+++ b/app/javascript/images/proof_providers/keybase.png
Binary files differdiff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index ea80c0efb..5badb0c49 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -71,9 +71,11 @@ export function normalizeStatus(status, normalOldStatus) {
 export function normalizePoll(poll) {
   const normalPoll = { ...poll };
 
+  const emojiMap = makeEmojiMap(normalPoll);
+
   normalPoll.options = poll.options.map(option => ({
     ...option,
-    title_emojified: emojify(escapeTextContentForBrowser(option.title)),
+    title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
   }));
 
   return normalPoll;
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 61fef19e9..b0861fc6b 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -7,6 +7,7 @@ import {
   importFetchedStatus,
   importFetchedStatuses,
 } from './importer';
+import { saveSettings } from './settings';
 import { defineMessages } from 'react-intl';
 import { List as ImmutableList } from 'immutable';
 import { unescapeHTML } from '../utils/html';
@@ -187,5 +188,6 @@ export function setFilter (filterType) {
       value: filterType,
     });
     dispatch(expandNotifications());
+    dispatch(saveSettings());
   };
 };
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
new file mode 100644
index 000000000..d1ca5bf75
--- /dev/null
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import illustration from '../../images/elephant_ui_disappointed.svg';
+
+export default class ErrorBoundary extends React.PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+  };
+
+  state = {
+    hasError: false,
+    stackTrace: undefined,
+    componentStack: undefined,
+  }
+
+  componentDidCatch(error, info) {
+    this.setState({
+      hasError: true,
+      stackTrace: error.stack,
+      componentStack: info && info.componentStack,
+    });
+  }
+
+  render() {
+    const { hasError } = this.state;
+
+    if (!hasError) {
+      return this.props.children;
+    }
+
+    return (
+      <div>
+        <img src={illustration} alt='' />
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js
index a1b297ce7..56331cb29 100644
--- a/app/javascript/mastodon/components/poll.js
+++ b/app/javascript/mastodon/components/poll.js
@@ -44,6 +44,11 @@ const timeRemainingString = (intl, date, now) => {
   return relativeTime;
 };
 
+const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
+  obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
+  return obj;
+}, {});
+
 export default @injectIntl
 class Poll extends ImmutablePureComponent {
 
@@ -99,6 +104,12 @@ class Poll extends ImmutablePureComponent {
     const active             = !!this.state.selected[`${optionIndex}`];
     const showResults        = poll.get('voted') || poll.get('expired');
 
+    let titleEmojified = option.get('title_emojified');
+    if (!titleEmojified) {
+      const emojiMap = makeEmojiMap(poll);
+      titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
+    }
+
     return (
       <li key={option.get('title')}>
         {showResults && (
@@ -122,7 +133,7 @@ class Poll extends ImmutablePureComponent {
           {!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
           {showResults && <span className='poll__number'>{Math.round(percent)}%</span>}
 
-          <span dangerouslySetInnerHTML={{ __html: option.get('title_emojified', emojify(escapeTextContentForBrowser(option.get('title')))) }} />
+          <span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
         </label>
       </li>
     );
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 2912540a0..542b68282 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -13,6 +13,7 @@ import { connectUserStream } from '../actions/streaming';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import initialState from '../initial_state';
+import ErrorBoundary from '../components/error_boundary';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
@@ -75,7 +76,9 @@ export default class Mastodon extends React.PureComponent {
     return (
       <IntlProvider locale={locale} messages={messages}>
         <Provider store={store}>
-          <MastodonMount />
+          <ErrorBoundary>
+            <MastodonMount />
+          </ErrorBoundary>
         </Provider>
       </IntlProvider>
     );
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 c1429c756..038d93483 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -50,6 +50,7 @@ class ModifierPickerMenu extends React.PureComponent {
     active: PropTypes.bool,
     onSelect: PropTypes.func.isRequired,
     onClose: PropTypes.func.isRequired,
+    modifier: PropTypes.number,
   };
 
   handleClick = e => {
@@ -86,20 +87,36 @@ class ModifierPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
+    if (this.node) {
+      this.node.querySelector('li:first-child button').focus(); // focus the first element when opened
+    }
   }
 
   render () {
-    const { active } = this.props;
+    const { active, modifier } = this.props;
 
     return (
-      <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
-        <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
-      </div>
+      <ul
+        className='emoji-picker-dropdown__modifiers__menu'
+        style={{ display: active ? 'block' : 'none' }}
+        role='menuitem'
+        ref={this.setRef}
+      >
+        {[1, 2, 3, 4, 5, 6].map(i => (
+          <li
+            onClick={this.handleClick}
+            role='menuitemradio'
+            aria-checked={i === (modifier || 1)}
+            data-index={i}
+            key={i}
+          >
+            <Emoji
+              emoji='fist' set='twitter' size={22} sheetSize={32} skin={i}
+              backgroundImageFn={backgroundImageFn}
+            />
+          </li>
+        ))}
+      </ul>
     );
   }
 
@@ -131,10 +148,22 @@ class ModifierPicker extends React.PureComponent {
   render () {
     const { active, modifier } = this.props;
 
+    function setRef(ref) {
+      if (!ref) {
+        return;
+      }
+      // TODO: It would be nice if we could pass props directly to emoji-mart's buttons.
+      const button = ref.querySelector('button');
+      button.setAttribute('aria-haspopup', 'true');
+      button.setAttribute('aria-expanded', active);
+    }
+
     return (
       <div className='emoji-picker-dropdown__modifiers'>
-        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
-        <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
+        <div ref={setRef}>
+          <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
+        </div>
+        <ModifierPickerMenu active={active} modifier={modifier} onSelect={this.handleSelect} onClose={this.props.onClose} />
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js
index ff0062425..4fb95f3c9 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.js
+++ b/app/javascript/mastodon/features/compose/components/poll_form.js
@@ -48,7 +48,7 @@ class Option extends React.PureComponent {
           <input
             type='text'
             placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
-            maxlength={25}
+            maxLength={25}
             value={title}
             onChange={this.handleOptionTitleChange}
           />
diff --git a/app/javascript/mastodon/features/emoji/emoji_picker.js b/app/javascript/mastodon/features/emoji/emoji_picker.js
index 044d38cb2..73fcaa8c8 100644
--- a/app/javascript/mastodon/features/emoji/emoji_picker.js
+++ b/app/javascript/mastodon/features/emoji/emoji_picker.js
@@ -1,5 +1,5 @@
-import Picker from 'emoji-mart/dist-es/components/picker/picker';
-import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
+import Picker from 'emoji-mart/dist-modern/components/picker/picker';
+import Emoji from 'emoji-mart/dist-modern/components/emoji/emoji';
 
 export {
   Picker,
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index 4bdf09166..41e9324e6 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -33,7 +33,7 @@ class Notification extends ImmutablePureComponent {
     onFavourite: PropTypes.func.isRequired,
     onReblog: PropTypes.func.isRequired,
     onToggleHidden: PropTypes.func.isRequired,
-    status: PropTypes.option,
+    status: ImmutablePropTypes.map,
     intl: PropTypes.object.isRequired,
     getScrollPosition: PropTypes.func,
     updateScrollBottom: PropTypes.func,
diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
index 0880d98c8..73919c39d 100644
--- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { expandHashtagTimeline } from 'mastodon/actions/timelines';
-import { connectHashtagStream } from 'mastodon/actions/streaming';
 import Masonry from 'react-masonry-infinite';
 import { List as ImmutableList } from 'immutable';
 import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
@@ -31,14 +30,6 @@ class HashtagTimeline extends React.PureComponent {
     const { dispatch, hashtag } = this.props;
 
     dispatch(expandHashtagTimeline(hashtag));
-    this.disconnect = dispatch(connectHashtagStream(hashtag, hashtag));
-  }
-
-  componentWillUnmount () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
   }
 
   handleLoadMore = () => {
diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js
index 10129e606..19b0b14be 100644
--- a/app/javascript/mastodon/features/standalone/public_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
-import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
 import Masonry from 'react-masonry-infinite';
 import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
 import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
@@ -37,27 +36,14 @@ class PublicTimeline extends React.PureComponent {
 
   componentDidUpdate (prevProps) {
     if (prevProps.local !== this.props.local) {
-      this._disconnect();
       this._connect();
     }
   }
 
-  componentWillUnmount () {
-    this._disconnect();
-  }
-
   _connect () {
     const { dispatch, local } = this.props;
 
     dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
-    this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream());
-  }
-
-  _disconnect () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
   }
 
   handleLoadMore = () => {
diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js
index e7d935251..a60ace35b 100644
--- a/app/javascript/mastodon/features/ui/components/bundle.js
+++ b/app/javascript/mastodon/features/ui/components/bundle.js
@@ -53,6 +53,11 @@ class Bundle extends React.PureComponent {
     const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
     const cachedMod = Bundle.cache.get(fetchComponent);
 
+    if (fetchComponent === undefined) {
+      this.setState({ mod: null });
+      return Promise.resolve();
+    }
+
     onFetch();
 
     if (cachedMod) {
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 5cd494314..0acc7aedb 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
   "compose_form.lock_disclaimer.lock": "مقفل",
   "compose_form.placeholder": "فيمَ تفكّر؟",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "بوّق",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "لقد تم تحديد هذه الصورة كحساسة",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
   "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
   "empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع المستخدمين الآخرين المتواجدين على الخوادم الأخرى لملء خيط المحادثات",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "ترخيص",
   "follow_request.reject": "رفض",
   "getting_started.developers": "المُطوِّرون",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "أساسية",
   "home.column_settings.show_reblogs": "عرض الترقيات",
   "home.column_settings.show_replies": "عرض الردود",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "التالي",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "كافة المنشورات التي نُشِرت إلى العامة على الخوادم الأخرى للفديفرس سوف يتم عرضها على الخيط المُوحَّد.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "إضبط خصوصية المنشور",
   "privacy.direct.long": "أنشر إلى المستخدمين المشار إليهم فقط",
   "privacy.direct.short": "مباشر",
@@ -356,6 +366,7 @@
   "upload_area.title": "إسحب ثم أفلت للرفع",
   "upload_button.label": "إضافة وسائط (JPEG، PNG، GIF، WebM، MP4، MOV)",
   "upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "وصف للمعاقين بصريا",
   "upload_form.focus": "قص",
   "upload_form.undo": "حذف",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 3da1030fb..86454c2d3 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "¿En qué pienses?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Entá nun silenciesti a dengún usuariu.",
   "empty_column.notifications": "Entá nun tienes dengún avisu. Interactua con otros p'aniciar la conversación.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Refugar",
   "getting_started.developers": "Desendolcadores",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Amosar toots compartíos",
   "home.column_settings.show_replies": "Amosar rempuestes",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descripción pa discapacitaos visuales",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Desaniciar",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 080200ebc..ef9c34cac 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "Какво си мислиш?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Раздумай",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Authorize",
   "follow_request.reject": "Reject",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Добави медия",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Отмяна",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index bc572d7a2..e7333f6fe 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.",
   "compose_form.lock_disclaimer.lock": "blocat",
   "compose_form.placeholder": "En què estàs pensant?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Mèdia marcat com a sensible",
@@ -128,7 +132,8 @@
   "empty_column.lists": "Encara no tens cap llista. Quan en facis una, apareixerà aquí.",
   "empty_column.mutes": "Encara no has silenciat cap usuari.",
   "empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.",
-  "empty_column.public": "No hi ha res aquí! Escriu alguna cosa públicament o segueix manualment usuaris d'altres instàncies per omplir-ho",
+  "empty_column.public": "No hi ha res aquí! Escriu públicament alguna cosa o manualment segueix usuaris d'altres servidors per omplir-ho",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autoritzar",
   "follow_request.reject": "Rebutjar",
   "getting_started.developers": "Desenvolupadors",
@@ -142,15 +147,18 @@
   "hashtag.column_header.tag_mode.all": "i {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sense {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.select.no_options_message": "No s'ha trobat cap suggeriment",
+  "hashtag.column_settings.select.placeholder": "Introdueix etiquetes…",
   "hashtag.column_settings.tag_mode.all": "Tots aquests",
   "hashtag.column_settings.tag_mode.any": "Qualsevol d’aquests",
   "hashtag.column_settings.tag_mode.none": "Cap d’aquests",
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "hashtag.column_settings.tag_toggle": "Inclou etiquetes addicionals per a aquesta columna",
   "home.column_settings.basic": "Bàsic",
   "home.column_settings.show_reblogs": "Mostrar impulsos",
   "home.column_settings.show_replies": "Mostrar respostes",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Següent",
   "introduction.federation.federated.headline": "Federada",
   "introduction.federation.federated.text": "Les publicacions públiques d'altres servidors del fedivers apareixeran a la línia de temps federada.",
@@ -206,7 +214,7 @@
   "lists.account.remove": "Treure de la llista",
   "lists.delete": "Delete list",
   "lists.edit": "Editar llista",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Canvi de títol",
   "lists.new.create": "Afegir llista",
   "lists.new.title_placeholder": "Nova llista",
   "lists.search": "Cercar entre les persones que segueixes",
@@ -227,7 +235,7 @@
   "navigation_bar.favourites": "Favorits",
   "navigation_bar.filters": "Paraules silenciades",
   "navigation_bar.follow_requests": "Sol·licituds de seguiment",
-  "navigation_bar.info": "Informació addicional",
+  "navigation_bar.info": "Sobre aquest servidor",
   "navigation_bar.keyboard_shortcuts": "Dreceres de teclat",
   "navigation_bar.lists": "Llistes",
   "navigation_bar.logout": "Tancar sessió",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Seguiments",
   "notifications.filter.mentions": "Mencions",
   "notifications.group": "{count} notificacions",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Finalitzada",
+  "poll.refresh": "Actualitza",
+  "poll.total_votes": "{count, plural, one {# vot} other {# vots}}",
+  "poll.vote": "Vota",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ajusta l'estat de privacitat",
   "privacy.direct.long": "Publicar només per als usuaris esmentats",
   "privacy.direct.short": "Directe",
@@ -283,7 +293,7 @@
   "reply_indicator.cancel": "Cancel·lar",
   "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 de la teva instància. Pots explicar perquè vols informar d'aquest compte aquí:",
+  "report.hint": "El informe s'enviarà als moderadors del teu servidor. Pots explicar perquè vols informar d'aquest compte aquí:",
   "report.placeholder": "Comentaris addicionals",
   "report.submit": "Enviar",
   "report.target": "Informes",
@@ -304,7 +314,7 @@
   "status.block": "Block @{name}",
   "status.cancel_reblog_private": "Desfer l'impuls",
   "status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
-  "status.copy": "Copy link to status",
+  "status.copy": "Copia l'enllaç a l'estat",
   "status.delete": "Esborrar",
   "status.detailed_status": "Visualització detallada de la conversa",
   "status.direct": "Missatge directe @{name}",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Local",
   "tabs_bar.notifications": "Notificacions",
   "tabs_bar.search": "Cerca",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# dia} other {# dies}} restants",
+  "time_remaining.hours": "{number, plural, one {# hora} other {# hores}} restants",
+  "time_remaining.minutes": "{number, plural, one {# minut} other {# minuts}} restants",
+  "time_remaining.moments": "Moments restants",
+  "time_remaining.seconds": "{number, plural, one {# segon} other {# segons}} restants",
   "trends.count_by_accounts": "{count} {rawCount, plural, una {person} altres {people}} parlant",
   "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.",
   "upload_area.title": "Arrossega i deixa anar per carregar",
   "upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.limit": "S'ha superat el límit de càrrega d'arxius.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descriure els problemes visuals",
   "upload_form.focus": "Modificar la previsualització",
   "upload_form.undo": "Esborra",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 6d5d11e48..5f4d520d6 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "U vostru contu ùn hè micca {locked}. Tuttu u mondu pò seguitavi è vede i vostri statuti privati.",
   "compose_form.lock_disclaimer.lock": "privatu",
   "compose_form.placeholder": "À chè pensate?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media indicatu cum'è sensibile",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.",
   "empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
   "empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altri servori per empie a linea pubblica",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Auturizà",
   "follow_request.reject": "Righjittà",
   "getting_started.developers": "Sviluppatori",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Bàsichi",
   "home.column_settings.show_reblogs": "Vede e spartere",
   "home.column_settings.show_replies": "Vede e risposte",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Cuntinuà",
   "introduction.federation.federated.headline": "Federata",
   "introduction.federation.federated.text": "I statuti pubblichi da l'altri servori di u fediverse saranu mustrati nant'à a linea pubblica federata.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Abbunamenti",
   "notifications.filter.mentions": "Minzione",
   "notifications.group": "{count} nutificazione",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Chjosu",
+  "poll.refresh": "Attualizà",
+  "poll.total_votes": "{count, plural, one {# votu} other {# voti}}",
+  "poll.vote": "Vutà",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Mudificà a cunfidenzialità di u statutu",
   "privacy.direct.long": "Mandà solu à quelli chì so mintuvati",
   "privacy.direct.short": "Direttu",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Lucale",
   "tabs_bar.notifications": "Nutificazione",
   "tabs_bar.search": "Cercà",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# ghjornu ferma} other {# ghjorni fermanu}}",
+  "time_remaining.hours": "{number, plural, one {# ora ferma} other {# ore fermanu}}",
+  "time_remaining.minutes": "{number, plural, one {# minuta ferma} other {# minute fermanu}} left",
+  "time_remaining.moments": "Ci fermanu qualchi mumentu",
+  "time_remaining.seconds": "{number, plural, one {# siconda ferma} other {# siconde fermanu}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} parlanu",
   "ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Mastodon.",
   "upload_area.title": "Drag & drop per caricà un fugliale",
   "upload_button.label": "Aghjunghje un media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limita di caricamentu di fugliali trapassata.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Discrive per i malvistosi",
   "upload_form.focus": "Cambià a vista",
   "upload_form.undo": "Sguassà",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index a9442d803..7ccbdef00 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -4,7 +4,7 @@
   "account.block": "Zablokovat uživatele @{name}",
   "account.block_domain": "Skrýt vše z {domain}",
   "account.blocked": "Blokován/a",
-  "account.direct": "Přímá zpráva pro uživatele @{name}",
+  "account.direct": "Poslat přímou zprávu uživateli @{name}",
   "account.disclaimer_full": "Níže uvedené informace nemusejí zcela odrážet profil uživatele.",
   "account.domain_blocked": "Doména skryta",
   "account.edit_profile": "Upravit profil",
@@ -21,9 +21,9 @@
   "account.media": "Média",
   "account.mention": "Zmínit uživatele @{name}",
   "account.moved_to": "{name} se přesunul/a na:",
-  "account.mute": "Ignorovat uživatele @{name}",
+  "account.mute": "Skrýt uživatele @{name}",
   "account.mute_notifications": "Skrýt oznámení od uživatele @{name}",
-  "account.muted": "Ztišen/a",
+  "account.muted": "Skryt/a",
   "account.posts": "Tooty",
   "account.posts_with_replies": "Tooty a odpovědi",
   "account.report": "Nahlásit uživatele @{name}",
@@ -34,8 +34,8 @@
   "account.unblock_domain": "Odkrýt doménu {domain}",
   "account.unendorse": "Nepředstavit na profilu",
   "account.unfollow": "Přestat sledovat",
-  "account.unmute": "Přestat ignorovat uživatele @{name}",
-  "account.unmute_notifications": "Odtišit oznámení od uživatele @{name}",
+  "account.unmute": "Odkrýt uživatele @{name}",
+  "account.unmute_notifications": "Odkrýt oznámení od uživatele @{name}",
   "account.view_full_profile": "Zobrazit celý profil",
   "alert.unexpected.message": "Objevila se neočekávaná chyba.",
   "alert.unexpected.title": "Jejda!",
@@ -54,14 +54,14 @@
   "column.follow_requests": "Požadavky o sledování",
   "column.home": "Domů",
   "column.lists": "Seznamy",
-  "column.mutes": "Ignorovaní uživatelé",
+  "column.mutes": "Skrytí uživatelé",
   "column.notifications": "Oznámení",
   "column.pins": "Připnuté tooty",
   "column.public": "Federovaná časová osa",
   "column_back_button.label": "Zpět",
   "column_header.hide_settings": "Skrýt nastavení",
-  "column_header.moveLeft_settings": "Přesunout sloupec doleva",
-  "column_header.moveRight_settings": "Přesunout sloupec doprava",
+  "column_header.moveLeft_settings": "Posunout sloupec doleva",
+  "column_header.moveRight_settings": "Posunout sloupec doprava",
   "column_header.pin": "Připnout",
   "column_header.show_settings": "Zobrazit nastavení",
   "column_header.unpin": "Odepnout",
@@ -73,10 +73,14 @@
   "compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledující.",
   "compose_form.lock_disclaimer.lock": "uzamčen",
   "compose_form.placeholder": "Co se vám honí hlavou?",
+  "compose_form.poll.add_option": "Přidat volbu",
+  "compose_form.poll.duration": "Délka ankety",
+  "compose_form.poll.option_placeholder": "Volba {number}",
+  "compose_form.poll.remove_option": "Odstranit tuto volbu",
   "compose_form.publish": "Tootnout",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.marked": "Mediální obsah je označen jako citlivý",
-  "compose_form.sensitive.unmarked": "Mediální obsah není označen jako citlivý",
+  "compose_form.sensitive.marked": "Média jsou označena jako citlivá",
+  "compose_form.sensitive.unmarked": "Média nejsou označena jako citlivá",
   "compose_form.spoiler.marked": "Text je skrytý za varováním",
   "compose_form.spoiler.unmarked": "Text není skrytý",
   "compose_form.spoiler_placeholder": "Sem napište vaše varování",
@@ -86,13 +90,13 @@
   "confirmations.delete.confirm": "Smazat",
   "confirmations.delete.message": "Jste si jistý/á, že chcete smazat tento toot?",
   "confirmations.delete_list.confirm": "Smazat",
-  "confirmations.delete_list.message": "Jste si jistý/á, že chcete tento seznam navždy vymazat?",
+  "confirmations.delete_list.message": "Jste si jistý/á, že chcete tento seznam navždy smazat?",
   "confirmations.domain_block.confirm": "Skrýt celou doménu",
-  "confirmations.domain_block.message": "Jste si opravdu, opravdu jistý/á, že chcete blokovat celou doménu {domain}? Ve většině případů stačí zablokovat nebo ignorovat pár konkrétních uživatelů, což se doporučuje. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledující z této domény budou odstraněni.",
-  "confirmations.mute.confirm": "Ignorovat",
-  "confirmations.mute.message": "Jste si jistý/á, že chcete ignorovat uživatele {name}?",
-  "confirmations.redraft.confirm": "Vymazat a přepsat",
-  "confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento toot? Oblíbení a boosty budou ztraceny a odpovědi na původní příspěvek budou opuštěny.",
+  "confirmations.domain_block.message": "Jste si opravdu, opravdu jistý/á, že chcete blokovat celou doménu {domain}? Ve většině případů stačí zablokovat nebo skrýt pár konkrétních uživatelů, což se doporučuje. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledující z této domény budou odstraněni.",
+  "confirmations.mute.confirm": "Skrýt",
+  "confirmations.mute.message": "Jste si jistý/á, že chcete skrýt uživatele {name}?",
+  "confirmations.redraft.confirm": "Smazat a přepsat",
+  "confirmations.redraft.message": "Jste si jistý/á, že chcete smazat a přepsat tento toot? Oblíbení a boosty budou ztraceny a odpovědi na původní příspěvek budou opuštěny.",
   "confirmations.reply.confirm": "Odpovědět",
   "confirmations.reply.message": "Odpovězením nyní přepíšete zprávu, kterou aktuálně píšete. Jste si jistý/á, že chcete pokračovat?",
   "confirmations.unfollow.confirm": "Přestat sledovat",
@@ -109,7 +113,7 @@
   "emoji_button.objects": "Předměty",
   "emoji_button.people": "Lidé",
   "emoji_button.recent": "Často používaná",
-  "emoji_button.search": "Hledat...",
+  "emoji_button.search": "Hledat…",
   "emoji_button.search_results": "Výsledky hledání",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestování a místa",
@@ -126,9 +130,10 @@
   "empty_column.home.public_timeline": "veřejné časové osy",
   "empty_column.list": "V tomto seznamu ještě nic není. Pokud budou členové tohoto seznamu psát nové tooty, objeví se zde.",
   "empty_column.lists": "Ještě nemáte žádný seznam. Pokud nějaký vytvoříte, zobrazí se zde.",
-  "empty_column.mutes": "Ještě neignorujete žádné uživatele.",
+  "empty_column.mutes": "Ještě jste neskryl/a žádné uživatele.",
   "empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.",
   "empty_column.public": "Tady nic není! Napište něco veřejně, nebo začněte ručně sledovat uživatele z jiných serverů, aby tu něco přibylo",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizovat",
   "follow_request.reject": "Odmítnout",
   "getting_started.developers": "Vývojáři",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Základní",
   "home.column_settings.show_reblogs": "Zobrazit boosty",
   "home.column_settings.show_replies": "Zobrazit odpovědi",
+  "intervals.full.days": "{number, plural, one {# den} few {# dny} many {# dne} other {# dní}}",
+  "intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodiny} other {# hodin}}",
+  "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minuty} other {# minut}}",
   "introduction.federation.action": "Další",
   "introduction.federation.federated.headline": "Federovaná",
   "introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů na fediverse se zobrazí na federované časové ose.",
@@ -182,11 +190,11 @@
   "keyboard_shortcuts.federated": "k otevření federované časové osy",
   "keyboard_shortcuts.heading": "Klávesové zkratky",
   "keyboard_shortcuts.home": "k otevření domovské časové osy",
-  "keyboard_shortcuts.hotkey": "Horká klávesa",
+  "keyboard_shortcuts.hotkey": "Klávesová zkratka",
   "keyboard_shortcuts.legend": "k zobrazení této legendy",
   "keyboard_shortcuts.local": "k otevření místní časové osy",
   "keyboard_shortcuts.mention": "ke zmínění autora",
-  "keyboard_shortcuts.muted": "k otevření seznamu ignorovaných uživatelů",
+  "keyboard_shortcuts.muted": "k otevření seznamu skrytých uživatelů",
   "keyboard_shortcuts.my_profile": "k otevření vašeho profilu",
   "keyboard_shortcuts.notifications": "k otevření sloupce s oznámeními",
   "keyboard_shortcuts.pinned": "k otevření seznamu připnutých tootů",
@@ -197,7 +205,7 @@
   "keyboard_shortcuts.start": "k otevření sloupce „začínáme“",
   "keyboard_shortcuts.toggle_hidden": "k zobrazení/skrytí textu za varováním o obsahu",
   "keyboard_shortcuts.toot": "k napsání úplně nového tootu",
-  "keyboard_shortcuts.unfocus": "ke zrušení soustředění na psací prostor/hledání",
+  "keyboard_shortcuts.unfocus": "ke zrušení zaměření na psací prostor/hledání",
   "keyboard_shortcuts.up": "k posunutí nahoru v seznamu",
   "lightbox.close": "Zavřít",
   "lightbox.next": "Další",
@@ -211,7 +219,7 @@
   "lists.new.title_placeholder": "Název nového seznamu",
   "lists.search": "Hledejte mezi lidmi, které sledujete",
   "lists.subheading": "Vaše seznamy",
-  "loading_indicator.label": "Načítám...",
+  "loading_indicator.label": "Načítám…",
   "media_gallery.toggle_visible": "Přepínat viditelnost",
   "missing_indicator.label": "Nenalezeno",
   "missing_indicator.sublabel": "Tento zdroj se nepodařilo najít",
@@ -231,7 +239,7 @@
   "navigation_bar.keyboard_shortcuts": "Klávesové zkratky",
   "navigation_bar.lists": "Seznamy",
   "navigation_bar.logout": "Odhlásit",
-  "navigation_bar.mutes": "Ignorovaní uživatelé",
+  "navigation_bar.mutes": "Skrytí uživatelé",
   "navigation_bar.personal": "Osobní",
   "navigation_bar.pins": "Připnuté tooty",
   "navigation_bar.preferences": "Předvolby",
@@ -244,7 +252,7 @@
   "notifications.clear": "Vymazat oznámení",
   "notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?",
   "notifications.column_settings.alert": "Desktopová oznámení",
-  "notifications.column_settings.favourite": "Oblíbené:",
+  "notifications.column_settings.favourite": "Oblíbení:",
   "notifications.column_settings.filter_bar.advanced": "Zobrazit všechny kategorie",
   "notifications.column_settings.filter_bar.category": "Panel rychlého filtrování",
   "notifications.column_settings.filter_bar.show": "Zobrazit",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Sledování",
   "notifications.filter.mentions": "Zmínky",
   "notifications.group": "{count} oznámení",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Uzavřena",
+  "poll.refresh": "Obnovit",
+  "poll.total_votes": "{count, plural, one {# hlas} few {# hlasy} many {# hlasu} other {# hlasů}}",
+  "poll.vote": "Hlasovat",
+  "poll_button.add_poll": "Přidat anketu",
+  "poll_button.remove_poll": "Odstranit anketu",
   "privacy.change": "Změnit soukromí tootu",
   "privacy.direct.long": "Odeslat pouze zmíněným uživatelům",
   "privacy.direct.short": "Přímý",
@@ -289,10 +299,10 @@
   "report.target": "Nahlášení uživatele {target}",
   "search.placeholder": "Hledat",
   "search_popout.search_format": "Pokročilé hledání",
-  "search_popout.tips.full_text": "Jednoduchý textový výpis tootů, které jste napsal/a, oblíbil/a si, boostnul/a, nebo v nich byl/a zmíněn/a, včetně odpovídajících přezdívek, zobrazovaných jmen a hashtagů.",
+  "search_popout.tips.full_text": "Jednoduchý text navrátí tooty, které jste napsal/a, oblíbil/a si, boostnul/a, nebo v nich byl/a zmíněn/a, a také odpovídající přezdívky, zobrazovaná jména a hashtagy.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "toot",
-  "search_popout.tips.text": "Jednoduchý textový výpis odpovídajících zobrazovaných jmen, přezdívek a hashtagů",
+  "search_popout.tips.text": "Jednoduchý text navrátí odpovídající zobrazovaná jména, přezdívky a hashtagy",
   "search_popout.tips.user": "uživatel",
   "search_results.accounts": "Lidé",
   "search_results.hashtags": "Hashtagy",
@@ -315,8 +325,8 @@
   "status.media_hidden": "Média skryta",
   "status.mention": "Zmínit uživatele @{name}",
   "status.more": "Více",
-  "status.mute": "Ignorovat uživatele @{name}",
-  "status.mute_conversation": "Ignorovat konverzaci",
+  "status.mute": "Skrýt uživatele @{name}",
+  "status.mute_conversation": "Skrýt konverzaci",
   "status.open": "Rozbalit tento toot",
   "status.pin": "Připnout na profil",
   "status.pinned": "Připnutý toot",
@@ -325,7 +335,7 @@
   "status.reblog_private": "Boostnout původnímu publiku",
   "status.reblogged_by": "{name} boostnul/a",
   "status.reblogs.empty": "Tento toot ještě nikdo neboostnul. Pokud to někdo udělá, zobrazí se zde.",
-  "status.redraft": "Vymazat a přepsat",
+  "status.redraft": "Smazat a přepsat",
   "status.reply": "Odpovědět",
   "status.replyAll": "Odpovědět na vlákno",
   "status.report": "Nahlásit uživatele @{name}",
@@ -337,7 +347,7 @@
   "status.show_more": "Zobrazit více",
   "status.show_more_all": "Zobrazit více pro všechny",
   "status.show_thread": "Zobrazit vlákno",
-  "status.unmute_conversation": "Přestat ignorovat konverzaci",
+  "status.unmute_conversation": "Odkrýt konverzaci",
   "status.unpin": "Odepnout z profilu",
   "suggestions.dismiss": "Odmítnout návrh",
   "suggestions.header": "Mohlo by vás zajímat…",
@@ -346,20 +356,21 @@
   "tabs_bar.local_timeline": "Místní",
   "tabs_bar.notifications": "Oznámení",
   "tabs_bar.search": "Hledat",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {Zbývá # den} few {Zbývají # dny} many {Zbývá # dne} other {Zbývá # dní}}",
+  "time_remaining.hours": "{number, plural, one {Zbývá # hodina} few {Zbývají # hodiny} many {Zbývá # hodiny} other {Zbývá # hodin}}",
+  "time_remaining.minutes": "{number, plural, one {Zbývá # minuta} few {Zbývají # minuty} many {Zbývá # minuty} other {Zbývá # minut}}",
+  "time_remaining.moments": "Zbývá několik sekund",
+  "time_remaining.seconds": "{number, plural, one {Zbývá # sekunda} few {Zbývají # sekundy} many {Zbývá # sekundy} other {Zbývá # sekund}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} few {lidé} many {lidí} other {lidí}} hovoří",
   "ui.beforeunload": "Váš koncept se ztratí, pokud Mastodon opustíte.",
   "upload_area.title": "Přetažením nahrajete",
   "upload_button.label": "Přidat média (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Byl překročen limit nahraných souborů.",
+  "upload_error.poll": "Nahrávání souborů není povoleno u anket.",
   "upload_form.description": "Popis pro zrakově postižené",
   "upload_form.focus": "Změnit náhled",
   "upload_form.undo": "Smazat",
-  "upload_progress.label": "Nahrávám...",
+  "upload_progress.label": "Nahrávám…",
   "video.close": "Zavřít video",
   "video.exit_fullscreen": "Ukončit celou obrazovku",
   "video.expand": "Otevřít video",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 828508b2a..635226d7b 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Nid yw eich cyfri wedi'i {locked}. Gall unrhyw un eich dilyn i weld eich tŵtiau dilynwyr-yn-unig.",
   "compose_form.lock_disclaimer.lock": "wedi ei gloi",
   "compose_form.placeholder": "Beth sydd ar eich meddwl?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tŵt",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Cyfryngau wedi'u marcio'n sensitif",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Nid ydych wedi tawelu unrhyw ddefnyddwyr eto.",
   "empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ac eraill i ddechrau'r sgwrs.",
   "empty_column.public": "Does dim byd yma! Ysgrifennwch rhywbeth yn gyhoeddus, neu dilynwch ddefnyddwyr o achosion eraill i'w lenwi",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Caniatau",
   "follow_request.reject": "Gwrthod",
   "getting_started.developers": "Datblygwyr",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Syml",
   "home.column_settings.show_reblogs": "Dangos bŵstiau",
   "home.column_settings.show_replies": "Dangos ymatebion",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Nesaf",
   "introduction.federation.federated.headline": "Ffederasiwn",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Addasu preifatrwdd y tŵt",
   "privacy.direct.long": "Cyhoeddi i'r defnyddwyr sy'n cael eu crybwyll yn unig",
   "privacy.direct.short": "Uniongyrchol",
@@ -356,6 +366,7 @@
   "upload_area.title": "Llusgwch & gollwing i uwchlwytho",
   "upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
   "upload_form.focus": "Newid rhagolwg",
   "upload_form.undo": "Dileu",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index 7e8f4d3f7..86df1447e 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Alle kan følge dig for at se dine følger-kun indlæg.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Hvad har du på hjertet?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Trut",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Medie er markeret som værende følsomt",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Du har endnu ikke dæmpet nogen som helst bruger.",
   "empty_column.notifications": "Du har endnu ingen notifikationer. Tag ud og bland dig med folkemængden for at starte samtalen.",
   "empty_column.public": "Der er ikke noget at se her! Skriv noget offentligt eller start ud med manuelt at følge brugere fra andre instanser for st udfylde tomrummet",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Godkend",
   "follow_request.reject": "Afvis",
   "getting_started.developers": "Udviklere",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Grundlæggende",
   "home.column_settings.show_reblogs": "Vis fremhævelser",
   "home.column_settings.show_replies": "Vis svar",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Næste",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ændre status privatliv",
   "privacy.direct.long": "Post til kun de nævnte brugere",
   "privacy.direct.short": "Direkte",
@@ -356,6 +366,7 @@
   "upload_area.title": "Træk og slip for at uploade",
   "upload_button.label": "Tilføj medie (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Beskriv for de svagtseende",
   "upload_form.focus": "Beskær",
   "upload_form.undo": "Slet",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 44d8e76fa..734737c55 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
   "compose_form.lock_disclaimer.lock": "gesperrt",
   "compose_form.placeholder": "Was gibt's Neues?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tröt",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Medien sind als heikel markiert",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Du hast keine Profile stummgeschaltet.",
   "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.",
   "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Servern, um die Zeitleiste aufzufüllen",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Erlauben",
   "follow_request.reject": "Ablehnen",
   "getting_started.developers": "Entwickler",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Einfach",
   "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
   "home.column_settings.show_replies": "Antworten anzeigen",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Weiter",
   "introduction.federation.federated.headline": "Föderiert",
   "introduction.federation.federated.text": "Öffentliche Beiträge von anderen Servern im Fediverse erscheinen in der föderierten Zeitleiste.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Sichtbarkeit des Beitrags anpassen",
   "privacy.direct.long": "Beitrag nur an erwähnte Profile",
   "privacy.direct.short": "Direkt",
@@ -356,6 +366,7 @@
   "upload_area.title": "Zum Hochladen hereinziehen",
   "upload_button.label": "Mediendatei hinzufügen (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Dateiupload-Limit erreicht.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",
   "upload_form.focus": "Thumbnail bearbeiten",
   "upload_form.undo": "Löschen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 78a5648af..8261ce578 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -1861,6 +1861,10 @@
       {
         "defaultMessage": "Boosts:",
         "id": "notifications.column_settings.reblog"
+      },
+      {
+        "defaultMessage": "Poll results:",
+        "id": "notifications.column_settings.poll"
       }
     ],
     "path": "app/javascript/mastodon/features/notifications/components/column_settings.json"
@@ -1880,6 +1884,10 @@
         "id": "notifications.filter.boosts"
       },
       {
+        "defaultMessage": "Poll results",
+        "id": "notifications.filter.polls"
+      },
+      {
         "defaultMessage": "Follows",
         "id": "notifications.filter.follows"
       },
@@ -1905,7 +1913,7 @@
         "id": "notification.reblog"
       },
       {
-        "defaultMessage": "Your poll has ended",
+        "defaultMessage": "A poll you have voted in has ended",
         "id": "notification.poll"
       }
     ],
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index a9ed36243..d0bbc830b 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σας προς τους ακολούθους σας.",
   "compose_form.lock_disclaimer.lock": "κλειδωμένος",
   "compose_form.placeholder": "Τι σκέφτεσαι;",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Τουτ",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Το πολυμέσο έχει σημειωθεί ως ευαίσθητο",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Δεν έχεις αποσιωπήσει κανένα χρήστη ακόμα.",
   "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.",
   "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλους κόμβους για να τη γεμίσεις",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Ενέκρινε",
   "follow_request.reject": "Απέρριψε",
   "getting_started.developers": "Ανάπτυξη",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Βασικά",
   "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
   "home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Επόμενο",
   "introduction.federation.federated.headline": "Ομοσπονδιακή",
   "introduction.federation.federated.text": "Οι δημόσιες αναρτήσεις από άλλους κόμβους του fediverse θα εμφανίζονται στην ομοσπονδιακή ροή.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Ακόλουθοι",
   "notifications.filter.mentions": "Αναφορές",
   "notifications.group": "{count} ειδοποιήσεις",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Κλειστή",
+  "poll.refresh": "Ανανέωση",
+  "poll.total_votes": "{count, plural, one {# ψήφος} other {# ψήφοι}}",
+  "poll.vote": "Ψήφισε",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Προσαρμογή ιδιωτικότητας δημοσίευσης",
   "privacy.direct.long": "Δημοσίευση μόνο σε όσους και όσες αναφέρονται",
   "privacy.direct.short": "Προσωπικά",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Τοπικά",
   "tabs_bar.notifications": "Ειδοποιήσεις",
   "tabs_bar.search": "Αναζήτηση",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "απομένουν {number, plural, one {# ημέρα} other {# ημέρες}}",
+  "time_remaining.hours": "απομένουν {number, plural, one {# ώρα} other {# ώρες}}",
+  "time_remaining.minutes": "απομένουν {number, plural, one {# λεπτό} other {# λεπτά}}",
+  "time_remaining.moments": "Απομένουν στιγμές",
+  "time_remaining.seconds": "απομένουν {number, plural, one {# δευτερόλεπτο} other {# δευτερόλεπτα}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} μιλάνε",
   "ui.beforeunload": "Το προσχέδιό σου θα χαθεί αν φύγεις από το Mastodon.",
   "upload_area.title": "Drag & drop για να ανεβάσεις",
   "upload_button.label": "Πρόσθεσε πολυμέσα (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Υπέρβαση ορίου μεγέθους ανεβασμένων αρχείων.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Περιέγραψε για όσους & όσες έχουν προβλήματα όρασης",
   "upload_form.focus": "Αλλαγή προεπισκόπησης",
   "upload_form.undo": "Διαγραφή",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index eb82cd9a9..1c45d6f20 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -263,6 +263,7 @@
   "notifications.column_settings.filter_bar.show": "Show",
   "notifications.column_settings.follow": "New followers:",
   "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
   "notifications.column_settings.push": "Push notifications",
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Show in column",
@@ -272,6 +273,7 @@
   "notifications.filter.favourites": "Favourites",
   "notifications.filter.follows": "Follows",
   "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 47820da90..05e7e12d8 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn, kiuj estas nur por sekvantoj.",
   "compose_form.lock_disclaimer.lock": "ŝlosita",
   "compose_form.placeholder": "Pri kio vi pensas?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Hup",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Aŭdovidaĵo markita tikla",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
   "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
   "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj serviloj por plenigi la publikan tempolinion",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Rajtigi",
   "follow_request.reject": "Rifuzi",
   "getting_started.developers": "Programistoj",
@@ -142,8 +147,8 @@
   "hashtag.column_header.tag_mode.all": "kaj {additional}",
   "hashtag.column_header.tag_mode.any": "aŭ {additional}",
   "hashtag.column_header.tag_mode.none": "sen {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.select.no_options_message": "Neniu sugesto trovita",
+  "hashtag.column_settings.select.placeholder": "Enmeti kradvortojn…",
   "hashtag.column_settings.tag_mode.all": "Ĉiuj",
   "hashtag.column_settings.tag_mode.any": "Iu ajn",
   "hashtag.column_settings.tag_mode.none": "Neniu",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Bazaj agordoj",
   "home.column_settings.show_reblogs": "Montri diskonigojn",
   "home.column_settings.show_replies": "Montri respondojn",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Sekva",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Publikaj mesaĝoj el aliaj serviloj de la Fediverse aperos en la fratara tempolinio.",
@@ -206,7 +214,7 @@
   "lists.account.remove": "Forigi de la listo",
   "lists.delete": "Forigi la liston",
   "lists.edit": "Redakti la liston",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Ŝanĝi titolon",
   "lists.new.create": "Aldoni liston",
   "lists.new.title_placeholder": "Titolo de la nova listo",
   "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Sekvoj",
   "notifications.filter.mentions": "Mencioj",
   "notifications.group": "{count} sciigoj",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Finita",
+  "poll.refresh": "Aktualigi",
+  "poll.total_votes": "{count, plural, one {# voĉdono} other {# voĉdonoj}}",
+  "poll.vote": "Voĉdoni",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Agordi mesaĝan privatecon",
   "privacy.direct.long": "Afiŝi nur al menciitaj uzantoj",
   "privacy.direct.short": "Rekta",
@@ -351,11 +361,12 @@
   "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, pluraj, unu {person} alia(j) {people}} parolas",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {persono} other {personoj}} parolas",
   "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
   "upload_area.title": "Altreni kaj lasi por alŝuti",
   "upload_button.label": "Aldoni aŭdovidaĵon (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limo de dosiera alŝutado transpasita.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Priskribi por misvidantaj homoj",
   "upload_form.focus": "Antaŭvido de ŝanĝo",
   "upload_form.undo": "Forigi",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 7bb1a304e..bf20e5f9d 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.",
   "compose_form.lock_disclaimer.lock": "bloqueado",
   "compose_form.placeholder": "¿En qué estás pensando?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tootear",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Material marcado como sensible",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Aún no has silenciado a ningún usuario.",
   "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
   "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Rechazar",
   "getting_started.developers": "Desarrolladores",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar retoots",
   "home.column_settings.show_replies": "Mostrar respuestas",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ajustar privacidad",
   "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
   "privacy.direct.short": "Directo",
@@ -356,6 +366,7 @@
   "upload_area.title": "Arrastra y suelta para subir",
   "upload_button.label": "Subir multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describir para los usuarios con dificultad visual",
   "upload_form.focus": "Recortar",
   "upload_form.undo": "Borrar",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 76f1c24f0..0915ee6cc 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Zure kontua ez dago {locked}. Edonork jarraitu zaitzake zure jarraitzaileentzako soilik diren mezuak ikusteko.",
   "compose_form.lock_disclaimer.lock": "giltzapetuta",
   "compose_form.placeholder": "Zer duzu buruan?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Multimedia edukia hunkigarri gisa markatu da",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Ez duzu erabiltzailerik mututu oraindik.",
   "empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.",
   "empty_column.public": "Ez dago ezer hemen! Idatzi zerbait publikoki edo jarraitu eskuz beste zerbitzari batzuetako erabiltzaileak hau betetzen joateko",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Baimendu",
   "follow_request.reject": "Ukatu",
   "getting_started.developers": "Garatzaileak",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Oinarrizkoa",
   "home.column_settings.show_reblogs": "Erakutsi bultzadak",
   "home.column_settings.show_replies": "Erakutsi erantzunak",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Hurrengoa",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Fedibertsoko beste zerbitzarietako bidalketa publikoak federatutako denbora-lerroan agertuko dira.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Doitu mezuaren pribatutasuna",
   "privacy.direct.long": "Bidali aipatutako erabiltzaileei besterik ez",
   "privacy.direct.short": "Zuzena",
@@ -356,6 +366,7 @@
   "upload_area.title": "Arrastatu eta jaregin igotzeko",
   "upload_button.label": "Gehitu multimedia  (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Fitxategi igoera muga gaindituta.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Deskribatu ikusmen arazoak dituztenentzat",
   "upload_form.focus": "Aldatu aurrebista",
   "upload_form.undo": "Ezabatu",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 5cdcf2441..e4003d6a0 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی می‌تواند پیگیر شما شود و نوشته‌های ویژهٔ پیگیران شما را ببیند.",
   "compose_form.lock_disclaimer.lock": "قفل",
   "compose_form.placeholder": "تازه چه خبر؟",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "بوق",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "این تصویر به عنوان حساس علامت‌گذاری شده",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "شما هنوز هیچ کاربری را بی‌صدا نکرده‌اید.",
   "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشته‌های دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
   "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران سرورهای دیگر را پی بگیرید تا این‌جا پر شود",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "اجازه دهید",
   "follow_request.reject": "اجازه ندهید",
   "getting_started.developers": "برای برنامه‌نویسان",
@@ -142,8 +147,8 @@
   "hashtag.column_header.tag_mode.all": "و {additional}",
   "hashtag.column_header.tag_mode.any": "یا {additional}",
   "hashtag.column_header.tag_mode.none": "بدون {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.select.no_options_message": "هیچ پیشنهادی پیدا نشد",
+  "hashtag.column_settings.select.placeholder": "برچسب‌ها را وارد کنید…",
   "hashtag.column_settings.tag_mode.all": "همهٔ این‌ها",
   "hashtag.column_settings.tag_mode.any": "هرکدام از این‌ها",
   "hashtag.column_settings.tag_mode.none": "هیچ‌کدام از این‌ها",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "اصلی",
   "home.column_settings.show_reblogs": "نمایش بازبوق‌ها",
   "home.column_settings.show_replies": "نمایش پاسخ‌ها",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "بعدی",
   "introduction.federation.federated.headline": "فهرست همهٔ سرورها",
   "introduction.federation.federated.text": "نوشته‌های عمومی سرورهای دیگر در این فهرست نمایش می‌یابند.",
@@ -206,7 +214,7 @@
   "lists.account.remove": "پاک‌کردن از فهرست",
   "lists.delete": "حذف فهرست",
   "lists.edit": "ویرایش فهرست",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "تغییر عنوان",
   "lists.new.create": "افزودن فهرست",
   "lists.new.title_placeholder": "نام فهرست تازه",
   "lists.search": "بین کسانی که پی می‌گیرید بگردید",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "پیگیری‌ها",
   "notifications.filter.mentions": "گفتگوها",
   "notifications.group": "{count} اعلان",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "پایان‌یافته",
+  "poll.refresh": "به‌روزرسانی",
+  "poll.total_votes": "{count, plural, one {# رأی} other {# رأی}}",
+  "poll.vote": "رأی",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "تنظیم حریم خصوصی نوشته‌ها",
   "privacy.direct.long": "تنها به کاربران نام‌برده‌شده نشان بده",
   "privacy.direct.short": "مستقیم",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "محلی",
   "tabs_bar.notifications": "اعلان‌ها",
   "tabs_bar.search": "جستجو",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# روز} other {# روز}} left",
+  "time_remaining.hours": "{number, plural, one {# ساعت} other {# ساعت}} left",
+  "time_remaining.minutes": "{number, plural, one {# دقیقه} other {# دقیقه}} left",
+  "time_remaining.moments": "زمان باقی‌مانده",
+  "time_remaining.seconds": "{number, plural, one {# ثانیه} other {# ثانیه}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {نفر نوشته است} other {نفر نوشته‌اند}}",
   "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.",
   "upload_area.title": "برای بارگذاری به این‌جا بکشید",
   "upload_button.label": "افزودن عکس و ویدیو (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "از حد مجاز باگذاری فراتر رفتید.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
   "upload_form.focus": "بریدن لبه‌ها",
   "upload_form.undo": "حذف",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 6ddd5a02d..9949d741e 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille rajaamasi julkaisut.",
   "compose_form.lock_disclaimer.lock": "lukittu",
   "compose_form.placeholder": "Mitä mietit?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tuuttaa",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Et ole mykistänyt vielä yhtään käyttäjää.",
   "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Aloita keskustelu juttelemalla muille.",
   "empty_column.public": "Täällä ei ole mitään! Saat sisältöä, kun kirjoitat jotain julkisesti tai käyt seuraamassa muiden instanssien käyttäjiä",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Valtuuta",
   "follow_request.reject": "Hylkää",
   "getting_started.developers": "Kehittäjille",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Perusasetukset",
   "home.column_settings.show_reblogs": "Näytä buustaukset",
   "home.column_settings.show_replies": "Näytä vastaukset",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Seuraava",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Säädä tuuttauksen näkyvyyttä",
   "privacy.direct.long": "Julkaise vain mainituille käyttäjille",
   "privacy.direct.short": "Suora viesti",
@@ -356,6 +366,7 @@
   "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän",
   "upload_button.label": "Lisää mediaa",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Anna kuvaus näkörajoitteisia varten",
   "upload_form.focus": "Rajaa",
   "upload_form.undo": "Peru",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 91ac04fcd..b257a16b9 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.",
   "compose_form.lock_disclaimer.lock": "verrouillé",
   "compose_form.placeholder": "Qu’avez-vous en tête ?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Pouet",
   "compose_form.publish_loud": "{publish} !",
   "compose_form.sensitive.marked": "Média marqué comme sensible",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Vous n’avez pas encore mis d'utilisateur·rice·s en silence.",
   "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
   "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Accepter",
   "follow_request.reject": "Rejeter",
   "getting_started.developers": "Développeur·euse·s",
@@ -147,10 +152,13 @@
   "hashtag.column_settings.tag_mode.all": "Tous ces éléments",
   "hashtag.column_settings.tag_mode.any": "Au moins un de ces éléments",
   "hashtag.column_settings.tag_mode.none": "Aucun de ces éléments",
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "hashtag.column_settings.tag_toggle": "Inclure des tags additionnels dans cette colonne",
   "home.column_settings.basic": "Basique",
   "home.column_settings.show_reblogs": "Afficher les partages",
   "home.column_settings.show_replies": "Afficher les réponses",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Suivant",
   "introduction.federation.federated.headline": "Fil public global",
   "introduction.federation.federated.text": "Les messages publics provenant d'autres serveurs du fediverse apparaîtront dans le fil public global.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Abonné·e·s",
   "notifications.filter.mentions": "Mentions",
   "notifications.group": "{count} notifications",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
+  "poll.closed": "Fermé",
+  "poll.refresh": "Actualiser",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.vote": "Voter",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ajuster la confidentialité du message",
   "privacy.direct.long": "N’envoyer qu’aux personnes mentionnées",
   "privacy.direct.short": "Direct",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Fil public local",
   "tabs_bar.notifications": "Notifications",
   "tabs_bar.search": "Chercher",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} restants",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} restantes",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} restantes",
+  "time_remaining.moments": "Encore quelques instants",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} restantes",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {personne} other {personnes}} discutent",
   "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
   "upload_area.title": "Glissez et déposez pour envoyer",
   "upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Taille maximale d'envoi de fichier dépassée.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Décrire pour les malvoyant·e·s",
   "upload_form.focus": "Modifier l’aperçu",
   "upload_form.undo": "Supprimer",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 29638d348..83adc563f 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.",
   "compose_form.lock_disclaimer.lock": "bloqueado",
   "compose_form.placeholder": "Qué contas?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Medios marcados como sensibles",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Non acalou ningunha usuaria polo de agora.",
   "empty_column.notifications": "Aínda non ten notificacións. Interactúe con outras para iniciar unha conversa.",
   "empty_column.public": "Nada por aquí! Escriba algo de xeito público, ou siga manualmente usuarias de outros servidores para ir enchéndoa",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Rexeitar",
   "getting_started.developers": "Desenvolvedoras",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar repeticións",
   "home.column_settings.show_replies": "Mostrar respostas",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Seguinte",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Publicacións públicas desde outros servidores do fediverso aparecerán na liña temporal federada.",
@@ -263,7 +271,9 @@
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.vote": "Votar",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Axustar a intimidade do estado",
   "privacy.direct.long": "Enviar exclusivamente as usuarias mencionadas",
   "privacy.direct.short": "Directa",
@@ -356,6 +366,7 @@
   "upload_area.title": "Arrastre e solte para subir",
   "upload_button.label": "Engadir medios (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describa para deficientes visuais",
   "upload_form.focus": "Cambiar vista previa",
   "upload_form.undo": "Eliminar",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index d40e339a8..07d2a9c34 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.",
   "compose_form.lock_disclaimer.lock": "נעול",
   "compose_form.placeholder": "מה עובר לך בראש?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "ללחוש",
   "compose_form.publish_loud": "לחצרץ!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב.",
   "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "קבלה",
   "follow_request.reject": "דחיה",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "למתחילים",
   "home.column_settings.show_reblogs": "הצגת הדהודים",
   "home.column_settings.show_replies": "הצגת תגובות",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "שינוי פרטיות ההודעה",
   "privacy.direct.long": "הצג רק למי שהודעה זו פונה אליו",
   "privacy.direct.short": "הודעה ישירה",
@@ -356,6 +366,7 @@
   "upload_area.title": "ניתן להעלות על ידי Drag & drop",
   "upload_button.label": "הוספת מדיה",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "תיאור לכבדי ראיה",
   "upload_form.focus": "Crop",
   "upload_form.undo": "ביטול",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index b17aa8058..0312da592 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Tvoj račun nije {locked}. Svatko te može slijediti kako bi vidio postove namijenjene samo tvojim sljedbenicima.",
   "compose_form.lock_disclaimer.lock": "zaključan",
   "compose_form.placeholder": "Što ti je na umu?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
   "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autoriziraj",
   "follow_request.reject": "Odbij",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Osnovno",
   "home.column_settings.show_reblogs": "Pokaži boostove",
   "home.column_settings.show_replies": "Pokaži odgovore",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Podesi status privatnosti",
   "privacy.direct.long": "Prikaži samo spomenutim korisnicima",
   "privacy.direct.short": "Direktno",
@@ -356,6 +366,7 @@
   "upload_area.title": "Povuci i spusti kako bi uploadao",
   "upload_button.label": "Dodaj media",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Poništi",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index f0c686212..20b21be73 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Az ön fiókja nincs {locked}. Bárki követni tud, hogy megtekintse a kizárt követőknek szánt üzeneteid.",
   "compose_form.lock_disclaimer.lock": "lezárva",
   "compose_form.placeholder": "Mire gondolsz?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tülk",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Jelenleg nincsenek értesítései. Lépj kapcsolatba másokkal, hogy indítsd el a beszélgetést.",
   "empty_column.public": "Jelenleg semmi nincs itt! Írj valamit publikusan vagy kövess más szervereken levő felhasználókat, hogy megtöltsd",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Engedélyez",
   "follow_request.reject": "Visszautasít",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Alap",
   "home.column_settings.show_reblogs": "Ismétlések mutatása",
   "home.column_settings.show_replies": "Válaszok mutatása",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Státusz láthatóságának módosítása",
   "privacy.direct.long": "Posztolás csak az említett felhasználóknak",
   "privacy.direct.short": "Egyenesen",
@@ -356,6 +366,7 @@
   "upload_area.title": "Húzza ide a feltöltéshez",
   "upload_button.label": "Média hozzáadása",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Mégsem",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index f9ef89fa9..b96d9499e 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Քո հաշիվը {locked} չէ։ Յուրաքանչյուր ոք կարող է հետեւել քեզ եւ տեսնել միայն հետեւողների համար նախատեսված գրառումները։",
   "compose_form.lock_disclaimer.lock": "փակ",
   "compose_form.placeholder": "Ի՞նչ կա մտքիդ",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Թթել",
   "compose_form.publish_loud": "Թթե՜լ",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Ոչ մի ծանուցում դեռ չունես։ Բզիր մյուսներին՝ խոսակցությունը սկսելու համար։",
   "empty_column.public": "Այստեղ բան չկա՛։ Հրապարակային մի բան գրիր կամ հետեւիր այլ հանգույցներից էակների՝ այն լցնելու համար։",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Վավերացնել",
   "follow_request.reject": "Մերժել",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Հիմնական",
   "home.column_settings.show_reblogs": "Ցուցադրել տարածածները",
   "home.column_settings.show_replies": "Ցուցադրել պատասխանները",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Կարգավորել թթի գաղտնիությունը",
   "privacy.direct.long": "Թթել միայն նշված օգտատերերի համար",
   "privacy.direct.short": "Հասցեագրված",
@@ -356,6 +366,7 @@
   "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
   "upload_button.label": "Ավելացնել մեդիա",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Հետարկել",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 3f6c420a6..2b00ece08 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
   "compose_form.lock_disclaimer.lock": "terkunci",
   "compose_form.placeholder": "Apa yang ada di pikiran anda?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Sumber ini telah ditandai sebagai sumber sensitif.",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
   "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisi ini",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Izinkan",
   "follow_request.reject": "Tolak",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Dasar",
   "home.column_settings.show_reblogs": "Tampilkan boost",
   "home.column_settings.show_replies": "Tampilkan balasan",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Tentukan privasi status",
   "privacy.direct.long": "Kirim hanya ke pengguna yang disebut",
   "privacy.direct.short": "Langsung",
@@ -356,6 +366,7 @@
   "upload_area.title": "Seret & lepaskan untuk mengunggah",
   "upload_button.label": "Tambahkan media",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Deskripsikan untuk mereka yang tidak bisa melihat dengan jelas",
   "upload_form.focus": "Potong",
   "upload_form.undo": "Undo",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 3b7d86ab0..7e0e5563c 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "Quo esas en tua spirito?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Siflar",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
   "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Yurizar",
   "follow_request.reject": "Refuzar",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Simpla",
   "home.column_settings.show_reblogs": "Montrar repeti",
   "home.column_settings.show_replies": "Montrar respondi",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Aranjar privateso di mesaji",
   "privacy.direct.long": "Sendar nur a mencionata uzeri",
   "privacy.direct.short": "Direte",
@@ -356,6 +366,7 @@
   "upload_area.title": "Tranar faligar por kargar",
   "upload_button.label": "Adjuntar kontenajo",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Desfacar",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 8be3e6163..e4cd96475 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Il tuo account non è {bloccato}. Chiunque può decidere di seguirti per vedere i tuoi post per soli seguaci.",
   "compose_form.lock_disclaimer.lock": "bloccato",
   "compose_form.placeholder": "A cosa stai pensando?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Questo media è contrassegnato come sensibile",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Non hai ancora silenziato nessun utente.",
   "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
   "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizza",
   "follow_request.reject": "Rifiuta",
   "getting_started.developers": "Sviluppatori",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Semplice",
   "home.column_settings.show_reblogs": "Mostra post condivisi",
   "home.column_settings.show_replies": "Mostra risposte",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Avanti",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "I post pubblici provenienti da altri server del fediverse saranno mostrati nella timeline federata.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Seguaci",
   "notifications.filter.mentions": "Menzioni",
   "notifications.group": "{count} notifiche",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Chiuso",
+  "poll.refresh": "Aggiorna",
+  "poll.total_votes": "{count, plural, one {# voto} other {# voti}}",
+  "poll.vote": "Vota",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Modifica privacy del post",
   "privacy.direct.long": "Invia solo a utenti menzionati",
   "privacy.direct.short": "Diretto",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Locale",
   "tabs_bar.notifications": "Notifiche",
   "tabs_bar.search": "Cerca",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# giorno} other {# giorni}} left",
+  "time_remaining.hours": "{number, plural, one {# ora} other {# ore}} left",
+  "time_remaining.minutes": "{number, plural, one {# minuto} other {# minuti}} left",
+  "time_remaining.moments": "Restano pochi istanti",
+  "time_remaining.seconds": "{number, plural, one {# secondo} other {# secondi}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {persona ne sta} other {persone ne stanno}} parlando",
   "ui.beforeunload": "La bozza andrà persa se esci da Mastodon.",
   "upload_area.title": "Trascina per caricare",
   "upload_button.label": "Aggiungi file multimediale",
   "upload_error.limit": "Limite al caricamento di file superato.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descrizione per utenti con disabilità visive",
   "upload_form.focus": "Modifica anteprima",
   "upload_form.undo": "Cancella",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 6388c7e9c..ca66fbf2a 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -252,7 +252,6 @@
   "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
   "notification.follow": "{name}さんにフォローされました",
   "notification.mention": "{name}さんがあなたに返信しました",
-  "notification.poll": "Your poll has ended",
   "notification.reblog": "{name}さんがあなたのトゥートをブーストしました",
   "notifications.clear": "通知を消去",
   "notifications.clear_confirmation": "本当に通知を消去しますか?",
@@ -273,9 +272,9 @@
   "notifications.filter.follows": "フォロー",
   "notifications.filter.mentions": "返信",
   "notifications.group": "{count} 件の通知",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.closed": "終了",
+  "poll.refresh": "更新",
+  "poll.total_votes": "{count, plural, one {# 票} other {# 票}}",
   "poll.vote": "Vote",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
@@ -352,7 +351,7 @@
   "status.show_more_all": "全て見る",
   "status.show_thread": "スレッドを表示",
   "status.unmute_conversation": "会話のミュートを解除",
-  "status.unpin": "プロフィールの固定表示を解除",
+  "status.unpin": "プロフィールへの固定を解除",
   "suggestions.dismiss": "隠す",
   "suggestions.header": "興味あるかもしれません…",
   "tabs_bar.federated_timeline": "連合",
@@ -360,11 +359,11 @@
   "tabs_bar.local_timeline": "ローカル",
   "tabs_bar.notifications": "通知",
   "tabs_bar.search": "検索",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "残り {number, plural, one {# 日} other {# 日}}",
+  "time_remaining.hours": "残り {number, plural, one {# 時間} other {# 時間}}",
+  "time_remaining.minutes": "残り {number, plural, one {# 分} other {# 分}}",
+  "time_remaining.moments": "まもなく終了",
+  "time_remaining.seconds": "残り {number, plural, one {# 秒} other {# 秒}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {人} other {人}} がトゥート",
   "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
   "upload_area.title": "ドラッグ&ドロップでアップロード",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index 2821d75e4..21afe8df3 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "თქვენი ანგარიში არაა {locked}. ნებისმიერს შეიძლია გამოგყვეთ, რომ იხილოს თქვენი მიმდევრებზე გათვლილი პოსტები.",
   "compose_form.lock_disclaimer.lock": "ჩაკეტილი",
   "compose_form.placeholder": "რაზე ფიქრობ?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "ტუტი",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "მედია მონიშნულია მგრძნობიარედ",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "ჯერ შეტყობინებები არ გაქვთ. საუბრის დასაწყებად იურთიერთქმედეთ სხვებთან.",
   "empty_column.public": "აქ არაფერია! შესავსებად, დაწერეთ რაიმე ღიად ან ხელით გაჰყევით მომხმარებლებს სხვა ინსტანციებისგან",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "ავტორიზაცია",
   "follow_request.reject": "უარყოფა",
   "getting_started.developers": "დეველოპერები",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "ძირითადი",
   "home.column_settings.show_reblogs": "ბუსტების ჩვენება",
   "home.column_settings.show_replies": "პასუხების ჩვენება",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "სტატუსის კონფიდენციალურობის მითითება",
   "privacy.direct.long": "დაიპოსტოს მხოლოდ დასახელებულ მომხმარებლებთან",
   "privacy.direct.short": "პირდაპირი",
@@ -356,6 +366,7 @@
   "upload_area.title": "გადმოწიეთ და ჩააგდეთ ასატვირთათ",
   "upload_button.label": "მედიის დამატება",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "აღწერილობა ვიზუალურად უფასურისთვის",
   "upload_form.focus": "კროპი",
   "upload_form.undo": "გაუქმება",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index 529459cf2..034810fdd 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Аккаунтыңыз {locked} емес. Кез келген адам жазылып, сізді оқи алады.",
   "compose_form.lock_disclaimer.lock": "жабық",
   "compose_form.placeholder": "Не бөліскіңіз келеді?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Түрт",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Медиа нәзік деп белгіленген",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Әзірше ешқандай үнсізге қойылған қолданушы жоқ.",
   "empty_column.notifications": "Әзірше ешқандай ескертпе жоқ. Басқалармен араласуды бастаңыз және пікірталастарға қатысыңыз.",
   "empty_column.public": "Ештеңе жоқ бұл жерде! Өзіңіз бастап жазып көріңіз немесе басқаларға жазылыңыз",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Авторизация",
   "follow_request.reject": "Қабылдамау",
   "getting_started.developers": "Жасаушылар тобы",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Негізгі",
   "home.column_settings.show_reblogs": "Бөлісулерді көрсету",
   "home.column_settings.show_replies": "Жауаптарды көрсету",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Келесі",
   "introduction.federation.federated.headline": "Жаһандық",
   "introduction.federation.federated.text": "Жаһандық желідегі жазбалар осында көрінетін болады.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Жазылулар",
   "notifications.filter.mentions": "Аталымдар",
   "notifications.group": "{count} ескертпе",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Жабық",
+  "poll.refresh": "Жаңарту",
+  "poll.total_votes": "{count, plural, one {# дауыс} other {# дауыс}}",
+  "poll.vote": "Дауыс беру",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Құпиялылықты реттеу",
   "privacy.direct.long": "Аталған адамдарға ғана көрінетін жазба",
   "privacy.direct.short": "Тікелей",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Жергілікті",
   "tabs_bar.notifications": "Ескертпелер",
   "tabs_bar.search": "Іздеу",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# күн} other {# күн}}",
+  "time_remaining.hours": "{number, plural, one {# сағат} other {# сағат}}",
+  "time_remaining.minutes": "{number, plural, one {# минут} other {# минут}}",
+  "time_remaining.moments": "Қалған уақыт",
+  "time_remaining.seconds": "{number, plural, one {# секунд} other {# секунд}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} жазған екен",
   "ui.beforeunload": "Mastodon желісінен шықсаңыз, нобайыңыз сақталмайды.",
   "upload_area.title": "Жүктеу үшін сүйреп әкеліңіз",
   "upload_button.label": "Медиа қосу (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Файл жүктеу лимитінен асып кеттіңіз.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Көру қабілеті нашар адамдар үшін сипаттаңыз",
   "upload_form.focus": "Превьюді өзгерту",
   "upload_form.undo": "Өшіру",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 6363e2de7..cbd68d195 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.",
   "compose_form.lock_disclaimer.lock": "비공개",
   "compose_form.placeholder": "지금 무엇을 하고 있나요?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "툿",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "미디어가 열람주의로 설정되어 있습니다",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",
   "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요.",
   "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 서버의 유저를 팔로우 해서 채워보세요",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "허가",
   "follow_request.reject": "거부",
   "getting_started.developers": "개발자",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "기본 설정",
   "home.column_settings.show_reblogs": "부스트 표시",
   "home.column_settings.show_replies": "답글 표시",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "다음",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "페디버스의 다른 서버의 공개 게시물이 연합 타임라인에 나타납니다.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "팔로우",
   "notifications.filter.mentions": "멘션",
   "notifications.group": "{count} 개의 알림",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "마감됨",
+  "poll.refresh": "새로고침",
+  "poll.total_votes": "명 참여",
+  "poll.vote": "투표",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "포스트의 프라이버시 설정을 변경",
   "privacy.direct.long": "멘션한 사용자에게만 공개",
   "privacy.direct.short": "다이렉트",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "로컬",
   "tabs_bar.notifications": "알림",
   "tabs_bar.search": "검색",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "일 남음",
+  "time_remaining.hours": "시간 남음",
+  "time_remaining.minutes": "분 남음",
+  "time_remaining.moments": "남은 시간",
+  "time_remaining.seconds": "초 남음",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {명} other {명}} 의 사람들이 말하고 있습니다",
   "ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.",
   "upload_area.title": "드래그 & 드롭으로 업로드",
   "upload_button.label": "미디어 추가 (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "파일 업로드 제한에 도달했습니다.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "시각장애인을 위한 설명",
   "upload_form.focus": "미리보기 변경",
   "upload_form.undo": "삭제",
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index 821d8c4b1..ab784d1b4 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Tavs konts nav {locked}. Ikviens var Tev sekot lai apskatītu tikai sekotājiem paredzētos ziņojumus.",
   "compose_form.lock_disclaimer.lock": "slēgts",
   "compose_form.placeholder": "Ko vēlies publicēt?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Publicēt",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Mēdijs ir atzīmēts kā sensitīvs",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Tu neesi nevienu apklusinājis.",
   "empty_column.notifications": "Tev nav paziņojumu. Iesaisties sarunās ar citiem.",
   "empty_column.public": "Šeit nekā nav, tukšums! Ieraksti kaut ko publiski, vai uzmeklē un seko kādam no citas instances",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizēt",
   "follow_request.reject": "Noraidīt",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Delete",
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index 21f066439..9f8f797c8 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Authorize",
   "follow_request.reject": "Reject",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Delete",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index f6d1041a0..84ae28037 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de toots zien die je alleen aan jouw volgers hebt gericht.",
   "compose_form.lock_disclaimer.lock": "besloten",
   "compose_form.placeholder": "Wat wil je kwijt?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is als gevoelig gemarkeerd",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.",
   "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.",
   "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Goedkeuren",
   "follow_request.reject": "Afkeuren",
   "getting_started.developers": "Ontwikkelaars",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Algemeen",
   "home.column_settings.show_reblogs": "Boosts tonen",
   "home.column_settings.show_replies": "Reacties tonen",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Volgende",
   "introduction.federation.federated.headline": "Globaal",
   "introduction.federation.federated.text": "Openbare toots van mensen op andere servers in de fediverse verschijnen op de globale tijdlijn.",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Die jij volgt",
   "notifications.filter.mentions": "Vermeldingen",
   "notifications.group": "{count} meldingen",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Gesloten",
+  "poll.refresh": "Vernieuwen",
+  "poll.total_votes": "{count, plural, one {# stem} other {# stemmen}}",
+  "poll.vote": "Stemmen",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Zichtbaarheid toot aanpassen",
   "privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
   "privacy.direct.short": "Direct",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Lokaal",
   "tabs_bar.notifications": "Meldingen",
   "tabs_bar.search": "Zoeken",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "{number, plural, one {# dag} other {# dagen}} left",
+  "time_remaining.hours": "{number, plural, one {# uur} other {# uur}} left",
+  "time_remaining.minutes": "{number, plural, one {# minuut} other {# minuten}} left",
+  "time_remaining.moments": "Nog enkele ogenblikken resterend",
+  "time_remaining.seconds": "{number, plural, one {# seconde} other {# seconden}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {persoon praat} other {mensen praten}} hierover",
   "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.",
   "upload_area.title": "Hierin slepen om te uploaden",
   "upload_button.label": "Media toevoegen (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Uploadlimiet van bestand overschreden.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
   "upload_form.focus": "Voorvertoning aanpassen",
   "upload_form.undo": "Verwijderen",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 8b6060d5d..45a3d500f 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Hvem som helst kan følge deg og se dine private poster.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Hva har du på hjertet?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
   "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorisér",
   "follow_request.reject": "Avvis",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Enkel",
   "home.column_settings.show_reblogs": "Vis fremhevinger",
   "home.column_settings.show_replies": "Vis svar",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Justér synlighet",
   "privacy.direct.long": "Post kun til nevnte brukere",
   "privacy.direct.short": "Direkte",
@@ -356,6 +366,7 @@
   "upload_area.title": "Dra og slipp for å laste opp",
   "upload_button.label": "Legg til media",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Beskriv for synshemmede",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Angre",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 5c5a583b6..625cd49f0 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo monde pòt vos sègre e veire los estatuts reservats als seguidors.",
   "compose_form.lock_disclaimer.lock": "clavat",
   "compose_form.placeholder": "A de qué pensatz ?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish} !",
   "compose_form.sensitive.marked": "Lo mèdia es marcat coma sensible",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Encara avètz pas mes en silenci degun.",
   "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
   "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autres servidors per garnir lo flux public",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Acceptar",
   "follow_request.reject": "Regetar",
   "getting_started.developers": "Desvelopaires",
@@ -142,8 +147,8 @@
   "hashtag.column_header.tag_mode.all": "e {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sens {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.select.no_options_message": "Cap de suggestion pas trobada",
+  "hashtag.column_settings.select.placeholder": "Picatz d’etiquetas…",
   "hashtag.column_settings.tag_mode.all": "Totes aquestes",
   "hashtag.column_settings.tag_mode.any": "Un d’aquestes",
   "hashtag.column_settings.tag_mode.none": "Cap d’aquestes",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Mostrar los partatges",
   "home.column_settings.show_replies": "Mostrar las responsas",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Seguent",
   "introduction.federation.federated.headline": "Federat",
   "introduction.federation.federated.text": "Los tuts publics d’autres servidors del fediverse apareisseràn dins lo flux d’actualitats.",
@@ -206,7 +214,7 @@
   "lists.account.remove": "Levar de la lista",
   "lists.delete": "Suprimir la lista",
   "lists.edit": "Modificar la lista",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Cambiar lo títol",
   "lists.new.create": "Ajustar una lista",
   "lists.new.title_placeholder": "Títol de la nòva lista",
   "lists.search": "Cercar demest lo monde que seguètz",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Seguiments",
   "notifications.filter.mentions": "Mencions",
   "notifications.group": "{count} notificacions",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Tampat",
+  "poll.refresh": "Actualizar",
+  "poll.total_votes": "{count, plural, one {# vòte} other {# vòtes}}",
+  "poll.vote": "Votar",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ajustar la confidencialitat del messatge",
   "privacy.direct.long": "Mostrar pas qu’a las personas mencionadas",
   "privacy.direct.short": "Dirècte",
@@ -304,7 +314,7 @@
   "status.block": "Blocar @{name}",
   "status.cancel_reblog_private": "Quitar de partejar",
   "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat",
-  "status.copy": "Copy link to status",
+  "status.copy": "Copiar lo ligam de l’estatut",
   "status.delete": "Escafar",
   "status.detailed_status": "Vista detalhada de la convèrsa",
   "status.direct": "Messatge per @{name}",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Flux public local",
   "tabs_bar.notifications": "Notificacions",
   "tabs_bar.search": "Recèrcas",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "demòra{number, plural, one  { # jorn} other {n # jorns}}",
+  "time_remaining.hours": "demòra{number, plural, one { # ora} other {n # oras}}",
+  "time_remaining.minutes": "demòr{number, plural, one { # minuta} other {nn # minutas}}",
+  "time_remaining.moments": "Moments restants",
+  "time_remaining.seconds": "demòra{number, plural, one { # segonda} other {n # segondas}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} ne charra other {people}} ne charran",
   "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.",
   "upload_area.title": "Lisatz e depausatz per mandar",
   "upload_button.label": "Ajustar un mèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.limit": "Talha maximum pels mandadís subrepassada.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descripcion pels mal vesents",
   "upload_form.focus": "Modificar l’apercebut",
   "upload_form.undo": "Suprimir",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index d387aa87f..9d7bef774 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -136,7 +136,8 @@
   "empty_column.lists": "Nie masz żadnych list. Kiedy utworzysz jedną, pojawi się tutaj.",
   "empty_column.mutes": "Nie wyciszyłeś(-aś) jeszcze żadnego użytkownika.",
   "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
-  "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić",
+  "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych serwerów, aby to wyświetlić",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autoryzuj",
   "follow_request.reject": "Odrzuć",
   "getting_started.developers": "Dla programistów",
@@ -159,9 +160,9 @@
   "home.column_settings.basic": "Podstawowe",
   "home.column_settings.show_reblogs": "Pokazuj podbicia",
   "home.column_settings.show_replies": "Pokazuj odpowiedzi",
-  "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}",
-  "intervals.full.hours": "{number, plural, one {# godzina} few {# godziny} many {# godzin} other {# godzin}}",
   "intervals.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}",
+  "intervals.full.hours": "{number, plural, one {# godzina} few {# godziny} many {# godzin} other {# godzin}}",
+  "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}",
   "introduction.federation.action": "Dalej",
   "introduction.federation.federated.headline": "Oś czasu federacji",
   "introduction.federation.federated.text": "Publiczne wpisy osób z tego całego Fediwersum pojawiają się na lokalnej osi czasu.",
@@ -297,7 +298,7 @@
   "reply_indicator.cancel": "Anuluj",
   "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 Twojej instancji. Poniżej możesz też umieścić wyjaśnienie dlaczego zgłaszasz to konto:",
+  "report.hint": "Zgłoszenie zostanie wysłane moderatorom Twojego serwera. Poniżej możesz też umieścić wyjaśnienie dlaczego zgłaszasz to konto:",
   "report.placeholder": "Dodatkowe komentarze",
   "report.submit": "Wyślij",
   "report.target": "Zgłaszanie {target}",
@@ -370,6 +371,7 @@
   "upload_area.title": "Przeciągnij i upuść aby wysłać",
   "upload_button.label": "Dodaj zawartość multimedialną (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Przekroczono limit plików do wysłania.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
   "upload_form.focus": "Dopasuj podgląd",
   "upload_form.undo": "Usuń",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 368663a01..9562ce648 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.",
   "compose_form.lock_disclaimer.lock": "trancada",
   "compose_form.placeholder": "No que você está pensando?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Publicar",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Mídia está marcada como sensível",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Você ainda não silenciou nenhum usuário.",
   "empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.",
   "empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Rejeitar",
   "getting_started.developers": "Desenvolvedores",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar compartilhamentos",
   "home.column_settings.show_replies": "Mostrar as respostas",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Próximo",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Posts públicos de outros servidores do fediverso vão aparecer na timeline global.",
@@ -263,7 +271,9 @@
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.vote": "Votar",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ajustar a privacidade da mensagem",
   "privacy.direct.long": "Apenas para usuários mencionados",
   "privacy.direct.short": "Direta",
@@ -356,6 +366,7 @@
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar mídia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descreva a imagem para deficientes visuais",
   "upload_form.focus": "Ajustar foco",
   "upload_form.undo": "Remover",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index c9a7cd6a3..fa2ade697 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -1,44 +1,44 @@
 {
-  "account.add_or_remove_from_list": "Add or Remove from lists",
-  "account.badges.bot": "Bot",
+  "account.add_or_remove_from_list": "Adicionar ou remover das listas",
+  "account.badges.bot": "Robô",
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Esconder tudo do domínio {domain}",
-  "account.blocked": "Blocked",
-  "account.direct": "Direct Message @{name}",
-  "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
-  "account.domain_blocked": "Domain hidden",
+  "account.blocked": "Bloqueado",
+  "account.direct": "Mensagem directa @{name}",
+  "account.disclaimer_full": "As informações abaixo podem reflectir o perfil do utilizador de forma incompleta.",
+  "account.domain_blocked": "Domínio escondido",
   "account.edit_profile": "Editar perfil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Atributo no perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Ainda ninguém segue este utilizador.",
   "account.follows": "Segue",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Este utilizador ainda não segue alguém.",
   "account.follows_you": "É teu seguidor",
   "account.hide_reblogs": "Esconder partilhas de @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.link_verified_on": "A posse deste link foi verificada em {date}",
+  "account.locked_info": "O estatuto de privacidade desta conta é fechado. O dono revê manualmente que a pode seguir.",
   "account.media": "Media",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} mudou a sua conta para:",
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
-  "account.muted": "Muted",
-  "account.posts": "Posts",
-  "account.posts_with_replies": "Toots with replies",
+  "account.muted": "Silenciada",
+  "account.posts": "Publicações",
+  "account.posts_with_replies": "Publicações e respostas",
   "account.report": "Denunciar @{name}",
-  "account.requested": "A aguardar aprovação",
+  "account.requested": "A aguardar aprovação. Clique para cancelar o pedido de seguimento",
   "account.share": "Partilhar o perfil @{name}",
   "account.show_reblogs": "Mostrar partilhas de @{name}",
-  "account.unblock": "Não bloquear @{name}",
+  "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Mostrar {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Não mostrar no perfil",
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Não silenciar @{name}",
   "account.unmute_notifications": "Deixar de silenciar @{name}",
   "account.view_full_profile": "Ver perfil completo",
-  "alert.unexpected.message": "An unexpected error occurred.",
-  "alert.unexpected.title": "Oops!",
+  "alert.unexpected.message": "Ocorreu um erro inesperado.",
+  "alert.unexpected.title": "Bolas!",
   "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
   "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
   "bundle_column_error.retry": "Tente de novo",
@@ -47,17 +47,17 @@
   "bundle_modal_error.message": "Algo de errado aconteceu enquanto este componente era carregado.",
   "bundle_modal_error.retry": "Tente de novo",
   "column.blocks": "Utilizadores Bloqueados",
-  "column.community": "Local",
-  "column.direct": "Direct messages",
-  "column.domain_blocks": "Hidden domains",
+  "column.community": "Cronologia local",
+  "column.direct": "Mensagens directas",
+  "column.domain_blocks": "Domínios escondidos",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores Pendentes",
   "column.home": "Início",
   "column.lists": "Listas",
   "column.mutes": "Utilizadores silenciados",
   "column.notifications": "Notificações",
-  "column.pins": "Posts fixos",
-  "column.public": "Global",
+  "column.pins": "Publicações fixas",
+  "column.public": "Cronologia federativa",
   "column_back_button.label": "Voltar",
   "column_header.hide_settings": "Esconder preferências",
   "column_header.moveLeft_settings": "Mover coluna para a esquerda",
@@ -66,41 +66,45 @@
   "column_header.show_settings": "Mostrar preferências",
   "column_header.unpin": "Desafixar",
   "column_subheading.settings": "Preferências",
-  "community.column_settings.media_only": "Media Only",
-  "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
-  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "community.column_settings.media_only": "Somente media",
+  "compose_form.direct_message_warning": "Esta publicação só  será enviada para os utilizadores mencionados.",
+  "compose_form.direct_message_warning_learn_more": "Aprender mais",
   "compose_form.hashtag_warning": "Esta pulbicacção não será listada em nenhuma hashtag por ser não listada. Somente publicações públicas podem ser pesquisadas por hashtag.",
   "compose_form.lock_disclaimer": "A tua conta não está {locked}. Qualquer pessoa pode seguir-te e ver as publicações direcionadas apenas a seguidores.",
-  "compose_form.lock_disclaimer.lock": "bloqueada",
+  "compose_form.lock_disclaimer.lock": "fechada",
   "compose_form.placeholder": "Em que estás a pensar?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Publicar",
-  "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.marked": "Media is marked as sensitive",
-  "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
-  "compose_form.spoiler.marked": "Text is hidden behind warning",
-  "compose_form.spoiler.unmarked": "Text is not hidden",
-  "compose_form.spoiler_placeholder": "Aviso de conteúdo",
+  "compose_form.publish_loud": "{publicar}!",
+  "compose_form.sensitive.marked": "Media marcado como sensível",
+  "compose_form.sensitive.unmarked": "Media não está marcado como sensível",
+  "compose_form.spoiler.marked": "Texto escondido atrás de aviso",
+  "compose_form.spoiler.unmarked": "O texto não está escondido",
+  "compose_form.spoiler_placeholder": "Escreve o teu aviso aqui",
   "confirmation_modal.cancel": "Cancelar",
-  "confirmations.block.confirm": "Block",
+  "confirmations.block.confirm": "Bloquear",
   "confirmations.block.message": "De certeza que queres bloquear {name}?",
   "confirmations.delete.confirm": "Eliminar",
   "confirmations.delete.message": "De certeza que queres eliminar esta publicação?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "Apagar",
   "confirmations.delete_list.message": "Tens a certeza de que desejas apagar permanentemente esta lista?",
   "confirmations.domain_block.confirm": "Esconder tudo deste domínio",
-  "confirmations.domain_block.message": "De certeza que queres bloquear por completo o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado.",
+  "confirmations.domain_block.message": "De certeza que queres bloquear completamente o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado. Não irás ver conteúdo daquele domínio em cronologia alguma, nem nas tuas notificações. Os teus seguidores daquele domínio serão removidos.",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.mute.message": "De certeza que queres silenciar {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
-  "confirmations.reply.confirm": "Reply",
-  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.redraft.confirm": "Apagar & redigir",
+  "confirmations.redraft.message": "Tens a certeza que queres apagar e redigir esta publicação?  Os favoritos e as partilhas perder-se-ão e as respostas à publicação original ficarão órfãs.",
+  "confirmations.reply.confirm": "Responder",
+  "confirmations.reply.message": "Responder agora irá reescrever a mensagem que estás a compor actualmente. Tens a certeza que queres continuar?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "De certeza que queres deixar de seguir {name}?",
-  "embed.instructions": "Publicar este post num outro site copiando o código abaixo.",
+  "embed.instructions": "Publica esta publicação no teu site copiando o código abaixo.",
   "embed.preview": "Podes ver aqui como irá ficar:",
   "emoji_button.activity": "Actividade",
-  "emoji_button.custom": "Especiais",
+  "emoji_button.custom": "Personalizar",
   "emoji_button.flags": "Bandeiras",
   "emoji_button.food": "Comida & Bebida",
   "emoji_button.label": "Inserir Emoji",
@@ -113,89 +117,93 @@
   "emoji_button.search_results": "Resultados da pesquisa",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
-  "empty_column.account_timeline": "No toots here!",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.account_timeline": "Sem publicações!",
+  "empty_column.blocks": "Ainda não bloqueaste qualquer utilizador.",
   "empty_column.community": "Ainda não existe conteúdo local para mostrar!",
-  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
-  "empty_column.domain_blocks": "There are no hidden domains yet.",
-  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
-  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
-  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.direct": "Ainda não tens qualquer mensagem directa. Quando enviares ou receberes alguma, ela irá aparecer aqui.",
+  "empty_column.domain_blocks": "Ainda não há qualquer domínio escondido.",
+  "empty_column.favourited_statuses": "Ainda não tens quaisquer publicações favoritas. Quando tiveres alguma, ela irá aparecer aqui.",
+  "empty_column.favourites": "Ainda ninguém favorizou esta publicação. Quando alguém o fizer, ela irá aparecer aqui.",
+  "empty_column.follow_requests": "Ainda não tens pedido de seguimento algum. Quando receberes algum, ele irá aparecer aqui.",
   "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.",
   "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
-  "empty_column.home.public_timeline": "global",
+  "empty_column.home.public_timeline": "Cronologia pública",
   "empty_column.list": "Ainda não existem publicações nesta lista. Quando membros desta lista fizerem novas publicações, elas aparecerão aqui.",
-  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
-  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.lists": "Ainda não tens qualquer lista. Quando criares uma, ela irá aparecer aqui.",
+  "empty_column.mutes": "Ainda não silenciaste qualquer utilizador.",
   "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
-  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos",
+  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para veres aqui os conteúdos públicos",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Rejeitar",
-  "getting_started.developers": "Developers",
-  "getting_started.directory": "Profile directory",
+  "getting_started.developers": "Responsáveis pelo desenvolvimento",
+  "getting_started.directory": "Directório de perfil",
   "getting_started.documentation": "Documentation",
   "getting_started.heading": "Primeiros passos",
-  "getting_started.invite": "Invite people",
+  "getting_started.invite": "Convidar pessoas",
   "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}.",
-  "getting_started.security": "Security",
-  "getting_started.terms": "Terms of service",
-  "hashtag.column_header.tag_mode.all": "and {additional}",
-  "hashtag.column_header.tag_mode.any": "or {additional}",
-  "hashtag.column_header.tag_mode.none": "without {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
-  "hashtag.column_settings.tag_mode.all": "All of these",
-  "hashtag.column_settings.tag_mode.any": "Any of these",
-  "hashtag.column_settings.tag_mode.none": "None of these",
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "getting_started.security": "Segurança",
+  "getting_started.terms": "Termos de serviço",
+  "hashtag.column_header.tag_mode.all": "e {additional}",
+  "hashtag.column_header.tag_mode.any": "ou {additional}",
+  "hashtag.column_header.tag_mode.none": "sem {additional}",
+  "hashtag.column_settings.select.no_options_message": "Não foram encontradas sugestões",
+  "hashtag.column_settings.select.placeholder": "Introduzir as hashtags…",
+  "hashtag.column_settings.tag_mode.all": "Todos estes",
+  "hashtag.column_settings.tag_mode.any": "Qualquer destes",
+  "hashtag.column_settings.tag_mode.none": "Nenhum destes",
+  "hashtag.column_settings.tag_toggle": "Incluir etiquetas adicionais para esta coluna",
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar as partilhas",
   "home.column_settings.show_replies": "Mostrar as respostas",
-  "introduction.federation.action": "Next",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "introduction.federation.action": "Seguinte",
   "introduction.federation.federated.headline": "Federated",
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+  "introduction.federation.federated.text": "Publicações públicas de outros servidores do fediverse aparecerão na cronologia federativa.",
   "introduction.federation.home.headline": "Home",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.home.text": "As publicações das pessoas que tu segues aparecerão na tua coluna inicial. Tu podes seguir qualquer pessoa em qualquer servidor!",
   "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
-  "introduction.interactions.action": "Finish tutorial!",
-  "introduction.interactions.favourite.headline": "Favourite",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
-  "introduction.interactions.reblog.headline": "Boost",
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
-  "introduction.interactions.reply.headline": "Reply",
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
-  "introduction.welcome.action": "Let's go!",
-  "introduction.welcome.headline": "First steps",
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "introduction.federation.local.text": "Publicações públicas de pessoas que tu segues no teu servidor aparecerão na coluna local.",
+  "introduction.interactions.action": "Terminar o tutorial!",
+  "introduction.interactions.favourite.headline": "Favorito",
+  "introduction.interactions.favourite.text": "Tu podes guardar um toot para depois e deixar o autor saber que gostaste dele, favoritando-o.",
+  "introduction.interactions.reblog.headline": "Partilhar",
+  "introduction.interactions.reblog.text": "Podes partilhar os toots de outras pessoas com os teus seguidores partilhando-os.",
+  "introduction.interactions.reply.headline": "Responder",
+  "introduction.interactions.reply.text": "Tu podes responder a toots de outras pessoas e aos teus, o que os irá juntar numa conversa.",
+  "introduction.welcome.action": "Vamos!",
+  "introduction.welcome.headline": "Primeiros passos",
+  "introduction.welcome.text": "Bem-vindo ao fediverse! Em pouco tempo poderás enviar mensagens e falar com os teus amigos numa grande variedade de servidores. Mas este servidor, {domain}, é especial—ele alberga o teu perfil. Por isso, lembra-te do seu nome.",
   "keyboard_shortcuts.back": "para voltar",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.blocked": "para abrir a lista de utilizadores bloqueados",
   "keyboard_shortcuts.boost": "para partilhar",
   "keyboard_shortcuts.column": "para focar uma publicação numa das colunas",
   "keyboard_shortcuts.compose": "para focar na área de publicação",
   "keyboard_shortcuts.description": "Descrição",
-  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.direct": "para abrir a coluna das mensagens directas",
   "keyboard_shortcuts.down": "para mover para baixo na lista",
   "keyboard_shortcuts.enter": "para expandir uma publicação",
   "keyboard_shortcuts.favourite": "para adicionar aos favoritos",
-  "keyboard_shortcuts.favourites": "to open favourites list",
-  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.favourites": "para abrir a lista dos favoritos",
+  "keyboard_shortcuts.federated": "para abrir a cronologia federativa",
   "keyboard_shortcuts.heading": "Atalhos do teclado",
-  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.home": "para abrir a cronologia inicial",
   "keyboard_shortcuts.hotkey": "Atalho",
   "keyboard_shortcuts.legend": "para mostrar esta legenda",
-  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.local": "para abrir a cronologia local",
   "keyboard_shortcuts.mention": "para mencionar o autor",
-  "keyboard_shortcuts.muted": "to open muted users list",
-  "keyboard_shortcuts.my_profile": "to open your profile",
-  "keyboard_shortcuts.notifications": "to open notifications column",
-  "keyboard_shortcuts.pinned": "to open pinned toots list",
-  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.muted": "para abrir a lista dos utilizadores silenciados",
+  "keyboard_shortcuts.my_profile": "para abrir o teu perfil",
+  "keyboard_shortcuts.notifications": "para abrir a coluna das notificações",
+  "keyboard_shortcuts.pinned": "para abrir a lista dos toots fixados",
+  "keyboard_shortcuts.profile": "para abrir o perfil do autor",
   "keyboard_shortcuts.reply": "para responder",
-  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.requests": "para abrir a lista dos pedidos de seguimento",
   "keyboard_shortcuts.search": "para focar na pesquisa",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
-  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+  "keyboard_shortcuts.start": "para abrir a coluna dos \"primeiros passos\"",
+  "keyboard_shortcuts.toggle_hidden": "para mostrar/esconder texto atrás de CW",
   "keyboard_shortcuts.toot": "para compor um novo post",
   "keyboard_shortcuts.unfocus": "para remover o foco da área de publicação/pesquisa",
   "keyboard_shortcuts.up": "para mover para cima na lista",
@@ -206,7 +214,7 @@
   "lists.account.remove": "Remover da lista",
   "lists.delete": "Delete list",
   "lists.edit": "Editar lista",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Mudar o título",
   "lists.new.create": "Adicionar lista",
   "lists.new.title_placeholder": "Novo título da lista",
   "lists.search": "Pesquisa entre as pessoas que segues",
@@ -216,18 +224,18 @@
   "missing_indicator.label": "Não encontrado",
   "missing_indicator.sublabel": "Este recurso não foi encontrado",
   "mute_modal.hide_notifications": "Esconder notificações deste utilizador?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Aplicações móveis",
   "navigation_bar.blocks": "Utilizadores bloqueados",
   "navigation_bar.community_timeline": "Local",
-  "navigation_bar.compose": "Compose new toot",
-  "navigation_bar.direct": "Direct messages",
-  "navigation_bar.discover": "Discover",
-  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.compose": "Escrever novo toot",
+  "navigation_bar.direct": "Mensagens directas",
+  "navigation_bar.discover": "Descobrir",
+  "navigation_bar.domain_blocks": "Domínios escondidos",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
-  "navigation_bar.filters": "Muted words",
+  "navigation_bar.filters": "Palavras silenciadas",
   "navigation_bar.follow_requests": "Seguidores pendentes",
-  "navigation_bar.info": "Mais informações",
+  "navigation_bar.info": "Sobre este servidor",
   "navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
   "navigation_bar.lists": "Listas",
   "navigation_bar.logout": "Sair",
@@ -236,7 +244,7 @@
   "navigation_bar.pins": "Posts fixos",
   "navigation_bar.preferences": "Preferências",
   "navigation_bar.public_timeline": "Global",
-  "navigation_bar.security": "Security",
+  "navigation_bar.security": "Segurança",
   "notification.favourite": "{name} adicionou o teu post aos favoritos",
   "notification.follow": "{name} seguiu-te",
   "notification.mention": "{name} mencionou-te",
@@ -245,25 +253,27 @@
   "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
   "notifications.column_settings.alert": "Notificações no computador",
   "notifications.column_settings.favourite": "Favoritos:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Mostrar todas as categorias",
+  "notifications.column_settings.filter_bar.category": "Barra de filtros rápidos",
+  "notifications.column_settings.filter_bar.show": "Mostrar",
   "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.mention": "Menções:",
   "notifications.column_settings.push": "Notificações Push",
   "notifications.column_settings.reblog": "Partilhas:",
   "notifications.column_settings.show": "Mostrar nas colunas",
   "notifications.column_settings.sound": "Reproduzir som",
-  "notifications.filter.all": "All",
-  "notifications.filter.boosts": "Boosts",
-  "notifications.filter.favourites": "Favourites",
-  "notifications.filter.follows": "Follows",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.group": "{count} notifications",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "notifications.filter.all": "Todas",
+  "notifications.filter.boosts": "Partilhas",
+  "notifications.filter.favourites": "Favoritas",
+  "notifications.filter.follows": "Seguimento",
+  "notifications.filter.mentions": "Referências",
+  "notifications.group": "{count} notificações",
+  "poll.closed": "Fechado",
+  "poll.refresh": "Recarregar",
+  "poll.total_votes": "{contar, plural, um {# vote} outro {# votes}}",
+  "poll.vote": "Votar",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ajustar a privacidade da mensagem",
   "privacy.direct.long": "Apenas para utilizadores mencionados",
   "privacy.direct.short": "Directo",
@@ -281,36 +291,36 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
-  "report.forward": "Forward to {target}",
-  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
-  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.forward": "Reenviar para {target}",
+  "report.forward_hint": "A conta é de outro servidor. Enviar uma cópia anónima do relatório para lá também?",
+  "report.hint": "O relatório será enviado para os moderadores do teu servidor. Podes fornecer, em baixo, uma explicação do motivo pelo qual estás a relatar esta conta:",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
   "report.target": "Denunciar",
   "search.placeholder": "Pesquisar",
   "search_popout.search_format": "Formato avançado de pesquisa",
-  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.full_text": "Texto simples devolve publicações que tu escreveste, favoritaste, partilhaste ou em que foste mencionado, tal como nomes de utilizador correspondentes, alcunhas e hashtags.",
   "search_popout.tips.hashtag": "hashtag",
-  "search_popout.tips.status": "status",
+  "search_popout.tips.status": "estado",
   "search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
   "search_popout.tips.user": "utilizador",
-  "search_results.accounts": "People",
+  "search_results.accounts": "Pessoas",
   "search_results.hashtags": "Hashtags",
-  "search_results.statuses": "Toots",
+  "search_results.statuses": "Publicações",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Espreitar lá dentro...",
-  "status.admin_account": "Open moderation interface for @{name}",
-  "status.admin_status": "Open this status in the moderation interface",
+  "status.admin_account": "Abrir a interface de moderação para @{name}",
+  "status.admin_status": "Abrir esta publicação na interface de moderação",
   "status.block": "Block @{name}",
-  "status.cancel_reblog_private": "Unboost",
+  "status.cancel_reblog_private": "Não partilhar",
   "status.cannot_reblog": "Este post não pode ser partilhado",
-  "status.copy": "Copy link to status",
+  "status.copy": "Copiar o link para a publicação",
   "status.delete": "Eliminar",
-  "status.detailed_status": "Detailed conversation view",
-  "status.direct": "Direct message @{name}",
+  "status.detailed_status": "Vista de conversação detalhada",
+  "status.direct": "Mensagem directa @{name}",
   "status.embed": "Incorporar",
   "status.favourite": "Adicionar aos favoritos",
-  "status.filtered": "Filtered",
+  "status.filtered": "Filtrada",
   "status.load_more": "Carregar mais",
   "status.media_hidden": "Media escondida",
   "status.mention": "Mencionar @{name}",
@@ -319,13 +329,13 @@
   "status.mute_conversation": "Silenciar conversa",
   "status.open": "Expandir",
   "status.pin": "Fixar no perfil",
-  "status.pinned": "Pinned toot",
-  "status.read_more": "Read more",
+  "status.pinned": "Publicação fixa",
+  "status.read_more": "Ler mais",
   "status.reblog": "Partilhar",
-  "status.reblog_private": "Boost to original audience",
+  "status.reblog_private": "Partilhar com a audiência original",
   "status.reblogged_by": "{name} partilhou",
-  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
-  "status.redraft": "Delete & re-draft",
+  "status.reblogs.empty": "Ainda ninguém partilhou esta publicação. Quando alguém o fizer, ela irá aparecer aqui.",
+  "status.redraft": "Apagar & reescrever",
   "status.reply": "Responder",
   "status.replyAll": "Responder à conversa",
   "status.report": "Denunciar @{name}",
@@ -333,33 +343,34 @@
   "status.sensitive_warning": "Conteúdo sensível",
   "status.share": "Compartilhar",
   "status.show_less": "Mostrar menos",
-  "status.show_less_all": "Show less for all",
+  "status.show_less_all": "Mostrar menos para todas",
   "status.show_more": "Mostrar mais",
-  "status.show_more_all": "Show more for all",
-  "status.show_thread": "Show thread",
+  "status.show_more_all": "Mostrar mais para todas",
+  "status.show_thread": "Mostrar conversa",
   "status.unmute_conversation": "Deixar de silenciar esta conversa",
   "status.unpin": "Não fixar no perfil",
-  "suggestions.dismiss": "Dismiss suggestion",
-  "suggestions.header": "You might be interested in…",
+  "suggestions.dismiss": "Dispensar a sugestão",
+  "suggestions.header": "Tu podes estar interessado em…",
   "tabs_bar.federated_timeline": "Global",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
   "tabs_bar.notifications": "Notificações",
-  "tabs_bar.search": "Search",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "tabs_bar.search": "Pesquisar",
+  "time_remaining.days": "{número, plural, um {# day} outro {# days}} faltam",
+  "time_remaining.hours": "{número, plural, um {# hour} outro {# hours}} faltam",
+  "time_remaining.minutes": "{número, plural, um {# minute} outro {# minutes}} faltam",
+  "time_remaining.moments": "Momentos em falta",
+  "time_remaining.seconds": "{número, plural, um {# second} outro {# seconds}} faltam",
+  "trends.count_by_accounts": "{count} {rawCount, plural, uma {person} outra {people}} a falar",
   "ui.beforeunload": "O teu rascunho vai ser perdido se abandonares o Mastodon.",
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar media",
-  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.limit": "Limite máximo do ficheiro a carregar excedido.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descrição da imagem para pessoas com dificuldades visuais",
-  "upload_form.focus": "Crop",
-  "upload_form.undo": "Anular",
-  "upload_progress.label": "A gravar...",
+  "upload_form.focus": "Alterar previsualização",
+  "upload_form.undo": "Apagar",
+  "upload_progress.label": "A enviar...",
   "video.close": "Fechar vídeo",
   "video.exit_fullscreen": "Sair de full screen",
   "video.expand": "Expandir vídeo",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index a0d5f9a27..071fbb507 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Contul tău nu este {locked}. Oricine te poate urmări fără aprobarea ta și vedea toate postările tale.",
   "compose_form.lock_disclaimer.lock": "privat",
   "compose_form.placeholder": "La ce te gândești?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Postează",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Conținutul media este marcat ca sensibil",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Nu ai oprit nici un utilizator incă.",
   "empty_column.notifications": "Nu ai nici o notificare încă. Interacționează cu alții pentru a începe o conversație.",
   "empty_column.public": "Nu este nimci aici încă! Scrie ceva public, sau urmărește alți utilizatori din alte instanțe pentru a porni fluxul",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizează",
   "follow_request.reject": "Respinge",
   "getting_started.developers": "Dezvoltatori",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "De bază",
   "home.column_settings.show_reblogs": "Arată redistribuirile",
   "home.column_settings.show_replies": "Arată răspunsurile",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Următorul",
   "introduction.federation.federated.headline": "Federalizat",
   "introduction.federation.federated.text": "Postările publice de pe alte servere din rețea vor apărea in fluxul global.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Cine vede asta",
   "privacy.direct.long": "Postează doar pentru utilizatorii menționați",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Trage și eliberează pentru a încărca",
   "upload_button.label": "Adaugă media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Adaugă o descriere pentru persoanele cu deficiențe de vedere",
   "upload_form.focus": "Schimbă previzualizarea",
   "upload_form.undo": "Șterge",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 01c915d71..1e149c470 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.",
   "compose_form.lock_disclaimer.lock": "закрыт",
   "compose_form.placeholder": "О чем Вы думаете?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Трубить",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Медиафайлы не отмечены как чувствительные",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Вы ещё никого не заглушили.",
   "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
   "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Авторизовать",
   "follow_request.reject": "Отказать",
   "getting_started.developers": "Для разработчиков",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Основные",
   "home.column_settings.show_reblogs": "Показывать продвижения",
   "home.column_settings.show_replies": "Показывать ответы",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Изменить видимость статуса",
   "privacy.direct.long": "Показать только упомянутым",
   "privacy.direct.short": "Направленный",
@@ -356,6 +366,7 @@
   "upload_area.title": "Перетащите сюда, чтобы загрузить",
   "upload_button.label": "Добавить медиаконтент",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Описать для людей с нарушениями зрения",
   "upload_form.focus": "Обрезать",
   "upload_form.undo": "Отменить",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index c11bebce8..17b50522f 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Váš účet nie je {locked}. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
   "compose_form.lock_disclaimer.lock": "zamknutý",
   "compose_form.placeholder": "Čo máš na mysli?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Pošli",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Médiálny obsah je označený ako chúlostivý",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Ešte si nestĺmil žiadných užívateľov.",
   "empty_column.notifications": "Ešte nemáš žiadne oznámenia. Začni komunikovať s ostatnými, aby diskusia mohla začať.",
   "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne, alebo začni sledovať užívateľov z iných serverov, aby tu niečo pribudlo",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Povoľ prístup",
   "follow_request.reject": "Odmietni",
   "getting_started.developers": "Vývojári",
@@ -142,15 +147,18 @@
   "hashtag.column_header.tag_mode.all": "a {additional}",
   "hashtag.column_header.tag_mode.any": "alebo {additional}",
   "hashtag.column_header.tag_mode.none": "bez {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.select.no_options_message": "Žiadne návrhy neboli nájdené",
+  "hashtag.column_settings.select.placeholder": "Zadaj haštagy…",
   "hashtag.column_settings.tag_mode.all": "Všetky tieto",
   "hashtag.column_settings.tag_mode.any": "Hociktorý z týchto",
   "hashtag.column_settings.tag_mode.none": "Žiaden z týchto",
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "hashtag.column_settings.tag_toggle": "Vlož dodatočné haštagy pre tento stĺpec",
   "home.column_settings.basic": "Základné",
   "home.column_settings.show_reblogs": "Zobraziť povýšené",
   "home.column_settings.show_replies": "Ukázať odpovede",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Ďalej",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Verejné príspevky z ostatných serverov vo fediverse budú zobrazenie vo federovanej časovej osi.",
@@ -206,7 +214,7 @@
   "lists.account.remove": "Odobrať zo zoznamu",
   "lists.delete": "Vymazať list",
   "lists.edit": "Uprav zoznam",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Zmeň názov",
   "lists.new.create": "Pridaj zoznam",
   "lists.new.title_placeholder": "Názov nového zoznamu",
   "lists.search": "Vyhľadávajte medzi užívateľmi ktorých sledujete",
@@ -260,10 +268,12 @@
   "notifications.filter.follows": "Sledovania",
   "notifications.filter.mentions": "Iba spomenutia",
   "notifications.group": "{count} oboznámení",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
+  "poll.closed": "Uzatvorená",
+  "poll.refresh": "Aktualizuj",
+  "poll.total_votes": "{count, plural, one {# hlas} few {# hlasov} many {# hlasov} other {# hlasy}}",
+  "poll.vote": "Hlasuj",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Uprav súkromie príspevku",
   "privacy.direct.long": "Pošli iba spomenutým používateľom",
   "privacy.direct.short": "Súkromne",
@@ -289,11 +299,11 @@
   "report.target": "Nahlásenie {target}",
   "search.placeholder": "Hľadaj",
   "search_popout.search_format": "Pokročilé vyhľadávanie",
-  "search_popout.tips.full_text": "Jednoduchý textový výpis statusov ktoré si napísal/a, ktoré si obľúbil/a, povýšil/a, alebo aj tých, v ktorých si bol/a spomenutý/á, a potom všetky zadaniu odpovedajúce prezívky, mená a haštagy.",
+  "search_popout.tips.full_text": "Vráti jednoduchý textový výpis príspevkov ktoré si napísal/a, ktoré si obľúbil/a, povýšil/a, alebo aj tých, v ktorých si bol/a spomenutý/á, a potom všetky zadaniu odpovedajúce prezívky, mená a haštagy.",
   "search_popout.tips.hashtag": "haštag",
   "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy",
-  "search_popout.tips.user": "používateľ",
+  "search_popout.tips.text": "Vráti jednoduchý textový výpis zhodujúcich sa mien, prezývok a haštagov",
+  "search_popout.tips.user": "užívateľ",
   "search_results.accounts": "Ľudia",
   "search_results.hashtags": "Haštagy",
   "search_results.statuses": "Príspevky",
@@ -304,7 +314,7 @@
   "status.block": "Blokovať @{name}",
   "status.cancel_reblog_private": "Nezdieľaj",
   "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
-  "status.copy": "Copy link to status",
+  "status.copy": "Skopíruj odkaz na príspevok",
   "status.delete": "Zmazať",
   "status.detailed_status": "Podrobný náhľad celej konverzácie",
   "status.direct": "Súkromná správa @{name}",
@@ -346,16 +356,17 @@
   "tabs_bar.local_timeline": "Lokálna",
   "tabs_bar.notifications": "Notifikácie",
   "tabs_bar.search": "Hľadaj",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.days": "Zostáva {number, plural, one {# deň} few {# dní} many {# dni} other {# dni}}",
+  "time_remaining.hours": "Zostáva {number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodiny}}",
+  "time_remaining.minutes": "Zostáva {number, plural, one {# minúta} few {# minút} many {# minút} other {# minúty}}",
+  "time_remaining.moments": "Ostáva už iba chviľka",
+  "time_remaining.seconds": "Zostáva {number, plural, one {# sekunda} few {# sekúnd} many {# sekúnd} other {# sekundy}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {človek vraví} other {ľudia vravia}}",
   "ui.beforeunload": "Čo máš rozpísané sa stratí, ak opustíš Mastodon.",
   "upload_area.title": "Pretiahni a pusť pre nahratie",
   "upload_button.label": "Pridať médiálny súbor (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.limit": "Limit pre nahrávanie súborov bol prekročený.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Opis pre slabo vidiacich",
   "upload_form.focus": "Pozmeň náhľad",
   "upload_form.undo": "Vymaž",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index b2404d178..9ee2e5b52 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Vaš račun ni {locked}. Vsakdo vam lahko sledi in si ogleda objave, ki so namenjene samo sledilcem.",
   "compose_form.lock_disclaimer.lock": "zaklenjen",
   "compose_form.placeholder": "O čem razmišljaš?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tutni",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Medij je označen kot občutljiv",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Nimate še nobenih obvestil. Poveži se z drugimi, da začnete pogovor.",
   "empty_column.public": "Tukaj ni ničesar! Da ga napolnite, napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih vozlišč",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Odobri",
   "follow_request.reject": "Zavrni",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Osnovno",
   "home.column_settings.show_reblogs": "Pokaži sunke",
   "home.column_settings.show_replies": "Pokaži odgovore",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Povlecite in spustite za pošiljanje",
   "upload_button.label": "Dodaj medij",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Opišite za slabovidne",
   "upload_form.focus": "Obreži",
   "upload_form.undo": "Izbriši",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index 9aaec4b46..498b1bdd6 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Llogaria juaj s’është {locked}. Mund ta ndjekë cilido, për të parë postimet tuaja vetëm për ndjekësit.",
   "compose_form.lock_disclaimer.lock": "e bllokuar",
   "compose_form.placeholder": "Ç’bluani në mendje?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Mesazh",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media është shënuar si rezervat",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "S’keni heshtuar ende ndonjë përdorues.",
   "empty_column.notifications": "Ende s’keni ndonjë njoftim. Ndërveproni me të tjerët që të nisë biseda.",
   "empty_column.public": "S’ka gjë këtu! Shkruani diçka publikisht, ose ndiqni dorazi përdorues prej instancash të tjera, që ta mbushni këtë zonë",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Autorizoje",
   "follow_request.reject": "Hidhe tej",
   "getting_started.developers": "Zhvillues",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Bazë",
   "home.column_settings.show_reblogs": "Shfaq përforcime",
   "home.column_settings.show_replies": "Shfaq përgjigje",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Pasuesi",
   "introduction.federation.federated.headline": "Të federuara",
   "introduction.federation.federated.text": "Postimet publike nga shërbyes të tjerë të fediversit do të shfaqen te rrjedha kohore e të federuarve.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Rregulloni privatësi gjendje",
   "privacy.direct.long": "Postoja vetëm përdoruesve të përmendur",
   "privacy.direct.short": "I drejtpërdrejtë",
@@ -356,6 +366,7 @@
   "upload_area.title": "Merreni & vëreni që të ngarkohet",
   "upload_button.label": "Shtoni media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "U tejkalua kufi ngarkimi kartelash.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Përshkruajeni për persona me probleme shikimi",
   "upload_form.focus": "Ndryshoni parapamjen",
   "upload_form.undo": "Fshije",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 59dc24ab3..abc1df5de 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Vaš nalog nije {locked}. Svako može da Vas zaprati i da vidi objave namenjene samo Vašim pratiocima.",
   "compose_form.lock_disclaimer.lock": "zaključan",
   "compose_form.placeholder": "Šta Vam je na umu?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Tutni",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Trenutno nemate obaveštenja. Družite se malo da započnete razgovore.",
   "empty_column.public": "Ovde nema ničega! Napišite nešto javno, ili nađite korisnike sa drugih instanci koje ćete zapratiti da popunite ovu prazninu",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Odobri",
   "follow_request.reject": "Odbij",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Osnovno",
   "home.column_settings.show_reblogs": "Prikaži i podržavanja",
   "home.column_settings.show_replies": "Prikaži odgovore",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Podesi status privatnosti",
   "privacy.direct.long": "Objavi samo korisnicima koji su pomenuti",
   "privacy.direct.short": "Direktno",
@@ -356,6 +366,7 @@
   "upload_area.title": "Prevucite ovde da otpremite",
   "upload_button.label": "Dodaj multimediju",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Opiši za slabovide osobe",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Opozovi",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 1097d48fb..ac7f97c6d 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Ваш налог није {locked}. Свако може да Вас запрати и да види објаве намењене само Вашим пратиоцима.",
   "compose_form.lock_disclaimer.lock": "закључан",
   "compose_form.placeholder": "Шта Вам је на уму?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Труби",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Медији су означени као осетљиви",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Још увек немате ућутканих корисника.",
   "empty_column.notifications": "Тренутно немате обавештења. Дружите се мало да започнете разговор.",
   "empty_column.public": "Овде нема ничега! Напишите нешто јавно, или нађите кориснике са других инстанци које ћете запратити да попуните ову празнину",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Одобри",
   "follow_request.reject": "Одбиј",
   "getting_started.developers": "Програмери",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Основно",
   "home.column_settings.show_reblogs": "Прикажи и подржавања",
   "home.column_settings.show_replies": "Прикажи одговоре",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Подеси статус приватности",
   "privacy.direct.long": "Објави само корисницима који су поменути",
   "privacy.direct.short": "Директно",
@@ -356,6 +366,7 @@
   "upload_area.title": "Превуците овде да отпремите",
   "upload_button.label": "Додај мултимедију (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Опишите за особе са оштећеним видом",
   "upload_form.focus": "Подесите",
   "upload_form.undo": "Обриши",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 90ac623af..cd002ee02 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Vad funderar du på?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media har markerats som känsligt",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.",
   "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Godkänn",
   "follow_request.reject": "Avvisa",
   "getting_started.developers": "Utvecklare",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Grundläggande",
   "home.column_settings.show_reblogs": "Visa knuffar",
   "home.column_settings.show_replies": "Visa svar",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Justera sekretess",
   "privacy.direct.long": "Skicka endast till nämnda användare",
   "privacy.direct.short": "Direkt",
@@ -356,6 +366,7 @@
   "upload_area.title": "Dra & släpp för att ladda upp",
   "upload_button.label": "Lägg till media",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Beskriv för synskadade",
   "upload_form.focus": "Beskär",
   "upload_form.undo": "Ta bort",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index 21f066439..9f8f797c8 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Authorize",
   "follow_request.reject": "Reject",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Delete",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index 806bc9f6f..02896333e 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "మీ ఖాతా {locked} చేయబడలేదు. ఎవరైనా మిమ్మల్ని అనుసరించి మీ అనుచరులకు-మాత్రమే పోస్ట్లను వీక్షించవచ్చు.",
   "compose_form.lock_disclaimer.lock": "బిగించబడినది",
   "compose_form.placeholder": "మీ మనస్సులో ఏముంది?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "టూట్",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "మీడియా సున్నితమైనదిగా గుర్తించబడింది",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "మీరు ఇంకా ఏ వినియోగదారులనూ మ్యూట్ చేయలేదు.",
   "empty_column.notifications": "మీకు ఇంకా ఏ నోటిఫికేషన్లు లేవు. సంభాషణను ప్రారంభించడానికి ఇతరులతో ప్రతిస్పందించండి.",
   "empty_column.public": "ఇక్కడ ఏమీ లేదు! దీన్ని నింపడానికి బహిరంగంగా ఏదైనా వ్రాయండి, లేదా ఇతర దృష్టాంతాల్లోని వినియోగదారులను అనుసరించండి",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "అనుమతించు",
   "follow_request.reject": "తిరస్కరించు",
   "getting_started.developers": "డెవలపర్లు",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "ప్రాథమిక",
   "home.column_settings.show_reblogs": "బూస్ట్ లను చూపించు",
   "home.column_settings.show_replies": "ప్రత్యుత్తరాలను చూపించు",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "తరువాత",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "ఫెడివర్స్ లోని ఇతర సర్వర్లకు చెందిన పబ్లిక్ టూట్లు ఫెడరేటెడ్ టైంలైన్ లో కనిపిస్తాయి.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "స్టేటస్ గోప్యతను సర్దుబాటు చేయండి",
   "privacy.direct.long": "పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయి",
   "privacy.direct.short": "ప్రత్యక్ష",
@@ -356,6 +366,7 @@
   "upload_area.title": "అప్లోడ్ చేయడానికి డ్రాగ్ & డ్రాప్ చేయండి",
   "upload_button.label": "మీడియాను జోడించండి (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "దృష్టి లోపమున్న వారి కోసం వివరించండి",
   "upload_form.focus": "ప్రివ్యూను మార్చు",
   "upload_form.undo": "తొలగించు",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 96c1a422b..6d120ac76 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Authorize",
   "follow_request.reject": "Reject",
   "getting_started.developers": "Developers",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -356,6 +366,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 62bff6cb2..28b34fa04 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Hesabınız {locked} değil. Sadece takipçilerle paylaştığınız gönderileri görebilmek için sizi herhangi bir kullanıcı takip edebilir.",
   "compose_form.lock_disclaimer.lock": "kilitli",
   "compose_form.placeholder": "Aklınızdan ne geçiyor?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Medya hassas olarak işaretlendi",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "Henüz hiçbir kullanıcıyı sessize almadınız.",
   "empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.",
   "empty_column.public": "Burada hiçbir şey yok! Herkese açık bir şeyler yazın veya burayı doldurmak için diğer sunuculardaki kullanıcıları takip edin",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Yetkilendir",
   "follow_request.reject": "Reddet",
   "getting_started.developers": "Geliştiriciler",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Temel",
   "home.column_settings.show_reblogs": "Boost edilenleri göster",
   "home.column_settings.show_replies": "Cevapları göster",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "İleri",
   "introduction.federation.federated.headline": "Birleşik",
   "introduction.federation.federated.text": "Diğer dosya sunucularından gelen genel yayınlar, birleşik zaman çizelgesinde görünecektir.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Gönderi gizliliğini ayarla",
   "privacy.direct.long": "Sadece bahsedilen kişilere gönder",
   "privacy.direct.short": "Direkt",
@@ -356,6 +366,7 @@
   "upload_area.title": "Upload için sürükle bırak yapınız",
   "upload_button.label": "Görsel ekle",
   "upload_error.limit": "Dosya yükleme sınırı aşıldı.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Geri al",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 02ecc9689..bd492e9e9 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.",
   "compose_form.lock_disclaimer.lock": "приватний",
   "compose_form.placeholder": "Що у Вас на думці?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Дмухнути",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Медіа відмічене <b>несприйнятливим</b>",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "У вас ще немає сповіщень. Переписуйтесь з іншими користувачами, щоб почати розмову.",
   "empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших інстанцій, щоб заповнити стрічку",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "Авторизувати",
   "follow_request.reject": "Відмовити",
   "getting_started.developers": "Розробникам",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "Основні",
   "home.column_settings.show_reblogs": "Показувати передмухи",
   "home.column_settings.show_replies": "Показувати відповіді",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Змінити видимість допису",
   "privacy.direct.long": "Показати тільки згаданим користувачам",
   "privacy.direct.short": "Направлений",
@@ -356,6 +366,7 @@
   "upload_area.title": "Перетягніть сюди, щоб завантажити",
   "upload_button.label": "Додати медіаконтент",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Опишіть для людей з вадами зору",
   "upload_form.focus": "Обрізати",
   "upload_form.undo": "Видалити",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 9941d99d1..ae319d232 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。",
   "compose_form.lock_disclaimer.lock": "开启保护",
   "compose_form.placeholder": "在想啥?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "嘟嘟",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "媒体已被标记为敏感内容",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "你还没有收到过任何通知,快向其他用户搭讪吧。",
   "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户后,这里就会有嘟文出现了哦!",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "同意",
   "follow_request.reject": "拒绝",
   "getting_started.developers": "开发",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "基本设置",
   "home.column_settings.show_reblogs": "显示转嘟",
   "home.column_settings.show_replies": "显示回复",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "设置嘟文可见范围",
   "privacy.direct.long": "只有被提及的用户能看到",
   "privacy.direct.short": "私信",
@@ -356,6 +366,7 @@
   "upload_area.title": "将文件拖放到此处开始上传",
   "upload_button.label": "上传媒体文件",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "为视觉障碍人士添加文字说明",
   "upload_form.focus": "剪裁",
   "upload_form.undo": "取消上传",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 7e1cf15e6..4fde7ef45 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "你的用戶狀態為「{locked}」,任何人都能立即關注你,然後看到「只有關注者能看」的文章。",
   "compose_form.lock_disclaimer.lock": "公共",
   "compose_form.placeholder": "你在想甚麼?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "發文",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "媒體被標示為敏感",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
   "empty_column.public": "跨站時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "批准",
   "follow_request.reject": "拒絕",
   "getting_started.developers": "開發者",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "基本",
   "home.column_settings.show_reblogs": "顯示被轉推的文章",
   "home.column_settings.show_replies": "顯示回應文章",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "調整私隱設定",
   "privacy.direct.long": "只有提及的用戶能看到",
   "privacy.direct.short": "私人訊息",
@@ -356,6 +366,7 @@
   "upload_area.title": "將檔案拖放至此上載",
   "upload_button.label": "上載媒體檔案",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "為視覺障礙人士添加文字說明",
   "upload_form.focus": "裁切",
   "upload_form.undo": "刪除",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index c2e807103..9a116a702 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -73,6 +73,10 @@
   "compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的嘟文。",
   "compose_form.lock_disclaimer.lock": "上鎖",
   "compose_form.placeholder": "在想些什麼?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "嘟掉",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "此媒體已被標註為敏感的",
@@ -129,6 +133,7 @@
   "empty_column.mutes": "你還沒有靜音任何使用者。",
   "empty_column.notifications": "還沒有任何通知。和別的使用者互動來開始對話。",
   "empty_column.public": "這裡什麼都沒有! 寫一些公開的嘟文,或著關注其他站點的使用者後,這裡就會有嘟文出現了",
+  "error_boundary.it_crashed": "It crashed!",
   "follow_request.authorize": "授權",
   "follow_request.reject": "拒絕",
   "getting_started.developers": "開發",
@@ -151,6 +156,9 @@
   "home.column_settings.basic": "基本",
   "home.column_settings.show_reblogs": "顯示轉推",
   "home.column_settings.show_replies": "顯示回應",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -264,6 +272,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "調整隱私狀態",
   "privacy.direct.long": "只有被提到的使用者能看到",
   "privacy.direct.short": "私訊",
@@ -356,6 +366,7 @@
   "upload_area.title": "拖放來上傳",
   "upload_button.label": "上傳媒體檔案 (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "為視障人士增加文字說明",
   "upload_form.focus": "裁切",
   "upload_form.undo": "刪除",
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index 465ef2c11..d3b4a5909 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -657,7 +657,7 @@ $small-breakpoint: 960px;
     display: flex;
     justify-content: center;
     align-items: center;
-    padding: 100px;
+    padding: 50px;
 
     img {
       height: 52px;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index cec59eb1a..5b86778bb 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -357,6 +357,11 @@
     padding-bottom: 0;
     padding-right: 10px + 22px;
     resize: none;
+    scrollbar-color: initial;
+
+    &::-webkit-scrollbar {
+      all: unset;
+    }
 
     @media screen and (max-width: 600px) {
       height: 100px !important; // prevent auto-resize textarea
@@ -3334,11 +3339,11 @@ a.status-card.compact:hover {
   box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
   overflow: hidden;
 
-  button {
+  li {
     display: block;
     cursor: pointer;
     border: 0;
-    padding: 4px 8px;
+    padding: 3px 8px;
     background: transparent;
 
     &:hover,
diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss
index e49084b5f..94578ffee 100644
--- a/app/javascript/styles/mastodon/emoji_picker.scss
+++ b/app/javascript/styles/mastodon/emoji_picker.scss
@@ -1,3 +1,5 @@
+@import '~emoji-mart/css/emoji-mart.css';
+
 .emoji-mart {
   &,
   * {
@@ -51,6 +53,14 @@
 
   &:hover {
     color: darken($lighter-text-color, 4%);
+
+    svg {
+      fill: darken($lighter-text-color, 4%);
+    }
+  }
+
+  svg {
+    fill: $lighter-text-color;
   }
 }
 
@@ -59,11 +69,19 @@
 
   &:hover {
     color: darken($highlight-text-color, 4%);
+
+    svg {
+      fill: darken($highlight-text-color, 4%);
+    }
   }
 
   .emoji-mart-anchor-bar {
     bottom: -1px;
   }
+
+  svg {
+    fill: $highlight-text-color;
+  }
 }
 
 .emoji-mart-anchor-bar {
@@ -83,7 +101,6 @@
   }
 
   svg {
-    fill: currentColor;
     max-height: 18px;
   }
 }
@@ -103,15 +120,14 @@
 }
 
 .emoji-mart-search {
-  padding: 10px;
-  padding-right: 45px;
+  margin: 10px 40px 10px 5px;
   background: $simple-background-color;
 
   input {
     font-size: 14px;
     font-weight: 400;
     padding: 7px 9px;
-    font-family: inherit;
+    font-family: $font-sans-serif;
     display: block;
     width: 100%;
     background: rgba($ui-secondary-color, 0.3);
@@ -166,6 +182,7 @@
     font-weight: 500;
     padding: 5px 6px;
     background: $simple-background-color;
+    font-family: $font-sans-serif;
   }
 }
 
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 6051c1d00..9ef45e425 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -801,3 +801,58 @@ code {
     }
   }
 }
+
+.connection-prompt {
+  margin-bottom: 25px;
+
+  .fa-link {
+    background-color: darken($ui-base-color, 4%);
+    border-radius: 100%;
+    font-size: 24px;
+    padding: 10px;
+  }
+
+  &__column {
+    align-items: center;
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+    flex-shrink: 1;
+
+    &-sep {
+      flex-grow: 0;
+      overflow: visible;
+      position: relative;
+      z-index: 1;
+    }
+  }
+
+  .account__avatar {
+    margin-bottom: 20px;
+  }
+
+  &__connection {
+    background-color: lighten($ui-base-color, 8%);
+    box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
+    border-radius: 4px;
+    padding: 25px 10px;
+    position: relative;
+    text-align: center;
+
+    &::after {
+      background-color: darken($ui-base-color, 4%);
+      content: '';
+      display: block;
+      height: 100%;
+      left: 50%;
+      position: absolute;
+      width: 1px;
+    }
+  }
+
+  &__row {
+    align-items: center;
+    display: flex;
+    flex-direction: row;
+  }
+}
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 9e8785679..11ac6dfeb 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -140,6 +140,19 @@ a.table-action-link {
       input {
         margin-top: 8px;
       }
+
+      &--aligned {
+        display: flex;
+        align-items: center;
+
+        input {
+          margin-top: 0;
+        }
+      }
+
+      @media screen and (max-width: $no-gap-breakpoint) {
+        display: none;
+      }
     }
 
     &__actions,
@@ -161,6 +174,10 @@ a.table-action-link {
       text-align: right;
       padding-right: 16px - 5px;
     }
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      display: none;
+    }
   }
 
   &__row {
@@ -168,6 +185,12 @@ a.table-action-link {
     border-top: 0;
     background: darken($ui-base-color, 4%);
 
+    @media screen and (max-width: $no-gap-breakpoint) {
+      &:first-child {
+        border-top: 1px solid darken($ui-base-color, 8%);
+      }
+    }
+
     &:hover {
       background: darken($ui-base-color, 2%);
     }
@@ -183,6 +206,10 @@ a.table-action-link {
     &__content {
       padding-top: 12px;
       padding-bottom: 16px;
+
+      &--unpadded {
+        padding: 0;
+      }
     }
   }
 
@@ -197,4 +224,20 @@ a.table-action-link {
       font-weight: 700;
     }
   }
+
+  .nothing-here {
+    border: 1px solid darken($ui-base-color, 8%);
+    border-top: 0;
+    box-shadow: none;
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      border-top: 1px solid darken($ui-base-color, 8%);
+    }
+  }
+
+  @media screen and (max-width: 870px) {
+    .accounts-table tbody td.optional {
+      display: none;
+    }
+  }
 }
diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb
index 0d10d6c3c..f73b93058 100644
--- a/app/lib/activitypub/activity/flag.rb
+++ b/app/lib/activitypub/activity/flag.rb
@@ -14,7 +14,8 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
         @account,
         target_account,
         status_ids: target_statuses.nil? ? [] : target_statuses.map(&:id),
-        comment: @json['content'] || ''
+        comment: @json['content'] || '',
+        uri: report_uri
       )
     end
   end
@@ -28,4 +29,8 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
   def object_uris
     @object_uris ||= Array(@object.is_a?(Array) ? @object.map { |item| value_or_id(item) } : value_or_id(@object))
   end
+
+  def report_uri
+    @json['id'] unless @json['id'].nil? || invalid_origin?(@json['id'])
+  end
 end
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 464e1ee7e..aadf03b2a 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -71,6 +71,12 @@ class Formatter
     html.html_safe # rubocop:disable Rails/OutputSafety
   end
 
+  def format_poll_option(status, option, **options)
+    html = encode(option.title)
+    html = encode_custom_emojis(html, status.emojis, options[:autoplay])
+    html.html_safe # rubocop:disable Rails/OutputSafety
+  end
+
   def format_display_name(account, **options)
     html = encode(account.display_name.presence || account.username)
     html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
diff --git a/app/lib/language_detector.rb b/app/lib/language_detector.rb
index 58c8e2069..70a9084d1 100644
--- a/app/lib/language_detector.rb
+++ b/app/lib/language_detector.rb
@@ -3,7 +3,8 @@
 class LanguageDetector
   include Singleton
 
-  CHARACTER_THRESHOLD = 140
+  CHARACTER_THRESHOLD    = 140
+  RELIABLE_CHARACTERS_RE = /[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}\p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}]+/m
 
   def initialize
     @identifier = CLD3::NNetLanguageIdentifier.new(1, 2048)
@@ -11,15 +12,14 @@ class LanguageDetector
 
   def detect(text, account)
     input_text = prepare_text(text)
+
     return if input_text.blank?
 
     detect_language_code(input_text) || default_locale(account)
   end
 
   def language_names
-    @language_names =
-      CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }
-                                             .uniq
+    @language_names = CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }.uniq
   end
 
   private
@@ -29,12 +29,29 @@ class LanguageDetector
   end
 
   def unreliable_input?(text)
-    text.size < CHARACTER_THRESHOLD
+    !reliable_input?(text)
+  end
+
+  def reliable_input?(text)
+    sufficient_text_length?(text) || language_specific_character_set?(text)
+  end
+
+  def sufficient_text_length?(text)
+    text.size >= CHARACTER_THRESHOLD
+  end
+
+  def language_specific_character_set?(text)
+    words = text.scan(RELIABLE_CHARACTERS_RE)
+
+    if words.present?
+      words.reduce(0) { |acc, elem| acc + elem.size }.to_f / text.size.to_f > 0.3
+    else
+      false
+    end
   end
 
   def detect_language_code(text)
     return if unreliable_input?(text)
-
     result = @identifier.find_language(text)
     iso6391(result.language.to_s).to_sym if result.reliable?
   end
@@ -77,6 +94,6 @@ class LanguageDetector
   end
 
   def default_locale(account)
-    return account.user_locale&.to_sym || I18n.default_locale if account.local?
+    account.user_locale&.to_sym || I18n.default_locale if account.local?
   end
 end
diff --git a/app/lib/proof_provider.rb b/app/lib/proof_provider.rb
new file mode 100644
index 000000000..102c50f4f
--- /dev/null
+++ b/app/lib/proof_provider.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module ProofProvider
+  SUPPORTED_PROVIDERS = %w(keybase).freeze
+
+  def self.find(identifier, proof = nil)
+    case identifier
+    when 'keybase'
+      ProofProvider::Keybase.new(proof)
+    end
+  end
+end
diff --git a/app/lib/proof_provider/keybase.rb b/app/lib/proof_provider/keybase.rb
new file mode 100644
index 000000000..96322a265
--- /dev/null
+++ b/app/lib/proof_provider/keybase.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+class ProofProvider::Keybase
+  BASE_URL = 'https://keybase.io'
+
+  class Error < StandardError; end
+
+  class ExpectedProofLiveError < Error; end
+
+  class UnexpectedResponseError < Error; end
+
+  def initialize(proof = nil)
+    @proof = proof
+  end
+
+  def serializer_class
+    ProofProvider::Keybase::Serializer
+  end
+
+  def worker_class
+    ProofProvider::Keybase::Worker
+  end
+
+  def validate!
+    unless @proof.token&.size == 66
+      @proof.errors.add(:base, I18n.t('identity_proofs.errors.keybase.invalid_token'))
+      return
+    end
+
+    return if @proof.provider_username.blank?
+
+    if verifier.valid?
+      @proof.verified = true
+      @proof.live     = false
+    else
+      @proof.errors.add(:base, I18n.t('identity_proofs.errors.keybase.verification_failed', kb_username: @proof.provider_username))
+    end
+  end
+
+  def refresh!
+    worker_class.new.perform(@proof)
+  rescue ProofProvider::Keybase::Error
+    nil
+  end
+
+  def on_success_path(user_agent = nil)
+    verifier.on_success_path(user_agent)
+  end
+
+  def badge
+    @badge ||= ProofProvider::Keybase::Badge.new(@proof.account.username, @proof.provider_username, @proof.token)
+  end
+
+  private
+
+  def verifier
+    @verifier ||= ProofProvider::Keybase::Verifier.new(@proof.account.username, @proof.provider_username, @proof.token)
+  end
+end
diff --git a/app/lib/proof_provider/keybase/badge.rb b/app/lib/proof_provider/keybase/badge.rb
new file mode 100644
index 000000000..3aa067ecf
--- /dev/null
+++ b/app/lib/proof_provider/keybase/badge.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+class ProofProvider::Keybase::Badge
+  include RoutingHelper
+
+  def initialize(local_username, provider_username, token)
+    @local_username    = local_username
+    @provider_username = provider_username
+    @token             = token
+  end
+
+  def proof_url
+    "#{ProofProvider::Keybase::BASE_URL}/#{@provider_username}/sigchain\##{@token}"
+  end
+
+  def profile_url
+    "#{ProofProvider::Keybase::BASE_URL}/#{@provider_username}"
+  end
+
+  def icon_url
+    "#{ProofProvider::Keybase::BASE_URL}/#{@provider_username}/proof_badge/#{@token}?username=#{@local_username}&domain=#{domain}"
+  end
+
+  def avatar_url
+    Rails.cache.fetch("proof_providers/keybase/#{@provider_username}/avatar_url", expires_in: 5.minutes) { remote_avatar_url } || default_avatar_url
+  end
+
+  private
+
+  def remote_avatar_url
+    request = Request.new(:get, "#{ProofProvider::Keybase::BASE_URL}/_/api/1.0/user/pic_url.json", params: { username: @provider_username })
+
+    request.perform do |res|
+      json = Oj.load(res.body_with_limit, mode: :strict)
+      json['pic_url'] if json.is_a?(Hash)
+    end
+  rescue Oj::ParseError, HTTP::Error, OpenSSL::SSL::SSLError
+    nil
+  end
+
+  def default_avatar_url
+    asset_pack_path('media/images/proof_providers/keybase.png')
+  end
+
+  def domain
+    Rails.configuration.x.local_domain
+  end
+end
diff --git a/app/lib/proof_provider/keybase/config_serializer.rb b/app/lib/proof_provider/keybase/config_serializer.rb
new file mode 100644
index 000000000..474ea74e2
--- /dev/null
+++ b/app/lib/proof_provider/keybase/config_serializer.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :version, :domain, :display_name, :username,
+             :brand_color, :logo, :description, :prefill_url,
+             :profile_url, :check_url, :check_path, :avatar_path,
+             :contact
+
+  def version
+    1
+  end
+
+  def domain
+    Rails.configuration.x.local_domain
+  end
+
+  def display_name
+    Setting.site_title
+  end
+
+  def logo
+    { svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')), svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')) }
+  end
+
+  def brand_color
+    '#282c37'
+  end
+
+  def description
+    Setting.site_short_description.presence || Setting.site_description.presence || I18n.t('about.about_mastodon_html')
+  end
+
+  def username
+    { min: 1, max: 30, re: Account::USERNAME_RE.inspect }
+  end
+
+  def prefill_url
+    params = {
+      provider: 'keybase',
+      token: '%{sig_hash}',
+      provider_username: '%{kb_username}',
+      username: '%{username}',
+      user_agent: '%{kb_ua}',
+    }
+
+    CGI.unescape(new_settings_identity_proof_url(params))
+  end
+
+  def profile_url
+    CGI.unescape(short_account_url('%{username}')) # rubocop:disable Style/FormatStringToken
+  end
+
+  def check_url
+    CGI.unescape(api_proofs_url(username: '%{username}', provider: 'keybase'))
+  end
+
+  def check_path
+    ['signatures']
+  end
+
+  def avatar_path
+    ['avatar']
+  end
+
+  def contact
+    [Setting.site_contact_email.presence].compact
+  end
+end
diff --git a/app/lib/proof_provider/keybase/serializer.rb b/app/lib/proof_provider/keybase/serializer.rb
new file mode 100644
index 000000000..d29283600
--- /dev/null
+++ b/app/lib/proof_provider/keybase/serializer.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ProofProvider::Keybase::Serializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attribute :avatar
+
+  has_many :identity_proofs, key: :signatures
+
+  def avatar
+    full_asset_url(object.avatar_original_url)
+  end
+
+  class AccountIdentityProofSerializer < ActiveModel::Serializer
+    attributes :sig_hash, :kb_username
+
+    def sig_hash
+      object.token
+    end
+
+    def kb_username
+      object.provider_username
+    end
+  end
+end
diff --git a/app/lib/proof_provider/keybase/verifier.rb b/app/lib/proof_provider/keybase/verifier.rb
new file mode 100644
index 000000000..86f249dd7
--- /dev/null
+++ b/app/lib/proof_provider/keybase/verifier.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+class ProofProvider::Keybase::Verifier
+  def initialize(local_username, provider_username, token)
+    @local_username    = local_username
+    @provider_username = provider_username
+    @token             = token
+  end
+
+  def valid?
+    request = Request.new(:get, "#{ProofProvider::Keybase::BASE_URL}/_/api/1.0/sig/proof_valid.json", params: query_params)
+
+    request.perform do |res|
+      json = Oj.load(res.body_with_limit, mode: :strict)
+
+      if json.is_a?(Hash)
+        json.fetch('proof_valid', false)
+      else
+        false
+      end
+    end
+  rescue Oj::ParseError, HTTP::Error, OpenSSL::SSL::SSLError
+    false
+  end
+
+  def on_success_path(user_agent = nil)
+    url = Addressable::URI.parse("#{ProofProvider::Keybase::BASE_URL}/_/proof_creation_success")
+    url.query_values = query_params.merge(kb_ua: user_agent || 'unknown')
+    url.to_s
+  end
+
+  def status
+    request = Request.new(:get, "#{ProofProvider::Keybase::BASE_URL}/_/api/1.0/sig/proof_live.json", params: query_params)
+
+    request.perform do |res|
+      raise ProofProvider::Keybase::UnexpectedResponseError unless res.code == 200
+
+      json = Oj.load(res.body_with_limit, mode: :strict)
+
+      raise ProofProvider::Keybase::UnexpectedResponseError unless json.is_a?(Hash) && json.key?('proof_valid') && json.key?('proof_live')
+
+      json
+    end
+  rescue Oj::ParseError, HTTP::Error, OpenSSL::SSL::SSLError
+    raise ProofProvider::Keybase::UnexpectedResponseError
+  end
+
+  private
+
+  def query_params
+    {
+      domain: domain,
+      kb_username: @provider_username,
+      username: @local_username,
+      sig_hash: @token,
+    }
+  end
+
+  def domain
+    Rails.configuration.x.local_domain
+  end
+end
diff --git a/app/lib/proof_provider/keybase/worker.rb b/app/lib/proof_provider/keybase/worker.rb
new file mode 100644
index 000000000..2872f59c1
--- /dev/null
+++ b/app/lib/proof_provider/keybase/worker.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ProofProvider::Keybase::Worker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: 'pull', retry: 20, unique: :until_executed
+
+  sidekiq_retry_in do |count, exception|
+    # Retry aggressively when the proof is valid but not live in Keybase.
+    # This is likely because Keybase just hasn't noticed the proof being
+    # served from here yet.
+
+    if exception.class == ProofProvider::Keybase::ExpectedProofLiveError
+      case count
+      when 0..2 then 0.seconds
+      when 2..6 then 1.second
+      end
+    end
+  end
+
+  def perform(proof_id)
+    proof    = proof_id.is_a?(AccountIdentityProof) ? proof_id : AccountIdentityProof.find(proof_id)
+    verifier = ProofProvider::Keybase::Verifier.new(proof.account.username, proof.provider_username, proof.token)
+    status   = verifier.status
+
+    # If Keybase thinks the proof is valid, and it exists here in Mastodon,
+    # then it should be live. Keybase just has to notice that it's here
+    # and then update its state. That might take a couple seconds.
+    raise ProofProvider::Keybase::ExpectedProofLiveError if status['proof_valid'] && !status['proof_live']
+
+    proof.update!(verified: status['proof_valid'], live: status['proof_live'])
+  end
+end
diff --git a/app/models/account_identity_proof.rb b/app/models/account_identity_proof.rb
new file mode 100644
index 000000000..e7a3f97e5
--- /dev/null
+++ b/app/models/account_identity_proof.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: account_identity_proofs
+#
+#  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  provider          :string           default(""), not null
+#  provider_username :string           default(""), not null
+#  token             :text             default(""), not null
+#  verified          :boolean          default(FALSE), not null
+#  live              :boolean          default(FALSE), not null
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#
+
+class AccountIdentityProof < ApplicationRecord
+  belongs_to :account
+
+  validates :provider, inclusion: { in: ProofProvider::SUPPORTED_PROVIDERS }
+  validates :provider_username, format: { with: /\A[a-z0-9_]+\z/i }, length: { minimum: 2, maximum: 15 }
+  validates :provider_username, uniqueness: { scope: [:account_id, :provider] }
+  validates :token, format: { with: /\A[a-f0-9]+\z/ }, length: { maximum: 66 }
+
+  validate :validate_with_provider, if: :token_changed?
+
+  scope :active, -> { where(verified: true, live: true) }
+
+  after_create_commit :queue_worker
+
+  delegate :refresh!, :on_success_path, :badge, to: :provider_instance
+
+  private
+
+  def provider_instance
+    @provider_instance ||= ProofProvider.find(provider, self)
+  end
+
+  def queue_worker
+    provider_instance.worker_class.perform_async(id)
+  end
+
+  def validate_with_provider
+    provider_instance.validate!
+  end
+end
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index 1b22f750c..ecccaf35e 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -7,6 +7,9 @@ module AccountAssociations
     # Local users
     has_one :user, inverse_of: :account, dependent: :destroy
 
+    # Identity proofs
+    has_many :identity_proofs, class_name: 'AccountIdentityProof', dependent: :destroy, inverse_of: :account
+
     # Timelines
     has_many :stream_entries, inverse_of: :account, dependent: :destroy
     has_many :statuses, inverse_of: :account, dependent: :destroy
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
new file mode 100644
index 000000000..60eaaf0e2
--- /dev/null
+++ b/app/models/form/account_batch.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+class Form::AccountBatch
+  include ActiveModel::Model
+
+  attr_accessor :account_ids, :action, :current_account
+
+  def save
+    case action
+    when 'unfollow'
+      unfollow!
+    when 'remove_from_followers'
+      remove_from_followers!
+    when 'block_domains'
+      block_domains!
+    end
+  end
+
+  private
+
+  def unfollow!
+    accounts.find_each do |target_account|
+      UnfollowService.new.call(current_account, target_account)
+    end
+  end
+
+  def remove_from_followers!
+    current_account.passive_relationships.where(account_id: account_ids).find_each do |follow|
+      reject_follow!(follow)
+    end
+  end
+
+  def block_domains!
+    AfterAccountDomainBlockWorker.push_bulk(account_domains) do |domain|
+      [current_account.id, domain]
+    end
+  end
+
+  def account_domains
+    accounts.pluck(Arel.sql('distinct domain')).compact
+  end
+
+  def accounts
+    Account.where(id: account_ids)
+  end
+
+  def reject_follow!(follow)
+    follow.destroy
+
+    return unless follow.account.activitypub?
+
+    json = ActiveModelSerializers::SerializableResource.new(
+      follow,
+      serializer: ActivityPub::RejectFollowSerializer,
+      adapter: ActivityPub::Adapter
+    ).to_json
+
+    ActivityPub::DeliveryWorker.perform_async(json, current_account.id, follow.account.inbox_url)
+  end
+end
diff --git a/app/models/poll.rb b/app/models/poll.rb
index 6df230337..8f72c7b11 100644
--- a/app/models/poll.rb
+++ b/app/models/poll.rb
@@ -60,6 +60,10 @@ class Poll < ApplicationRecord
     !local?
   end
 
+  def emojis
+    @emojis ||= CustomEmoji.from_text(options.join(' '), account.domain)
+  end
+
   class Option < ActiveModelSerializers::Model
     attributes :id, :title, :votes_count, :poll
 
diff --git a/app/models/report.rb b/app/models/report.rb
index 2804020f5..86c303798 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -13,6 +13,7 @@
 #  action_taken_by_account_id :bigint(8)
 #  target_account_id          :bigint(8)        not null
 #  assigned_account_id        :bigint(8)
+#  uri                        :string
 #
 
 class Report < ApplicationRecord
@@ -28,6 +29,12 @@ class Report < ApplicationRecord
 
   validates :comment, length: { maximum: 1000 }
 
+  def local?
+    false # Force uri_for to use uri attribute
+  end
+
+  before_validation :set_uri, only: :create
+
   def object_type
     :flag
   end
@@ -89,4 +96,8 @@ class Report < ApplicationRecord
 
     Admin::ActionLog.from("(#{sql}) AS admin_action_logs")
   end
+
+  def set_uri
+    self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil? && account.local?
+  end
 end
diff --git a/app/models/status.rb b/app/models/status.rb
index f576489b4..c049401e8 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -73,7 +73,9 @@ class Status < ApplicationRecord
   validates_with StatusLengthValidator
   validates_with DisallowedHashtagsValidator
   validates :reblog, uniqueness: { scope: :account }, if: :reblog?
-  validates_associated :owned_poll
+  validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
+
+  accepts_nested_attributes_for :owned_poll
 
   default_scope { recent }
 
@@ -216,7 +218,11 @@ class Status < ApplicationRecord
   end
 
   def emojis
-    @emojis ||= CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain)
+    return @emojis if defined?(@emojis)
+    fields = [spoiler_text, text]
+    fields += owned_poll.options unless owned_poll.nil?
+    @emojis = CustomEmoji.from_text(fields.join(' '), account.domain)
+    @emojis
   end
 
   def mark_for_mass_destruction!
@@ -471,8 +477,8 @@ class Status < ApplicationRecord
   end
 
   def set_visibility
+    self.visibility = reblog.visibility if reblog? && visibility.nil?
     self.visibility = (account.locked? ? :private : :public) if visibility.nil?
-    self.visibility = reblog.visibility if reblog?
     self.sensitive  = false if sensitive.nil?
   end
 
diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb
index 53e8f726d..1e7a46dd9 100644
--- a/app/serializers/activitypub/flag_serializer.rb
+++ b/app/serializers/activitypub/flag_serializer.rb
@@ -5,7 +5,6 @@ class ActivityPub::FlagSerializer < ActiveModel::Serializer
   attribute :virtual_object, key: :object
 
   def id
-    # This is nil for now
     ActivityPub::TagManager.instance.uri_for(object)
   end
 
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 97fed63d1..98c53c84a 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -32,7 +32,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
   end
 
   def thumbnail
-    instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('preview.jpg')
+    instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg')
   end
 
   def max_toot_chars
diff --git a/app/serializers/rest/poll_serializer.rb b/app/serializers/rest/poll_serializer.rb
index 4dae1c09f..356c45b83 100644
--- a/app/serializers/rest/poll_serializer.rb
+++ b/app/serializers/rest/poll_serializer.rb
@@ -5,6 +5,7 @@ class REST::PollSerializer < ActiveModel::Serializer
              :multiple, :votes_count
 
   has_many :loaded_options, key: :options
+  has_many :emojis, serializer: REST::CustomEmojiSerializer
 
   attribute :voted, if: :current_user?
 
diff --git a/app/serializers/rest/preferences_serializer.rb b/app/serializers/rest/preferences_serializer.rb
new file mode 100644
index 000000000..119f0e06d
--- /dev/null
+++ b/app/serializers/rest/preferences_serializer.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class REST::PreferencesSerializer < ActiveModel::Serializer
+  attribute :posting_default_privacy, key: 'posting:default:visibility'
+  attribute :posting_default_sensitive, key: 'posting:default:sensitive'
+  attribute :posting_default_language, key: 'posting:default:language'
+
+  attribute :reading_default_sensitive_media, key: 'reading:expand:media'
+  attribute :reading_default_sensitive_text, key: 'reading:expand:spoilers'
+
+  def posting_default_privacy
+    object.user.setting_default_privacy
+  end
+
+  def posting_default_sensitive
+    object.user.setting_default_sensitive
+  end
+
+  def posting_default_language
+    object.user.setting_default_language.presence
+  end
+
+  def reading_default_sensitive_media
+    object.user.setting_display_media
+  end
+
+  def reading_default_sensitive_text
+    object.user.setting_expand_spoilers
+  end
+end
diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb
index 712b1347a..88eca79ed 100644
--- a/app/serializers/rss/account_serializer.rb
+++ b/app/serializers/rss/account_serializer.rb
@@ -11,7 +11,7 @@ class RSS::AccountSerializer
     builder.title("#{display_name(account)} (@#{account.local_username_and_domain})")
            .description(account_description(account))
            .link(TagManager.instance.url_for(account))
-           .logo(full_asset_url(asset_pack_path('logo.svg')))
+           .logo(full_pack_url('media/images/logo.svg'))
            .accent_color('2b90d9')
 
     builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?
diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb
index 7680a8da5..644380149 100644
--- a/app/serializers/rss/tag_serializer.rb
+++ b/app/serializers/rss/tag_serializer.rb
@@ -12,7 +12,7 @@ class RSS::TagSerializer
     builder.title("##{tag.name}")
            .description(strip_tags(I18n.t('about.about_hashtag_html', hashtag: tag.name)))
            .link(tag_url(tag))
-           .logo(full_asset_url(asset_pack_path('logo.svg')))
+           .logo(full_pack_url('media/images/logo.svg'))
            .accent_color('2b90d9')
 
     statuses.each do |status|
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index b9952369d..820c553c9 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -29,7 +29,6 @@ class PostStatusService < BaseService
     return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
 
     validate_media!
-    validate_poll!
     preprocess_attributes!
 
     if scheduled?
@@ -74,6 +73,7 @@ class PostStatusService < BaseService
 
   def schedule_status!
     status_for_validation = @account.statuses.build(status_attributes)
+
     if status_for_validation.valid?
       status_for_validation.destroy
 
@@ -110,12 +110,6 @@ class PostStatusService < BaseService
     raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
   end
 
-  def validate_poll!
-    return if @options[:poll].blank?
-
-    @poll = @account.polls.new(@options[:poll])
-  end
-
   def language_from_option(str)
     ISO_639.find(str)&.alpha2
   end
@@ -168,13 +162,13 @@ class PostStatusService < BaseService
       text: @text,
       media_attachments: @media || [],
       thread: @in_reply_to,
-      owned_poll: @poll,
+      owned_poll_attributes: poll_attributes,
       sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
       spoiler_text: @options[:spoiler_text] || '',
       visibility: @visibility,
       language: language_from_option(@options[:language]) || @account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(@text, @account),
       application: @options[:application],
-    }
+    }.compact
   end
 
   def scheduled_status_attributes
@@ -185,6 +179,12 @@ class PostStatusService < BaseService
     }
   end
 
+  def poll_attributes
+    return if @options[:poll].blank?
+
+    @options[:poll].merge(account: @account)
+  end
+
   def scheduled_options
     @options.tap do |options_hash|
       options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index 03db27406..deaa0549e 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -7,8 +7,9 @@ class ReblogService < BaseService
   # Reblog a status and notify its remote author
   # @param [Account] account Account to reblog from
   # @param [Status] reblogged_status Status to be reblogged
+  # @param [Hash] options
   # @return [Status]
-  def call(account, reblogged_status)
+  def call(account, reblogged_status, options = {})
     reblogged_status = reblogged_status.reblog if reblogged_status.reblog?
 
     authorize_with account, reblogged_status, :reblog?
@@ -17,7 +18,7 @@ class ReblogService < BaseService
 
     return reblog unless reblog.nil?
 
-    reblog = account.statuses.create!(reblog: reblogged_status, text: '')
+    reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: options[:visibility] || account.user&.setting_default_privacy)
 
     DistributionWorker.perform_async(reblog.id)
 
@@ -38,7 +39,7 @@ class ReblogService < BaseService
     reblogged_status = reblog.reblog
 
     if reblogged_status.account.local?
-      NotifyService.new.call(reblogged_status.account, reblog)
+      LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name)
     elsif reblogged_status.account.ostatus?
       NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), reblog.account_id, reblogged_status.account_id)
     elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account)
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 7eec11ddf..6e4998e07 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -14,17 +14,23 @@ class RemoveStatusService < BaseService
     @stream_entry = status.stream_entry
     @options      = options
 
-    remove_from_self if status.account.local?
-    remove_from_followers
-    remove_from_lists
-    remove_from_affected
-    remove_reblogs
-    remove_from_hashtags
-    remove_from_public
-    remove_from_media if status.media_attachments.any?
-    remove_from_direct if status.direct_visibility?
-
-    @status.destroy!
+    RedisLock.acquire(lock_options) do |lock|
+      if lock.acquired?
+        remove_from_self if status.account.local?
+        remove_from_followers
+        remove_from_lists
+        remove_from_affected
+        remove_reblogs
+        remove_from_hashtags
+        remove_from_public
+        remove_from_media if status.media_attachments.any?
+        remove_from_direct if status.direct_visibility?
+
+        @status.destroy!
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
 
     # There is no reason to send out Undo activities when the
     # cause is that the original object has been removed, since
@@ -164,4 +170,8 @@ class RemoveStatusService < BaseService
     end
     Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local?
   end
+
+  def lock_options
+    { redis: Redis.current, key: "distribute:#{@status.id}" }
+  end
 end
diff --git a/app/services/report_service.rb b/app/services/report_service.rb
index 1bcc1c0d5..73bd6694f 100644
--- a/app/services/report_service.rb
+++ b/app/services/report_service.rb
@@ -21,7 +21,8 @@ class ReportService < BaseService
     @report = @source_account.reports.create!(
       target_account: @target_account,
       status_ids: @status_ids,
-      comment: @comment
+      comment: @comment,
+      uri: @options[:uri]
     )
   end
 
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index 24fa1be69..6c2ecad30 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -68,7 +68,7 @@ class SuspendAccountService < BaseService
   end
 
   def purge_content!
-    distribute_delete_actor! if @account.local?
+    distribute_delete_actor! if @account.local? && !@options[:skip_distribution]
 
     @account.statuses.reorder(nil).find_in_batches do |statuses|
       BatchedRemoveStatusService.new.call(statuses, skip_side_effects: @options[:destroy])
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 87f1071d9..f02a7906a 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -8,7 +8,7 @@
   .column-0
     .public-account-header.public-account-header--no-bar
       .public-account-header__image
-        = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax'
+        = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax'
 
   .column-1
     .landing-page__call-to-action{ dir: 'ltr' }
@@ -24,7 +24,7 @@
             %span= t 'about.status_count_after', count: @instance_presenter.status_count
         .row__mascot
           .landing-page__mascot
-            = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('elephant_ui_plane.svg'), alt: ''
+            = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
 
   .column-2
     .landing-page__information.contact-widget
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 15d0af64e..45e5f0717 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -8,7 +8,7 @@
 .landing
   .landing__brand
     = link_to root_url, class: 'brand' do
-      = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+      = image_pack_tag 'logo_full.svg', alt: 'Mastodon'
       %span.brand__tagline=t 'about.tagline'
 
   .landing__grid
@@ -17,23 +17,25 @@
         = render 'registration'
 
       .directory
-        .directory__tag{ class: Setting.profile_directory ? nil : 'disabled' }
-          = optional_link_to Setting.profile_directory, explore_path do
-            %h4
-              = fa_icon 'address-book fw'
-              = t('about.discover_users')
-              %small= t('about.browse_directory')
+        - if Setting.profile_directory
+          .directory__tag
+            = optional_link_to Setting.profile_directory, explore_path do
+              %h4
+                = fa_icon 'address-book fw'
+                = t('about.discover_users')
+                %small= t('about.browse_directory')
 
-            .avatar-stack
-              - @instance_presenter.sample_accounts.each do |account|
-                = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar'
+              .avatar-stack
+                - @instance_presenter.sample_accounts.each do |account|
+                  = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar'
 
-        .directory__tag{ class: Setting.timeline_preview ? nil : 'disabled' }
-          = optional_link_to Setting.timeline_preview, public_timeline_path do
-            %h4
-              = fa_icon 'globe fw'
-              = t('about.see_whats_happening')
-              %small= t('about.browse_public_posts')
+        - if Setting.timeline_preview
+          .directory__tag
+            = optional_link_to Setting.timeline_preview, public_timeline_path do
+              %h4
+                = fa_icon 'globe fw'
+                = t('about.see_whats_happening')
+                %small= t('about.browse_public_posts')
 
         .directory__tag
           = link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener' do
@@ -48,7 +50,7 @@
 
       .hero-widget
         .hero-widget__img
-          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 
         - if @instance_presenter.site_short_description.present?
           .hero-widget__text
diff --git a/app/views/accounts/_bio.html.haml b/app/views/accounts/_bio.html.haml
index 2ea34a048..efc26d136 100644
--- a/app/views/accounts/_bio.html.haml
+++ b/app/views/accounts/_bio.html.haml
@@ -1,7 +1,17 @@
+- proofs = account.identity_proofs.active
+- fields = account.fields
+
 .public-account-bio
-  - unless account.fields.empty?
+  - unless fields.empty? && proofs.empty?
     .account__header__fields
-      - account.fields.each do |field|
+      - proofs.each do |proof|
+        %dl
+          %dt= proof.provider.capitalize
+          %dd.verified
+            = link_to fa_icon('check'), proof.badge.proof_url, class: 'verified__mark', title: t('accounts.link_verified_on', date: l(proof.updated_at))
+            = link_to proof.provider_username, proof.badge.profile_url
+
+      - fields.each do |field|
         %dl
           %dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
           %dd{ title: field.value, class: custom_field_classes(field) }
@@ -9,6 +19,7 @@
               %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
                 = fa_icon 'check'
             = Formatter.instance.format_field(account, field.value, custom_emojify: true)
+
   = account_badge(account)
 
   - if account.note.present?
diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml
index ee0eacaf5..e6ad9de34 100644
--- a/app/views/admin/invites/_invite.html.haml
+++ b/app/views/admin/invites/_invite.html.haml
@@ -10,10 +10,7 @@
       = image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar'
       %span.username= invite.user.account.username
 
-  - if invite.expired?
-    %td{ colspan: 2 }
-      = t('invites.expired')
-  - else
+  - if invite.valid_for_use?
     %td
       = fa_icon 'user fw'
       = invite.uses
@@ -24,6 +21,10 @@
       - else
         %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) }
           = l invite.expires_at
+  - else
+    %td{ colspan: 2 }
+      = t('invites.expired')
+
   %td
-    - if !invite.expired? && policy(invite).destroy?
+    - if invite.valid_for_use? && policy(invite).destroy?
       = table_link_to 'times', t('invites.delete'), admin_invite_path(invite), method: :delete
diff --git a/app/views/application/_sidebar.html.haml b/app/views/application/_sidebar.html.haml
index 2ff14b252..b5ce5845e 100644
--- a/app/views/application/_sidebar.html.haml
+++ b/app/views/application/_sidebar.html.haml
@@ -1,6 +1,6 @@
 .hero-widget
   .hero-widget__img
-    = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+    = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 
   .hero-widget__text
     %p= @instance_presenter.site_short_description.html_safe.presence || @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 5f32635e5..6c5268b61 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -9,7 +9,7 @@
 
 .app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
   %noscript
-    = image_tag asset_pack_path('logo.svg'), alt: 'Mastodon'
+    = image_pack_tag 'logo.svg', alt: 'Mastodon'
 
     %div
       = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml
index 4240aa3e7..62799ca5b 100644
--- a/app/views/invites/_invite.html.haml
+++ b/app/views/invites/_invite.html.haml
@@ -5,10 +5,7 @@
         %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) }
       %button{ type: :button }= t('generic.copy')
 
-  - if invite.expired?
-    %td{ colspan: 2 }
-      = t('invites.expired')
-  - else
+  - if invite.valid_for_use?
     %td
       = fa_icon 'user fw'
       = invite.uses
@@ -19,7 +16,10 @@
       - else
         %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) }
           = l invite.expires_at
+  - else
+    %td{ colspan: 2 }
+      = t('invites.expired')
 
   %td
-    - if !invite.expired? && policy(invite).destroy?
+    - if invite.valid_for_use? && policy(invite).destroy?
       = table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 0e52702dc..a0cb7c4fe 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -3,7 +3,7 @@
     .sidebar-wrapper
       .sidebar
         = link_to root_path do
-          = image_tag asset_pack_path('logo.svg'), class: 'logo', alt: 'Mastodon'
+          = image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon'
 
         = render_navigation
     .content-wrapper
diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml
index ca9c13945..fcbd29fe9 100644
--- a/app/views/layouts/auth.html.haml
+++ b/app/views/layouts/auth.html.haml
@@ -3,7 +3,7 @@
     .logo-container
       %h1
         = link_to root_path do
-          = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+          = image_pack_tag 'logo_full.svg', alt: 'Mastodon'
 
     .form-container
       = render 'flashes'
diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml
index 6321fec61..26fb697bb 100644
--- a/app/views/layouts/mailer.html.haml
+++ b/app/views/layouts/mailer.html.haml
@@ -24,7 +24,7 @@
                               %tr
                                 %td.column-cell
                                   = link_to root_url do
-                                    = image_tag full_pack_url('logo_full.png'), alt: 'Mastodon', height: 34, class: 'logo'
+                                    = image_tag full_pack_url('media/images/mailer/logo_full.png'), alt: 'Mastodon', height: 34, class: 'logo'
 
     = yield
 
@@ -49,4 +49,4 @@
                                 %p= link_to t('application_mailer.notification_preferences'), settings_notifications_url
                               %td.column-cell.text-right
                                 = link_to root_url do
-                                  = image_tag full_pack_url('logo_transparent.png'), alt: 'Mastodon', height: 24
+                                  = image_tag full_pack_url('media/images/mailer/logo_transparent.png'), alt: 'Mastodon', height: 24
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index 1d3519b8a..b4a21caf1 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -5,7 +5,7 @@
         %nav.header
           .nav-left
             = link_to root_url, class: 'brand' do
-              = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+              = image_pack_tag 'logo_full.svg', alt: 'Mastodon'
 
             = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
             = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml
index 7d1b494d0..a715d615c 100644
--- a/app/views/notification_mailer/favourite.html.haml
+++ b/app/views/notification_mailer/favourite.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_grade.png'), alt:''
+                                      = image_tag full_pack_url('media/images/mailer/icon_grade.png'), alt:''
 
                               %h1= t 'notification_mailer.favourite.title'
                               %p.lead= t('notification_mailer.favourite.body', name: @account.acct)
diff --git a/app/views/notification_mailer/follow.html.haml b/app/views/notification_mailer/follow.html.haml
index 31a2b7445..cd84f7858 100644
--- a/app/views/notification_mailer/follow.html.haml
+++ b/app/views/notification_mailer/follow.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_person_add.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: ''
 
                               %h1= t 'notification_mailer.follow.title'
                               %p.lead= t('notification_mailer.follow.body', name: @account.acct)
diff --git a/app/views/notification_mailer/follow_request.html.haml b/app/views/notification_mailer/follow_request.html.haml
index 44f1911c4..a63e27a90 100644
--- a/app/views/notification_mailer/follow_request.html.haml
+++ b/app/views/notification_mailer/follow_request.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_person_add.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: ''
 
                               %h1= t 'notification_mailer.follow_request.title'
                               %p.lead= t('notification_mailer.follow_request.body', name: @account.acct)
diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml
index 479fed41c..619873cfa 100644
--- a/app/views/notification_mailer/mention.html.haml
+++ b/app/views/notification_mailer/mention.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_reply.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_reply.png'), alt: ''
 
                               %h1= t 'notification_mailer.mention.title'
                               %p.lead= t('notification_mailer.mention.body', name: @status.account.acct)
diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml
index 85b202cf9..a2811be23 100644
--- a/app/views/notification_mailer/reblog.html.haml
+++ b/app/views/notification_mailer/reblog.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_cached.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_cached.png'), alt: ''
 
                               %h1= t 'notification_mailer.reblog.title'
                               %p.lead= t('notification_mailer.reblog.body', name: @account.acct)
diff --git a/app/views/relationships/_account.html.haml b/app/views/relationships/_account.html.haml
new file mode 100644
index 000000000..6c22deb51
--- /dev/null
+++ b/app/views/relationships/_account.html.haml
@@ -0,0 +1,20 @@
+.batch-table__row
+  %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
+    = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
+  .batch-table__row__content.batch-table__row__content--unpadded
+    %table.accounts-table
+      %tbody
+        %tr
+          %td= account_link_to account
+          %td.accounts-table__count.optional
+            = number_to_human account.statuses_count, strip_insignificant_zeros: true
+            %small= t('accounts.posts', count: account.statuses_count).downcase
+          %td.accounts-table__count.optional
+            = number_to_human account.followers_count, strip_insignificant_zeros: true
+            %small= t('accounts.followers', count: account.followers_count).downcase
+          %td.accounts-table__count
+            - if account.last_status_at.present?
+              %time.time-ago{ datetime: account.last_status_at.iso8601, title: l(account.last_status_at) }= l account.last_status_at
+            - else
+              \-
+            %small= t('accounts.last_active')
diff --git a/app/views/relationships/show.html.haml b/app/views/relationships/show.html.haml
new file mode 100644
index 000000000..fc9613731
--- /dev/null
+++ b/app/views/relationships/show.html.haml
@@ -0,0 +1,47 @@
+- content_for :page_title do
+  = t('settings.relationships')
+
+.filters
+  .filter-subset
+    %strong= t 'relationships.relationship'
+    %ul
+      %li= filter_link_to t('accounts.following', count: current_account.following_count), relationship: nil
+      %li= filter_link_to t('accounts.followers', count: current_account.followers_count), relationship: 'followed_by'
+      %li= filter_link_to t('relationships.mutual'), relationship: 'mutual'
+
+  .filter-subset
+    %strong= t 'relationships.status'
+    %ul
+      %li= filter_link_to t('generic.all'), status: nil
+      %li= filter_link_to t('relationships.primary'), status: 'primary'
+      %li= filter_link_to t('relationships.moved'), status: 'moved'
+
+  .filter-subset
+    %strong= t 'relationships.activity'
+    %ul
+      %li= filter_link_to t('generic.all'), activity: nil
+      %li= filter_link_to t('relationships.dormant'), activity: 'dormant'
+
+= form_for(@form, url: relationships_path, method: :patch) do |f|
+  = hidden_field_tag :page, params[:page] || 1
+  = hidden_field_tag :relationship, params[:relationship]
+  = hidden_field_tag :status, params[:status]
+  = hidden_field_tag :activity, params[:activity]
+
+  .batch-table
+    .batch-table__toolbar
+      %label.batch-table__toolbar__select.batch-checkbox-all
+        = check_box_tag :batch_checkbox_all, nil, false
+      .batch-table__toolbar__actions
+        = f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless followed_by_relationship?
+
+        = f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless following_relationship?
+
+        = f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
+    .batch-table__body
+      - if @accounts.empty?
+        = nothing_here 'nothing-here--under-tabs'
+      - else
+        = render partial: 'account', collection: @accounts, locals: { f: f }
+
+= paginate @accounts
diff --git a/app/views/settings/flavours/show.html.haml b/app/views/settings/flavours/show.html.haml
index a5126d9c5..c3f785aa0 100644
--- a/app/views/settings/flavours/show.html.haml
+++ b/app/views/settings/flavours/show.html.haml
@@ -5,7 +5,7 @@
   = render 'shared/error_messages', object: current_user
 
   - Themes.instance.flavour(@selected)['screenshot'].each do |screen|
-    %img.flavour-screen{ src: asset_pack_path(screen) }
+    %img.flavour-screen{ src: full_pack_url("media/#{screen}") }
 
   .flavour-description
     = t "flavours.#{@selected}.description", default: ''
diff --git a/app/views/settings/follower_domains/show.html.haml b/app/views/settings/follower_domains/show.html.haml
deleted file mode 100644
index f1687d4d2..000000000
--- a/app/views/settings/follower_domains/show.html.haml
+++ /dev/null
@@ -1,34 +0,0 @@
-- content_for :page_title do
-  = t('settings.followers')
-
-= form_tag settings_follower_domains_path, method: :patch, class: 'table-form' do
-  - unless @account.locked?
-    .warning
-      %strong
-        = fa_icon('warning')
-        = t('followers.unlocked_warning_title')
-      = t('followers.unlocked_warning_html', lock_link: link_to(t('followers.lock_link'), settings_profile_url))
-
-  %p= t('followers.explanation_html')
-  %p= t('followers.true_privacy_html')
-
-  .table-wrapper
-    %table.table
-      %thead
-        %tr
-          %th
-          %th= t('followers.domain')
-          %th= t('followers.followers_count')
-      %tbody
-        - @domains.each do |domain|
-          %tr
-            %td
-              = check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil?
-            %td
-              %samp= domain.domain.presence || Rails.configuration.x.local_domain
-            %td= number_with_delimiter domain.accounts_from_domain
-
-  .action-pagination
-    .actions
-      = button_tag t('followers.purge'), type: :submit, class: 'button', disabled: !@account.locked?
-    = paginate @domains
diff --git a/app/views/settings/identity_proofs/_proof.html.haml b/app/views/settings/identity_proofs/_proof.html.haml
new file mode 100644
index 000000000..524827ad7
--- /dev/null
+++ b/app/views/settings/identity_proofs/_proof.html.haml
@@ -0,0 +1,20 @@
+%tr
+  %td
+    = link_to proof.badge.profile_url, class: 'name-tag' do
+      = image_tag proof.badge.avatar_url, width: 15, height: 15, alt: '', class: 'avatar'
+      %span.username
+        = proof.provider_username
+        %span= "(#{proof.provider.capitalize})"
+
+  %td
+    - if proof.live?
+      %span.positive-hint
+        = fa_icon 'check-circle fw'
+        = t('identity_proofs.active')
+    - else
+      %span.negative-hint
+        = fa_icon 'times-circle fw'
+        = t('identity_proofs.inactive')
+
+  %td
+    = table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url
diff --git a/app/views/settings/identity_proofs/index.html.haml b/app/views/settings/identity_proofs/index.html.haml
new file mode 100644
index 000000000..d0ea03ecd
--- /dev/null
+++ b/app/views/settings/identity_proofs/index.html.haml
@@ -0,0 +1,17 @@
+- content_for :page_title do
+  = t('settings.identity_proofs')
+
+%p= t('identity_proofs.explanation_html')
+
+- unless @proofs.empty?
+  %hr.spacer/
+
+  .table-wrapper
+    %table.table
+      %thead
+        %tr
+          %th= t('identity_proofs.identity')
+          %th= t('identity_proofs.status')
+          %th
+      %tbody
+        = render partial: 'settings/identity_proofs/proof', collection: @proofs, as: :proof
diff --git a/app/views/settings/identity_proofs/new.html.haml b/app/views/settings/identity_proofs/new.html.haml
new file mode 100644
index 000000000..8ce6e61c9
--- /dev/null
+++ b/app/views/settings/identity_proofs/new.html.haml
@@ -0,0 +1,31 @@
+- content_for :page_title do
+  = t('identity_proofs.authorize_connection_prompt')
+
+.form-container
+  .oauth-prompt
+    %h2= t('identity_proofs.authorize_connection_prompt')
+
+  = simple_form_for @proof, url: settings_identity_proofs_url, html: { method: :post } do |f|
+    = f.input :provider, as: :hidden
+    = f.input :provider_username, as: :hidden
+    = f.input :token, as: :hidden
+
+    = hidden_field_tag :user_agent, params[:user_agent]
+
+    .connection-prompt
+      .connection-prompt__row.connection-prompt__connection
+        .connection-prompt__column
+          = image_tag current_account.avatar.url(:original), size: 96, class: 'account__avatar'
+
+          %p= t('identity_proofs.i_am_html', username: content_tag(:strong,current_account.username), service: site_hostname)
+
+        .connection-prompt__column.connection-prompt__column-sep
+          = fa_icon 'link'
+
+        .connection-prompt__column
+          = image_tag @proof.badge.avatar_url, size: 96, class: 'account__avatar'
+
+          %p= t('identity_proofs.i_am_html', username: content_tag(:strong, @proof.provider_username), service: @proof.provider.capitalize)
+
+    = f.button :button, t('identity_proofs.authorize'), type: :submit
+    = link_to t('simple_form.no'), settings_identity_proofs_url, class: 'button negative'
diff --git a/app/views/shared/_og.html.haml b/app/views/shared/_og.html.haml
index 802d8c41d..67238fc8b 100644
--- a/app/views/shared/_og.html.haml
+++ b/app/views/shared/_og.html.haml
@@ -8,7 +8,7 @@
 = opengraph 'og:type', 'website'
 = opengraph 'og:title', @instance_presenter.site_title
 = opengraph 'og:description', description
-= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('preview.jpg', protocol: :request))
+= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg', protocol: :request))
 = opengraph 'og:image:width', thumbnail ? thumbnail.meta['width'] : '1200'
 = opengraph 'og:image:height', thumbnail ? thumbnail.meta['height'] : '630'
 = opengraph 'twitter:card', 'summary_large_image'
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index b19d2452a..d18ecd37a 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -24,7 +24,7 @@
 
   - if status.poll
     = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
-      = render partial: 'stream_entries/poll', locals: { poll: status.poll }
+      = render partial: 'stream_entries/poll', locals: { status: status, poll: status.poll, autoplay: autoplay }
   - elsif !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
diff --git a/app/views/stream_entries/_poll.html.haml b/app/views/stream_entries/_poll.html.haml
index d6b2c0cd9..ba34890df 100644
--- a/app/views/stream_entries/_poll.html.haml
+++ b/app/views/stream_entries/_poll.html.haml
@@ -10,11 +10,11 @@
 
           %label.poll__text><
             %span.poll__number= percent.round
-            = option.title
+            = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
         - else
           %label.poll__text><
             %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><
-            = option.title
+            = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
   .poll__footer
     - unless show_results
       %button.button.button-secondary{ disabled: true }
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index d3441ca90..1952128a0 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -29,7 +29,7 @@
 
   - if status.poll
     = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
-      = render partial: 'stream_entries/poll', locals: { poll: status.poll }
+      = render partial: 'stream_entries/poll', locals: { status: status, poll: status.poll, autoplay: autoplay }
   - elsif !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
diff --git a/app/views/user_mailer/backup_ready.html.haml b/app/views/user_mailer/backup_ready.html.haml
index d5a4b8b48..85140b08b 100644
--- a/app/views/user_mailer/backup_ready.html.haml
+++ b/app/views/user_mailer/backup_ready.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_file_download.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_file_download.png'), alt: ''
 
                               %h1= t 'user_mailer.backup_ready.title'
 
diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml
index 70d0f5a24..39a83faff 100644
--- a/app/views/user_mailer/confirmation_instructions.html.haml
+++ b/app/views/user_mailer/confirmation_instructions.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_email.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: ''
 
                               %h1= t 'devise.mailer.confirmation_instructions.title'
 
diff --git a/app/views/user_mailer/email_changed.html.haml b/app/views/user_mailer/email_changed.html.haml
index 0802aaf96..7e91e87ad 100644
--- a/app/views/user_mailer/email_changed.html.haml
+++ b/app/views/user_mailer/email_changed.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_email.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: ''
 
                               %h1= t 'devise.mailer.email_changed.title'
                               %p.lead= t 'devise.mailer.email_changed.explanation'
diff --git a/app/views/user_mailer/password_change.html.haml b/app/views/user_mailer/password_change.html.haml
index 26314a217..559abf027 100644
--- a/app/views/user_mailer/password_change.html.haml
+++ b/app/views/user_mailer/password_change.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_lock_open.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
 
                               %h1= t 'devise.mailer.password_change.title'
                               %p.lead= t 'devise.mailer.password_change.explanation'
diff --git a/app/views/user_mailer/reconfirmation_instructions.html.haml b/app/views/user_mailer/reconfirmation_instructions.html.haml
index e3be8e295..7f10ba94f 100644
--- a/app/views/user_mailer/reconfirmation_instructions.html.haml
+++ b/app/views/user_mailer/reconfirmation_instructions.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_email.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: ''
 
                               %h1= t 'devise.mailer.reconfirmation_instructions.title'
                               %p.lead= t 'devise.mailer.reconfirmation_instructions.explanation'
diff --git a/app/views/user_mailer/reset_password_instructions.html.haml b/app/views/user_mailer/reset_password_instructions.html.haml
index 5d9ce6a75..eeed38c9e 100644
--- a/app/views/user_mailer/reset_password_instructions.html.haml
+++ b/app/views/user_mailer/reset_password_instructions.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_lock_open.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
 
                               %h1= t 'devise.mailer.reset_password_instructions.title'
                               %p.lead= t 'devise.mailer.reset_password_instructions.explanation'
diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml
index c5e1f5a28..72ea5e5d2 100644
--- a/app/views/user_mailer/warning.html.haml
+++ b/app/views/user_mailer/warning.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_warning.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_warning.png'), alt: ''
 
                               %h1= t "user_mailer.warning.title.#{@warning.action}"
 
diff --git a/app/views/user_mailer/welcome.html.haml b/app/views/user_mailer/welcome.html.haml
index 4a5788bf6..1f75ff48a 100644
--- a/app/views/user_mailer/welcome.html.haml
+++ b/app/views/user_mailer/welcome.html.haml
@@ -17,7 +17,7 @@
                                 %tbody
                                   %tr
                                     %td
-                                      = image_tag full_pack_url('icon_done.png'), alt: ''
+                                      = image_tag full_pack_url('media/images/mailer/icon_done.png'), alt: ''
 
                               %h1= t 'user_mailer.welcome.title', name: @resource.account.username
                               %p.lead= t 'user_mailer.welcome.explanation'
diff --git a/app/workers/distribution_worker.rb b/app/workers/distribution_worker.rb
index f423d43ae..4e20ef31b 100644
--- a/app/workers/distribution_worker.rb
+++ b/app/workers/distribution_worker.rb
@@ -4,7 +4,13 @@ class DistributionWorker
   include Sidekiq::Worker
 
   def perform(status_id)
-    FanOutOnWriteService.new.call(Status.find(status_id))
+    RedisLock.acquire(redis: Redis.current, key: "distribute:#{status_id}") do |lock|
+      if lock.acquired?
+        FanOutOnWriteService.new.call(Status.find(status_id))
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
   rescue ActiveRecord::RecordNotFound
     true
   end