about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/activitypub/inboxes_controller.rb2
-rw-r--r--app/controllers/admin/base_controller.rb7
-rw-r--r--app/controllers/admin/domain_blocks_controller.rb2
-rw-r--r--app/controllers/admin/reports_controller.rb9
-rw-r--r--app/controllers/api/v1/conversations_controller.rb20
-rw-r--r--app/controllers/api/v1/reports_controller.rb1
-rw-r--r--app/controllers/application_controller.rb1
-rw-r--r--app/controllers/auth/registrations_controller.rb4
-rw-r--r--app/controllers/filters_controller.rb5
-rw-r--r--app/controllers/invites_controller.rb5
-rw-r--r--app/controllers/settings/base_controller.rb5
-rw-r--r--app/controllers/settings/follower_domains_controller.rb2
-rw-r--r--app/controllers/settings/sessions_controller.rb5
-rw-r--r--app/helpers/application_helper.rb6
-rw-r--r--app/javascript/core/admin.js21
-rw-r--r--app/javascript/core/settings.js16
-rw-r--r--app/javascript/flavours/glitch/actions/compose.js5
-rw-r--r--app/javascript/flavours/glitch/components/display_name.js2
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js10
-rw-r--r--app/javascript/flavours/glitch/components/status.js1
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js2
-rw-r--r--app/javascript/flavours/glitch/features/composer/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/dropdown/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/composer/spoiler/index.js19
-rw-r--r--app/javascript/flavours/glitch/features/composer/upload_form/item/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/follow.js4
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js5
-rw-r--r--app/javascript/flavours/glitch/styles/forms.scss17
-rw-r--r--app/javascript/flavours/glitch/styles/rtl.scss77
-rw-r--r--app/javascript/flavours/glitch/styles/stream_entries.scss1
-rw-r--r--app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js22
-rw-r--r--app/javascript/mastodon/actions/cards.js52
-rw-r--r--app/javascript/mastodon/actions/compose.js27
-rw-r--r--app/javascript/mastodon/actions/conversations.js22
-rw-r--r--app/javascript/mastodon/actions/statuses.js2
-rw-r--r--app/javascript/mastodon/actions/suggestions.js52
-rw-r--r--app/javascript/mastodon/components/account.js13
-rw-r--r--app/javascript/mastodon/components/avatar_composite.js96
-rw-r--r--app/javascript/mastodon/components/display_name.js19
-rw-r--r--app/javascript/mastodon/components/status.js32
-rw-r--r--app/javascript/mastodon/components/status_content.js3
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js6
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/search_results.js43
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.js18
-rw-r--r--app/javascript/mastodon/features/compose/containers/compose_form_container.js4
-rw-r--r--app/javascript/mastodon/features/compose/containers/search_results_container.js9
-rw-r--r--app/javascript/mastodon/features/compose/containers/upload_container.js4
-rw-r--r--app/javascript/mastodon/features/compose/index.js3
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.js63
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversations_list.js18
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js12
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js2
-rw-r--r--app/javascript/mastodon/features/direct_timeline/index.js5
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_mart_search_light.js22
-rw-r--r--app/javascript/mastodon/features/status/components/card.js20
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js4
-rw-r--r--app/javascript/mastodon/features/status/containers/card_container.js8
-rw-r--r--app/javascript/mastodon/features/status/index.js2
-rw-r--r--app/javascript/mastodon/features/ui/index.js2
-rw-r--r--app/javascript/mastodon/initial_state.js1
-rw-r--r--app/javascript/mastodon/locales/ar.json17
-rw-r--r--app/javascript/mastodon/locales/ast.json5
-rw-r--r--app/javascript/mastodon/locales/bg.json5
-rw-r--r--app/javascript/mastodon/locales/ca.json7
-rw-r--r--app/javascript/mastodon/locales/co.json7
-rw-r--r--app/javascript/mastodon/locales/cs.json29
-rw-r--r--app/javascript/mastodon/locales/cy.json672
-rw-r--r--app/javascript/mastodon/locales/da.json7
-rw-r--r--app/javascript/mastodon/locales/de.json15
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json8
-rw-r--r--app/javascript/mastodon/locales/el.json65
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/eo.json5
-rw-r--r--app/javascript/mastodon/locales/es.json5
-rw-r--r--app/javascript/mastodon/locales/eu.json5
-rw-r--r--app/javascript/mastodon/locales/fa.json11
-rw-r--r--app/javascript/mastodon/locales/fi.json5
-rw-r--r--app/javascript/mastodon/locales/fr.json31
-rw-r--r--app/javascript/mastodon/locales/gl.json91
-rw-r--r--app/javascript/mastodon/locales/he.json5
-rw-r--r--app/javascript/mastodon/locales/hr.json5
-rw-r--r--app/javascript/mastodon/locales/hu.json5
-rw-r--r--app/javascript/mastodon/locales/hy.json5
-rw-r--r--app/javascript/mastodon/locales/id.json5
-rw-r--r--app/javascript/mastodon/locales/io.json5
-rw-r--r--app/javascript/mastodon/locales/it.json7
-rw-r--r--app/javascript/mastodon/locales/ja.json4
-rw-r--r--app/javascript/mastodon/locales/ka.json5
-rw-r--r--app/javascript/mastodon/locales/ko.json7
-rw-r--r--app/javascript/mastodon/locales/nl.json7
-rw-r--r--app/javascript/mastodon/locales/no.json5
-rw-r--r--app/javascript/mastodon/locales/oc.json7
-rw-r--r--app/javascript/mastodon/locales/pl.json2
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json59
-rw-r--r--app/javascript/mastodon/locales/pt.json5
-rw-r--r--app/javascript/mastodon/locales/ro.json5
-rw-r--r--app/javascript/mastodon/locales/ru.json5
-rw-r--r--app/javascript/mastodon/locales/sk.json5
-rw-r--r--app/javascript/mastodon/locales/sl.json23
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json5
-rw-r--r--app/javascript/mastodon/locales/sr.json5
-rw-r--r--app/javascript/mastodon/locales/sv.json5
-rw-r--r--app/javascript/mastodon/locales/ta.json5
-rw-r--r--app/javascript/mastodon/locales/te.json5
-rw-r--r--app/javascript/mastodon/locales/th.json5
-rw-r--r--app/javascript/mastodon/locales/tr.json5
-rw-r--r--app/javascript/mastodon/locales/uk.json5
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json5
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json5
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json5
-rw-r--r--app/javascript/mastodon/reducers/cards.js14
-rw-r--r--app/javascript/mastodon/reducers/conversations.js17
-rw-r--r--app/javascript/mastodon/reducers/index.js4
-rw-r--r--app/javascript/mastodon/reducers/suggestions.js30
-rw-r--r--app/javascript/packs/public.js1
-rw-r--r--app/javascript/styles/mastodon-light/diff.scss14
-rw-r--r--app/javascript/styles/mastodon/components.scss86
-rw-r--r--app/javascript/styles/mastodon/forms.scss17
-rw-r--r--app/javascript/styles/mastodon/rtl.scss92
-rw-r--r--app/javascript/styles/mastodon/stream_entries.scss1
-rw-r--r--app/lib/activitypub/activity.rb2
-rw-r--r--app/lib/activitypub/activity/create.rb122
-rw-r--r--app/lib/activitypub/activity/flag.rb8
-rw-r--r--app/lib/activitypub/tag_manager.rb6
-rw-r--r--app/lib/feed_manager.rb8
-rw-r--r--app/lib/formatter.rb2
-rw-r--r--app/lib/ostatus/atom_serializer.rb2
-rw-r--r--app/models/account.rb14
-rw-r--r--app/models/account_conversation.rb7
-rw-r--r--app/models/domain_block.rb13
-rw-r--r--app/models/media_attachment.rb6
-rw-r--r--app/models/mention.rb8
-rw-r--r--app/models/notification.rb2
-rw-r--r--app/models/status.rb25
-rw-r--r--app/models/status_stat.rb8
-rw-r--r--app/models/stream_entry.rb2
-rw-r--r--app/policies/status_policy.rb8
-rw-r--r--app/serializers/activitypub/note_serializer.rb2
-rw-r--r--app/serializers/initial_state_serializer.rb7
-rw-r--r--app/serializers/rest/conversation_serializer.rb7
-rw-r--r--app/serializers/rest/status_serializer.rb15
-rw-r--r--app/services/activitypub/fetch_remote_account_service.rb3
-rw-r--r--app/services/batched_remove_status_service.rb2
-rw-r--r--app/services/fan_out_on_write_service.rb4
-rw-r--r--app/services/fetch_link_card_service.rb13
-rw-r--r--app/services/notify_service.rb5
-rw-r--r--app/services/post_status_service.rb5
-rw-r--r--app/services/remove_status_service.rb2
-rw-r--r--app/services/verify_link_service.rb2
-rw-r--r--app/views/admin/accounts/show.html.haml6
-rw-r--r--app/views/admin/domain_blocks/_domain_block.html.haml5
-rw-r--r--app/views/admin/domain_blocks/index.html.haml1
-rw-r--r--app/views/admin/domain_blocks/new.html.haml3
-rw-r--r--app/views/admin/email_domain_blocks/_email_domain_block.html.haml2
-rw-r--r--app/views/admin/instances/_instance.html.haml2
-rw-r--r--app/views/admin/reports/show.html.haml6
-rw-r--r--app/views/admin/settings/edit.html.haml2
-rw-r--r--app/views/layouts/admin.html.haml2
-rw-r--r--app/views/layouts/embedded.html.haml8
-rw-r--r--app/views/settings/preferences/show.html.haml2
-rw-r--r--app/views/settings/profiles/show.html.haml8
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml27
-rw-r--r--app/views/stream_entries/_simple_status.html.haml28
-rw-r--r--app/views/stream_entries/show.html.haml2
-rw-r--r--app/workers/activitypub/distribution_worker.rb2
-rw-r--r--app/workers/activitypub/processing_worker.rb4
-rw-r--r--app/workers/activitypub/reply_distribution_worker.rb6
169 files changed, 1813 insertions, 964 deletions
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index af51e32d5..8f5e1887e 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -36,6 +36,6 @@ class ActivityPub::InboxesController < Api::BaseController
   end
 
   def process_payload
-    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'))
+    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'), @account&.id)
   end
 end
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index fc299f74c..f2190ddf9 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -9,6 +9,13 @@ module Admin
 
     before_action :require_staff!
     before_action :set_pack
+    before_action :set_body_classes
+
+    private
+
+    def set_body_classes
+      @body_classes = 'admin'
+    end
 
     def set_pack
       use_pack 'admin'
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index 64de2cbf0..90c70275a 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -46,7 +46,7 @@ module Admin
     end
 
     def resource_params
-      params.require(:domain_block).permit(:domain, :severity, :reject_media, :retroactive)
+      params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :retroactive)
     end
 
     def retroactive_unblock?
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb
index 5d7f43e00..e97ddb9b6 100644
--- a/app/controllers/admin/reports_controller.rb
+++ b/app/controllers/admin/reports_controller.rb
@@ -44,6 +44,14 @@ module Admin
       when 'resolve'
         @report.resolve!(current_account)
         log_action :resolve, @report
+      when 'disable'
+        @report.resolve!(current_account)
+        @report.target_account.user.disable!
+
+        log_action :resolve, @report
+        log_action :disable, @report.target_account.user
+
+        resolve_all_target_account_reports
       when 'silence'
         @report.resolve!(current_account)
         @report.target_account.update!(silenced: true)
@@ -55,6 +63,7 @@ module Admin
       else
         raise ActiveRecord::RecordNotFound
       end
+
       @report.reload
     end
 
diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb
index 736cb21ca..b19f27ebf 100644
--- a/app/controllers/api/v1/conversations_controller.rb
+++ b/app/controllers/api/v1/conversations_controller.rb
@@ -3,9 +3,11 @@
 class Api::V1::ConversationsController < Api::BaseController
   LIMIT = 20
 
-  before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
+  before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :index
+  before_action -> { doorkeeper_authorize! :write, :'write:conversations' }, except: :index
   before_action :require_user!
-  after_action :insert_pagination_headers
+  before_action :set_conversation, except: :index
+  after_action :insert_pagination_headers, only: :index
 
   respond_to :json
 
@@ -14,8 +16,22 @@ class Api::V1::ConversationsController < Api::BaseController
     render json: @conversations, each_serializer: REST::ConversationSerializer
   end
 
+  def read
+    @conversation.update!(unread: false)
+    render json: @conversation, serializer: REST::ConversationSerializer
+  end
+
+  def destroy
+    @conversation.destroy!
+    render_empty
+  end
+
   private
 
+  def set_conversation
+    @conversation = AccountConversation.where(account: current_account).find(params[:id])
+  end
+
   def paginated_conversations
     AccountConversation.where(account: current_account)
                        .paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index 726817927..e182a9c6c 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 class Api::V1::ReportsController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :read, :'read:reports' }, except: [:create]
   before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create]
   before_action :require_user!
 
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index dca6c5a5a..983b116c9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -201,6 +201,7 @@ class ApplicationController < ActionController::Base
   def respond_with_error(code)
     respond_to do |format|
       format.any  { head code }
+
       format.html do
         set_locale
         use_pack 'error'
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index fcfd1830a..0696dea86 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -9,7 +9,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   before_action :set_pack
   before_action :set_sessions, only: [:edit, :update]
   before_action :set_instance_presenter, only: [:new, :create, :update]
-  before_action :set_body_classes, only: [:new, :create]
+  before_action :set_body_classes, only: [:new, :create, :edit, :update]
 
   def destroy
     not_found
@@ -86,7 +86,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   end
 
   def set_body_classes
-    @body_classes = 'lighter'
+    @body_classes = %w(edit update).include?(action_name) ? 'admin' : 'lighter'
   end
 
   def set_invite
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index 0d1200fcc..f1e110d87 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -8,6 +8,7 @@ class FiltersController < ApplicationController
   before_action :set_filters, only: :index
   before_action :set_filter, only: [:edit, :update, :destroy]
   before_action :set_pack
+  before_action :set_body_classes
 
   def index
     @filters = current_account.custom_filters
@@ -59,4 +60,8 @@ class FiltersController < ApplicationController
   def resource_params
     params.require(:custom_filter).permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 3dc934761..52cddc404 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -7,6 +7,7 @@ class InvitesController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_pack
+  before_action :set_body_classes
 
   def index
     authorize :invite, :create?
@@ -49,4 +50,8 @@ class InvitesController < ApplicationController
   def resource_params
     params.require(:invite).permit(:max_uses, :expires_in, :autofollow)
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
index 7322d461b..34ef16568 100644
--- a/app/controllers/settings/base_controller.rb
+++ b/app/controllers/settings/base_controller.rb
@@ -5,8 +5,13 @@ class Settings::BaseController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_pack
+  before_action :set_body_classes
 
   def set_pack
     use_pack 'settings'
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb
index 83945df52..8aae379aa 100644
--- a/app/controllers/settings/follower_domains_controller.rb
+++ b/app/controllers/settings/follower_domains_controller.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-bulk'
-
 class Settings::FollowerDomainsController < Settings::BaseController
   def show
     @account = current_account
diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb
index 780ea64b4..f235dd477 100644
--- a/app/controllers/settings/sessions_controller.rb
+++ b/app/controllers/settings/sessions_controller.rb
@@ -3,6 +3,7 @@
 #  Intentionally does not inherit from BaseController
 class Settings::SessionsController < ApplicationController
   before_action :set_session, only: :destroy
+  before_action :set_body_classes
 
   def destroy
     @session.destroy!
@@ -15,4 +16,8 @@ class Settings::SessionsController < ApplicationController
   def set_session
     @session = current_user.session_activations.find(params[:id])
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e9b48fa98..5e7fdffb0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -84,7 +84,7 @@ module ApplicationHelper
   end
 
   def cdn_host
-    ENV['CDN_HOST'].presence
+    Rails.configuration.action_controller.asset_host
   end
 
   def cdn_host?
@@ -92,10 +92,10 @@ module ApplicationHelper
   end
 
   def storage_host
-    ENV['S3_ALIAS_HOST'].presence || ENV['S3_CLOUDFRONT_HOST'].presence
+    "https://#{ENV['S3_ALIAS_HOST'].presence || ENV['S3_CLOUDFRONT_HOST']}"
   end
 
   def storage_host?
-    storage_host.present?
+    ENV['S3_ALIAS_HOST'].present? || ENV['S3_CLOUDFRONT_HOST'].present?
   end
 end
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index 0c26dc18d..3f6f187bc 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -2,18 +2,6 @@
 
 import { delegate } from 'rails-ujs';
 
-function handleDeleteStatus(event) {
-  const [data] = event.detail;
-  const element = document.querySelector(`[data-id="${data.id}"]`);
-  if (element) {
-    element.parentNode.removeChild(element);
-  }
-}
-
-[].forEach.call(document.querySelectorAll('.trash-button'), (content) => {
-  content.addEventListener('ajax:success', handleDeleteStatus);
-});
-
 const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
 
 delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
@@ -24,6 +12,7 @@ delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
 
 delegate(document, batchCheckboxClassName, 'change', () => {
   const checkAllElement = document.querySelector('#batch_checkbox_all');
+
   if (checkAllElement) {
     checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
     checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
@@ -43,8 +32,14 @@ delegate(document, '.media-spoiler-hide-button', 'click', () => {
 });
 
 delegate(document, '#domain_block_severity', 'change', ({ target }) => {
-  const rejectMediaDiv = document.querySelector('.input.with_label.domain_block_reject_media');
+  const rejectMediaDiv   = document.querySelector('.input.with_label.domain_block_reject_media');
+  const rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
+
   if (rejectMediaDiv) {
     rejectMediaDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
   }
+
+  if (rejectReportsDiv) {
+    rejectReportsDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
+  }
 });
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index af97c84f9..23a303747 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -1,30 +1,16 @@
 //  This file will be loaded on settings pages, regardless of theme.
 
-const { length } = require('stringz');
 const { delegate } = require('rails-ujs');
 import emojify from '../mastodon/features/emoji/emoji';
 
 delegate(document, '#account_display_name', 'input', ({ target }) => {
-  const nameCounter = document.querySelector('.name-counter');
-  const name        = document.querySelector('.card .display-name strong');
-
-  if (nameCounter) {
-    nameCounter.textContent = 30 - length(target.value);
-  }
+  const name = document.querySelector('.card .display-name strong');
 
   if (name) {
     name.innerHTML = emojify(target.value);
   }
 });
 
-delegate(document, '#account_note', 'input', ({ target }) => {
-  const noteCounter = document.querySelector('.note-counter');
-
-  if (noteCounter) {
-    noteCounter.textContent = 500 - length(target.value);
-  }
-});
-
 delegate(document, '#account_avatar', 'change', ({ target }) => {
   const avatar = document.querySelector('.card .avatar img');
   const [file] = target.files || [];
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index fb311fc0a..58f2b3786 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -120,6 +120,7 @@ export function submitCompose() {
   return function (dispatch, getState) {
     let status = getState().getIn(['compose', 'text'], '');
     let media  = getState().getIn(['compose', 'media_attachments']);
+    let spoilerText = getState().getIn(['compose', 'spoiler_text'], '');
 
     if ((!status || !status.length) && media.size === 0) {
       return;
@@ -133,8 +134,8 @@ export function submitCompose() {
       status,
       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
       media_ids: media.map(item => item.get('id')),
-      sensitive: getState().getIn(['compose', 'sensitive']),
-      spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
+      sensitive: getState().getIn(['compose', 'sensitive']) || spoilerText.length > 0,
+      spoiler_text: spoilerText,
       visibility: getState().getIn(['compose', 'privacy']),
     }, {
       headers: {
diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js
index 4c65aaefa..d6ac4907d 100644
--- a/app/javascript/flavours/glitch/components/display_name.js
+++ b/app/javascript/flavours/glitch/components/display_name.js
@@ -15,7 +15,7 @@ export default function DisplayName ({
   //  The result.
   return account ? (
     <span className={computedClass}>
-      <strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} />
+      <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
       {inline ? ' ' : null}
       <span className='display-name__account'>@{account.get('acct')}</span>
     </span>
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 613318102..e8dfd6f8e 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -215,6 +215,7 @@ export default class MediaGallery extends React.PureComponent {
     standalone: PropTypes.bool,
     letterbox: PropTypes.bool,
     fullwidth: PropTypes.bool,
+    hidden: PropTypes.bool,
     media: ImmutablePropTypes.list.isRequired,
     size: PropTypes.object,
     onOpenMedia: PropTypes.func.isRequired,
@@ -235,6 +236,14 @@ export default class MediaGallery extends React.PureComponent {
     }
   }
 
+  componentDidUpdate (prevProps) {
+    if (this.node && this.node.offsetWidth) {
+      this.setState({
+        width: this.node.offsetWidth,
+      });
+    }
+  }
+
   handleOpen = () => {
     this.setState({ visible: !this.state.visible });
   }
@@ -244,6 +253,7 @@ export default class MediaGallery extends React.PureComponent {
   }
 
   handleRef = (node) => {
+    this.node = node;
     if (node /*&& this.isStandaloneEligible()*/) {
       // offsetWidth triggers a layout, so only calculate when we need to
       this.setState({
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index 665aa457a..449532ea4 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -465,6 +465,7 @@ export default class Status extends ImmutablePureComponent {
                 sensitive={status.get('sensitive')}
                 letterbox={settings.getIn(['media', 'letterbox'])}
                 fullwidth={settings.getIn(['media', 'fullwidth'])}
+                hidden={isCollapsed || !isExpanded}
                 onOpenMedia={this.props.onOpenMedia}
               />
             )}
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
index 1c0e081cc..280389bba 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
@@ -34,7 +34,7 @@ export default class MovedNote extends ImmutablePureComponent {
       <div className='account__moved-note'>
         <div className='account__moved-note__message'>
           <div className='account__moved-note__icon-wrapper'><i className='fa fa-fw fa-suitcase account__moved-note__icon' /></div>
-          <FormattedMessage id='account.moved_to' defaultMessage='{name} has moved to:' values={{ name: <strong dangerouslySetInnerHTML={displayNameHtml} /> }} />
+          <FormattedMessage id='account.moved_to' defaultMessage='{name} has moved to:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} />
         </div>
 
         <a href={to.get('url')} onClick={this.handleAccountClick} className='detailed-status__display-name'>
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
index 257797047..029b11a36 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/composer/index.js
@@ -437,6 +437,7 @@ class Composer extends React.Component {
           intl={intl}
           onChange={handleChangeSpoiler}
           onSubmit={handleSubmit}
+          onSecondarySubmit={handleSecondarySubmit}
           text={spoilerText}
           ref={handleRefSpoilerText}
         />
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
index 8cfbac1bb..7817cc964 100644
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
@@ -131,7 +131,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
     this.state = {
       needsModalUpdate: false,
       open: false,
-      placement: null,
+      placement: 'bottom',
     };
   }
 
diff --git a/app/javascript/flavours/glitch/features/composer/spoiler/index.js b/app/javascript/flavours/glitch/features/composer/spoiler/index.js
index a7fecbcf5..1c3c962f0 100644
--- a/app/javascript/flavours/glitch/features/composer/spoiler/index.js
+++ b/app/javascript/flavours/glitch/features/composer/spoiler/index.js
@@ -25,18 +25,31 @@ const handlers = {
     ctrlKey,
     keyCode,
     metaKey,
+    altKey,
   }) {
-    const { onSubmit } = this.props;
+    const { onSubmit, onSecondarySubmit } = this.props;
 
     //  We submit the status on control/meta + enter.
     if (onSubmit && keyCode === 13 && (ctrlKey || metaKey)) {
       onSubmit();
     }
+
+    // Submit the status with secondary visibility on alt + enter.
+    if (onSecondarySubmit && keyCode === 13 && altKey) {
+      onSecondarySubmit();
+    }
   },
 
   handleRefSpoilerText (spoilerText) {
     this.spoilerText = spoilerText;
   },
+
+  //  When the escape key is released, we focus the UI.
+  handleKeyUp ({ key }) {
+    if (key === 'Escape') {
+      document.querySelector('.ui').parentElement.focus();
+    }
+  },
 };
 
 //  The component.
@@ -50,7 +63,7 @@ export default class ComposerSpoiler extends React.PureComponent {
 
   //  Rendering.
   render () {
-    const { handleKeyDown, handleRefSpoilerText } = this.handlers;
+    const { handleKeyDown, handleKeyUp, handleRefSpoilerText } = this.handlers;
     const {
       hidden,
       intl,
@@ -69,6 +82,7 @@ export default class ComposerSpoiler extends React.PureComponent {
             id='glitch.composer.spoiler.input'
             onChange={onChange}
             onKeyDown={handleKeyDown}
+            onKeyUp={handleKeyUp}
             placeholder={intl.formatMessage(messages.placeholder)}
             type='text'
             value={text}
@@ -87,5 +101,6 @@ ComposerSpoiler.propTypes = {
   intl: PropTypes.object.isRequired,
   onChange: PropTypes.func,
   onSubmit: PropTypes.func,
+  onSecondarySubmit: PropTypes.func,
   text: PropTypes.string,
 };
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
index 5addccfb1..93fa4e39e 100644
--- a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
+++ b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
@@ -14,6 +14,7 @@ import IconButton from 'flavours/glitch/components/icon_button';
 //  Utils.
 import Motion from 'flavours/glitch/util/optional_motion';
 import { assignHandlers } from 'flavours/glitch/util/react_helpers';
+import { isUserTouching } from 'flavours/glitch/util/is_mobile';
 
 //  Messages.
 const messages = defineMessages({
@@ -130,7 +131,7 @@ export default class ComposerUploadFormItem extends React.PureComponent {
       hovered,
       dirtyDescription,
     } = this.state;
-    const active = hovered || focused;
+    const active = hovered || focused || isUserTouching();
     const computedClass = classNames('composer--upload_form--item', { active });
     const x = ((focusX /  2) + .5) * 100;
     const y = ((focusY / -2) + .5) * 100;
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js
index 54506f67c..ea81d9ab4 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/follow.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js
@@ -63,13 +63,13 @@ export default class NotificationFollow extends ImmutablePureComponent {
     //  Links to the display name.
     const displayName = account.get('display_name_html') || account.get('username');
     const link = (
-      <Permalink
+      <bdi><Permalink
         className='notification__display-name'
         href={account.get('url')}
         title={account.get('acct')}
         to={`/accounts/${account.get('id')}`}
         dangerouslySetInnerHTML={{ __html: displayName }}
-      />
+      /></bdi>
     );
 
     //  Renders.
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 4382748d5..cfa1450f6 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -127,7 +127,7 @@ export default class Status extends ImmutablePureComponent {
     if (status.get('favourited')) {
       this.props.dispatch(unfavourite(status));
     } else {
-      if (e.shiftKey || !favouriteModal) {
+      if ((e && e.shiftKey) || !favouriteModal) {
         this.handleModalFavourite(status);
       } else {
         this.props.dispatch(openModal('FAVOURITE', { status, onFavourite: this.handleModalFavourite }));
@@ -164,7 +164,7 @@ export default class Status extends ImmutablePureComponent {
     if (status.get('reblogged')) {
       this.props.dispatch(unreblog(status));
     } else {
-      if (e.shiftKey || !boostModal) {
+      if ((e && e.shiftKey) || !boostModal) {
         this.handleModalReblog(status);
       } else {
         this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index 227f298e4..4f95aea96 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -220,6 +220,11 @@ export default class Video extends React.PureComponent {
   }
 
   componentDidUpdate (prevProps) {
+    if (this.player && this.player.offsetWidth && !this.state.fullscreen) {
+      this.setState({
+        containerWidth: this.player.offsetWidth,
+      });
+    }
     if (this.video && this.state.revealed && this.props.preventPlayback && !prevProps.preventPlayback) {
       this.video.pause();
     }
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index be2bf7cea..8c4c934ea 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -427,7 +427,7 @@ code {
 
     &__append {
       position: absolute;
-      right: 1px;
+      right: 3px;
       top: 1px;
       padding: 10px;
       padding-bottom: 9px;
@@ -460,9 +460,20 @@ code {
   border-radius: 4px;
   padding: 15px 10px;
   margin-bottom: 30px;
-  box-shadow: 0 0 5px rgba($base-shadow-color, 0.2);
   text-align: center;
 
+  &.notice {
+    border: 1px solid rgba($valid-value-color, 0.5);
+    background: rgba($valid-value-color, 0.25);
+    color: $valid-value-color;
+  }
+
+  &.alert {
+    border: 1px solid rgba($error-value-color, 0.5);
+    background: rgba($error-value-color, 0.25);
+    color: $error-value-color;
+  }
+
   p {
     margin-bottom: 15px;
   }
@@ -551,12 +562,12 @@ code {
 .oauth-prompt,
 .follow-prompt {
   margin-bottom: 30px;
-  text-align: center;
   color: $darker-text-color;
 
   h2 {
     font-size: 16px;
     margin-bottom: 30px;
+    text-align: center;
   }
 
   strong {
diff --git a/app/javascript/flavours/glitch/styles/rtl.scss b/app/javascript/flavours/glitch/styles/rtl.scss
index 70aaa5bb1..d4618440d 100644
--- a/app/javascript/flavours/glitch/styles/rtl.scss
+++ b/app/javascript/flavours/glitch/styles/rtl.scss
@@ -73,7 +73,7 @@ body.rtl {
     float: left;
   }
 
-  .setting-toggle {
+  .setting-toggle__label {
     margin-left: 0;
     margin-right: 8px;
   }
@@ -200,6 +200,10 @@ body.rtl {
     right: -2.14285714em;
   }
 
+  .admin-wrapper {
+    direction: rtl;
+  }
+
   .admin-wrapper .sidebar ul a i.fa,
   a.table-action-link i.fa {
     margin-right: 0;
@@ -218,22 +222,47 @@ body.rtl {
     right: 0;
   }
 
+  .simple_form .input.radio_buttons .radio {
+    left: auto;
+    right: 0;
+  }
+
+  .simple_form .input.radio_buttons .radio > label {
+    padding-right: 28px;
+    padding-left: 0;
+  }
+
   .simple_form .input-with-append .input input {
     padding-left: 142px;
     padding-right: 0;
   }
 
-  .simple_form .input-with-append .append {
+  .simple_form .input.boolean label.checkbox {
+    left: auto;
+    right: 0;
+  }
+
+  .simple_form .input.boolean .label_input,
+  .simple_form .input.boolean .hint {
+    padding-left: 0;
+    padding-right: 28px;
+  }
+
+  .simple_form .label_input__append {
     right: auto;
-    left: 0;
+    left: 3px;
 
     &::after {
       right: auto;
       left: 0;
-      background-image: linear-gradient(to left, rgba($ui-base-color, 0), $ui-base-color);
+      background-image: linear-gradient(to left, rgba(darken($ui-base-color, 10%), 0), darken($ui-base-color, 10%));
     }
   }
 
+  .simple_form select {
+    background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>") no-repeat left 8px center / auto 16px;
+  }
+
   .table th,
   .table td {
     text-align: right;
@@ -249,6 +278,10 @@ body.rtl {
     left: auto;
   }
 
+  .landing-page__call-to-action .row__information-board {
+    direction: rtl;
+  }
+
   .landing-page .header .hero .floats .float-1 {
     left: -120px;
     right: auto;
@@ -312,4 +345,40 @@ body.rtl {
       margin-right: 20px;
     }
   }
+
+  .landing-page__information {
+    .account__display-name {
+      margin-right: 0;
+      margin-left: 5px;
+    }
+
+    .account__avatar-wrapper {
+      margin-left: 12px;
+      margin-right: 0;
+    }
+  }
+
+  .card__bar .display-name {
+    margin-left: 0;
+    margin-right: 15px;
+    text-align: right;
+  }
+
+  .fa-chevron-left::before {
+    content: "\F054";
+  }
+
+  .fa-chevron-right::before {
+    content: "\F053";
+  }
+
+  .column-back-button__icon {
+    margin-right: 0;
+    margin-left: 5px;
+  }
+
+  .column-header__setting-arrows .column-header__setting-btn:last-child {
+    padding-left: 0;
+    padding-right: 10px;
+  }
 }
diff --git a/app/javascript/flavours/glitch/styles/stream_entries.scss b/app/javascript/flavours/glitch/styles/stream_entries.scss
index c458fcb79..80e2d0ca0 100644
--- a/app/javascript/flavours/glitch/styles/stream_entries.scss
+++ b/app/javascript/flavours/glitch/styles/stream_entries.scss
@@ -3,7 +3,6 @@
   border-radius: 4px;
   overflow: hidden;
   margin-bottom: 10px;
-  text-align: left;
 
   @media screen and (max-width: $no-gap-breakpoint) {
     margin-bottom: 0;
diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js b/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js
index 36351ec02..164fdcc0b 100644
--- a/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js
+++ b/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js
@@ -2,7 +2,7 @@
 // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
 
 import data from './emoji_mart_data_light';
-import { getData, getSanitizedData, intersect } from './emoji_utils';
+import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
 
 let originalPool = {};
 let index = {};
@@ -103,7 +103,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
       }
     }
 
-    allResults = values.map((value) => {
+    const searchValue = (value) => {
       let aPool = pool,
         aIndex = index,
         length = 0;
@@ -150,15 +150,23 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
       }
 
       return aIndex.results;
-    }).filter(a => a);
+    };
 
-    if (allResults.length > 1) {
-      results = intersect.apply(null, allResults);
-    } else if (allResults.length) {
-      results = allResults[0];
+    if (values.length > 1) {
+      results = searchValue(value);
     } else {
       results = [];
     }
+
+    allResults = values.map(searchValue).filter(a => a);
+
+    if (allResults.length > 1) {
+      allResults = intersect.apply(null, allResults);
+    } else if (allResults.length) {
+      allResults = allResults[0];
+    }
+
+    results = uniq(results.concat(allResults));
   }
 
   if (results) {
diff --git a/app/javascript/mastodon/actions/cards.js b/app/javascript/mastodon/actions/cards.js
deleted file mode 100644
index baf04833a..000000000
--- a/app/javascript/mastodon/actions/cards.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import api from '../api';
-
-export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST';
-export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS';
-export const STATUS_CARD_FETCH_FAIL    = 'STATUS_CARD_FETCH_FAIL';
-
-export function fetchStatusCard(id) {
-  return (dispatch, getState) => {
-    if (getState().getIn(['cards', id], null) !== null) {
-      return;
-    }
-
-    dispatch(fetchStatusCardRequest(id));
-
-    api(getState).get(`/api/v1/statuses/${id}/card`).then(response => {
-      if (!response.data.url) {
-        return;
-      }
-
-      dispatch(fetchStatusCardSuccess(id, response.data));
-    }).catch(error => {
-      dispatch(fetchStatusCardFail(id, error));
-    });
-  };
-};
-
-export function fetchStatusCardRequest(id) {
-  return {
-    type: STATUS_CARD_FETCH_REQUEST,
-    id,
-    skipLoading: true,
-  };
-};
-
-export function fetchStatusCardSuccess(id, card) {
-  return {
-    type: STATUS_CARD_FETCH_SUCCESS,
-    id,
-    card,
-    skipLoading: true,
-  };
-};
-
-export function fetchStatusCardFail(id, error) {
-  return {
-    type: STATUS_CARD_FETCH_FAIL,
-    id,
-    error,
-    skipLoading: true,
-    skipAlert: true,
-  };
-};
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 6d975cd1e..86d83122f 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -56,7 +56,7 @@ export function changeCompose(text) {
   };
 };
 
-export function replyCompose(status, router) {
+export function replyCompose(status, routerHistory) {
   return (dispatch, getState) => {
     dispatch({
       type: COMPOSE_REPLY,
@@ -64,7 +64,7 @@ export function replyCompose(status, router) {
     });
 
     if (!getState().getIn(['compose', 'mounted'])) {
-      router.push('/statuses/new');
+      routerHistory.push('/statuses/new');
     }
   };
 };
@@ -81,7 +81,7 @@ export function resetCompose() {
   };
 };
 
-export function mentionCompose(account, router) {
+export function mentionCompose(account, routerHistory) {
   return (dispatch, getState) => {
     dispatch({
       type: COMPOSE_MENTION,
@@ -89,12 +89,12 @@ export function mentionCompose(account, router) {
     });
 
     if (!getState().getIn(['compose', 'mounted'])) {
-      router.push('/statuses/new');
+      routerHistory.push('/statuses/new');
     }
   };
 };
 
-export function directCompose(account, router) {
+export function directCompose(account, routerHistory) {
   return (dispatch, getState) => {
     dispatch({
       type: COMPOSE_DIRECT,
@@ -102,12 +102,12 @@ export function directCompose(account, router) {
     });
 
     if (!getState().getIn(['compose', 'mounted'])) {
-      router.push('/statuses/new');
+      routerHistory.push('/statuses/new');
     }
   };
 };
 
-export function submitCompose() {
+export function submitCompose(routerHistory) {
   return function (dispatch, getState) {
     const status = getState().getIn(['compose', 'text'], '');
     const media  = getState().getIn(['compose', 'media_attachments']);
@@ -133,21 +133,24 @@ export function submitCompose() {
       dispatch(insertIntoTagHistory(response.data.tags, status));
       dispatch(submitComposeSuccess({ ...response.data }));
 
-      // To make the app more responsive, immediately get the status into the columns
+      // To make the app more responsive, immediately push the status
+      // into the columns
 
-      const insertIfOnline = (timelineId) => {
+      const insertIfOnline = timelineId => {
         if (getState().getIn(['timelines', timelineId, 'items', 0]) !== null) {
           dispatch(updateTimeline(timelineId, { ...response.data }));
         }
       };
 
-      insertIfOnline('home');
+      if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
+        routerHistory.push('/timelines/direct');
+      } else if (response.data.visibility !== 'direct') {
+        insertIfOnline('home');
+      }
 
       if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
         insertIfOnline('community');
         insertIfOnline('public');
-      } else if (response.data.visibility === 'direct') {
-        insertIfOnline('direct');
       }
     }).catch(function (error) {
       dispatch(submitComposeFail(error));
diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js
index 3840d23ca..aefd2fef7 100644
--- a/app/javascript/mastodon/actions/conversations.js
+++ b/app/javascript/mastodon/actions/conversations.js
@@ -5,11 +5,33 @@ import {
   importFetchedStatus,
 } from './importer';
 
+export const CONVERSATIONS_MOUNT   = 'CONVERSATIONS_MOUNT';
+export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT';
+
 export const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST';
 export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS';
 export const CONVERSATIONS_FETCH_FAIL    = 'CONVERSATIONS_FETCH_FAIL';
 export const CONVERSATIONS_UPDATE        = 'CONVERSATIONS_UPDATE';
 
+export const CONVERSATIONS_READ = 'CONVERSATIONS_READ';
+
+export const mountConversations = () => ({
+  type: CONVERSATIONS_MOUNT,
+});
+
+export const unmountConversations = () => ({
+  type: CONVERSATIONS_UNMOUNT,
+});
+
+export const markConversationRead = conversationId => (dispatch, getState) => {
+  dispatch({
+    type: CONVERSATIONS_READ,
+    id: conversationId,
+  });
+
+  api(getState).post(`/api/v1/conversations/${conversationId}/read`);
+};
+
 export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
   dispatch(expandConversationsRequest());
 
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index 8d5e72bec..e7c89b4ba 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -3,7 +3,6 @@ import openDB from '../storage/db';
 import { evictStatus } from '../storage/modifier';
 
 import { deleteFromTimelines } from './timelines';
-import { fetchStatusCard } from './cards';
 import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer';
 
 export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
@@ -86,7 +85,6 @@ export function fetchStatus(id) {
     const skipLoading = getState().getIn(['statuses', id], null) !== null;
 
     dispatch(fetchContext(id));
-    dispatch(fetchStatusCard(id));
 
     if (skipLoading) {
       return;
diff --git a/app/javascript/mastodon/actions/suggestions.js b/app/javascript/mastodon/actions/suggestions.js
new file mode 100644
index 000000000..b15bd916b
--- /dev/null
+++ b/app/javascript/mastodon/actions/suggestions.js
@@ -0,0 +1,52 @@
+import api from '../api';
+import { importFetchedAccounts } from './importer';
+
+export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
+export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
+export const SUGGESTIONS_FETCH_FAIL    = 'SUGGESTIONS_FETCH_FAIL';
+
+export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
+
+export function fetchSuggestions() {
+  return (dispatch, getState) => {
+    dispatch(fetchSuggestionsRequest());
+
+    api(getState).get('/api/v1/suggestions').then(response => {
+      dispatch(importFetchedAccounts(response.data));
+      dispatch(fetchSuggestionsSuccess(response.data));
+    }).catch(error => dispatch(fetchSuggestionsFail(error)));
+  };
+};
+
+export function fetchSuggestionsRequest() {
+  return {
+    type: SUGGESTIONS_FETCH_REQUEST,
+    skipLoading: true,
+  };
+};
+
+export function fetchSuggestionsSuccess(accounts) {
+  return {
+    type: SUGGESTIONS_FETCH_SUCCESS,
+    accounts,
+    skipLoading: true,
+  };
+};
+
+export function fetchSuggestionsFail(error) {
+  return {
+    type: SUGGESTIONS_FETCH_FAIL,
+    error,
+    skipLoading: true,
+    skipAlert: true,
+  };
+};
+
+export const dismissSuggestion = accountId => (dispatch, getState) => {
+  dispatch({
+    type: SUGGESTIONS_DISMISS,
+    id: accountId,
+  });
+
+  api(getState).delete(`/api/v1/suggestions/${accountId}`);
+};
diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js
index c021e3267..2bcea8b67 100644
--- a/app/javascript/mastodon/components/account.js
+++ b/app/javascript/mastodon/components/account.js
@@ -30,6 +30,9 @@ class Account extends ImmutablePureComponent {
     onMuteNotifications: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
     hidden: PropTypes.bool,
+    actionIcon: PropTypes.string,
+    actionTitle: PropTypes.string,
+    onActionClick: PropTypes.func,
   };
 
   handleFollow = () => {
@@ -52,8 +55,12 @@ class Account extends ImmutablePureComponent {
     this.props.onMuteNotifications(this.props.account, false);
   }
 
+  handleAction = () => {
+    this.props.onActionClick(this.props.account);
+  }
+
   render () {
-    const { account, intl, hidden } = this.props;
+    const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props;
 
     if (!account) {
       return <div />;
@@ -70,7 +77,9 @@ class Account extends ImmutablePureComponent {
 
     let buttons;
 
-    if (account.get('id') !== me && account.get('relationship', null) !== null) {
+    if (onActionClick && actionIcon) {
+      buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
+    } else if (account.get('id') !== me && account.get('relationship', null) !== null) {
       const following = account.getIn(['relationship', 'following']);
       const requested = account.getIn(['relationship', 'requested']);
       const blocking  = account.getIn(['relationship', 'blocking']);
diff --git a/app/javascript/mastodon/components/avatar_composite.js b/app/javascript/mastodon/components/avatar_composite.js
new file mode 100644
index 000000000..4a9a73c51
--- /dev/null
+++ b/app/javascript/mastodon/components/avatar_composite.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { autoPlayGif } from '../initial_state';
+
+export default class AvatarComposite extends React.PureComponent {
+
+  static propTypes = {
+    accounts: ImmutablePropTypes.list.isRequired,
+    animate: PropTypes.bool,
+    size: PropTypes.number.isRequired,
+  };
+
+  static defaultProps = {
+    animate: autoPlayGif,
+  };
+
+  renderItem (account, size, index) {
+    const { animate } = this.props;
+
+    let width  = 50;
+    let height = 100;
+    let top    = 'auto';
+    let left   = 'auto';
+    let bottom = 'auto';
+    let right  = 'auto';
+
+    if (size === 1) {
+      width = 100;
+    }
+
+    if (size === 4 || (size === 3 && index > 0)) {
+      height = 50;
+    }
+
+    if (size === 2) {
+      if (index === 0) {
+        right = '2px';
+      } else {
+        left = '2px';
+      }
+    } else if (size === 3) {
+      if (index === 0) {
+        right = '2px';
+      } else if (index > 0) {
+        left = '2px';
+      }
+
+      if (index === 1) {
+        bottom = '2px';
+      } else if (index > 1) {
+        top = '2px';
+      }
+    } else if (size === 4) {
+      if (index === 0 || index === 2) {
+        right = '2px';
+      }
+
+      if (index === 1 || index === 3) {
+        left = '2px';
+      }
+
+      if (index < 2) {
+        bottom = '2px';
+      } else {
+        top = '2px';
+      }
+    }
+
+    const style = {
+      left: left,
+      top: top,
+      right: right,
+      bottom: bottom,
+      width: `${width}%`,
+      height: `${height}%`,
+      backgroundSize: 'cover',
+      backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
+    };
+
+    return (
+      <div key={account.get('id')} style={style} />
+    );
+  }
+
+  render() {
+    const { accounts, size } = this.props;
+
+    return (
+      <div className='account__avatar-composite' style={{ width: `${size}px`, height: `${size}px` }}>
+        {accounts.take(4).map((account, i) => this.renderItem(account, accounts.size, i))}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js
index c3a9ab921..c2c40cb3f 100644
--- a/app/javascript/mastodon/components/display_name.js
+++ b/app/javascript/mastodon/components/display_name.js
@@ -1,25 +1,28 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
 
 export default class DisplayName extends React.PureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
-    withAcct: PropTypes.bool,
-  };
-
-  static defaultProps = {
-    withAcct: true,
+    others: ImmutablePropTypes.list,
   };
 
   render () {
-    const { account, withAcct } = this.props;
+    const { account, others } = this.props;
     const displayNameHtml = { __html: account.get('display_name_html') };
 
+    let suffix;
+
+    if (others && others.size > 1) {
+      suffix = `+${others.size}`;
+    } else {
+      suffix = <span className='display-name__account'>@{account.get('acct')}</span>;
+    }
+
     return (
       <span className='display-name'>
-        <bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {withAcct && <span className='display-name__account'>@{account.get('acct')}</span>}
+        <bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {suffix}
       </span>
     );
   }
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 90c689a75..9fa8cc008 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -3,11 +3,13 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import Avatar from './avatar';
 import AvatarOverlay from './avatar_overlay';
+import AvatarComposite from './avatar_composite';
 import RelativeTimestamp from './relative_timestamp';
 import DisplayName from './display_name';
 import StatusContent from './status_content';
 import StatusActionBar from './status_action_bar';
 import AttachmentList from './attachment_list';
+import Card from '../features/status/components/card';
 import { injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { MediaGallery, Video } from '../features/ui/util/async-components';
@@ -45,6 +47,8 @@ class Status extends ImmutablePureComponent {
   static propTypes = {
     status: ImmutablePropTypes.map,
     account: ImmutablePropTypes.map,
+    otherAccounts: ImmutablePropTypes.list,
+    onClick: PropTypes.func,
     onReply: PropTypes.func,
     onFavourite: PropTypes.func,
     onReblog: PropTypes.func,
@@ -60,6 +64,7 @@ class Status extends ImmutablePureComponent {
     onToggleHidden: PropTypes.func,
     muted: PropTypes.bool,
     hidden: PropTypes.bool,
+    unread: PropTypes.bool,
     onMoveUp: PropTypes.func,
     onMoveDown: PropTypes.func,
   };
@@ -74,6 +79,11 @@ class Status extends ImmutablePureComponent {
   ]
 
   handleClick = () => {
+    if (this.props.onClick) {
+      this.props.onClick();
+      return;
+    }
+
     if (!this.context.router) {
       return;
     }
@@ -158,7 +168,7 @@ class Status extends ImmutablePureComponent {
     let media = null;
     let statusAvatar, prepend, rebloggedByText;
 
-    const { intl, hidden, featured } = this.props;
+    const { intl, hidden, featured, otherAccounts, unread } = this.props;
 
     let { status, account, ...other } = this.props;
 
@@ -247,11 +257,21 @@ class Status extends ImmutablePureComponent {
           </Bundle>
         );
       }
+    } else if (status.get('spoiler_text').length === 0 && status.get('card')) {
+      media = (
+        <Card
+          onOpenMedia={this.props.onOpenMedia}
+          card={status.get('card')}
+          compact
+        />
+      );
     }
 
-    if (account === undefined || account === null) {
+    if (otherAccounts) {
+      statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />;
+    } else if (account === undefined || account === null) {
       statusAvatar = <Avatar account={status.get('account')} size={48} />;
-    }else{
+    } else {
       statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
     }
 
@@ -269,10 +289,10 @@ class Status extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={handlers}>
-        <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}>
+        <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}>
           {prepend}
 
-          <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
+          <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
             <div className='status__info'>
               <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
 
@@ -281,7 +301,7 @@ class Status extends ImmutablePureComponent {
                   {statusAvatar}
                 </div>
 
-                <DisplayName account={status.get('account')} />
+                <DisplayName account={status.get('account')} others={otherAccounts} />
               </a>
             </div>
 
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index eda7d6ac3..5e3365618 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -159,7 +159,7 @@ export default class StatusContent extends React.PureComponent {
     }
 
     const readMoreButton = (
-      <button className='status__content__read-more-button' onClick={this.props.onClick}>
+      <button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
         <FormattedMessage id='status.read_more' defaultMessage='Read more' /><i className='fa fa-fw fa-angle-right' />
       </button>
     );
@@ -197,6 +197,7 @@ export default class StatusContent extends React.PureComponent {
         <div
           ref={this.setRef}
           tabIndex='0'
+          key='content'
           className={classNames}
           style={directionStyle}
           dangerouslySetInnerHTML={content}
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index eec93fc28..4b56c7fdd 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -31,6 +31,10 @@ const messages = defineMessages({
 export default @injectIntl
 class ComposeForm extends ImmutablePureComponent {
 
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
   static propTypes = {
     intl: PropTypes.object.isRequired,
     text: PropTypes.string.isRequired,
@@ -85,7 +89,7 @@ class ComposeForm extends ImmutablePureComponent {
       return;
     }
 
-    this.props.onSubmit();
+    this.props.onSubmit(this.context.router ? this.context.router.history : null);
   }
 
   onSuggestionsClearRequested = () => {
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 7b5482f05..5698765d9 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -164,7 +164,7 @@ class PrivacyDropdown extends React.PureComponent {
 
   state = {
     open: false,
-    placement: null,
+    placement: 'bottom',
   };
 
   handleToggle = ({ target }) => {
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index c351b84bb..f0ddf6d71 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -1,19 +1,56 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 import AccountContainer from '../../../containers/account_container';
 import StatusContainer from '../../../containers/status_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Hashtag from '../../../components/hashtag';
 
-export default class SearchResults extends ImmutablePureComponent {
+const messages = defineMessages({
+  dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
+});
+
+export default @injectIntl
+class SearchResults extends ImmutablePureComponent {
 
   static propTypes = {
     results: ImmutablePropTypes.map.isRequired,
+    suggestions: ImmutablePropTypes.list.isRequired,
+    fetchSuggestions: PropTypes.func.isRequired,
+    dismissSuggestion: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
   };
 
+  componentDidMount () {
+    this.props.fetchSuggestions();
+  }
+
   render () {
-    const { results } = this.props;
+    const { intl, results, suggestions, dismissSuggestion } = this.props;
+
+    if (results.isEmpty() && !suggestions.isEmpty()) {
+      return (
+        <div className='search-results'>
+          <div className='trends'>
+            <div className='trends__header'>
+              <i className='fa fa-user-plus fa-fw' />
+              <FormattedMessage id='suggestions.header' defaultMessage='You might be interested in…' />
+            </div>
+
+            {suggestions && suggestions.map(accountId => (
+              <AccountContainer
+                key={accountId}
+                id={accountId}
+                actionIcon='times'
+                actionTitle={intl.formatMessage(messages.dismissSuggestion)}
+                onActionClick={dismissSuggestion}
+              />
+            ))}
+          </div>
+        </div>
+      );
+    }
 
     let accounts, statuses, hashtags;
     let count = 0;
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index e377da90c..a1e99dcbb 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -14,6 +14,10 @@ const messages = defineMessages({
 export default @injectIntl
 class Upload extends ImmutablePureComponent {
 
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
     intl: PropTypes.object.isRequired,
@@ -37,14 +41,16 @@ class Upload extends ImmutablePureComponent {
 
   handleSubmit = () => {
     this.handleInputBlur();
-    this.props.onSubmit();
+    this.props.onSubmit(this.context.router.history);
   }
 
-  handleUndoClick = () => {
+  handleUndoClick = e => {
+    e.stopPropagation();
     this.props.onUndo(this.props.media.get('id'));
   }
 
-  handleFocalPointClick = () => {
+  handleFocalPointClick = e => {
+    e.stopPropagation();
     this.props.onOpenFocalPoint(this.props.media.get('id'));
   }
 
@@ -64,6 +70,10 @@ class Upload extends ImmutablePureComponent {
     this.setState({ focused: true });
   }
 
+  handleClick = () => {
+    this.setState({ focused: true });
+  }
+
   handleInputBlur = () => {
     const { dirtyDescription } = this.state;
 
@@ -84,7 +94,7 @@ class Upload extends ImmutablePureComponent {
     const y = ((focusY / -2) + .5) * 100;
 
     return (
-      <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+      <div className='compose-form__upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
         <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
           {({ scale }) => (
             <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index 3822dd711..5d7fb8852 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -33,8 +33,8 @@ const mapDispatchToProps = (dispatch) => ({
     dispatch(changeCompose(text));
   },
 
-  onSubmit () {
-    dispatch(submitCompose());
+  onSubmit (router) {
+    dispatch(submitCompose(router));
   },
 
   onClearSuggestions () {
diff --git a/app/javascript/mastodon/features/compose/containers/search_results_container.js b/app/javascript/mastodon/features/compose/containers/search_results_container.js
index 16d95d417..f9637861a 100644
--- a/app/javascript/mastodon/features/compose/containers/search_results_container.js
+++ b/app/javascript/mastodon/features/compose/containers/search_results_container.js
@@ -1,8 +1,15 @@
 import { connect } from 'react-redux';
 import SearchResults from '../components/search_results';
+import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions';
 
 const mapStateToProps = state => ({
   results: state.getIn(['search', 'results']),
+  suggestions: state.getIn(['suggestions', 'items']),
 });
 
-export default connect(mapStateToProps)(SearchResults);
+const mapDispatchToProps = dispatch => ({
+  fetchSuggestions: () => dispatch(fetchSuggestions()),
+  dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(SearchResults);
diff --git a/app/javascript/mastodon/features/compose/containers/upload_container.js b/app/javascript/mastodon/features/compose/containers/upload_container.js
index 9f3aab4bc..b6d81f03a 100644
--- a/app/javascript/mastodon/features/compose/containers/upload_container.js
+++ b/app/javascript/mastodon/features/compose/containers/upload_container.js
@@ -22,8 +22,8 @@ const mapDispatchToProps = dispatch => ({
     dispatch(openModal('FOCAL_POINT', { id }));
   },
 
-  onSubmit () {
-    dispatch(submitCompose());
+  onSubmit (router) {
+    dispatch(submitCompose(router));
   },
 
 });
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index cf1714b71..d76cd76e6 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -13,6 +13,7 @@ import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 import { changeComposing } from '../../actions/compose';
 import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
+import { mascot } from '../../initial_state';
 
 const messages = defineMessages({
   start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -107,7 +108,7 @@ class Compose extends React.PureComponent {
             <ComposeFormContainer />
             {multiColumn && (
               <div className='drawer__inner__mastodon'>
-                <img alt='' draggable='false' src={elephantUIPlane} />
+                <img alt='' draggable='false' src={mascot || elephantUIPlane} />
               </div>
             )}
           </div>}
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
index f9a8d4f72..ffcd6d281 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -2,12 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import StatusContent from '../../../components/status_content';
-import RelativeTimestamp from '../../../components/relative_timestamp';
-import DisplayName from '../../../components/display_name';
-import Avatar from '../../../components/avatar';
-import AttachmentList from '../../../components/attachment_list';
-import { HotKeys } from 'react-hotkeys';
+import StatusContainer from '../../../containers/status_container';
 
 export default class Conversation extends ImmutablePureComponent {
 
@@ -18,9 +13,11 @@ export default class Conversation extends ImmutablePureComponent {
   static propTypes = {
     conversationId: PropTypes.string.isRequired,
     accounts: ImmutablePropTypes.list.isRequired,
-    lastStatus: ImmutablePropTypes.map.isRequired,
+    lastStatusId: PropTypes.string,
+    unread:PropTypes.bool.isRequired,
     onMoveUp: PropTypes.func,
     onMoveDown: PropTypes.func,
+    markRead: PropTypes.func.isRequired,
   };
 
   handleClick = () => {
@@ -28,8 +25,13 @@ export default class Conversation extends ImmutablePureComponent {
       return;
     }
 
-    const { lastStatus } = this.props;
-    this.context.router.history.push(`/statuses/${lastStatus.get('id')}`);
+    const { lastStatusId, unread, markRead } = this.props;
+
+    if (unread) {
+      markRead();
+    }
+
+    this.context.router.history.push(`/statuses/${lastStatusId}`);
   }
 
   handleHotkeyMoveUp = () => {
@@ -41,44 +43,21 @@ export default class Conversation extends ImmutablePureComponent {
   }
 
   render () {
-    const { accounts, lastStatus, lastAccount } = this.props;
+    const { accounts, lastStatusId, unread } = this.props;
 
-    if (lastStatus === null) {
+    if (lastStatusId === null) {
       return null;
     }
 
-    const handlers = {
-      moveDown: this.handleHotkeyMoveDown,
-      moveUp: this.handleHotkeyMoveUp,
-      open: this.handleClick,
-    };
-
-    let media;
-
-    if (lastStatus.get('media_attachments').size > 0) {
-      media = <AttachmentList compact media={lastStatus.get('media_attachments')} />;
-    }
-
     return (
-      <HotKeys handlers={handlers}>
-        <div className='conversation focusable' tabIndex='0' onClick={this.handleClick} role='button'>
-          <div className='conversation__header'>
-            <div className='conversation__avatars'>
-              <div>{accounts.map(account => <Avatar key={account.get('id')} size={36} account={account} />)}</div>
-            </div>
-
-            <div className='conversation__time'>
-              <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
-              <br />
-              <DisplayName account={lastAccount} withAcct={false} />
-            </div>
-          </div>
-
-          <StatusContent status={lastStatus} onClick={this.handleClick} />
-
-          {media}
-        </div>
-      </HotKeys>
+      <StatusContainer
+        id={lastStatusId}
+        unread={unread}
+        otherAccounts={accounts}
+        onMoveUp={this.handleHotkeyMoveUp}
+        onMoveDown={this.handleHotkeyMoveDown}
+        onClick={this.handleClick}
+      />
     );
   }
 
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
index 4684548e0..635c03c1d 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
@@ -9,14 +9,14 @@ import { debounce } from 'lodash';
 export default class ConversationsList extends ImmutablePureComponent {
 
   static propTypes = {
-    conversationIds: ImmutablePropTypes.list.isRequired,
+    conversations: ImmutablePropTypes.list.isRequired,
     hasMore: PropTypes.bool,
     isLoading: PropTypes.bool,
     onLoadMore: PropTypes.func,
     shouldUpdateScroll: PropTypes.func,
   };
 
-  getCurrentIndex = id => this.props.conversationIds.indexOf(id)
+  getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id)
 
   handleMoveUp = id => {
     const elementIndex = this.getCurrentIndex(id) - 1;
@@ -41,22 +41,22 @@ export default class ConversationsList extends ImmutablePureComponent {
   }
 
   handleLoadOlder = debounce(() => {
-    const last = this.props.conversationIds.last();
+    const last = this.props.conversations.last();
 
-    if (last) {
-      this.props.onLoadMore(last);
+    if (last && last.get('last_status')) {
+      this.props.onLoadMore(last.get('last_status'));
     }
   }, 300, { leading: true })
 
   render () {
-    const { conversationIds, onLoadMore, ...other } = this.props;
+    const { conversations, onLoadMore, ...other } = this.props;
 
     return (
       <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}>
-        {conversationIds.map(item => (
+        {conversations.map(item => (
           <ConversationContainer
-            key={item}
-            conversationId={item}
+            key={item.get('id')}
+            conversationId={item.get('id')}
             onMoveUp={this.handleMoveUp}
             onMoveDown={this.handleMoveDown}
           />
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
index 4166ee2ac..bd6f6bfb0 100644
--- a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
@@ -1,15 +1,19 @@
 import { connect } from 'react-redux';
 import Conversation from '../components/conversation';
+import { markConversationRead } from '../../../actions/conversations';
 
 const mapStateToProps = (state, { conversationId }) => {
   const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
-  const lastStatus   = state.getIn(['statuses', conversation.get('last_status')], null);
 
   return {
     accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
-    lastStatus,
-    lastAccount: lastStatus === null ? null : state.getIn(['accounts', lastStatus.get('account')], null),
+    unread: conversation.get('unread'),
+    lastStatusId: conversation.get('last_status', null),
   };
 };
 
-export default connect(mapStateToProps)(Conversation);
+const mapDispatchToProps = (dispatch, { conversationId }) => ({
+  markRead: () => dispatch(markConversationRead(conversationId)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Conversation);
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
index 81ea812ad..57e17d96f 100644
--- a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
@@ -3,7 +3,7 @@ import ConversationsList from '../components/conversations_list';
 import { expandConversations } from '../../../actions/conversations';
 
 const mapStateToProps = state => ({
-  conversationIds: state.getIn(['conversations', 'items']).map(x => x.get('id')),
+  conversations: state.getIn(['conversations', 'items']),
   isLoading: state.getIn(['conversations', 'isLoading'], true),
   hasMore: state.getIn(['conversations', 'hasMore'], false),
 });
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
index 41ec73d98..d202f3bfd 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.js
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import Column from '../../components/column';
 import ColumnHeader from '../../components/column_header';
-import { expandConversations } from '../../actions/conversations';
+import { mountConversations, unmountConversations, expandConversations } from '../../actions/conversations';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { connectDirectStream } from '../../actions/streaming';
@@ -48,11 +48,14 @@ class DirectTimeline extends React.PureComponent {
   componentDidMount () {
     const { dispatch } = this.props;
 
+    dispatch(mountConversations());
     dispatch(expandConversations());
     this.disconnect = dispatch(connectDirectStream());
   }
 
   componentWillUnmount () {
+    this.props.dispatch(unmountConversations());
+
     if (this.disconnect) {
       this.disconnect();
       this.disconnect = null;
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
index 36351ec02..164fdcc0b 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
@@ -2,7 +2,7 @@
 // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
 
 import data from './emoji_mart_data_light';
-import { getData, getSanitizedData, intersect } from './emoji_utils';
+import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
 
 let originalPool = {};
 let index = {};
@@ -103,7 +103,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
       }
     }
 
-    allResults = values.map((value) => {
+    const searchValue = (value) => {
       let aPool = pool,
         aIndex = index,
         length = 0;
@@ -150,15 +150,23 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
       }
 
       return aIndex.results;
-    }).filter(a => a);
+    };
 
-    if (allResults.length > 1) {
-      results = intersect.apply(null, allResults);
-    } else if (allResults.length) {
-      results = allResults[0];
+    if (values.length > 1) {
+      results = searchValue(value);
     } else {
       results = [];
     }
+
+    allResults = values.map(searchValue).filter(a => a);
+
+    if (allResults.length > 1) {
+      allResults = intersect.apply(null, allResults);
+    } else if (allResults.length) {
+      allResults = allResults[0];
+    }
+
+    results = uniq(results.concat(allResults));
   }
 
   if (results) {
diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js
index b52f3c4fa..743fe779a 100644
--- a/app/javascript/mastodon/features/status/components/card.js
+++ b/app/javascript/mastodon/features/status/components/card.js
@@ -59,10 +59,12 @@ export default class Card extends React.PureComponent {
     card: ImmutablePropTypes.map,
     maxDescription: PropTypes.number,
     onOpenMedia: PropTypes.func.isRequired,
+    compact: PropTypes.bool,
   };
 
   static defaultProps = {
     maxDescription: 50,
+    compact: false,
   };
 
   state = {
@@ -118,7 +120,7 @@ export default class Card extends React.PureComponent {
     const content   = { __html: addAutoPlay(card.get('html')) };
     const { width } = this.state;
     const ratio     = card.get('width') / card.get('height');
-    const height    = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
+    const height    = width / ratio;
 
     return (
       <div
@@ -131,25 +133,25 @@ export default class Card extends React.PureComponent {
   }
 
   render () {
-    const { card, maxDescription } = this.props;
-    const { width, embedded }      = this.state;
+    const { card, maxDescription, compact } = this.props;
+    const { width, embedded } = this.state;
 
     if (card === null) {
       return null;
     }
 
     const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
-    const horizontal  = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link';
-    const className   = classnames('status-card', { horizontal });
+    const horizontal  = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
     const interactive = card.get('type') !== 'link';
+    const className   = classnames('status-card', { horizontal, compact, interactive });
     const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
     const ratio       = card.get('width') / card.get('height');
-    const height      = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
+    const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
 
     const description = (
       <div className='status-card__content'>
         {title}
-        {!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
+        {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
         <span className='status-card__host'>{provider}</span>
       </div>
     );
@@ -174,7 +176,7 @@ export default class Card extends React.PureComponent {
             <div className='status-card__actions'>
               <div>
                 <button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button>
-                <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>
+                {horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>}
               </div>
             </div>
           </div>
@@ -184,7 +186,7 @@ export default class Card extends React.PureComponent {
       return (
         <div className={className} ref={this.setRef}>
           {embed}
-          {description}
+          {!compact && description}
         </div>
       );
     } else if (card.get('image')) {
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index b4bbda161..b0dea8817 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -8,7 +8,7 @@ import MediaGallery from '../../../components/media_gallery';
 import AttachmentList from '../../../components/attachment_list';
 import { Link } from 'react-router-dom';
 import { FormattedDate, FormattedNumber } from 'react-intl';
-import CardContainer from '../containers/card_container';
+import Card from './card';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Video from '../../video';
 
@@ -80,7 +80,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
         );
       }
     } else if (status.get('spoiler_text').length === 0) {
-      media = <CardContainer onOpenMedia={this.props.onOpenMedia} statusId={status.get('id')} />;
+      media = <Card onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
     }
 
     if (status.get('application')) {
diff --git a/app/javascript/mastodon/features/status/containers/card_container.js b/app/javascript/mastodon/features/status/containers/card_container.js
deleted file mode 100644
index a97404de1..000000000
--- a/app/javascript/mastodon/features/status/containers/card_container.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { connect } from 'react-redux';
-import Card from '../components/card';
-
-const mapStateToProps = (state, { statusId }) => ({
-  card: state.getIn(['cards', statusId], null),
-});
-
-export default connect(mapStateToProps)(Card);
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 2cd17b805..b36d82865 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -181,7 +181,7 @@ class Status extends ImmutablePureComponent {
     if (status.get('reblogged')) {
       this.props.dispatch(unreblog(status));
     } else {
-      if (e.shiftKey || !boostModal) {
+      if ((e && e.shiftKey) || !boostModal) {
         this.handleModalReblog(status);
       } else {
         this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index fb6f675f4..662375a76 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -460,7 +460,7 @@ class UI extends React.PureComponent {
     };
 
     return (
-      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
+      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
         <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
           <TabsBar />
 
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 1d3d84f9d..05c209d8f 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -15,5 +15,6 @@ export const searchEnabled = getMeta('search_enabled');
 export const maxChars = (initialState && initialState.max_toot_chars) || 500;
 export const invitesEnabled = getMeta('invites_enabled');
 export const version = getMeta('version');
+export const mascot = getMeta('mascot');
 
 export default initialState;
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index a74207db6..db2593afc 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "هذا المستخدِم لا يتبع أحدًا بعد.",
   "account.follows_you": "يتابعك",
   "account.hide_reblogs": "إخفاء ترقيات @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "تم التحقق مِن مالك هذا الرابط بتاريخ {date}",
   "account.media": "وسائط",
   "account.mention": "أُذكُر @{name}",
   "account.moved_to": "{name} إنتقل إلى :",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
   "confirmations.redraft.confirm": "إزالة و إعادة الصياغة",
   "confirmations.redraft.message": "هل أنت متأكد من أنك تريد حذف هذا المنشور و إعادة صياغته ؟ سوف تفقد جميع الإعجابات و الترقيات أما الردود المتصلة به فستُصبِح يتيمة.",
+  "confirmations.reply.confirm": "رد",
+  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "إلغاء المتابعة",
   "confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟",
   "embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
@@ -113,9 +115,9 @@
   "empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !",
   "empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
   "empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.",
-  "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.favourited_statuses": "ليس لديك أية تبويقات مفضلة بعد. عندما ستقوم بالإعجاب بواحد، سيظهر هنا.",
+  "empty_column.favourites": "لم يقم أي أحد بالإعجاب بهذا التبويق بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
+  "empty_column.follow_requests": "ليس عندك أي طلب للمتابعة بعد. سوف تظهر طلباتك هنا إن قمت بتلقي البعض منها.",
   "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
   "empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسية فارغ. قم بزيارة {public} أو استخدم حقل البحث لكي تكتشف مستخدمين آخرين.",
   "empty_column.home.public_timeline": "الخيط العام",
@@ -163,7 +165,7 @@
   "keyboard_shortcuts.reply": "للردّ",
   "keyboard_shortcuts.requests": "لفتح قائمة طلبات المتابعة",
   "keyboard_shortcuts.search": "للتركيز على البحث",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.start": "لفتح عمود \"هيا نبدأ\"",
   "keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير",
   "keyboard_shortcuts.toot": "لتحرير تبويق جديد",
   "keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث",
@@ -280,7 +282,7 @@
   "status.cancel_reblog_private": "إلغاء الترقية",
   "status.cannot_reblog": "تعذرت ترقية هذا المنشور",
   "status.delete": "إحذف",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "تفاصيل المحادثة",
   "status.direct": "رسالة خاصة إلى @{name}",
   "status.embed": "إدماج",
   "status.favourite": "أضف إلى المفضلة",
@@ -294,6 +296,7 @@
   "status.open": "وسع هذه المشاركة",
   "status.pin": "تدبيس على الملف الشخصي",
   "status.pinned": "تبويق مثبَّت",
+  "status.read_more": "اقرأ المزيد",
   "status.reblog": "رَقِّي",
   "status.reblog_private": "القيام بالترقية إلى الجمهور الأصلي",
   "status.reblogged_by": "رقّاه {name}",
@@ -311,6 +314,8 @@
   "status.show_more_all": "توسيع الكل",
   "status.unmute_conversation": "فك الكتم عن المحادثة",
   "status.unpin": "فك التدبيس من الملف الشخصي",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "الموحَّد",
   "tabs_bar.home": "الرئيسية",
   "tabs_bar.local_timeline": "المحلي",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 147c5ad2a..d84774f34 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "¿De xuru que quies silenciar a {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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Espander esti estáu",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Aniciu",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 756c33393..a4366126f 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Are you sure you want to mute {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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Споделяне",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} сподели",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Начало",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 4701c9316..2e766da6a 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Aquest usuari encara no segueix a ningú.",
   "account.follows_you": "Et segueix",
   "account.hide_reblogs": "Amaga els impulsos de @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "La propietat d'aquest enllaç es va verificar el dia {date}",
   "account.media": "Media",
   "account.mention": "Esmentar @{name}",
   "account.moved_to": "{name} s'ha mogut a:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
   "confirmations.redraft.confirm": "Esborrar i refer",
   "confirmations.redraft.message": "Estàs segur que vols esborrar aquesta publicació i tornar a redactar-la? Perderàs totes els impulsos i favorits, i les respostes a la publicació original es quedaran orfes.",
+  "confirmations.reply.confirm": "Respon",
+  "confirmations.reply.message": "Responen ara es sobreescriurà el missatge que estàs editant. Estàs segur que vols continuar?",
   "confirmations.unfollow.confirm": "Deixa de seguir",
   "confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?",
   "embed.instructions": "Incrusta aquest estat al lloc web copiant el codi a continuació.",
@@ -294,6 +296,7 @@
   "status.open": "Ampliar aquest estat",
   "status.pin": "Fixat en el perfil",
   "status.pinned": "Toot fixat",
+  "status.read_more": "Llegir més",
   "status.reblog": "Impuls",
   "status.reblog_private": "Impulsar a l'audiència original",
   "status.reblogged_by": "{name} ha retootejat",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Mostra més per a tot",
   "status.unmute_conversation": "Activar conversació",
   "status.unpin": "Deslliga del perfil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federada",
   "tabs_bar.home": "Inici",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 62976c98e..357ff0ac8 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "St'utilizatore ùn seguita nisunu.",
   "account.follows_you": "Vi seguita",
   "account.hide_reblogs": "Piattà spartere da @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "A prupietà di stu ligame hè stata verificata u {date}",
   "account.media": "Media",
   "account.mention": "Mintuvà @{name}",
   "account.moved_to": "{name} hè partutu nant'à:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Site sicuru·a che vulete piattà @{name}?",
   "confirmations.redraft.confirm": "Sguassà è riscrive",
   "confirmations.redraft.message": "Site sicuru·a chè vulete sguassà stu statutu è riscrivelu? I favuriti è spartere saranu persi, è e risposte diventeranu orfane.",
+  "confirmations.reply.confirm": "Risponde",
+  "confirmations.reply.message": "Risponde avà sguasserà u missaghju chì scrivite. Site sicuru·a chì vulete cuntinuà?",
   "confirmations.unfollow.confirm": "Disabbunassi",
   "confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
   "embed.instructions": "Integrà stu statutu à u vostru situ cù u codice quì sottu.",
@@ -294,6 +296,7 @@
   "status.open": "Apre stu statutu",
   "status.pin": "Puntarulà à u prufile",
   "status.pinned": "Statutu puntarulatu",
+  "status.read_more": "Leghje di più",
   "status.reblog": "Sparte",
   "status.reblog_private": "Sparte à l'audienza uriginale",
   "status.reblogged_by": "{name} hà spartutu",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Slibrà tuttu",
   "status.unmute_conversation": "Ùn piattà più a cunversazione",
   "status.unpin": "Spuntarulà da u prufile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Glubale",
   "tabs_bar.home": "Accolta",
   "tabs_bar.local_timeline": "Lucale",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 2c34fd34c..5f82dd8e0 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -8,14 +8,14 @@
   "account.domain_blocked": "Doména skryta",
   "account.edit_profile": "Upravit profil",
   "account.endorse": "Představit na profilu",
-  "account.follow": "Sleduj",
+  "account.follow": "Sledovat",
   "account.followers": "Sledovatelé",
   "account.followers.empty": "Tohoto uživatele ještě nikdo nesleduje.",
   "account.follows": "Sleduje",
   "account.follows.empty": "Tento uživatel ještě nikoho nesleduje.",
   "account.follows_you": "Sleduje vás",
   "account.hide_reblogs": "Skrýt boosty od uživatele @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Vlastnictví tohoto odkazu bylo zkontrolováno {date}",
   "account.media": "Média",
   "account.mention": "Zmínit uživatele @{name}",
   "account.moved_to": "{name} se přesunul/a na:",
@@ -75,8 +75,8 @@
   "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.spoiler.marked": "Text je ukrytý za varováním",
-  "compose_form.spoiler.unmarked": "Text není ukrytý",
+  "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í",
   "confirmation_modal.cancel": "Zrušit",
   "confirmations.block.confirm": "Blokovat",
@@ -91,6 +91,8 @@
   "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 příspěvek? 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",
   "confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?",
   "embed.instructions": "Pro přidání příspěvku na vaši webovou stránku zkopírujte níže uvedený kód.",
@@ -112,7 +114,7 @@
   "empty_column.blocks": "Ještě jste nezablokoval/a žádného uživatele.",
   "empty_column.community": "Místní časová osa je prázdná. Napište něco veřejně a rozhýbejte to tu!",
   "empty_column.direct": "Ještě nemáte žádné přímé zprávy. Pokud nějakou pošlete nebo dostanete, zobrazí se zde.",
-  "empty_column.domain_blocks": "Ještě zde nejsou žádné skryté domény.",
+  "empty_column.domain_blocks": "Ještě nejsou žádné skryté domény.",
   "empty_column.favourited_statuses": "Ještě nemáte žádné oblíbené tooty. Pokud si nějaký oblíbíte, zobrazí se zde.",
   "empty_column.favourites": "Tento toot si ještě nikdo neoblíbil. Pokud to někdo udělá, zobrazí se zde.",
   "empty_column.follow_requests": "Ještě nemáte žádné požadavky o sledování. Pokud nějaký obdržíte, zobrazí se zde.",
@@ -127,7 +129,7 @@
   "follow_request.authorize": "Autorizovat",
   "follow_request.reject": "Odmítnout",
   "getting_started.developers": "Vývojáři",
-  "getting_started.documentation": "Documentation",
+  "getting_started.documentation": "Dokumentace",
   "getting_started.find_friends": "Najděte si přátele z Twitteru",
   "getting_started.heading": "Začínáme",
   "getting_started.invite": "Pozvat lidi",
@@ -163,7 +165,7 @@
   "keyboard_shortcuts.reply": "k odpovězení",
   "keyboard_shortcuts.requests": "k otevření seznamu požadavků o sledování",
   "keyboard_shortcuts.search": "k zaměření na vyhledávání",
-  "keyboard_shortcuts.start": "k otevření sloupce \"začít\"",
+  "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í",
@@ -241,15 +243,15 @@
   "onboarding.page_three.search": "Pomocí vyhledávacího řádku najděte lidi a podívejte se na hashtagy jako {illustration} a {introductions}. Chcete-li najít někoho, kdo není na této instanci, použijte jeho celou adresu profilu.",
   "onboarding.page_two.compose": "Příspěvky pište z pole na komponování. Ikonami níže můžete nahrávat obrázky, změnit nastavení soukromí a přidat varování o obsahu.",
   "onboarding.skip": "Přeskočit",
-  "privacy.change": "Změnit viditelnost příspěvku",
+  "privacy.change": "Změnit soukromí příspěvku",
   "privacy.direct.long": "Odeslat pouze zmíněným uživatelům",
-  "privacy.direct.short": "Přímé",
+  "privacy.direct.short": "Přímý",
   "privacy.private.long": "Odeslat pouze sledovatelům",
   "privacy.private.short": "Pouze pro sledovatele",
   "privacy.public.long": "Odeslat na veřejné časové osy",
-  "privacy.public.short": "Veřejné",
-  "privacy.unlisted.long": "Do not show in public timelines",
-  "privacy.unlisted.short": "Nezobrazované",
+  "privacy.public.short": "Veřejný",
+  "privacy.unlisted.long": "Neodeslat na veřejné časové osy",
+  "privacy.unlisted.short": "Neuvedený",
   "regeneration_indicator.label": "Načítám…",
   "regeneration_indicator.sublabel": "Váš domovský proud se připravuje!",
   "relative_time.days": "{number} d",
@@ -294,6 +296,7 @@
   "status.open": "Rozbalit tento příspěvek",
   "status.pin": "Připnout na profil",
   "status.pinned": "Připnutý toot",
+  "status.read_more": "Číst více",
   "status.reblog": "Boostnout",
   "status.reblog_private": "Boostnout původnímu publiku",
   "status.reblogged_by": "{name} boostnul/a",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Zobrazit více pro všechny",
   "status.unmute_conversation": "Přestat ignorovat konverzaci",
   "status.unpin": "Odepnout z profilu",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federovaná",
   "tabs_bar.home": "Domů",
   "tabs_bar.local_timeline": "Místní",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 90f3f8e30..78c8d02f0 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -1,335 +1,341 @@
 {
-    "account.badges.bot": "Bot",
-    "account.block": "Blociwch @{name}",
-    "account.block_domain": "Cuddiwch bopeth rhag {domain}",
-    "account.blocked": "Blociwyd",
-    "account.direct": "Neges breifat @{name}",
-    "account.disclaimer_full": "Gall y wybodaeth isod adlewyrchu darlun anghyflawn o broffil defnyddiwr.",
-    "account.domain_blocked": "Parth wedi ei guddio",
-    "account.edit_profile": "Golygu proffil",
-    "account.endorse": "Arddangos ar fy mhroffil",
-    "account.follow": "Dilyn",
-    "account.followers": "Dilynwyr",
-    "account.followers.empty": "Nid oes neb yn dilyn y defnyddiwr hwn eto.",
-    "account.follows": "Yn dilyn",
-    "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.",
-    "account.follows_you": "Yn eich dilyn chi",
-    "account.hide_reblogs": "Cuddio bwstiau o @{name}",
-    "account.media": "Cyfryngau",
-    "account.mention": "Crybwyll @{name}",
-    "account.moved_to": "Mae @{name} wedi symud i:",
-    "account.mute": "Tawelu @{name}",
-    "account.mute_notifications": "Cuddio hysbysiadau o @{name}",
-    "account.muted": "Distewyd",
-    "account.posts": "Tŵtiau",
-    "account.posts_with_replies": "Tŵtiau ac atebion",
-    "account.report": "Adroddwch @{name}",
-    "account.requested": "Aros am gymeradwyaeth. Cliciwch er mwyn canslo cais dilyn",
-    "account.share": "Rhannwch broffil @{name}",
-    "account.show_reblogs": "Dangoswch bwstiau o @{name}",
-    "account.unblock": "Dadflociwch @{name}",
-    "account.unblock_domain": "Dadguddiwch {domain}",
-    "account.unendorse": "Peidwch a'i arddangos ar fy mhroffil",
-    "account.unfollow": "Daddilynwch",
-    "account.unmute": "Dad-dawelu @{name}",
-    "account.unmute_notifications": "Dad-dawelu hysbysiadau o @{name}",
-    "account.view_full_profile": "Gweld proffil llawn",
-    "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
-    "alert.unexpected.title": "Wps!",
-    "boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
-    "bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
-    "bundle_column_error.retry": "Ceisiwch eto",
-    "bundle_column_error.title": "Gwall rhwydwaith",
-    "bundle_modal_error.close": "Cau",
-    "bundle_modal_error.message": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
-    "bundle_modal_error.retry": "Ceiswich eto",
-    "column.blocks": "Defnyddwyr a flociwyd",
-    "column.community": "Llinell amser lleol",
-    "column.direct": "Negeseuon preifat",
-    "column.domain_blocks": "Parthau cuddiedig",
-    "column.favourites": "Ffefrynnau",
-    "column.follow_requests": "Ceisiadau dilyn",
-    "column.home": "Hafan",
-    "column.lists": "Rhestrau",
-    "column.mutes": "Defnyddwyr a ddistewyd",
-    "column.notifications": "Hysbysiadau",
-    "column.pins": "Tŵtiau wedi eu pinio",
-    "column.public": "",
-    "column_back_button.label": "Nôl",
-    "column_header.hide_settings": "Cuddiwch dewisiadau",
-    "column_header.moveLeft_settings": "Symudwch y golofn i'r chwith",
-    "column_header.moveRight_settings": "Symudwch y golofn i'r dde",
-    "column_header.pin": "Piniwch",
-    "column_header.show_settings": "Dangos gosodiadau",
-    "column_header.unpin": "Dadbiniwch",
-    "column_subheading.settings": "Gosodiadau",
-    "community.column_settings.media_only": "Cyfryngau yn unig",
-    "compose_form.direct_message_warning": "Mi fydd y tŵt hwn ond yn cael ei anfon at y defnyddwyr sy'n cael eu crybwyll.",
-    "compose_form.direct_message_warning_learn_more": "Dysgwch fwy",
-    "compose_form.hashtag_warning": "Ni fydd y tŵt hwn wedi ei restru o dan unrhyw hashnod gan ei fod heb ei restru. Dim ond tŵtiau cyhoeddus gellid chwilota amdanynt drwy hashnod.",
-    "compose_form.lock_disclaimer": "Nid yw eich cyfri wedi'i {locked}. Gall unrhyw un eich dilyn i weld eich POSTS dilynwyr-yn-unig.",
-    "compose_form.lock_disclaimer.lock": "wedi ei gloi",
-    "compose_form.placeholder": "Be syd ar eich meddwl?",
-    "compose_form.publish": "Tŵt",
-    "compose_form.publish_loud": "{publish}!",
-    "compose_form.sensitive.marked": "",
-    "compose_form.sensitive.unmarked": "",
-    "compose_form.spoiler.marked": "Testun wedi ei guddio gan rybudd",
-    "compose_form.spoiler.unmarked": "Nid yw'r testun wedi ei guddio",
-    "compose_form.spoiler_placeholder": "Ysgrifenwch eich rhybudd yma",
-    "confirmation_modal.cancel": "Canslo",
-    "confirmations.block.confirm": "Blociwch",
-    "confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
-    "confirmations.delete.confirm": "Dileu",
-    "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y statws hwn?",
-    "confirmations.delete_list.confirm": "Dileu",
-    "confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
-    "confirmations.domain_block.confirm": "",
-    "confirmations.domain_block.message": "",
-    "confirmations.mute.confirm": "Tawelu",
-    "confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
-    "confirmations.redraft.confirm": "Dilëwch & ailddrafftio",
-    "confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y statws hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r statws gwreiddiol yn cael eu hamddifadu.",
-    "confirmations.unfollow.confirm": "Dad-ddilynwch",
-    "confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
-    "embed.instructions": "Mewnblannwch y statws hwn ar eich gwefan drwy gopïo'r côd isod.",
-    "embed.preview": "Dyma sut olwg fydd arno:",
-    "emoji_button.activity": "Gweithgarwch",
-    "emoji_button.custom": "",
-    "emoji_button.flags": "Baneri",
-    "emoji_button.food": "Bwyd a Diod",
-    "emoji_button.label": "Mewnosodwch emoji",
-    "emoji_button.nature": "Natur",
-    "emoji_button.not_found": "Dim emojos!! (╯°□°)╯︵ ┻━┻",
-    "emoji_button.objects": "Gwrthrychau",
-    "emoji_button.people": "Pobl",
-    "emoji_button.recent": "Defnyddir yn aml",
-    "emoji_button.search": "Chwilio...",
-    "emoji_button.search_results": "Canlyniadau chwilio",
-    "emoji_button.symbols": "Symbolau",
-    "emoji_button.travel": "Teithio & Llefydd",
-    "empty_column.blocks": "Nid ydych wedi blocio unrhyw ddefnyddwyr eto.",
-    "empty_column.community": "",
-    "empty_column.direct": "Nid oes gennych unrhyw negeseuon preifat eto. Pan y byddwch yn anfon neu derbyn un, mi fydd yn ymddangos yma.",
-    "empty_column.domain_blocks": "Nid oes yna unrhyw barthau cuddiedig eto.",
-    "empty_column.favourited_statuses": "Nid oes gennych unrhyw hoff dwtiau eto. Pan y byddwch yn hoffi un, mi fydd yn ymddangos yma.",
-    "empty_column.favourites": "Nid oes neb wedi hoffi'r tŵt yma eto. Pan bydd rhywun yn ei hoffi, mi fyddent yn ymddangos yma.",
-    "empty_column.follow_requests": "Nid oes gennych unrhyw geisiadau dilyn eto. Pan dderbyniwch chi un, bydd yn ymddangos yma.",
-    "empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
-    "empty_column.home": "",
-    "empty_column.home.public_timeline": "y ffrwd cyhoeddus",
-    "empty_column.list": "Nid oes dim yn y rhestr yma eto. Pan y bydd aelodau'r rhestr yn cyhoeddi statws newydd, mi fydd yn ymddangos yma.",
-    "empty_column.lists": "Nid oes gennych unrhyw restrau eto. Pan grëwch chi un, mi fydd yn ymddangos yma.",
-    "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 INSTANCES eraill i'w lenwi",
-    "follow_request.authorize": "Caniatau",
-    "follow_request.reject": "Gwrthod",
-    "getting_started.developers": "Datblygwyr",
-    "getting_started.documentation": "Dogfennaeth",
-    "getting_started.find_friends": "Canfod ffrindiau o Twitter",
-    "getting_started.heading": "Dechrau",
-    "getting_started.invite": "Gwahoddwch bobl",
-    "getting_started.open_source_notice": "Mae Mastodon yn feddalwedd côd agored. Mae modd cyfrannu neu adrodd materion ar GitHUb ar {github}.",
-    "getting_started.security": "Diogelwch",
-    "getting_started.terms": "Telerau Gwasanaeth",
-    "home.column_settings.basic": "Syml",
-    "home.column_settings.show_reblogs": "",
-    "home.column_settings.show_replies": "Dangoswch ymatebion",
-    "keyboard_shortcuts.back": "",
-    "keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd",
-    "keyboard_shortcuts.boost": "",
-    "keyboard_shortcuts.column": "",
-    "keyboard_shortcuts.compose": "",
-    "keyboard_shortcuts.description": "Disgrifiad",
-    "keyboard_shortcuts.direct": "i agor colofn negeseuon preifat",
-    "keyboard_shortcuts.down": "i symud lawr yn y rhestr",
-    "keyboard_shortcuts.enter": "i agor statws",
-    "keyboard_shortcuts.favourite": "i hoffi",
-    "keyboard_shortcuts.favourites": "i agor rhestr hoffi",
-    "keyboard_shortcuts.federated": "",
-    "keyboard_shortcuts.heading": "",
-    "keyboard_shortcuts.home": "i agor ffrwd cartref",
-    "keyboard_shortcuts.hotkey": "Hotkey",
-    "keyboard_shortcuts.legend": "",
-    "keyboard_shortcuts.local": "i agor ffrwd lleol",
-    "keyboard_shortcuts.mention": "i grybwyll yr awdur",
-    "keyboard_shortcuts.muted": "i agor rhestr defnyddwyr a dawelwyd",
-    "keyboard_shortcuts.my_profile": "i agor eich proffil",
-    "keyboard_shortcuts.notifications": "i agor colofn hysbysiadau",
-    "keyboard_shortcuts.pinned": "",
-    "keyboard_shortcuts.profile": "i agor proffil yr awdur",
-    "keyboard_shortcuts.reply": "i ateb",
-    "keyboard_shortcuts.requests": "i agor rhestr ceisiadau dilyn",
-    "keyboard_shortcuts.search": "",
-    "keyboard_shortcuts.start": "",
-    "keyboard_shortcuts.toggle_hidden": "",
-    "keyboard_shortcuts.toot": "i ddechrau tŵt newydd sbon",
-    "keyboard_shortcuts.unfocus": "",
-    "keyboard_shortcuts.up": "i symud yn uwch yn y rhestr",
-    "lightbox.close": "Cau",
-    "lightbox.next": "Nesaf",
-    "lightbox.previous": "",
-    "lists.account.add": "Ychwanegwch at restr",
-    "lists.account.remove": "",
-    "lists.delete": "Dileu rhestr",
-    "lists.edit": "Golygwch restr",
-    "lists.new.create": "Ychwanegwch restr",
-    "lists.new.title_placeholder": "Teitl rhestr newydd",
-    "lists.search": "",
-    "lists.subheading": "Eich rhestrau",
-    "loading_indicator.label": "Llwytho...",
-    "media_gallery.toggle_visible": "",
-    "missing_indicator.label": "Heb ei ganfod",
-    "missing_indicator.sublabel": "Ni ellid canfod yr adnodd hwn",
-    "mute_modal.hide_notifications": "Cuddiwch hysbysiadau rhag y defnyddiwr hwn?",
-    "navigation_bar.apps": "Apiau symudol",
-    "navigation_bar.blocks": "Defnyddwyr wedi eu blocio",
-    "navigation_bar.community_timeline": "",
-    "navigation_bar.compose": "Cyfansoddwch dŵt newydd",
-    "navigation_bar.direct": "Negeseuon preifat",
-    "navigation_bar.discover": "Darganfyddwch",
-    "navigation_bar.domain_blocks": "Parthau cuddiedig",
-    "navigation_bar.edit_profile": "Golygu proffil",
-    "navigation_bar.favourites": "Ffefrynnau",
-    "navigation_bar.filters": "Geiriau a dawelwyd",
-    "navigation_bar.follow_requests": "Ceisiadau dilyn",
-    "navigation_bar.info": "",
-    "navigation_bar.keyboard_shortcuts": "",
-    "navigation_bar.lists": "Rhestrau",
-    "navigation_bar.logout": "Allgofnodi",
-    "navigation_bar.mutes": "Defnyddwyr a dawelwyd",
-    "navigation_bar.personal": "Personol",
-    "navigation_bar.pins": "Tŵtiau wedi eu pinio",
-    "navigation_bar.preferences": "Dewisiadau",
-    "navigation_bar.public_timeline": "",
-    "navigation_bar.security": "Diogelwch",
-    "notification.favourite": "hoffodd {name} eich statws",
-    "notification.follow": "dilynodd {name} chi",
-    "notification.mention": "Soniodd {name} amdanoch chi",
-    "notification.reblog": "",
-    "notifications.clear": "Clirio hysbysiadau",
-    "notifications.clear_confirmation": "",
-    "notifications.column_settings.alert": "",
-    "notifications.column_settings.favourite": "Ffefrynnau:",
-    "notifications.column_settings.follow": "Dilynwyr newydd:",
-    "notifications.column_settings.mention": "",
-    "notifications.column_settings.push": "Hysbysiadau push",
-    "notifications.column_settings.reblog": "",
-    "notifications.column_settings.show": "",
-    "notifications.column_settings.sound": "Chwarae sain",
-    "notifications.group": "{count} o hysbysiadau",
-    "onboarding.done": "Wedi'i wneud",
-    "onboarding.next": "Nesaf",
-    "onboarding.page_five.public_timelines": "",
-    "onboarding.page_four.home": "Mae'r ffrwd gartref yn dangos twtiau o bobl yr ydych yn dilyn.",
-    "onboarding.page_four.notifications": "",
-    "onboarding.page_one.federation": "",
-    "onboarding.page_one.full_handle": "",
-    "onboarding.page_one.handle_hint": "",
-    "onboarding.page_one.welcome": "Croeso i Mastodon!",
-    "onboarding.page_six.admin": "",
-    "onboarding.page_six.almost_done": "Bron a gorffen...",
-    "onboarding.page_six.appetoot": "Bon Apetŵt!",
-    "onboarding.page_six.apps_available": "Mae yna {apps} ar gael i iOS, Android a platfformau eraill.",
-    "onboarding.page_six.github": "Mae Mastodon yn feddalwedd côd agored rhad ac am ddim. Mae modd adrodd bygiau, gwneud ceisiadau am nodweddion penodol, neu gyfrannu i'r côd ar {github}.",
-    "onboarding.page_six.guidelines": "canllawiau cymunedol",
-    "onboarding.page_six.read_guidelines": "Darllenwch {guidelines} y {domain} os gwelwch yn dda!",
-    "onboarding.page_six.various_app": "apiau symudol",
-    "onboarding.page_three.profile": "",
-    "onboarding.page_three.search": "",
-    "onboarding.page_two.compose": "",
-    "onboarding.skip": "Sgipiwch",
-    "privacy.change": "",
-    "privacy.direct.long": "",
-    "privacy.direct.short": "Uniongyrchol",
-    "privacy.private.long": "Cyhoeddi i ddilynwyr yn unig",
-    "privacy.private.short": "Dilynwyr-yn-unig",
-    "privacy.public.long": "Cyhoeddi i ffrydiau cyhoeddus",
-    "privacy.public.short": "Cyhoeddus",
-    "privacy.unlisted.long": "Peidio a cyhoeddi i ffrydiau cyhoeddus",
-    "privacy.unlisted.short": "Heb ei restru",
-    "regeneration_indicator.label": "Llwytho…",
-    "regeneration_indicator.sublabel": "Mae eich ffrwd cartref yn cael ei baratoi!",
-    "relative_time.days": "{number}d",
-    "relative_time.hours": "{number}h",
-    "relative_time.just_now": "nawr",
-    "relative_time.minutes": "{number}m",
-    "relative_time.seconds": "{number}s",
-    "reply_indicator.cancel": "Canslo",
-    "report.forward": "",
-    "report.forward_hint": "",
-    "report.hint": "",
-    "report.placeholder": "Sylwadau ychwanegol",
-    "report.submit": "Cyflwyno",
-    "report.target": "",
-    "search.placeholder": "Chwilio",
-    "search_popout.search_format": "Fformat chwilio uwch",
-    "search_popout.tips.full_text": "",
-    "search_popout.tips.hashtag": "hashnod",
-    "search_popout.tips.status": "statws",
-    "search_popout.tips.text": "",
-    "search_popout.tips.user": "defnyddiwr",
-    "search_results.accounts": "Pobl",
-    "search_results.hashtags": "Hanshnodau",
-    "search_results.statuses": "Twtiau",
-    "search_results.total": "",
-    "standalone.public_title": "Golwg tu fewn...",
-    "status.block": "Blociwch @{name}",
-    "status.cancel_reblog_private": "",
-    "status.cannot_reblog": "",
-    "status.delete": "Dileu",
-    "status.detailed_status": "",
-    "status.direct": "Neges breifat @{name}",
-    "status.embed": "Plannu",
-    "status.favourite": "",
-    "status.filtered": "",
-    "status.load_more": "Llwythwch mwy",
-    "status.media_hidden": "",
-    "status.mention": "",
-    "status.more": "Mwy",
-    "status.mute": "Tawelu @{name}",
-    "status.mute_conversation": "",
-    "status.open": "",
-    "status.pin": "",
-    "status.pinned": "",
-    "status.reblog": "",
-    "status.reblog_private": "",
-    "status.reblogged_by": "",
-    "status.reblogs.empty": "",
-    "status.redraft": "Dilëwh & ailddrafftio",
-    "status.reply": "Ateb",
-    "status.replyAll": "Ateb i edefyn",
-    "status.report": "",
-    "status.sensitive_toggle": "",
-    "status.sensitive_warning": "Cynnwys sensitif",
-    "status.share": "Rhannwch",
-    "status.show_less": "Dangoswch lai",
-    "status.show_less_all": "Dangoswch lai i bawb",
-    "status.show_more": "Dangoswch fwy",
-    "status.show_more_all": "",
-    "status.unmute_conversation": "Dad-dawelu sgwrs",
-    "status.unpin": "",
-    "tabs_bar.federated_timeline": "",
-    "tabs_bar.home": "Hafan",
-    "tabs_bar.local_timeline": "Lleol",
-    "tabs_bar.notifications": "Hysbysiadau",
-    "tabs_bar.search": "Chwilio",
-    "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} yn siarad",
-    "ui.beforeunload": "Mi fyddwch yn colli eich drafft os gadewch Mastodon.",
-    "upload_area.title": "Llusgwch & gollwing i uwchlwytho",
-    "upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
-    "upload_form.description": "",
-    "upload_form.focus": "",
-    "upload_form.undo": "Dileu",
-    "upload_progress.label": "Uwchlwytho...",
-    "video.close": "Cau fideo",
-    "video.exit_fullscreen": "Gadael sgrîn llawn",
-    "video.expand": "Ymestyn fideo",
-    "video.fullscreen": "Sgrîn llawn",
-    "video.hide": "Cuddio fideo",
-    "video.mute": "Tawelu sain",
-    "video.pause": "Oedi",
-    "video.play": "Chwarae",
-    "video.unmute": "Dad-dawelu sain"
+  "account.badges.bot": "Bot",
+  "account.block": "Blocio @{name}",
+  "account.block_domain": "Cuddio popeth rhag {domain}",
+  "account.blocked": "Blociwyd",
+  "account.direct": "Neges breifat @{name}",
+  "account.disclaimer_full": "Gall y wybodaeth isod adlewyrchu darlun anghyflawn o broffil defnyddiwr.",
+  "account.domain_blocked": "Parth wedi ei guddio",
+  "account.edit_profile": "Golygu proffil",
+  "account.endorse": "Arddangos ar fy mhroffil",
+  "account.follow": "Dilyn",
+  "account.followers": "Dilynwyr",
+  "account.followers.empty": "Nid oes neb yn dilyn y defnyddiwr hwn eto.",
+  "account.follows": "Yn dilyn",
+  "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.",
+  "account.follows_you": "Yn eich dilyn chi",
+  "account.hide_reblogs": "Cuddio bwstiau o @{name}",
+  "account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
+  "account.media": "Cyfryngau",
+  "account.mention": "Crybwyll @{name}",
+  "account.moved_to": "Mae @{name} wedi symud i:",
+  "account.mute": "Tawelu @{name}",
+  "account.mute_notifications": "Cuddio hysbysiadau o @{name}",
+  "account.muted": "Distewyd",
+  "account.posts": "Tŵtiau",
+  "account.posts_with_replies": "Tŵtiau ac atebion",
+  "account.report": "Adrodd @{name}",
+  "account.requested": "Aros am gymeradwyaeth. Cliciwch er mwyn canslo cais dilyn",
+  "account.share": "Rhannwch broffil @{name}",
+  "account.show_reblogs": "Dangos bwstiau o @{name}",
+  "account.unblock": "Dadflocio @{name}",
+  "account.unblock_domain": "Dadguddio {domain}",
+  "account.unendorse": "Peidio a'i arddangos ar fy mhroffil",
+  "account.unfollow": "Dad-ddilyn",
+  "account.unmute": "Dad-dawelu @{name}",
+  "account.unmute_notifications": "Dad-dawelu hysbysiadau o @{name}",
+  "account.view_full_profile": "Gweld proffil llawn",
+  "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
+  "alert.unexpected.title": "Wps!",
+  "boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
+  "bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
+  "bundle_column_error.retry": "Ceisiwch eto",
+  "bundle_column_error.title": "Gwall rhwydwaith",
+  "bundle_modal_error.close": "Cau",
+  "bundle_modal_error.message": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
+  "bundle_modal_error.retry": "Ceiswich eto",
+  "column.blocks": "Defnyddwyr a flociwyd",
+  "column.community": "Llinell amser lleol",
+  "column.direct": "Negeseuon preifat",
+  "column.domain_blocks": "Parthau cuddiedig",
+  "column.favourites": "Ffefrynnau",
+  "column.follow_requests": "Ceisiadau dilyn",
+  "column.home": "Hafan",
+  "column.lists": "Rhestrau",
+  "column.mutes": "Defnyddwyr a ddistewyd",
+  "column.notifications": "Hysbysiadau",
+  "column.pins": "Tŵtiau wedi eu pinio",
+  "column.public": "Ffrwd y ffederasiwn",
+  "column_back_button.label": "Nôl",
+  "column_header.hide_settings": "Cuddio dewisiadau",
+  "column_header.moveLeft_settings": "Symud y golofn i'r chwith",
+  "column_header.moveRight_settings": "Symud y golofn i'r dde",
+  "column_header.pin": "Pinio",
+  "column_header.show_settings": "Dangos gosodiadau",
+  "column_header.unpin": "Dadbinio",
+  "column_subheading.settings": "Gosodiadau",
+  "community.column_settings.media_only": "Cyfryngau yn unig",
+  "compose_form.direct_message_warning": "Mi fydd y tŵt hwn ond yn cael ei anfon at y defnyddwyr sy'n cael eu crybwyll.",
+  "compose_form.direct_message_warning_learn_more": "Dysgu mwy",
+  "compose_form.hashtag_warning": "Ni fydd y tŵt hwn wedi ei restru o dan unrhyw hashnod gan ei fod heb ei restru. Dim ond tŵtiau cyhoeddus gellid chwilota amdanynt drwy hashnod.",
+  "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.publish": "Tŵt",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.marked": "Cyfryngau wedi'u marcio'n sensitif",
+  "compose_form.sensitive.unmarked": "Nid yw'r cyfryngau wedi'u marcio'n sensitif",
+  "compose_form.spoiler.marked": "Testun wedi ei guddio gan rybudd",
+  "compose_form.spoiler.unmarked": "Nid yw'r testun wedi ei guddio",
+  "compose_form.spoiler_placeholder": "Ysgrifenwch eich rhybudd yma",
+  "confirmation_modal.cancel": "Canslo",
+  "confirmations.block.confirm": "Blocio",
+  "confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
+  "confirmations.delete.confirm": "Dileu",
+  "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y statws hwn?",
+  "confirmations.delete_list.confirm": "Dileu",
+  "confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
+  "confirmations.domain_block.confirm": "Cuddio parth cyfan",
+  "confirmations.domain_block.message": "A ydych yn hollol, hollol sicr eich bod am flocio y {domain} cyfan? Yn y nifer helaeth o achosion mae blocio neu tawelu ambell gyfrif yn ddigonol ac yn well. Ni fyddwch yn gweld cynnwys o'r parth hwnnw mewn unrhyw ffrydiau cyhoeddus na chwaith yn eich hysbysiadau. Bydd hyn yn cael gwared o'ch dilynwyr o'r parth hwnnw.",
+  "confirmations.mute.confirm": "Tawelu",
+  "confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
+  "confirmations.redraft.confirm": "Dileu & ailddrafftio",
+  "confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y statws hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r statws gwreiddiol yn cael eu hamddifadu.",
+  "confirmations.reply.confirm": "Ateb",
+  "confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n sicr yr ydych am barhau?",
+  "confirmations.unfollow.confirm": "Dad-ddilynwch",
+  "confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
+  "embed.instructions": "Mewnblannwch y statws hwn ar eich gwefan drwy gopïo'r côd isod.",
+  "embed.preview": "Dyma sut olwg fydd arno:",
+  "emoji_button.activity": "Gweithgarwch",
+  "emoji_button.custom": "Unigryw",
+  "emoji_button.flags": "Baneri",
+  "emoji_button.food": "Bwyd a Diod",
+  "emoji_button.label": "Mewnosodwch emoji",
+  "emoji_button.nature": "Natur",
+  "emoji_button.not_found": "Dim emojo!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Gwrthrychau",
+  "emoji_button.people": "Pobl",
+  "emoji_button.recent": "Defnyddir yn aml",
+  "emoji_button.search": "Chwilio...",
+  "emoji_button.search_results": "Canlyniadau chwilio",
+  "emoji_button.symbols": "Symbolau",
+  "emoji_button.travel": "Teithio & Llefydd",
+  "empty_column.blocks": "Nid ydych wedi blocio unrhyw ddefnyddwyr eto.",
+  "empty_column.community": "Mae'r ffrwd lleol yn wag. Ysgrifenwch rhywbeth yn gyhoeddus i gael dechrau arni!",
+  "empty_column.direct": "Nid oes gennych unrhyw negeseuon preifat eto. Pan y byddwch yn anfon neu derbyn un, mi fydd yn ymddangos yma.",
+  "empty_column.domain_blocks": "Nid oes yna unrhyw barthau cuddiedig eto.",
+  "empty_column.favourited_statuses": "Nid oes gennych unrhyw hoff dwtiau eto. Pan y byddwch yn hoffi un, mi fydd yn ymddangos yma.",
+  "empty_column.favourites": "Nid oes neb wedi hoffi'r tŵt yma eto. Pan bydd rhywun yn ei hoffi, byddent yn ymddangos yma.",
+  "empty_column.follow_requests": "Nid oes gennych unrhyw geisiadau dilyn eto. Pan dderbyniwch chi un, byddent yn ymddangos yma.",
+  "empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
+  "empty_column.home": "Mae eich ffrwd gartref yn wag! Ymwelwch a {public} neu defnyddiwch y chwilotwr i ddechrau arni ac i gwrdd a defnyddwyr eraill.",
+  "empty_column.home.public_timeline": "y ffrwd gyhoeddus",
+  "empty_column.list": "Nid oes dim yn y rhestr yma eto. Pan y bydd aelodau'r rhestr yn cyhoeddi statws newydd, mi fydd yn ymddangos yma.",
+  "empty_column.lists": "Nid oes gennych unrhyw restrau eto. Pan grëwch chi un, mi fydd yn ymddangos yma.",
+  "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",
+  "follow_request.authorize": "Caniatau",
+  "follow_request.reject": "Gwrthod",
+  "getting_started.developers": "Datblygwyr",
+  "getting_started.documentation": "Dogfennaeth",
+  "getting_started.find_friends": "Canfod ffrindiau o Twitter",
+  "getting_started.heading": "Dechrau",
+  "getting_started.invite": "Gwahodd pobl",
+  "getting_started.open_source_notice": "Mae Mastodon yn feddalwedd côd agored. Mae modd cyfrannu neu adrodd materion ar GitHUb ar {github}.",
+  "getting_started.security": "Diogelwch",
+  "getting_started.terms": "Telerau Gwasanaeth",
+  "home.column_settings.basic": "Syml",
+  "home.column_settings.show_reblogs": "Dangos bŵstiau",
+  "home.column_settings.show_replies": "Dangos ymatebion",
+  "keyboard_shortcuts.back": "i lywio nôl",
+  "keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd",
+  "keyboard_shortcuts.boost": "i fŵstio",
+  "keyboard_shortcuts.column": "i ffocysu statws yn un o'r colofnau",
+  "keyboard_shortcuts.compose": "i ffocysu yr ardal cyfansoddi testun",
+  "keyboard_shortcuts.description": "Disgrifiad",
+  "keyboard_shortcuts.direct": "i agor colofn negeseuon preifat",
+  "keyboard_shortcuts.down": "i symud lawr yn y rhestr",
+  "keyboard_shortcuts.enter": "i agor statws",
+  "keyboard_shortcuts.favourite": "i hoffi",
+  "keyboard_shortcuts.favourites": "i agor rhestr hoffi",
+  "keyboard_shortcuts.federated": "i agor ffrwd y ffederasiwn",
+  "keyboard_shortcuts.heading": "Llwybrau byr allweddell",
+  "keyboard_shortcuts.home": "i agor ffrwd cartref",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "i ddangos yr arwr yma",
+  "keyboard_shortcuts.local": "i agor ffrwd lleol",
+  "keyboard_shortcuts.mention": "i grybwyll yr awdur",
+  "keyboard_shortcuts.muted": "i agor rhestr defnyddwyr a dawelwyd",
+  "keyboard_shortcuts.my_profile": "i agor eich proffil",
+  "keyboard_shortcuts.notifications": "i agor colofn hysbysiadau",
+  "keyboard_shortcuts.pinned": "i agor rhestr tŵtiau wedi'i pinio",
+  "keyboard_shortcuts.profile": "i agor proffil yr awdur",
+  "keyboard_shortcuts.reply": "i ateb",
+  "keyboard_shortcuts.requests": "i agor rhestr ceisiadau dilyn",
+  "keyboard_shortcuts.search": "i ffocysu chwilio",
+  "keyboard_shortcuts.start": "i agor colofn \"dechrau arni\"",
+  "keyboard_shortcuts.toggle_hidden": "i ddangos/cuddio testun tu ôl i CW",
+  "keyboard_shortcuts.toot": "i ddechrau tŵt newydd sbon",
+  "keyboard_shortcuts.unfocus": "i ddad-ffocysu ardal cyfansoddi testun/chwilio",
+  "keyboard_shortcuts.up": "i symud yn uwch yn y rhestr",
+  "lightbox.close": "Cau",
+  "lightbox.next": "Nesaf",
+  "lightbox.previous": "Blaenorol",
+  "lists.account.add": "Ychwanegwch at restr",
+  "lists.account.remove": "Dileu o'r rhestr",
+  "lists.delete": "Dileu rhestr",
+  "lists.edit": "Golygwch rhestr",
+  "lists.new.create": "Ychwanegu rhestr",
+  "lists.new.title_placeholder": "Teitl rhestr newydd",
+  "lists.search": "Chwilio ymysg pobl yr ydych yn ei ddilyn",
+  "lists.subheading": "Eich rhestrau",
+  "loading_indicator.label": "Llwytho...",
+  "media_gallery.toggle_visible": "Toglo gwelededd",
+  "missing_indicator.label": "Heb ei ganfod",
+  "missing_indicator.sublabel": "Ni ellid canfod yr adnodd hwn",
+  "mute_modal.hide_notifications": "Cuddio hysbysiadau rhag y defnyddiwr hwn?",
+  "navigation_bar.apps": "Apiau symudol",
+  "navigation_bar.blocks": "Defnyddwyr wedi eu blocio",
+  "navigation_bar.community_timeline": "Ffrwd leol",
+  "navigation_bar.compose": "Cyfansoddi tŵt newydd",
+  "navigation_bar.direct": "Negeseuon preifat",
+  "navigation_bar.discover": "Darganfod",
+  "navigation_bar.domain_blocks": "Parthau cuddiedig",
+  "navigation_bar.edit_profile": "Golygu proffil",
+  "navigation_bar.favourites": "Ffefrynnau",
+  "navigation_bar.filters": "Geiriau a dawelwyd",
+  "navigation_bar.follow_requests": "Ceisiadau dilyn",
+  "navigation_bar.info": "Ynghylch yr achos hwn",
+  "navigation_bar.keyboard_shortcuts": "Bysellau brys",
+  "navigation_bar.lists": "Rhestrau",
+  "navigation_bar.logout": "Allgofnodi",
+  "navigation_bar.mutes": "Defnyddwyr a dawelwyd",
+  "navigation_bar.personal": "Personol",
+  "navigation_bar.pins": "Tŵtiau wedi eu pinio",
+  "navigation_bar.preferences": "Dewisiadau",
+  "navigation_bar.public_timeline": "Ffrwd y ffederasiwn",
+  "navigation_bar.security": "Diogelwch",
+  "notification.favourite": "hoffodd {name} eich statws",
+  "notification.follow": "dilynodd {name} chi",
+  "notification.mention": "Soniodd {name} amdanoch chi",
+  "notification.reblog": "{name} boosted your status",
+  "notifications.clear": "Clirio hysbysiadau",
+  "notifications.clear_confirmation": "Ydych chi'n sicr eich bod am glirio'ch holl hysbysiadau am byth?",
+  "notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith",
+  "notifications.column_settings.favourite": "Ffefrynnau:",
+  "notifications.column_settings.follow": "Dilynwyr newydd:",
+  "notifications.column_settings.mention": "Crybwylliadau:",
+  "notifications.column_settings.push": "Hysbysiadau push",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Dangos yn y golofn",
+  "notifications.column_settings.sound": "Chwarae sain",
+  "notifications.group": "{count} o hysbysiadau",
+  "onboarding.done": "Wedi'i wneud",
+  "onboarding.next": "Nesaf",
+  "onboarding.page_five.public_timelines": "Mae'r ffrwd leol yn dangos tŵtiau cyhoeddus o bawb ar y {domain}. Mae ffrwd y ffederasiwn yn dangos tŵtiau cyhoeddus o bawb y mae pobl ar y {domain} yn dilyn. Mae rhain yn Ffrydiau Cyhoeddus, ffordd wych o ddarganfod pobl newydd.",
+  "onboarding.page_four.home": "Mae'r ffrwd gartref yn dangos twtiau o bobl yr ydych yn dilyn.",
+  "onboarding.page_four.notifications": "Mae'r golofn hysbysiadau yn dangos pan mae rhywun yn ymwneud a chi.",
+  "onboarding.page_one.federation": "Mae mastodon yn rwydwaith o weinyddwyr anibynnol sy'n uno i greu un rhwydwaith gymdeithasol mwy. Yr ydym yn galw'r gweinyddwyr yma yn achosion.",
+  "onboarding.page_one.full_handle": "Eich enw Mastodon llawn",
+  "onboarding.page_one.handle_hint": "Dyma beth y bysech chi'n dweud wrth eich ffrindiau i chwilota amdano.",
+  "onboarding.page_one.welcome": "Croeso i Mastodon!",
+  "onboarding.page_six.admin": "Gweinyddwr eich achos yw {admin}.",
+  "onboarding.page_six.almost_done": "Bron a gorffen...",
+  "onboarding.page_six.appetoot": "Bon Apetŵt!",
+  "onboarding.page_six.apps_available": "Mae yna {apps} ar gael i iOS, Android a platfformau eraill.",
+  "onboarding.page_six.github": "Mae Mastodon yn feddalwedd côd agored rhad ac am ddim. Mae modd adrodd bygiau, gwneud ceisiadau am nodweddion penodol, neu gyfrannu i'r côd ar {github}.",
+  "onboarding.page_six.guidelines": "canllawiau cymunedol",
+  "onboarding.page_six.read_guidelines": "Darllenwch {guidelines} y {domain} os gwelwch yn dda!",
+  "onboarding.page_six.various_app": "apiau symudol",
+  "onboarding.page_three.profile": "Golygwch eich proffil i newid eich afatar, bywgraffiad, ac enw arddangos. Yno fe fyddwch hefyd yn canfod gosodiadau eraill.",
+  "onboarding.page_three.search": "Defnyddiwch y bar chwilio i ganfod pobl ac i edrych ar eu hashnodau, megis {illustration} ac {introductions}. I chwilio am rhywun nad ydynt ar yr achos hwn, defnyddiwch eu enw Mastodon llawn.",
+  "onboarding.page_two.compose": "Ysrgifenwch dŵtiau o'r golofn cyfansoddi. Mae modd uwchlwytho lluniau, newid gosodiadau preifatrwydd, ac ychwanegu rhybudd cynnwys gyda'r eiconau isod.",
+  "onboarding.skip": "Sgipio",
+  "privacy.change": "Addasu preifatrwdd y statws",
+  "privacy.direct.long": "Cyhoeddi i'r defnyddwyr sy'n cael eu crybwyll yn unig",
+  "privacy.direct.short": "Uniongyrchol",
+  "privacy.private.long": "Cyhoeddi i ddilynwyr yn unig",
+  "privacy.private.short": "Dilynwyr-yn-unig",
+  "privacy.public.long": "Cyhoeddi i ffrydiau cyhoeddus",
+  "privacy.public.short": "Cyhoeddus",
+  "privacy.unlisted.long": "Peidio a chyhoeddi i ffrydiau cyhoeddus",
+  "privacy.unlisted.short": "Heb ei restru",
+  "regeneration_indicator.label": "Llwytho…",
+  "regeneration_indicator.sublabel": "Mae eich ffrwd cartref yn cael ei baratoi!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "nawr",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Canslo",
+  "report.forward": "Ymlaen i {target}",
+  "report.forward_hint": "Mae'r cyfrif o weinydd arall. Anfon copi anhysbys o'r adroddiad yno hefyd?",
+  "report.hint": "Bydd yr adroddiad yn cael ei anfon i arolygydd eich achos. Mae modd darparu esboniad o pam yr ydych yn cwyno am y cyfrif hwn isod:",
+  "report.placeholder": "Sylwadau ychwanegol",
+  "report.submit": "Cyflwyno",
+  "report.target": "Cwyno am {target}",
+  "search.placeholder": "Chwilio",
+  "search_popout.search_format": "Fformat chwilio uwch",
+  "search_popout.tips.full_text": "Mae testun syml yn dychwelyd tŵtiau yr ydych wedi ysgrifennu, hoffi, wedi'u bŵstio, neu wedi'ch crybwyll ynddynt, ynghyd a chyfateb a enwau defnyddwyr, enwau arddangos ac hashnodau.",
+  "search_popout.tips.hashtag": "hashnod",
+  "search_popout.tips.status": "statws",
+  "search_popout.tips.text": "Mae testun syml yn dychwelyd enwau arddangos, enwau defnyddwyr a hashnodau sy'n cyfateb",
+  "search_popout.tips.user": "defnyddiwr",
+  "search_results.accounts": "Pobl",
+  "search_results.hashtags": "Hanshnodau",
+  "search_results.statuses": "Tŵtiau",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "standalone.public_title": "Golwg tu fewn...",
+  "status.block": "Blocio @{name}",
+  "status.cancel_reblog_private": "Dadfŵstio",
+  "status.cannot_reblog": "Ni ellir sbarduno'r tŵt hwn",
+  "status.delete": "Dileu",
+  "status.detailed_status": "Golwg manwl o'r sgwrs",
+  "status.direct": "Neges breifat @{name}",
+  "status.embed": "Plannu",
+  "status.favourite": "Hoffi",
+  "status.filtered": "Filtered",
+  "status.load_more": "Llwythwch mwy",
+  "status.media_hidden": "Cyfryngau wedi'u cuddio",
+  "status.mention": "Crybwyll @{name}",
+  "status.more": "Mwy",
+  "status.mute": "Tawelu @{name}",
+  "status.mute_conversation": "Tawelu sgwrs",
+  "status.open": "Ehangu'r statws hwn",
+  "status.pin": "Pinio ar y proffil",
+  "status.pinned": "Pinio tŵt",
+  "status.read_more": "Darllen mwy",
+  "status.reblog": "Hybu",
+  "status.reblog_private": "Hybu i'r gynulleidfa wreiddiol",
+  "status.reblogged_by": "Bŵstio {name}",
+  "status.reblogs.empty": "Does neb wedi bŵstio'r tŵt yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.",
+  "status.redraft": "Dileu & ailddrafftio",
+  "status.reply": "Ateb",
+  "status.replyAll": "Ateb i edefyn",
+  "status.report": "Adrodd @{name}",
+  "status.sensitive_toggle": "Clicio i weld",
+  "status.sensitive_warning": "Cynnwys sensitif",
+  "status.share": "Rhannu",
+  "status.show_less": "Dangos llai",
+  "status.show_less_all": "Dangos llai i bawb",
+  "status.show_more": "Dangos mwy",
+  "status.show_more_all": "Dangos mwy i bawb",
+  "status.unmute_conversation": "Dad-dawelu sgwrs",
+  "status.unpin": "Dadbinio o'r proffil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Wedi'i ffedereiddio",
+  "tabs_bar.home": "Hafan",
+  "tabs_bar.local_timeline": "Lleol",
+  "tabs_bar.notifications": "Hysbysiadau",
+  "tabs_bar.search": "Chwilio",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} yn siarad",
+  "ui.beforeunload": "Mi fyddwch yn colli eich drafft os gadewch Mastodon.",
+  "upload_area.title": "Llusgwch & gollwing i uwchlwytho",
+  "upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
+  "upload_form.focus": "Cropio",
+  "upload_form.undo": "Dileu",
+  "upload_progress.label": "Uwchlwytho...",
+  "video.close": "Cau fideo",
+  "video.exit_fullscreen": "Gadael sgrîn llawn",
+  "video.expand": "Ymestyn fideo",
+  "video.fullscreen": "Sgrîn llawn",
+  "video.hide": "Cuddio fideo",
+  "video.mute": "Tawelu sain",
+  "video.pause": "Oedi",
+  "video.play": "Chwarae",
+  "video.unmute": "Dad-dawelu sain"
 }
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index e3d040ea8..d76f4ac1f 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Denne bruger følger endnu ikke nogen.",
   "account.follows_you": "Følger dig",
   "account.hide_reblogs": "Skjul fremhævelserne fra @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Ejerskabet af dette link blev tjekket den %{date}",
   "account.media": "Medie",
   "account.mention": "Nævn @{name}",
   "account.moved_to": "{name} er flyttet til:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Er du sikker på, du vil dæmpe {name}?",
   "confirmations.redraft.confirm": "Slet & omskriv",
   "confirmations.redraft.message": "Er du sikker på, du vil slette denne status og omskrive den? Favoritter og fremhævelser vil gå tabt og svar til det oprindelige opslag vil blive forældreløse.",
+  "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.unfollow.confirm": "Følg ikke længere",
   "confirmations.unfollow.message": "Er du sikker på, du ikke længere vil følge {name}?",
   "embed.instructions": "Indlejre denne status på din side ved at kopiere nedenstående kode.",
@@ -294,6 +296,7 @@
   "status.open": "Udvid denne status",
   "status.pin": "Fastgør til profil",
   "status.pinned": "Fastgjort trut",
+  "status.read_more": "Read more",
   "status.reblog": "Fremhæv",
   "status.reblog_private": "Fremhæv til oprindeligt publikum",
   "status.reblogged_by": "{name} fremhævede",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Vis mere for alle",
   "status.unmute_conversation": "Fjern dæmpningen fra samtale",
   "status.unpin": "Fjern som fastgjort fra profil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Fælles",
   "tabs_bar.home": "Hjem",
   "tabs_bar.local_timeline": "Lokal",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index d798878fb..81b8ceedd 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Dieses Profil folgt noch niemandem.",
   "account.follows_you": "Folgt dir",
   "account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Besitz dieses Links wurde geprüft am {date}",
   "account.media": "Medien",
   "account.mention": "@{name} erwähnen",
   "account.moved_to": "{name} ist umgezogen auf:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Bist du dir sicher, dass du {name} stummschalten möchtest?",
   "confirmations.redraft.confirm": "Löschen und neu erstellen",
   "confirmations.redraft.message": "Bist du dir sicher, dass du diesen Status löschen und neu machen möchtest? Favoriten und Boosts werden verloren gehen und Antworten zu diesem Post werden verwaist sein.",
+  "confirmations.reply.confirm": "Antworten",
+  "confirmations.reply.message": "Wenn du jetzt antwortest wird es die gesamte Nachricht verwerfen, die du gerade schreibst. Möchtest du wirklich fortfahren?",
   "confirmations.unfollow.confirm": "Entfolgen",
   "confirmations.unfollow.message": "Bist du dir sicher, dass du {name} entfolgen möchtest?",
   "embed.instructions": "Du kannst diesen Beitrag auf deiner Webseite einbetten, indem du den folgenden Code einfügst.",
@@ -127,7 +129,7 @@
   "follow_request.authorize": "Erlauben",
   "follow_request.reject": "Ablehnen",
   "getting_started.developers": "Entwickler",
-  "getting_started.documentation": "Documentation",
+  "getting_started.documentation": "Dokumentation",
   "getting_started.find_friends": "Finde Freunde von Twitter",
   "getting_started.heading": "Erste Schritte",
   "getting_started.invite": "Leute einladen",
@@ -141,7 +143,7 @@
   "keyboard_shortcuts.blocked": "Liste blockierter Profile öffnen",
   "keyboard_shortcuts.boost": "boosten",
   "keyboard_shortcuts.column": "einen Status in einer der Spalten fokussieren",
-  "keyboard_shortcuts.compose": "fokussiere das Tröt-Eingabefeld",
+  "keyboard_shortcuts.compose": "fokussiere das Eingabefeld",
   "keyboard_shortcuts.description": "Beschreibung",
   "keyboard_shortcuts.direct": "Direct-Message-Spalte öffnen",
   "keyboard_shortcuts.down": "sich in der Liste hinunter bewegen",
@@ -158,7 +160,7 @@
   "keyboard_shortcuts.muted": "Liste stummgeschalteter Profile öffnen",
   "keyboard_shortcuts.my_profile": "Dein Profil öffnen",
   "keyboard_shortcuts.notifications": "Benachrichtigungsspalte öffnen",
-  "keyboard_shortcuts.pinned": "Liste angehefteter Tröts öffnen",
+  "keyboard_shortcuts.pinned": "Liste angehefteter Beiträge öffnen",
   "keyboard_shortcuts.profile": "Profil des Autors öffnen",
   "keyboard_shortcuts.reply": "antworten",
   "keyboard_shortcuts.requests": "Liste der Folge-Anfragen  öffnen",
@@ -280,7 +282,7 @@
   "status.cancel_reblog_private": "Nicht mehr teilen",
   "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
   "status.delete": "Löschen",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "Detaillierte Ansicht der Konversation",
   "status.direct": "Direktnachricht @{name}",
   "status.embed": "Einbetten",
   "status.favourite": "Favorisieren",
@@ -294,6 +296,7 @@
   "status.open": "Diesen Beitrag öffnen",
   "status.pin": "Im Profil anheften",
   "status.pinned": "Angehefteter Beitrag",
+  "status.read_more": "Mehr lesen",
   "status.reblog": "Teilen",
   "status.reblog_private": "An das eigentliche Publikum teilen",
   "status.reblogged_by": "{name} teilte",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Zeige mehr für alles",
   "status.unmute_conversation": "Stummschaltung von Thread aufheben",
   "status.unpin": "Vom Profil lösen",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Föderation",
   "tabs_bar.home": "Startseite",
   "tabs_bar.local_timeline": "Lokal",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index b2ed49309..737c55d37 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -873,6 +873,14 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Dismiss suggestion",
+        "id": "suggestions.dismiss"
+      },
+      {
+        "defaultMessage": "You might be interested in…",
+        "id": "suggestions.header"
+      },
+      {
         "defaultMessage": "People",
         "id": "search_results.accounts"
       },
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 4e4b733ec..8e67e7f90 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -7,15 +7,15 @@
   "account.disclaimer_full": "Οι παρακάτω πληροφορίες μπορει να μην αντανακλούν το προφίλ του χρήστη επαρκως.",
   "account.domain_blocked": "Κρυμμένος τομέας",
   "account.edit_profile": "Επεξεργάσου το προφίλ",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Προβολή στο προφίλ",
   "account.follow": "Ακολούθησε",
   "account.followers": "Ακόλουθοι",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Κανείς δεν ακολουθεί αυτό τον χρήστη ακόμα.",
   "account.follows": "Ακολουθεί",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.",
   "account.follows_you": "Σε ακολουθεί",
   "account.hide_reblogs": "Απόκρυψη προωθήσεων από @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου εκλέχθηκε την {date}",
   "account.media": "Πολυμέσα",
   "account.mention": "Ανάφερε @{name}",
   "account.moved_to": "{name} μεταφέρθηκε στο:",
@@ -30,7 +30,7 @@
   "account.show_reblogs": "Δείξε τις προωθήσεις του/της @{name}",
   "account.unblock": "Ξεμπλόκαρε τον/την @{name}",
   "account.unblock_domain": "Αποκάλυψε το {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Άνευ προβολής στο προφίλ",
   "account.unfollow": "Διακοπή παρακολούθησης",
   "account.unmute": "Διακοπή αποσιώπησης του/της @{name}",
   "account.unmute_notifications": "Διακοπή αποσιώπησης ειδοποιήσεων του/της @{name}",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Σίγουρα θες να αποσιωπήσεις τον/την {name};",
   "confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
   "confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την κατάσταση και να την ξαναγράψεις; Οι αναφορές και τα αγαπημένα της θα χαθούν ενώ οι απαντήσεις προς αυτή θα μείνουν ορφανές.",
+  "confirmations.reply.confirm": "Απάντησε",
+  "confirmations.reply.message": "Απαντώντας τώρα θα αντικαταστήσεις το κείμενο που ήδη γράφεις. Σίγουρα θέλεις να συνεχίσεις;",
   "confirmations.unfollow.confirm": "Διακοπή παρακολούθησης",
   "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
   "embed.instructions": "Ενσωματώστε αυτή την κατάσταση στην ιστοσελίδα σας αντιγράφοντας τον παρακάτω κώδικα.",
@@ -109,19 +111,19 @@
   "emoji_button.search_results": "Αποτελέσματα αναζήτησης",
   "emoji_button.symbols": "Σύμβολα",
   "emoji_button.travel": "Ταξίδια & Τοποθεσίες",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.blocks": "Δεν έχεις αποκλείσει κανέναν χρήστη ακόμα.",
   "empty_column.community": "Η τοπική ροή είναι κενή. Γράψε κάτι δημόσιο παραμύθι ν' αρχινίσει!",
   "empty_column.direct": "Δεν έχεις προσωπικά μηνύματα ακόμα. Όταν στείλεις ή λάβεις κανένα, θα εμφανιστεί εδώ.",
-  "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.domain_blocks": "Δεν υπάρχουν αποκλεισμένοι τομείς ακόμα.",
+  "empty_column.favourited_statuses": "Δεν έχεις κανένα αγαπημένο τουτ ακόμα. Μόλις αγαπήσεις κάποιο, θα εμφανιστεί εδώ.",
+  "empty_column.favourites": "Κανείς δεν έχει αγαπήσει αυτό το τουτ ακόμα. Μόλις το κάνει κάποια, θα εμφανιστούν εδώ.",
+  "empty_column.follow_requests": "Δεν έχεις κανένα αίτημα παρακολούθησης ακόμα. Μόλις λάβεις κάποιο, θα εμφανιστεί εδώ.",
   "empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ταμπέλα.",
   "empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
   "empty_column.home.public_timeline": "η δημόσια ροή",
   "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.",
-  "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": "Δεν έχεις καμία λίστα ακόμα. Μόλις φτιάξεις μια, θα εμφανιστεί εδώ.",
+  "empty_column.mutes": "Δεν έχεις αποσιωπήσει κανένα χρήστη ακόμα.",
   "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.",
   "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλα instances για να τη γεμίσεις",
   "follow_request.authorize": "Ενέκρινε",
@@ -138,32 +140,32 @@
   "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
   "home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
   "keyboard_shortcuts.back": "για επιστροφή πίσω",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.blocked": "άνοιγμα λίστας αποκλεισμένων χρηστών",
   "keyboard_shortcuts.boost": "για προώθηση",
   "keyboard_shortcuts.column": "για εστίαση μιας κατάστασης σε μια από τις στήλες",
   "keyboard_shortcuts.compose": "για εστίαση στην περιοχή κειμένου συγγραφής",
   "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.direct": "άνοιγμα κολώνας απευθείας μηνυμάτων",
   "keyboard_shortcuts.down": "για κίνηση προς τα κάτω στη λίστα",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "για σημείωση αγαπημένου",
-  "keyboard_shortcuts.favourites": "to open favourites list",
-  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.favourites": "άνοιγμα λίστας αγαπημένων",
+  "keyboard_shortcuts.federated": "άνοιγμα ομοσπονδιακής ροής",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.home": "άνοιγμα αρχικής ροής",
   "keyboard_shortcuts.hotkey": "Συντόμευση",
   "keyboard_shortcuts.legend": "για να εμφανίσεις αυτόν τον οδηγό",
-  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.local": "άνοιγμα τοπικής ροής",
   "keyboard_shortcuts.mention": "για να αναφέρεις το συγγραφέα",
-  "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": "άνοιγμα λίστας αποσιωπημενων χρηστών",
+  "keyboard_shortcuts.my_profile": "άνοιγμα του προφίλ σου",
+  "keyboard_shortcuts.notifications": "άνοιγμα κολώνας ειδοποιήσεων",
+  "keyboard_shortcuts.pinned": "άνοιγμα λίστας καρφιτσωμένων τουτ",
+  "keyboard_shortcuts.profile": "άνοιγμα προφίλ συγγραφέα",
   "keyboard_shortcuts.reply": "για απάντηση",
-  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.requests": "άνοιγμα λίστας αιτημάτων παρακολούθησης",
   "keyboard_shortcuts.search": "για εστίαση αναζήτησης",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.start": "άνοιγμα κολώνας \"Ξεκινώντας\"",
   "keyboard_shortcuts.toggle_hidden": "για εμφάνιση/απόκρυψη κειμένου πίσω από την προειδοποίηση",
   "keyboard_shortcuts.toot": "για δημιουργία ολοκαίνουριου τουτ",
   "keyboard_shortcuts.unfocus": "για την απο-εστίαση του πεδίου σύνθεσης/αναζήτησης",
@@ -184,10 +186,10 @@
   "missing_indicator.label": "Δε βρέθηκε",
   "missing_indicator.sublabel": "Αδύνατη η εύρεση αυτού του πόρου",
   "mute_modal.hide_notifications": "Απόκρυψη ειδοποιήσεων αυτού του χρήστη;",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Εφαρμογές φορητών συσκευών",
   "navigation_bar.blocks": "Αποκλεισμένοι χρήστες",
   "navigation_bar.community_timeline": "Τοπική ροή",
-  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.compose": "Γράψε νέο τουτ",
   "navigation_bar.direct": "Προσωπικά μηνύματα",
   "navigation_bar.discover": "Ανακάλυψη",
   "navigation_bar.domain_blocks": "Κρυμμένοι τομείς",
@@ -274,13 +276,13 @@
   "search_results.accounts": "Άνθρωποι",
   "search_results.hashtags": "Ταμπέλες",
   "search_results.statuses": "Τουτ",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "search_results.total": "{count, number} {count, plural, ένα {result} υπόλοιπα {results}}",
   "standalone.public_title": "Μια πρώτη γεύση...",
   "status.block": "Block @{name}",
   "status.cancel_reblog_private": "Ακύρωσε την προώθηση",
   "status.cannot_reblog": "Αυτή η δημοσίευση δεν μπορεί να προωθηθεί",
   "status.delete": "Διαγραφή",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "Προβολή λεπτομερειών συζήτησης",
   "status.direct": "Προσωπικό μήνυμα προς @{name}",
   "status.embed": "Ενσωμάτωσε",
   "status.favourite": "Σημείωσε ως αγαπημένο",
@@ -294,10 +296,11 @@
   "status.open": "Διεύρυνε αυτή την κατάσταση",
   "status.pin": "Καρφίτσωσε στο προφίλ",
   "status.pinned": "Καρφιτσωμένο τουτ",
+  "status.read_more": "Περισσότερα",
   "status.reblog": "Προώθησε",
   "status.reblog_private": "Προώθησε στους αρχικούς παραλήπτες",
   "status.reblogged_by": "{name} προώθησε",
-  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.reblogs.empty": "Κανείς δεν προώθησε αυτό το τουτ ακόμα. Μόλις το κάνει κάποια, θα εμφανιστούν εδώ.",
   "status.redraft": "Σβήσε & ξαναγράψε",
   "status.reply": "Απάντησε",
   "status.replyAll": "Απάντησε στην συζήτηση",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Δείξε περισσότερα για όλα",
   "status.unmute_conversation": "Διέκοψε την αποσιώπηση της συζήτησης",
   "status.unpin": "Ξεκαρφίτσωσε από το προφίλ",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Ομοσπονδιακή",
   "tabs_bar.home": "Αρχική",
   "tabs_bar.local_timeline": "Τοπικά",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 0d85ec744..a45d762c1 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -319,6 +319,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 86bee46b1..0522ce95b 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
   "confirmations.redraft.confirm": "Forigi kaj reskribi",
   "confirmations.redraft.message": "Ĉu vi certas, ke vi volas forigi tiun mesaĝon kaj reskribi ĝin? Vi perdos ĉiujn respondojn, diskonigojn kaj stelumojn ligitajn al ĝi.",
+  "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.unfollow.confirm": "Ne plu sekvi",
   "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
   "embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
@@ -294,6 +296,7 @@
   "status.open": "Grandigi",
   "status.pin": "Alpingli profile",
   "status.pinned": "Alpinglita mesaĝo",
+  "status.read_more": "Read more",
   "status.reblog": "Diskonigi",
   "status.reblog_private": "Diskonigi al la originala atentaro",
   "status.reblogged_by": "{name} diskonigis",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Grandigi ĉiujn",
   "status.unmute_conversation": "Malsilentigi konversacion",
   "status.unpin": "Depingli de profilo",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Fratara tempolinio",
   "tabs_bar.home": "Hejmo",
   "tabs_bar.local_timeline": "Loka tempolinio",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 63f197c28..f06ac11a4 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "¿Estás seguro de que quieres silenciar a {name}?",
   "confirmations.redraft.confirm": "Borrar y volver a borrador",
   "confirmations.redraft.message": "Estás seguro de que quieres borrar este estado y volverlo a borrador? Perderás todas las respuestas, impulsos y favoritos asociados a él, y las respuestas a la publicación original quedarán huérfanos.",
+  "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.unfollow.confirm": "Dejar de seguir",
   "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?",
   "embed.instructions": "Añade este toot a tu sitio web con el siguiente código.",
@@ -294,6 +296,7 @@
   "status.open": "Expandir estado",
   "status.pin": "Fijar",
   "status.pinned": "Toot fijado",
+  "status.read_more": "Read more",
   "status.reblog": "Retootear",
   "status.reblog_private": "Implusar a la audiencia original",
   "status.reblogged_by": "Retooteado por {name}",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Mostrar más para todo",
   "status.unmute_conversation": "Dejar de silenciar conversación",
   "status.unpin": "Dejar de fijar",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federado",
   "tabs_bar.home": "Inicio",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index e4b1154b7..300e7eae4 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Ziur {name} mututu nahi duzula?",
   "confirmations.redraft.confirm": "Ezabatu eta berridatzi",
   "confirmations.redraft.message": "Ziur mezu hau ezabatu eta berridatzi nahi duzula? Gogokoak eta bultzadak galduko dira eta jaso dituen erantzunak umezurtz geratuko dira.",
+  "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.unfollow.confirm": "Utzi jarraitzeari",
   "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?",
   "embed.instructions": "Txertatu mezu hau zure webgunean beheko kodea kopatuz.",
@@ -294,6 +296,7 @@
   "status.open": "Hedatu mezu hau",
   "status.pin": "Finkatu profilean",
   "status.pinned": "Finkatutako toot-a",
+  "status.read_more": "Read more",
   "status.reblog": "Bultzada",
   "status.reblog_private": "Bultzada jatorrizko hartzaileei",
   "status.reblogged_by": "{name}(r)en bultzada",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Erakutsi denetarik gehiago",
   "status.unmute_conversation": "Desmututu elkarrizketa",
   "status.unpin": "Desfinkatu profiletik",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federatua",
   "tabs_bar.home": "Hasiera",
   "tabs_bar.local_timeline": "Lokala",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index fb4ded11c..7198931c4 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "این کاربر هنوز هیچ کسی را پی نمی‌گیرد.",
   "account.follows_you": "پیگیر شماست",
   "account.hide_reblogs": "پنهان کردن بازبوق‌های @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "مالکیت این نشانی در تایخ {date} بررسی شد",
   "account.media": "عکس و ویدیو",
   "account.mention": "نام‌بردن از @{name}",
   "account.moved_to": "{name} منتقل شده است به:",
@@ -58,8 +58,8 @@
   "column.public": "نوشته‌های همه‌جا",
   "column_back_button.label": "بازگشت",
   "column_header.hide_settings": "نهفتن تنظیمات",
-  "column_header.moveLeft_settings": "انتقال ستون به چپ",
-  "column_header.moveRight_settings": "انتقال ستون به راست",
+  "column_header.moveLeft_settings": "انتقال ستون به راست",
+  "column_header.moveRight_settings": "انتقال ستون به چپ",
   "column_header.pin": "ثابت‌کردن",
   "column_header.show_settings": "نمایش تنظیمات",
   "column_header.unpin": "رهاکردن",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
   "confirmations.redraft.confirm": "پاک‌کردن و بازنویسی",
   "confirmations.redraft.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید و آن را از نو بنویسید؟ با این کار بازبوق‌ها و پسندیده‌شدن‌های آن از دست می‌رود و پاسخ‌ها به آن بی‌مرجع می‌شود.",
+  "confirmations.reply.confirm": "پاسخ",
+  "confirmations.reply.message": "اگر الان پاسخ دهید، چیزی که در حال نوشتنش بودید پاک خواهد شد. آیا همین را می‌خواهید؟",
   "confirmations.unfollow.confirm": "لغو پیگیری",
   "confirmations.unfollow.message": "آیا واقعاً می‌خواهید به پیگیری از {name} پایان دهید؟",
   "embed.instructions": "برای جاگذاری این نوشته در سایت خودتان، کد زیر را کپی کنید.",
@@ -294,6 +296,7 @@
   "status.open": "این نوشته را باز کن",
   "status.pin": "نوشتهٔ ثابت نمایه",
   "status.pinned": "بوق ثابت",
+  "status.read_more": "بیشتر بخوانید",
   "status.reblog": "بازبوقیدن",
   "status.reblog_private": "بازبوق به مخاطبان اولیه",
   "status.reblogged_by": "‫{name}‬ بازبوقید",
@@ -311,6 +314,8 @@
   "status.show_more_all": "نمایش بیشتر همه",
   "status.unmute_conversation": "باصداکردن گفتگو",
   "status.unpin": "برداشتن نوشتهٔ ثابت نمایه",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "همگانی",
   "tabs_bar.home": "خانه",
   "tabs_bar.local_timeline": "محلی",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index caf949e8c..fc623dab8 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Haluatko varmasti mykistää käyttäjän {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.unfollow.confirm": "Lakkaa seuraamasta",
   "confirmations.unfollow.message": "Haluatko varmasti lakata seuraamasta käyttäjää {name}?",
   "embed.instructions": "Upota statuspäivitys sivullesi kopioimalla alla oleva koodi.",
@@ -294,6 +296,7 @@
   "status.open": "Laajenna tilapäivitys",
   "status.pin": "Kiinnitä profiiliin",
   "status.pinned": "Kiinnitetty tuuttaus",
+  "status.read_more": "Read more",
   "status.reblog": "Buustaa",
   "status.reblog_private": "Buustaa alkuperäiselle yleisölle",
   "status.reblogged_by": "{name} buustasi",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Näytä lisää kaikista",
   "status.unmute_conversation": "Poista keskustelun mykistys",
   "status.unpin": "Irrota profiilista",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Yleinen",
   "tabs_bar.home": "Koti",
   "tabs_bar.local_timeline": "Paikallinen",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index ff09a1402..d8b16672b 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -10,12 +10,12 @@
   "account.endorse": "Figure sur le profil",
   "account.follow": "Suivre",
   "account.followers": "Abonné⋅e⋅s",
-  "account.followers.empty": "Personne ne suit cet utilisateur pour l'instant.",
+  "account.followers.empty": "Personne ne suit cet utilisateur pour l’instant.",
   "account.follows": "Abonnements",
-  "account.follows.empty": "Cet utilisateur ne suit personne pour l'instant.",
+  "account.follows.empty": "Cet utilisateur ne suit personne pour l’instant.",
   "account.follows_you": "Vous suit",
   "account.hide_reblogs": "Masquer les partages de @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}",
   "account.media": "Média",
   "account.mention": "Mentionner",
   "account.moved_to": "{name} a déménagé vers :",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Confirmez-vous le masquage de {name} ?",
   "confirmations.redraft.confirm": "Effacer et ré-écrire",
   "confirmations.redraft.message": "Êtes-vous sûr·e de vouloir effacer ce statut pour le ré-écrire ? Ses partages ainsi que ses mises en favori seront perdu·e·s et ses réponses seront orphelines.",
+  "confirmations.reply.confirm": "Répondre",
+  "confirmations.reply.message": "Répondre maintenant écrasera le message que vous êtes en train de composer. Voulez-vous vraiment continuer ?",
   "confirmations.unfollow.confirm": "Ne plus suivre",
   "confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?",
   "embed.instructions": "Intégrez ce statut à votre site en copiant le code ci-dessous.",
@@ -109,19 +111,19 @@
   "emoji_button.search_results": "Résultats de la recherche",
   "emoji_button.symbols": "Symboles",
   "emoji_button.travel": "Lieux & Voyages",
-  "empty_column.blocks": "Vous n'avez bloqué aucun utilisateur pour le moment.",
+  "empty_column.blocks": "Vous n’avez bloqué aucun utilisateur pour le moment.",
   "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
   "empty_column.direct": "Vous n’avez pas encore de messages directs. Lorsque vous en enverrez ou recevrez un, il s’affichera ici.",
-  "empty_column.domain_blocks": "Il n'y a aucun domaine caché pour le moment.",
-  "empty_column.favourited_statuses": "Vous n'avez aucun pouet favoris pour le moment. Lorsque vous en mettrez un en favori, il apparaîtra ici.",
-  "empty_column.favourites": "Personne n'a encore mis ce pouet en favori. Lorsque quelqu'un le fera, il apparaîtra ici.",
-  "empty_column.follow_requests": "Vous n'avez pas encore de demande de suivi. Lorsque vous en recevrez une, elle apparaîtra ici.",
+  "empty_column.domain_blocks": "Il n’y a aucun domaine caché pour le moment.",
+  "empty_column.favourited_statuses": "Vous n’avez aucun pouet favoris pour le moment. Lorsque vous en mettrez un en favori, il apparaîtra ici.",
+  "empty_column.favourites": "Personne n’a encore mis ce pouet en favori. Lorsque quelqu’un le fera, il apparaîtra ici.",
+  "empty_column.follow_requests": "Vous n’avez pas encore de demande de suivi. Lorsque vous en recevrez une, elle apparaîtra ici.",
   "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
   "empty_column.home": "Vous ne suivez personne. Visitez {public} ou utilisez la recherche pour trouver d’autres personnes à suivre.",
   "empty_column.home.public_timeline": "le fil public",
   "empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Dès que des personnes de cette liste publieront de nouveaux statuts, ils apparaîtront ici.",
-  "empty_column.lists": "Vous n'avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
-  "empty_column.mutes": "Vous n'avez pas encore mis des utilisateurs en silence.",
+  "empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
+  "empty_column.mutes": "Vous n’avez pas encore mis des utilisateurs 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",
   "follow_request.authorize": "Accepter",
@@ -138,7 +140,7 @@
   "home.column_settings.show_reblogs": "Afficher les partages",
   "home.column_settings.show_replies": "Afficher les réponses",
   "keyboard_shortcuts.back": "revenir en arrière",
-  "keyboard_shortcuts.blocked": "pour ouvrir une liste d'utilisateurs bloqués",
+  "keyboard_shortcuts.blocked": "pour ouvrir une liste d’utilisateurs bloqués",
   "keyboard_shortcuts.boost": "partager",
   "keyboard_shortcuts.column": "focaliser un statut dans l’une des colonnes",
   "keyboard_shortcuts.compose": "pour centrer la zone de rédaction",
@@ -150,7 +152,7 @@
   "keyboard_shortcuts.favourites": "pour ouvrir une liste de favoris",
   "keyboard_shortcuts.federated": "pour ouvrir le fil public global",
   "keyboard_shortcuts.heading": "Raccourcis clavier",
-  "keyboard_shortcuts.home": "pour ouvrir l'accueil",
+  "keyboard_shortcuts.home": "pour ouvrir l’accueil",
   "keyboard_shortcuts.hotkey": "Raccourci",
   "keyboard_shortcuts.legend": "pour afficher cette légende",
   "keyboard_shortcuts.local": "pour ouvrir le fil public local",
@@ -294,10 +296,11 @@
   "status.open": "Déplier ce statut",
   "status.pin": "Épingler sur le profil",
   "status.pinned": "Pouet épinglé",
+  "status.read_more": "En savoir plus",
   "status.reblog": "Partager",
   "status.reblog_private": "Booster vers l’audience originale",
   "status.reblogged_by": "{name} a partagé :",
-  "status.reblogs.empty": "Personne n'a encore partagé ce pouet. Lorsque quelqu'un le fera, il apparaîtra ici.",
+  "status.reblogs.empty": "Personne n’a encore partagé ce pouet. Lorsque quelqu’un le fera, il apparaîtra ici.",
   "status.redraft": "Effacer et ré-écrire",
   "status.reply": "Répondre",
   "status.replyAll": "Répondre au fil",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Tout déplier",
   "status.unmute_conversation": "Ne plus masquer la conversation",
   "status.unpin": "Retirer du profil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Fil public global",
   "tabs_bar.home": "Accueil",
   "tabs_bar.local_timeline": "Fil public local",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 6afa21c9f..2f8b76d3a 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -7,15 +7,15 @@
   "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.",
   "account.domain_blocked": "Dominio agochado",
   "account.edit_profile": "Editar perfil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Mostrado no perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidoras",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Ninguén está a seguir esta usuaria por agora.",
   "account.follows": "Seguindo",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
   "account.follows_you": "Séguena",
   "account.hide_reblogs": "Ocultar repeticións de @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "A propiedade de esta ligazón foi comprobada en {date}",
   "account.media": "Medios",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} marchou a:",
@@ -30,7 +30,7 @@
   "account.show_reblogs": "Mostrar repeticións de @{name}",
   "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Non ocultar {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Non mostrar no perfil",
   "account.unfollow": "Non seguir",
   "account.unmute": "Non acalar @{name}",
   "account.unmute_notifications": "Desbloquear as notificacións de @{name}",
@@ -64,9 +64,9 @@
   "column_header.show_settings": "Mostras axustes",
   "column_header.unpin": "Soltar",
   "column_subheading.settings": "Axustes",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Só medios",
   "compose_form.direct_message_warning": "Este toot enviarase só as usuarias mencionadas. Porén, a súa proveedora de internet e calquera das instancias receptoras poderían examinar esta mensaxe.",
-  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.direct_message_warning_learn_more": "Coñecer máis",
   "compose_form.hashtag_warning": "Esta mensaxe non será listada baixo ningunha etiqueta xa que está marcada como non listada. Só os toots públicos poden buscarse por etiquetas.",
   "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",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Está segura de que quere acalar a {name}?",
   "confirmations.redraft.confirm": "Eliminar e reescribir",
   "confirmations.redraft.message": "Está segura de querer eliminar este estado e voltalo a escribir? Perderá réplicas e favoritas, e as respostas ao orixinal quedarán orfas.",
+  "confirmations.reply.confirm": "Respostar",
+  "confirmations.reply.message": "Respostando agora sobreescribirá a mensaxe que está a compoñer. Segura de querer proceder?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Quere deixar de seguir a {name}?",
   "embed.instructions": "Copie o código inferior para incrustar no seu sitio web este estado.",
@@ -109,61 +111,61 @@
   "emoji_button.search_results": "Resultados da busca",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viaxes e Lugares",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.blocks": "Non bloqueou ningunha usuaria polo de agora.",
   "empty_column.community": "A liña temporal local está baldeira. Escriba algo de xeito público para que rule!",
   "empty_column.direct": "Aínda non ten mensaxes directas. Cando envíe ou reciba unha, aparecerá aquí.",
-  "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.domain_blocks": "Aínda non  ocultou ningún dominio.",
+  "empty_column.favourited_statuses": "Aínda non ten toots favoritos. Cando favoreza algún, aparecerá aquí.",
+  "empty_column.favourites": "Ninguén favoreceu este toot polo momento. Cando o faga alguén, aparecerán aquí.",
+  "empty_column.follow_requests": "Non ten peticións de seguimento. Cando reciba unha, mostrarase aquí.",
   "empty_column.hashtag": "Aínda non hai nada con esta etiqueta.",
   "empty_column.home": "A súa liña temporal de inicio está baldeira! Visite {public} ou utilice a busca para atopar outras usuarias.",
   "empty_column.home.public_timeline": "a liña temporal pública",
   "empty_column.list": "Aínda non hai nada en esta lista. Cando as usuarias incluídas na lista publiquen mensaxes, aparecerán aquí.",
-  "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": "Aínda non ten listas. Cando cree unha, mostrarase aquí.",
+  "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 outras instancias para ir enchéndoa",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Rexeitar",
-  "getting_started.developers": "Developers",
+  "getting_started.developers": "Desenvolvedoras",
   "getting_started.documentation": "Documentation",
-  "getting_started.find_friends": "Find friends from Twitter",
+  "getting_started.find_friends": "Atope amigos da Twitter",
   "getting_started.heading": "Comezando",
-  "getting_started.invite": "Invite people",
+  "getting_started.invite": "Convide a xente",
   "getting_started.open_source_notice": "Mastodon é software de código aberto. Pode contribuír ou informar de fallos en GitHub en {github}.",
-  "getting_started.security": "Security",
-  "getting_started.terms": "Terms of service",
+  "getting_started.security": "Seguridade",
+  "getting_started.terms": "Termos do servizo",
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar repeticións",
   "home.column_settings.show_replies": "Mostrar respostas",
   "keyboard_shortcuts.back": "voltar atrás",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.blocked": "abrir lista de usuarias bloqueadas",
   "keyboard_shortcuts.boost": "promover",
   "keyboard_shortcuts.column": "destacar un estado en unha das columnas",
   "keyboard_shortcuts.compose": "Foco no área de escritura",
   "keyboard_shortcuts.description": "Descrición",
-  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.direct": "abrir columna de mensaxes directas",
   "keyboard_shortcuts.down": "ir hacia abaixo na lista",
   "keyboard_shortcuts.enter": "abrir estado",
   "keyboard_shortcuts.favourite": "marcar como favorito",
-  "keyboard_shortcuts.favourites": "to open favourites list",
-  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.favourites": "abrir lista de favoritos",
+  "keyboard_shortcuts.federated": "abrir liña temporal federada",
   "keyboard_shortcuts.heading": "Atallos do teclado",
-  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.home": "abrir liña temporal de inicio",
   "keyboard_shortcuts.hotkey": "Tecla de acceso directo",
   "keyboard_shortcuts.legend": "para mostrar esta lenda",
-  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.local": "abrir liña temporal 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": "abrir lista de usuarias acaladas",
+  "keyboard_shortcuts.my_profile": "abrir o seu perfil",
+  "keyboard_shortcuts.notifications": "abrir columna de notificacións",
+  "keyboard_shortcuts.pinned": "abrir lista de toots fixados",
+  "keyboard_shortcuts.profile": "abrir perfil da autora",
   "keyboard_shortcuts.reply": "para responder",
-  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.requests": "abrir lista de peticións de seguimento",
   "keyboard_shortcuts.search": "para centrar a busca",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.start": "abrir columna \"comezando\"",
   "keyboard_shortcuts.toggle_hidden": "mostrar/agochar un texto detrás do AC",
   "keyboard_shortcuts.toot": "escribir un toot novo",
   "keyboard_shortcuts.unfocus": "quitar o foco do área de escritura/busca",
@@ -184,16 +186,16 @@
   "missing_indicator.label": "Non atopado",
   "missing_indicator.sublabel": "Non se puido atopar o recurso",
   "mute_modal.hide_notifications": "Esconder notificacións deste usuario?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Apps móbiles",
   "navigation_bar.blocks": "Usuarias bloqueadas",
   "navigation_bar.community_timeline": "Liña temporal local",
-  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.compose": "Escribir novo toot",
   "navigation_bar.direct": "Mensaxes directas",
-  "navigation_bar.discover": "Discover",
+  "navigation_bar.discover": "Descubrir",
   "navigation_bar.domain_blocks": "Dominios agochados",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritas",
-  "navigation_bar.filters": "Muted words",
+  "navigation_bar.filters": "Palabras acaladas",
   "navigation_bar.follow_requests": "Peticións de seguimento",
   "navigation_bar.info": "Sobre esta instancia",
   "navigation_bar.keyboard_shortcuts": "Atallos",
@@ -204,7 +206,7 @@
   "navigation_bar.pins": "Mensaxes fixadas",
   "navigation_bar.preferences": "Preferencias",
   "navigation_bar.public_timeline": "Liña temporal federada",
-  "navigation_bar.security": "Security",
+  "navigation_bar.security": "Seguridade",
   "notification.favourite": "{name} marcou como favorito o seu estado",
   "notification.follow": "{name} está a seguila",
   "notification.mention": "{name} mencionoute",
@@ -219,7 +221,7 @@
   "notifications.column_settings.reblog": "Promocións:",
   "notifications.column_settings.show": "Mostrar en columna",
   "notifications.column_settings.sound": "Reproducir son",
-  "notifications.group": "{count} notifications",
+  "notifications.group": "{count} notificacións",
   "onboarding.done": "Feito",
   "onboarding.next": "Seguinte",
   "onboarding.page_five.public_timelines": "A liña de tempo local mostra as publicacións públicas de todos en {domain}. A liña de tempo federada mostra as publicacións públicas de todos os que as persoas en {domain} seguen. Estas son as Liñas de tempo públicas, unha boa forma de descubrir novas persoas.",
@@ -280,11 +282,11 @@
   "status.cancel_reblog_private": "Non promover",
   "status.cannot_reblog": "Esta mensaxe non pode ser promovida",
   "status.delete": "Eliminar",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "Vista detallada da conversa",
   "status.direct": "Mensaxe directa @{name}",
   "status.embed": "Incrustar",
   "status.favourite": "Favorita",
-  "status.filtered": "Filtered",
+  "status.filtered": "Filtrado",
   "status.load_more": "Cargar máis",
   "status.media_hidden": "Medios ocultos",
   "status.mention": "Mencionar @{name}",
@@ -294,11 +296,12 @@
   "status.open": "Expandir este estado",
   "status.pin": "Fixar no perfil",
   "status.pinned": "Toot fixado",
+  "status.read_more": "Lea máis",
   "status.reblog": "Promover",
   "status.reblog_private": "Promover a audiencia orixinal",
   "status.reblogged_by": "{name} promoveu",
-  "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": "Ninguén promoveu este toot polo de agora. Cando alguén o faga, mostraránse aquí.",
+  "status.redraft": "Eliminar & reescribir",
   "status.reply": "Resposta",
   "status.replyAll": "Resposta a conversa",
   "status.report": "Informar @{name}",
@@ -311,12 +314,14 @@
   "status.show_more_all": "Mostrar máis para todas",
   "status.unmute_conversation": "Non acalar a conversa",
   "status.unpin": "Despegar do perfil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federado",
   "tabs_bar.home": "Inicio",
   "tabs_bar.local_timeline": "Local",
   "tabs_bar.notifications": "Notificacións",
   "tabs_bar.search": "Buscar",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} outras {people}} conversando",
   "ui.beforeunload": "O borrador perderase se sae de Mastodon.",
   "upload_area.title": "Arrastre e solte para subir",
   "upload_button.label": "Engadir medios (JPEG, PNG, GIF, WebM, MP4, MOV)",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index d670d8a55..1ef20f231 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "להשתיק את {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.unfollow.confirm": "להפסיק מעקב",
   "confirmations.unfollow.message": "להפסיק מעקב אחרי {name}?",
   "embed.instructions": "ניתן להטמיע את ההודעה באתרך ע\"י העתקת הקוד שלהלן.",
@@ -294,6 +296,7 @@
   "status.open": "הרחבת הודעה",
   "status.pin": "לקבע באודות",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "הדהוד",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "הודהד על ידי {name}",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "הסרת השתקת שיחה",
   "status.unpin": "לשחרר מקיבוע באודות",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
   "tabs_bar.home": "בבית",
   "tabs_bar.local_timeline": "ציר זמן מקומי",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index b76b82e1b..c9b8e7f75 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Jesi li siguran da želiš utišati {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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Proširi ovaj status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Podigni",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} je podigao",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Poništi utišavanje razgovora",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federalni",
   "tabs_bar.home": "Dom",
   "tabs_bar.local_timeline": "Lokalno",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 57a8b7cfa..66a1d4c09 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Biztos benne, hogy némítani szeretné {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.unfollow.confirm": "Követés visszavonása",
   "confirmations.unfollow.message": "Biztos benne, hogy vissza szeretné vonni {name} követését?",
   "embed.instructions": "Ágyazza be ezen státuszt weboldalába az alábbi kód másolásával.",
@@ -294,6 +296,7 @@
   "status.open": "Státusz kinagyítása",
   "status.pin": "Kitűzés a profilra",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Reblog",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} reblogolta",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Beszélgetés némításának elvonása",
   "status.unpin": "Kitűzés eltávolítása a profilról",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federált",
   "tabs_bar.home": "Kezdőlap",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index 077748a0a..e7d251a35 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Վստա՞հ ես, որ ուզում ես {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.unfollow.confirm": "Ապահետեւել",
   "confirmations.unfollow.message": "Վստա՞հ ես, որ ուզում ես այլեւս չհետեւել {name}֊ին։",
   "embed.instructions": "Այս թութը քո կայքում ներդնելու համար կարող ես պատճենել ներքոհիշյալ կոդը։",
@@ -294,6 +296,7 @@
   "status.open": "Ընդարձակել այս թութը",
   "status.pin": "Ամրացնել անձնական էջում",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Տարածել",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} տարածել է",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Ապալռեցնել խոսակցությունը",
   "status.unpin": "Հանել անձնական էջից",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Դաշնային",
   "tabs_bar.home": "Հիմնական",
   "tabs_bar.local_timeline": "Տեղական",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 3d80c0949..1c84ed061 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Apa anda yakin ingin membisukan {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.unfollow.confirm": "Berhenti mengikuti",
   "confirmations.unfollow.message": "Apakah anda ingin berhenti mengikuti {name}?",
   "embed.instructions": "Sematkan status ini di website anda dengan menyalin kode di bawah ini.",
@@ -294,6 +296,7 @@
   "status.open": "Tampilkan status ini",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "di-boost {name}",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Gabungan",
   "tabs_bar.home": "Beranda",
   "tabs_bar.local_timeline": "Lokal",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 9059b3a2b..9963a52a5 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Are you sure you want to mute {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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Detaligar ca mesajo",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Repetar",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} repetita",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federata",
   "tabs_bar.home": "Hemo",
   "tabs_bar.local_timeline": "Lokala",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 5d8e3fe4a..711a360a9 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Questo utente non segue ancora nessuno.",
   "account.follows_you": "Ti segue",
   "account.hide_reblogs": "Nascondi condivisioni da @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "La proprietà di questo link è stata controllata il {date}",
   "account.media": "Media",
   "account.mention": "Menziona @{name}",
   "account.moved_to": "{name} si è trasferito su:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Sei sicuro di voler silenziare {name}?",
   "confirmations.redraft.confirm": "Cancella e riscrivi",
   "confirmations.redraft.message": "Sei sicuro di voler cancellare questo stato e riscriverlo? Perderai tutte le risposte, condivisioni e preferiti.",
+  "confirmations.reply.confirm": "Rispondi",
+  "confirmations.reply.message": "Se rispondi ora, il messaggio che stai componendo sarà sovrascritto. Sei sicuro di voler continuare?",
   "confirmations.unfollow.confirm": "Smetti di seguire",
   "confirmations.unfollow.message": "Sei sicuro che non vuoi più seguire {name}?",
   "embed.instructions": "Inserisci questo status nel tuo sito copiando il codice qui sotto.",
@@ -294,6 +296,7 @@
   "status.open": "Espandi questo post",
   "status.pin": "Fissa in cima sul profilo",
   "status.pinned": "Toot fissato in cima",
+  "status.read_more": "Leggi altro",
   "status.reblog": "Condividi",
   "status.reblog_private": "Condividi con i destinatari iniziali",
   "status.reblogged_by": "{name} ha condiviso",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Mostra di più per tutti",
   "status.unmute_conversation": "Annulla silenzia conversazione",
   "status.unpin": "Non fissare in cima al profilo",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federazione",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Locale",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index ae2c7e3c4..4dcc18518 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "まだ誰もフォローしていません。",
   "account.follows_you": "フォローされています",
   "account.hide_reblogs": "@{name}さんからのブーストを非表示",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "このリンクの所有権は{date}に確認されました",
   "account.media": "メディア",
   "account.mention": "@{name}さんにトゥート",
   "account.moved_to": "{name}さんは引っ越しました:",
@@ -319,6 +319,8 @@
   "status.show_more_all": "全て見る",
   "status.unmute_conversation": "会話のミュートを解除",
   "status.unpin": "プロフィールの固定表示を解除",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "連合",
   "tabs_bar.home": "ホーム",
   "tabs_bar.local_timeline": "ローカル",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index 21cd7d644..9d6d0d66d 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "დარწმუნებული ხართ, გსურთ გააჩუმოთ {name}?",
   "confirmations.redraft.confirm": "გაუქმება და გადანაწილება",
   "confirmations.redraft.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი და გადაანაწილოთ? დაკარგავთ ყველა პასუხს, ბუსტს და მასზედ არსებულ ფავორიტს.",
+  "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.unfollow.confirm": "ნუღარ მიჰყვები",
   "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?",
   "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.",
@@ -294,6 +296,7 @@
   "status.open": "ამ სტატუსის გაფართოება",
   "status.pin": "აპინე პროფილზე",
   "status.pinned": "აპინული ტუტი",
+  "status.read_more": "Read more",
   "status.reblog": "ბუსტი",
   "status.reblog_private": "დაიბუსტოს საწყის აუდიტორიაზე",
   "status.reblogged_by": "{name} დაიბუსტა",
@@ -311,6 +314,8 @@
   "status.show_more_all": "აჩვენე მეტი ყველაზე",
   "status.unmute_conversation": "საუბარზე გაჩუმების მოშორება",
   "status.unpin": "პროფილიდან პინის მოშორება",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "ფედერალური",
   "tabs_bar.home": "სახლი",
   "tabs_bar.local_timeline": "ლოკალური",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 8e0f9f59f..c779017f3 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "이 유저는 아직 아무도 팔로우 하고 있지 않습니다.",
   "account.follows_you": "날 팔로우합니다",
   "account.hide_reblogs": "@{name}의 부스트를 숨기기",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "{date}에 이 링크의 소유권이 확인 됨",
   "account.media": "미디어",
   "account.mention": "@{name}에게 글쓰기",
   "account.moved_to": "{name}는 계정을 이동했습니다:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
   "confirmations.redraft.confirm": "삭제하고 다시 쓰기",
   "confirmations.redraft.message": "정말로 이 포스트를 삭제하고 다시 쓰시겠습니까? 해당 포스트에 대한 부스트와 즐겨찾기를 잃게 되고 원본에 대한 답장은 연결 되지 않습니다.",
+  "confirmations.reply.confirm": "답글",
+  "confirmations.reply.message": "답글을 달기 위해 현재 작성 중인 메시지가 덮어 씌워집니다. 진행하시겠습니까?",
   "confirmations.unfollow.confirm": "언팔로우",
   "confirmations.unfollow.message": "정말로 {name}를 언팔로우하시겠습니까?",
   "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.",
@@ -294,6 +296,7 @@
   "status.open": "상세 정보 표시",
   "status.pin": "고정",
   "status.pinned": "고정 된 툿",
+  "status.read_more": "더 보기",
   "status.reblog": "부스트",
   "status.reblog_private": "원래의 수신자들에게 부스트",
   "status.reblogged_by": "{name}님이 부스트 했습니다",
@@ -311,6 +314,8 @@
   "status.show_more_all": "모두 펼치기",
   "status.unmute_conversation": "이 대화의 뮤트 해제하기",
   "status.unpin": "고정 해제",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "연합",
   "tabs_bar.home": "홈",
   "tabs_bar.local_timeline": "로컬",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index e6b85692c..7a8ff6868 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Deze gebruiker volgt nog niemand.",
   "account.follows_you": "Volgt jou",
   "account.hide_reblogs": "Verberg boosts van @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
   "account.media": "Media",
   "account.mention": "Vermeld @{name}",
   "account.moved_to": "{name} is verhuisd naar:",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
   "confirmations.redraft.confirm": "Verwijderen en herschrijven",
   "confirmations.redraft.message": "Weet je zeker dat je deze toot wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en reacties op de originele toot zitten niet meer aan de nieuwe toot vast.",
+  "confirmations.reply.confirm": "Reageren",
+  "confirmations.reply.message": "Door nu  te reageren overschrijf je de toot die je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
   "confirmations.unfollow.confirm": "Ontvolgen",
   "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
   "embed.instructions": "Embed deze toot op jouw website, door de onderstaande code te kopiëren.",
@@ -294,6 +296,7 @@
   "status.open": "Toot volledig tonen",
   "status.pin": "Aan profielpagina vastmaken",
   "status.pinned": "Vastgemaakte toot",
+  "status.read_more": "Meer lezen",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost naar oorspronkelijke ontvangers",
   "status.reblogged_by": "{name} boostte",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Alles meer tonen",
   "status.unmute_conversation": "Conversatie niet langer negeren",
   "status.unpin": "Van profielpagina losmaken",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Globaal",
   "tabs_bar.home": "Start",
   "tabs_bar.local_timeline": "Lokaal",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 4a8176e82..7c7e7600e 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Er du sikker på at du vil dempe {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.unfollow.confirm": "Slutt å følge",
   "confirmations.unfollow.message": "Er du sikker på at du vil slutte å følge {name}?",
   "embed.instructions": "Kopier koden under for å bygge inn denne statusen på hjemmesiden din.",
@@ -294,6 +296,7 @@
   "status.open": "Utvid denne statusen",
   "status.pin": "Fest på profilen",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Fremhev",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "Fremhevd av {name}",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Ikke demp samtale",
   "status.unpin": "Angre festing på profilen",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Felles",
   "tabs_bar.home": "Hjem",
   "tabs_bar.local_timeline": "Lokal",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 64cbaef55..64ada60da 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -15,7 +15,7 @@
   "account.follows.empty": "Aqueste utilizaire sèc pas degun pel moment.",
   "account.follows_you": "Vos sèc",
   "account.hide_reblogs": "Rescondre los partatges de @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "La proprietat d’aqueste ligam foguèt verificada lo {date}",
   "account.media": "Mèdias",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} a mudat los catons a :",
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Volètz vertadièrament rescondre {name} ?",
   "confirmations.redraft.confirm": "Escafar & tornar formular",
   "confirmations.redraft.message": "Volètz vertadièrament escafar aqueste estatut e lo reformular ? Tote sos partiments e favorits seràn perduts, e sas responsas seràn orfanèlas.",
+  "confirmations.reply.confirm": "Respondre",
+  "confirmations.reply.message": "Respondre remplaçarà lo messatge que sètz a escriure. Volètz vertadièrament contunhar ?",
   "confirmations.unfollow.confirm": "Quitar de sègre",
   "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
   "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.",
@@ -294,6 +296,7 @@
   "status.open": "Desplegar aqueste estatut",
   "status.pin": "Penjar al perfil",
   "status.pinned": "Tut penjat",
+  "status.read_more": "Ne legir mai",
   "status.reblog": "Partejar",
   "status.reblog_private": "Partejar a l’audiéncia d’origina",
   "status.reblogged_by": "{name} a partejat",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Los desplegar totes",
   "status.unmute_conversation": "Tornar mostrar la conversacion",
   "status.unpin": "Tirar del perfil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Flux public global",
   "tabs_bar.home": "Acuèlh",
   "tabs_bar.local_timeline": "Flux public local",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 76b340af3..3f596bc37 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -319,6 +319,8 @@
   "status.show_more_all": "Rozwiń wszystkie",
   "status.unmute_conversation": "Cofnij wyciszenie konwersacji",
   "status.unpin": "Odepnij z profilu",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Globalne",
   "tabs_bar.home": "Strona główna",
   "tabs_bar.local_timeline": "Lokalne",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 3e5b5da8e..061d10f4d 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -10,12 +10,12 @@
   "account.endorse": "Destacar no perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Ninguém segue esse usuário no momento.",
   "account.follows": "Segue",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Esse usuário não segue ninguém no momento.",
   "account.follows_you": "Segue você",
   "account.hide_reblogs": "Esconder compartilhamentos de @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "A posse desse link foi verificada em {date}",
   "account.media": "Mídia",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} se mudou para:",
@@ -90,7 +90,9 @@
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.mute.message": "Você tem certeza de que quer silenciar {name}?",
   "confirmations.redraft.confirm": "Apagar & usar como rascunho",
-  "confirmations.redraft.message": "Você tem certeza que deseja apagar esse status e usá-lo como rascunho? Você vai perder todas as respostas, compartilhamentos e favoritos relacionados a ele.",
+  "confirmations.redraft.message": "Você tem certeza que deseja apagar esse status e usá-lo como rascunho? Seus compartilhamentos e favoritos serão perdidos e as respostas ao toot original ficarão desconectadas.",
+  "confirmations.reply.confirm": "Responder",
+  "confirmations.reply.message": "Responder agora vai sobrescrever a mensagem que você está compondo. Você tem certeza que quer continuar?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Você tem certeza de que quer deixar de seguir {name}?",
   "embed.instructions": "Incorpore esta postagem em seu site copiando o código abaixo.",
@@ -109,19 +111,19 @@
   "emoji_button.search_results": "Resultados da busca",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.blocks": "Você ainda não bloqueou nenhum usuário.",
   "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
   "empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.",
-  "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.domain_blocks": "Ainda não há nenhum domínio escondido.",
+  "empty_column.favourited_statuses": "Você ainda não tem nenhum toot favorito. Quando você favoritar um toot, ele aparecerá aqui.",
+  "empty_column.favourites": "Ninguém favoritou esse toot até agora. Quando alguém favoritar, a pessoa aparecerá aqui.",
+  "empty_column.follow_requests": "Você não tem nenhum pedido de seguir por agora. Quando você receber um, ele aparecerá aqui.",
   "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.",
   "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
   "empty_column.home.public_timeline": "global",
   "empty_column.list": "Ainda não há nada nesta lista. Quando membros dessa lista fizerem novas postagens, 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": "Você ainda não tem nenhuma lista. Quando você criar uma, ela aparecerá aqui.",
+  "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",
   "follow_request.authorize": "Autorizar",
@@ -138,32 +140,32 @@
   "home.column_settings.show_reblogs": "Mostrar compartilhamentos",
   "home.column_settings.show_replies": "Mostrar as respostas",
   "keyboard_shortcuts.back": "para navegar de volta",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.blocked": "para abrir a lista de usuários bloqueados",
   "keyboard_shortcuts.boost": "para compartilhar",
   "keyboard_shortcuts.column": "Focar um status em uma das colunas",
   "keyboard_shortcuts.compose": "para focar a área de redação",
   "keyboard_shortcuts.description": "Descrição",
-  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.direct": "para abrir a coluna de mensagens diretas",
   "keyboard_shortcuts.down": "para mover para baixo na lista",
   "keyboard_shortcuts.enter": "para expandir um status",
   "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 de favoritos",
+  "keyboard_shortcuts.federated": "para abrir a timeline global",
   "keyboard_shortcuts.heading": "Atalhos de teclado",
-  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.home": "para abrir a página inicial",
   "keyboard_shortcuts.hotkey": "Atalho",
   "keyboard_shortcuts.legend": "para mostrar essa legenda",
-  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.local": "para abrir a timeline 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.muted": "para abrir a lista de usuários silenciados",
+  "keyboard_shortcuts.my_profile": "para abrir o seu perfil",
+  "keyboard_shortcuts.notifications": "para abrir a coluna de notificações",
+  "keyboard_shortcuts.pinned": "para abrir a lista de 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 de seguidores pendentes",
   "keyboard_shortcuts.search": "para focar a pesquisa",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.start": "para abrir a coluna \"primeiros passos\"",
   "keyboard_shortcuts.toggle_hidden": "mostrar/esconder o texto com aviso de conteúdo",
   "keyboard_shortcuts.toot": "para compor um novo toot",
   "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa",
@@ -187,7 +189,7 @@
   "navigation_bar.apps": "Apps",
   "navigation_bar.blocks": "Usuários bloqueados",
   "navigation_bar.community_timeline": "Local",
-  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.compose": "Compor um novo toot",
   "navigation_bar.direct": "Mensagens diretas",
   "navigation_bar.discover": "Descobrir",
   "navigation_bar.domain_blocks": "Domínios escondidos",
@@ -280,7 +282,7 @@
   "status.cancel_reblog_private": "Desfazer compartilhamento",
   "status.cannot_reblog": "Esta postagem não pode ser compartilhada",
   "status.delete": "Excluir",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "Visão detalhada da conversa",
   "status.direct": "Enviar mensagem direta a @{name}",
   "status.embed": "Incorporar",
   "status.favourite": "Adicionar aos favoritos",
@@ -294,10 +296,11 @@
   "status.open": "Expandir",
   "status.pin": "Fixar no perfil",
   "status.pinned": "Toot fixado",
+  "status.read_more": "Ler mais",
   "status.reblog": "Compartilhar",
   "status.reblog_private": "Compartilhar com a audiência original",
   "status.reblogged_by": "{name} compartilhou",
-  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.reblogs.empty": "Ninguém compartilhou esse toot até agora. Quando alguém o fizer, eles aparecerão aqui.",
   "status.redraft": "Apagar & usar como rascunho",
   "status.reply": "Responder",
   "status.replyAll": "Responder à sequência",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Mostrar mais para todas as mensagens",
   "status.unmute_conversation": "Desativar silêncio desta conversa",
   "status.unpin": "Desafixar do perfil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Global",
   "tabs_bar.home": "Página inicial",
   "tabs_bar.local_timeline": "Local",
@@ -319,7 +324,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {pessoa} other {pessoas}} falando sobre",
   "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.",
   "upload_area.title": "Arraste e solte para enviar",
-  "upload_button.label": "Adicionar mídia",
+  "upload_button.label": "Adicionar mídia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "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 e9d91f631..adb10dd07 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -91,6 +91,8 @@
   "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.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.",
@@ -294,6 +296,7 @@
   "status.open": "Expandir",
   "status.pin": "Fixar no perfil",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Partilhar",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} partilhou",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "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…",
   "tabs_bar.federated_timeline": "Global",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index 1d6e73bfd..2a6479b91 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Ești sigur că vrei să oprești {name}?",
   "confirmations.redraft.confirm": "Șterge și salvează ca ciornă",
   "confirmations.redraft.message": "Ești sigur că vrei să faci asta? Tot ce ține de această postare, inclusiv răspunsurile vor fi deconectate.",
+  "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.unfollow.confirm": "Nu mai urmări",
   "confirmations.unfollow.message": "Ești sigur că nu mai vrei să îl urmărești pe {name}?",
   "embed.instructions": "Inserează această postare pe site-ul tău adăugând codul de mai jos.",
@@ -294,6 +296,7 @@
   "status.open": "Extinde acest status",
   "status.pin": "Fixează pe profil",
   "status.pinned": "Postare fixată",
+  "status.read_more": "Read more",
   "status.reblog": "Redistribuie",
   "status.reblog_private": "Redistribuie către audiența originală",
   "status.reblogged_by": "{name} redistribuit",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Arată mai mult pentru toți",
   "status.unmute_conversation": "Repornește conversația",
   "status.unpin": "Eliberează din profil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Global",
   "tabs_bar.home": "Acasă",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 15fbfac3f..cd65adcb5 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
   "confirmations.redraft.confirm": "Удалить и исправить",
   "confirmations.redraft.message": "Вы уверены, что хотите удалить этот статус и превратить в черновик? Вы потеряете все ответы, продвижения и отметки 'нравится' к нему.",
+  "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.unfollow.confirm": "Отписаться",
   "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?",
   "embed.instructions": "Встройте этот статус на Вашем сайте, скопировав код внизу.",
@@ -294,6 +296,7 @@
   "status.open": "Развернуть статус",
   "status.pin": "Закрепить в профиле",
   "status.pinned": "Закреплённый статус",
+  "status.read_more": "Read more",
   "status.reblog": "Продвинуть",
   "status.reblog_private": "Продвинуть для своей аудитории",
   "status.reblogged_by": "{name} продвинул(а)",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Развернуть для всех",
   "status.unmute_conversation": "Снять глушение с треда",
   "status.unpin": "Открепить от профиля",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Глобальная",
   "tabs_bar.home": "Главная",
   "tabs_bar.local_timeline": "Локальная",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 11a6b76c1..cca2e3c62 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
   "confirmations.redraft.confirm": "Vyčistiť a prepísať",
   "confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté odpovede, povýšenia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
+  "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.unfollow.confirm": "Nesledovať",
   "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
   "embed.instructions": "Umiestni kód uvedený nižšie pre pridanie tohto statusu na tvoju web stránku.",
@@ -294,6 +296,7 @@
   "status.open": "Otvoriť tento status",
   "status.pin": "Pripni na profil",
   "status.pinned": "Pripnutý príspevok",
+  "status.read_more": "Read more",
   "status.reblog": "Povýšiť",
   "status.reblog_private": "Povýš k pôvodnému publiku",
   "status.reblogged_by": "{name} povýšil/a",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Všetkým ukáž viac",
   "status.unmute_conversation": "Prestať ignorovať konverzáciu",
   "status.unpin": "Odopnúť z profilu",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federovaná",
   "tabs_bar.home": "Domov",
   "tabs_bar.local_timeline": "Lokálna",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index f715abe85..175a34efd 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -7,15 +7,15 @@
   "account.disclaimer_full": "Spodnje informacije lahko nepopolno prikazujejo profil uporabnika.",
   "account.domain_blocked": "Skrita domena",
   "account.edit_profile": "Uredi profil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Zmožnost profila",
   "account.follow": "Sledi",
   "account.followers": "Sledilci",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Nihče ne sledi tega uporabnika.",
   "account.follows": "Sledi",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Ta uporabnik še ne sledi nikomur.",
   "account.follows_you": "Ti sledi",
   "account.hide_reblogs": "Skrij napuhke od @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}",
   "account.media": "Mediji",
   "account.mention": "Omeni @{name}",
   "account.moved_to": "{name} se je premaknil na:",
@@ -64,9 +64,9 @@
   "column_header.show_settings": "Prikaži nastavitve",
   "column_header.unpin": "Odpni",
   "column_subheading.settings": "Nastavitve",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Samo mediji",
   "compose_form.direct_message_warning": "Ta tut bo viden le vsem omenjenim uporabnikom.",
-  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.direct_message_warning_learn_more": "Nauči se več",
   "compose_form.hashtag_warning": "Ta tut ne bo naveden pod nobenim hashtagom, ker ni dodan hashtag. Samo javne tute lahko iščete pod hashtagom.",
   "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",
@@ -89,8 +89,10 @@
   "confirmations.domain_block.message": "Ali ste res, res prepričani, da želite blokirati celotno {domain}? V večini primerov je nekaj ciljnih blokiranj ali utišanj dovolj in boljše.",
   "confirmations.mute.confirm": "Utišanje",
   "confirmations.mute.message": "Ali ste prepričani, da želite utišati {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.redraft.confirm": "Izbriši in preoblikuj",
   "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": "Odgovarjanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?",
   "confirmations.unfollow.confirm": "Prenehaj slediti",
   "confirmations.unfollow.message": "Ali ste prepričani, da ne želite več slediti {name}?",
   "embed.instructions": "Vstavi ta status na svojo spletno stran tako, da kopirate spodnjo kodo.",
@@ -109,10 +111,10 @@
   "emoji_button.search_results": "Rezultati iskanja",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Potovanja in Kraji",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.blocks": "Niste še blokirali nobenega uporabnika.",
   "empty_column.community": "Lokalna časovnica je prazna. Napišite nekaj javnega, da se bo žoga zakotalila!",
   "empty_column.direct": "Nimate še nobenih neposrednih sporočil. Ko ga pošljete ali prejmete, se prikaže tukaj.",
-  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.domain_blocks": "Še vedno ni skritih domen.",
   "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.",
@@ -294,6 +296,7 @@
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pripeti tut",
+  "status.read_more": "Read more",
   "status.reblog": "Suni",
   "status.reblog_private": "Suni v prvotno občinstvo",
   "status.reblogged_by": "{name} sunjen",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Prikaži več za vse",
   "status.unmute_conversation": "Odtišaj pogovor",
   "status.unpin": "Odpni iz profila",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Združeno",
   "tabs_bar.home": "Domov",
   "tabs_bar.local_timeline": "Lokalno",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 7131d3044..6020512c4 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Da li stvarno želite da ućutkate korisnika {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.unfollow.confirm": "Otprati",
   "confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?",
   "embed.instructions": "Ugradi ovaj status na Vaš veb sajt kopiranjem koda ispod.",
@@ -294,6 +296,7 @@
   "status.open": "Proširi ovaj status",
   "status.pin": "Prikači na profil",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Podrži",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} podržao(la)",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Uključi prepisku",
   "status.unpin": "Otkači sa profila",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federisano",
   "tabs_bar.home": "Početna",
   "tabs_bar.local_timeline": "Lokalno",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 806c0acb3..41d9e12b0 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Да ли стварно желите да ућуткате корисника {name}?",
   "confirmations.redraft.confirm": "Избриши и преправи",
   "confirmations.redraft.message": "Да ли сте сигурни да желите да избришете овај статус и да га преправите? Сва стављања у омиљене трубе, као и подршке ће бити изгубљене, а одговори на оригинални пост ће бити поништени.",
+  "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.unfollow.confirm": "Отпрати",
   "confirmations.unfollow.message": "Да ли сте сигурни да желите да отпратите корисника {name}?",
   "embed.instructions": "Угради овај статус на Ваш веб сајт копирањем кода испод.",
@@ -294,6 +296,7 @@
   "status.open": "Прошири овај статус",
   "status.pin": "Закачи на профил",
   "status.pinned": "Закачена труба",
+  "status.read_more": "Read more",
   "status.reblog": "Подржи",
   "status.reblog_private": "Подржи да види првобитна публика",
   "status.reblogged_by": "{name} подржао/ла",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Прикажи више за све",
   "status.unmute_conversation": "Укључи преписку",
   "status.unpin": "Откачи са профила",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Федерисано",
   "tabs_bar.home": "Почетна",
   "tabs_bar.local_timeline": "Локално",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index f9129d368..dbe9f709a 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Är du säker du vill tysta ner {name}?",
   "confirmations.redraft.confirm": "Radera och gör om",
   "confirmations.redraft.message": "Är du säker på att du vill radera meddelandet och göra om det? Du kommer förlora alla svar, knuffar och favoriter som hänvisar till meddelandet.",
+  "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.unfollow.confirm": "Sluta följa",
   "confirmations.unfollow.message": "Är du säker på att du vill sluta följa {name}?",
   "embed.instructions": "Bädda in den här statusen på din webbplats genom att kopiera koden nedan.",
@@ -294,6 +296,7 @@
   "status.open": "Utvidga denna status",
   "status.pin": "Fäst i profil",
   "status.pinned": "Fäst toot",
+  "status.read_more": "Read more",
   "status.reblog": "Knuff",
   "status.reblog_private": "Knuffa till de ursprungliga åhörarna",
   "status.reblogged_by": "{name} knuffade",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Visa mer för alla",
   "status.unmute_conversation": "Öppna konversation",
   "status.unpin": "Ångra fäst i profil",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Förenad",
   "tabs_bar.home": "Hem",
   "tabs_bar.local_timeline": "Lokal",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index 427e9a3dc..803e004cc 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+  "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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index bc13b02f1..af036e300 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "{name}ను మీరు ఖచ్చితంగా మ్యూట్ చేయాలనుకుంటున్నారా?",
   "confirmations.redraft.confirm": "తొలగించు & తిరగరాయు",
   "confirmations.redraft.message": "మీరు ఖచ్చితంగా ఈ స్టేటస్ ని తొలగించి తిరగరాయాలనుకుంటున్నారా? ఈ స్టేటస్ యొక్క బూస్ట్ లు మరియు ఇష్టాలు పోతాయి,మరియు ప్రత్యుత్తరాలు అనాధలు అయిపోతాయి.",
+  "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.unfollow.confirm": "అనుసరించవద్దు",
   "confirmations.unfollow.message": "{name}ను మీరు ఖచ్చితంగా అనుసరించవద్దనుకుంటున్నారా?",
   "embed.instructions": "దిగువ కోడ్ను కాపీ చేయడం ద్వారా మీ వెబ్సైట్లో ఈ స్టేటస్ ని పొందుపరచండి.",
@@ -294,6 +296,7 @@
   "status.open": "ఈ స్టేటస్ ను విస్తరించు",
   "status.pin": "ప్రొఫైల్లో అతికించు",
   "status.pinned": "అతికించిన టూట్",
+  "status.read_more": "Read more",
   "status.reblog": "బూస్ట్",
   "status.reblog_private": "అసలు ప్రేక్షకులకు బూస్ట్ చేయి",
   "status.reblogged_by": "{name} బూస్ట్ చేసారు",
@@ -311,6 +314,8 @@
   "status.show_more_all": "అన్నిటికీ ఇంకా చూపించు",
   "status.unmute_conversation": "సంభాషణను అన్మ్యూట్ చేయి",
   "status.unpin": "ప్రొఫైల్ నుండి పీకివేయు",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "సమాఖ్య",
   "tabs_bar.home": "హోమ్",
   "tabs_bar.local_timeline": "స్థానిక",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 3114bca60..fe36a966c 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Are you sure you want to mute {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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index a661b022b..323617f1d 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?",
   "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.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "embed.instructions": "Embed this status on your website by copying the code below.",
@@ -294,6 +296,7 @@
   "status.open": "Bu gönderiyi genişlet",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Boost'la",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boost etti",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federe",
   "tabs_bar.home": "Ana sayfa",
   "tabs_bar.local_timeline": "Yerel",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 116dfc489..cdc13c574 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "Ви впевнені, що хочете заглушити {name}?",
   "confirmations.redraft.confirm": "Видалити і перестворити",
   "confirmations.redraft.message": "Ви впевнені, що хочете видалити допис і перестворити його? Ви втратите всі відповіді, передмухи та вподобайки допису.",
+  "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.unfollow.confirm": "Відписатися",
   "confirmations.unfollow.message": "Ви впевнені, що хочете відписатися від {name}?",
   "embed.instructions": "Інтегруйте цей статус на вашому вебсайті, скопіювавши код нижче.",
@@ -294,6 +296,7 @@
   "status.open": "Розгорнути допис",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
   "status.reblog": "Передмухнути",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} передмухнув(-ла)",
@@ -311,6 +314,8 @@
   "status.show_more_all": "Show more for all",
   "status.unmute_conversation": "Зняти глушення з діалогу",
   "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Глобальна",
   "tabs_bar.home": "Головна",
   "tabs_bar.local_timeline": "Локальна",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 69ecd9431..9fee25e15 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "你确定要隐藏 {name} 吗?",
   "confirmations.redraft.confirm": "删除并重新编辑",
   "confirmations.redraft.message": "你确定要删除这条嘟文并重新编辑它吗?所有相关的回复、转嘟和收藏都会被清除。",
+  "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.unfollow.confirm": "取消关注",
   "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?",
   "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。",
@@ -294,6 +296,7 @@
   "status.open": "展开嘟文",
   "status.pin": "在个人资料页面置顶",
   "status.pinned": "置顶嘟文",
+  "status.read_more": "Read more",
   "status.reblog": "转嘟",
   "status.reblog_private": "转嘟给原有关注者",
   "status.reblogged_by": "{name} 转嘟了",
@@ -311,6 +314,8 @@
   "status.show_more_all": "显示所有内容",
   "status.unmute_conversation": "不再隐藏此对话",
   "status.unpin": "在个人资料页面取消置顶",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "跨站",
   "tabs_bar.home": "主页",
   "tabs_bar.local_timeline": "本站",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index fc5376699..26eba48f8 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "你確定要將{name}靜音嗎?",
   "confirmations.redraft.confirm": "刪除並編輯",
   "confirmations.redraft.message": "你確定要刪除並重新編輯嗎?所有相關的回覆、轉推與最愛都會被刪除。",
+  "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.unfollow.confirm": "取消關注",
   "confirmations.unfollow.message": "真的不要繼續關注 {name} 了嗎?",
   "embed.instructions": "要內嵌此文章,請將以下代碼貼進你的網站。",
@@ -294,6 +296,7 @@
   "status.open": "展開文章",
   "status.pin": "置頂到資料頁",
   "status.pinned": "置頂文章",
+  "status.read_more": "Read more",
   "status.reblog": "轉推",
   "status.reblog_private": "轉推到原讀者",
   "status.reblogged_by": "{name} 轉推",
@@ -311,6 +314,8 @@
   "status.show_more_all": "顯示更多這類文章",
   "status.unmute_conversation": "解禁對話",
   "status.unpin": "解除置頂",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "跨站",
   "tabs_bar.home": "主頁",
   "tabs_bar.local_timeline": "本站",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 458af6b95..6d4a9a0bb 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -91,6 +91,8 @@
   "confirmations.mute.message": "你確定要消音 {name} ?",
   "confirmations.redraft.confirm": "刪除 & 編輯",
   "confirmations.redraft.message": "你確定要刪除這條嘟文並重新編輯它嗎?所有相關的轉嘟與最愛都會被刪除,而對原始嘟文的回覆將會變成孤兒。",
+  "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.unfollow.confirm": "取消關注",
   "confirmations.unfollow.message": "真的不要繼續關注 {name} 了嗎?",
   "embed.instructions": "要內嵌此嘟文,請將以下代碼貼進你的網站。",
@@ -294,6 +296,7 @@
   "status.open": "展開嘟文",
   "status.pin": "置頂到個人資訊頁",
   "status.pinned": "置頂嘟文",
+  "status.read_more": "Read more",
   "status.reblog": "轉嘟",
   "status.reblog_private": "轉嘟給原有關注者",
   "status.reblogged_by": "{name} 轉嘟了",
@@ -311,6 +314,8 @@
   "status.show_more_all": "顯示更多這類嘟文",
   "status.unmute_conversation": "解除此對話的靜音",
   "status.unpin": "解除置頂",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "其他站點",
   "tabs_bar.home": "主頁",
   "tabs_bar.local_timeline": "本站",
diff --git a/app/javascript/mastodon/reducers/cards.js b/app/javascript/mastodon/reducers/cards.js
deleted file mode 100644
index 4d86b0d7e..000000000
--- a/app/javascript/mastodon/reducers/cards.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
-
-import { Map as ImmutableMap, fromJS } from 'immutable';
-
-const initialState = ImmutableMap();
-
-export default function cards(state = initialState, action) {
-  switch(action.type) {
-  case STATUS_CARD_FETCH_SUCCESS:
-    return state.set(action.id, fromJS(action.card));
-  default:
-    return state;
-  }
-};
diff --git a/app/javascript/mastodon/reducers/conversations.js b/app/javascript/mastodon/reducers/conversations.js
index f339abf56..ea39fccee 100644
--- a/app/javascript/mastodon/reducers/conversations.js
+++ b/app/javascript/mastodon/reducers/conversations.js
@@ -1,9 +1,12 @@
 import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 import {
+  CONVERSATIONS_MOUNT,
+  CONVERSATIONS_UNMOUNT,
   CONVERSATIONS_FETCH_REQUEST,
   CONVERSATIONS_FETCH_SUCCESS,
   CONVERSATIONS_FETCH_FAIL,
   CONVERSATIONS_UPDATE,
+  CONVERSATIONS_READ,
 } from '../actions/conversations';
 import compareId from '../compare_id';
 
@@ -11,10 +14,12 @@ const initialState = ImmutableMap({
   items: ImmutableList(),
   isLoading: false,
   hasMore: true,
+  mounted: false,
 });
 
 const conversationToMap = item => ImmutableMap({
   id: item.id,
+  unread: item.unread,
   accounts: ImmutableList(item.accounts.map(a => a.id)),
   last_status: item.last_status.id,
 });
@@ -73,6 +78,18 @@ export default function conversations(state = initialState, action) {
     return expandNormalizedConversations(state, action.conversations, action.next);
   case CONVERSATIONS_UPDATE:
     return updateConversation(state, action.conversation);
+  case CONVERSATIONS_MOUNT:
+    return state.update('mounted', count => count + 1);
+  case CONVERSATIONS_UNMOUNT:
+    return state.update('mounted', count => count - 1);
+  case CONVERSATIONS_READ:
+    return state.update('items', list => list.map(item => {
+      if (item.get('id') === action.id) {
+        return item.set('unread', false);
+      }
+
+      return item;
+    }));
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index d3b98d4f6..2c98af1db 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -14,7 +14,6 @@ import relationships from './relationships';
 import settings from './settings';
 import push_notifications from './push_notifications';
 import status_lists from './status_lists';
-import cards from './cards';
 import mutes from './mutes';
 import reports from './reports';
 import contexts from './contexts';
@@ -28,6 +27,7 @@ import lists from './lists';
 import listEditor from './list_editor';
 import filters from './filters';
 import conversations from './conversations';
+import suggestions from './suggestions';
 
 const reducers = {
   dropdown_menu,
@@ -45,7 +45,6 @@ const reducers = {
   relationships,
   settings,
   push_notifications,
-  cards,
   mutes,
   reports,
   contexts,
@@ -59,6 +58,7 @@ const reducers = {
   listEditor,
   filters,
   conversations,
+  suggestions,
 };
 
 export default combineReducers(reducers);
diff --git a/app/javascript/mastodon/reducers/suggestions.js b/app/javascript/mastodon/reducers/suggestions.js
new file mode 100644
index 000000000..9f4b89d58
--- /dev/null
+++ b/app/javascript/mastodon/reducers/suggestions.js
@@ -0,0 +1,30 @@
+import {
+  SUGGESTIONS_FETCH_REQUEST,
+  SUGGESTIONS_FETCH_SUCCESS,
+  SUGGESTIONS_FETCH_FAIL,
+  SUGGESTIONS_DISMISS,
+} from '../actions/suggestions';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+});
+
+export default function suggestionsReducer(state = initialState, action) {
+  switch(action.type) {
+  case SUGGESTIONS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case SUGGESTIONS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('items', fromJS(action.accounts.map(x => x.id)));
+      map.set('isLoading', false);
+    });
+  case SUGGESTIONS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case SUGGESTIONS_DISMISS:
+    return state.update('items', list => list.filterNot(id => id === action.id));
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index a62974ec0..9cf783c84 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -5,7 +5,6 @@ import { start } from '../mastodon/common';
 start();
 
 function main() {
-  const { length } = require('stringz');
   const IntlMessageFormat = require('intl-messageformat').default;
   const { timeAgoString } = require('../mastodon/components/relative_timestamp');
   const { delegate } = require('rails-ujs');
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index ac161a004..78bc2dbb6 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -286,20 +286,6 @@
   }
 }
 
-.flash-message {
-  box-shadow: none;
-
-  &.notice {
-    background: rgba($success-green, 0.5);
-    color: lighten($success-green, 12%);
-  }
-
-  &.alert {
-    background: rgba($error-red, 0.5);
-    color: lighten($error-red, 12%);
-  }
-}
-
 .simple_form,
 .table-form {
   .warning {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 6aabf5777..f778ba06b 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -801,7 +801,7 @@
   padding: 8px 10px;
   padding-left: 68px;
   position: relative;
-  min-height: 48px;
+  min-height: 54px;
   border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: default;
 
@@ -823,7 +823,7 @@
     margin-top: 8px;
   }
 
-  &.status-direct {
+  &.status-direct:not(.read) {
     background: lighten($ui-base-color, 8%);
     border-bottom-color: lighten($ui-base-color, 12%);
   }
@@ -1133,6 +1133,18 @@
     vertical-align: middle;
     margin-right: 5px;
   }
+
+  &-composite {
+    @include avatar-radius();
+    overflow: hidden;
+
+    & > div {
+      @include avatar-radius();
+      float: left;
+      position: relative;
+      box-sizing: border-box;
+    }
+  }
 }
 
 a .account__avatar {
@@ -1657,6 +1669,7 @@ a.account__display-name {
   padding: 4px 0;
   border-radius: 4px;
   box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
+  z-index: 9999;
 
   ul {
     list-style: none;
@@ -2548,6 +2561,9 @@ a.status-card {
   display: block;
   margin-top: 5px;
   font-size: 13px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 .status-card__image {
@@ -2572,6 +2588,31 @@ a.status-card {
   }
 }
 
+.status-card.compact {
+  border-color: lighten($ui-base-color, 4%);
+
+  &.interactive {
+    border: 0;
+  }
+
+  .status-card__content {
+    padding: 8px;
+    padding-top: 10px;
+  }
+
+  .status-card__title {
+    white-space: nowrap;
+  }
+
+  .status-card__image {
+    flex: 0 0 60px;
+  }
+}
+
+a.status-card.compact:hover {
+  background-color: lighten($ui-base-color, 4%);
+}
+
 .status-card__image-image {
   border-radius: 4px 0 0 4px;
   display: block;
@@ -5497,44 +5538,3 @@ noscript {
     }
   }
 }
-
-.conversation {
-  padding: 14px 10px;
-  border-bottom: 1px solid lighten($ui-base-color, 8%);
-  cursor: pointer;
-
-  &__header {
-    display: flex;
-    margin-bottom: 15px;
-  }
-
-  &__avatars {
-    overflow: hidden;
-    flex: 1 1 auto;
-
-    & > div {
-      display: flex;
-      flex-wrap: none;
-      width: 900px;
-    }
-
-    .account__avatar {
-      margin-right: 10px;
-    }
-  }
-
-  &__time {
-    flex: 0 0 auto;
-    font-size: 14px;
-    color: $darker-text-color;
-    text-align: right;
-
-    .display-name {
-      color: $secondary-text-color;
-    }
-  }
-
-  .attachment-list.compact {
-    margin-top: 15px;
-  }
-}
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index be2bf7cea..8c4c934ea 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -427,7 +427,7 @@ code {
 
     &__append {
       position: absolute;
-      right: 1px;
+      right: 3px;
       top: 1px;
       padding: 10px;
       padding-bottom: 9px;
@@ -460,9 +460,20 @@ code {
   border-radius: 4px;
   padding: 15px 10px;
   margin-bottom: 30px;
-  box-shadow: 0 0 5px rgba($base-shadow-color, 0.2);
   text-align: center;
 
+  &.notice {
+    border: 1px solid rgba($valid-value-color, 0.5);
+    background: rgba($valid-value-color, 0.25);
+    color: $valid-value-color;
+  }
+
+  &.alert {
+    border: 1px solid rgba($error-value-color, 0.5);
+    background: rgba($error-value-color, 0.25);
+    color: $error-value-color;
+  }
+
   p {
     margin-bottom: 15px;
   }
@@ -551,12 +562,12 @@ code {
 .oauth-prompt,
 .follow-prompt {
   margin-bottom: 30px;
-  text-align: center;
   color: $darker-text-color;
 
   h2 {
     font-size: 16px;
     margin-bottom: 30px;
+    text-align: center;
   }
 
   strong {
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 9644f8e02..176fb5ce0 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -73,7 +73,7 @@ body.rtl {
     float: left;
   }
 
-  .setting-toggle {
+  .setting-toggle__label {
     margin-left: 0;
     margin-right: 8px;
   }
@@ -130,17 +130,6 @@ body.rtl {
     float: left;
   }
 
-  .activity-stream .detailed-status.light .detailed-status__display-name > div {
-    float: right;
-    margin-right: 0;
-    margin-left: 10px;
-  }
-
-  .activity-stream .detailed-status.light .detailed-status__meta span > span {
-    margin-left: 0;
-    margin-right: 6px;
-  }
-
   .status__action-bar {
 
     &__counter {
@@ -174,6 +163,10 @@ body.rtl {
     margin-right: 0;
   }
 
+  .detailed-status__display-name .display-name {
+    text-align: right;
+  }
+
   .detailed-status__display-avatar {
     margin-right: 0;
     margin-left: 10px;
@@ -196,6 +189,10 @@ body.rtl {
     right: -2.14285714em;
   }
 
+  .admin-wrapper {
+    direction: rtl;
+  }
+
   .admin-wrapper .sidebar ul a i.fa,
   a.table-action-link i.fa {
     margin-right: 0;
@@ -214,22 +211,47 @@ body.rtl {
     right: 0;
   }
 
+  .simple_form .input.radio_buttons .radio {
+    left: auto;
+    right: 0;
+  }
+
+  .simple_form .input.radio_buttons .radio > label {
+    padding-right: 28px;
+    padding-left: 0;
+  }
+
   .simple_form .input-with-append .input input {
     padding-left: 142px;
     padding-right: 0;
   }
 
-  .simple_form .input-with-append .append {
+  .simple_form .input.boolean label.checkbox {
+    left: auto;
+    right: 0;
+  }
+
+  .simple_form .input.boolean .label_input,
+  .simple_form .input.boolean .hint {
+    padding-left: 0;
+    padding-right: 28px;
+  }
+
+  .simple_form .label_input__append {
     right: auto;
-    left: 0;
+    left: 3px;
 
     &::after {
       right: auto;
       left: 0;
-      background-image: linear-gradient(to left, rgba($ui-base-color, 0), $ui-base-color);
+      background-image: linear-gradient(to left, rgba(darken($ui-base-color, 10%), 0), darken($ui-base-color, 10%));
     }
   }
 
+  .simple_form select {
+    background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>") no-repeat left 8px center / auto 16px;
+  }
+
   .table th,
   .table td {
     text-align: right;
@@ -245,6 +267,10 @@ body.rtl {
     left: auto;
   }
 
+  .landing-page__call-to-action .row__information-board {
+    direction: rtl;
+  }
+
   .landing-page .header .hero .floats .float-1 {
     left: -120px;
     right: auto;
@@ -308,4 +334,40 @@ body.rtl {
       margin-right: 20px;
     }
   }
+
+  .landing-page__information {
+    .account__display-name {
+      margin-right: 0;
+      margin-left: 5px;
+    }
+
+    .account__avatar-wrapper {
+      margin-left: 12px;
+      margin-right: 0;
+    }
+  }
+
+  .card__bar .display-name {
+    margin-left: 0;
+    margin-right: 15px;
+    text-align: right;
+  }
+
+  .fa-chevron-left::before {
+    content: "\F054";
+  }
+
+  .fa-chevron-right::before {
+    content: "\F053";
+  }
+
+  .column-back-button__icon {
+    margin-right: 0;
+    margin-left: 5px;
+  }
+
+  .column-header__setting-arrows .column-header__setting-btn:last-child {
+    padding-left: 0;
+    padding-right: 10px;
+  }
 }
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss
index 14306c8bd..d8bd30377 100644
--- a/app/javascript/styles/mastodon/stream_entries.scss
+++ b/app/javascript/styles/mastodon/stream_entries.scss
@@ -3,7 +3,6 @@
   border-radius: 4px;
   overflow: hidden;
   margin-bottom: 10px;
-  text-align: left;
 
   @media screen and (max-width: $no-gap-breakpoint) {
     margin-bottom: 0;
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 3a39b723e..999954cb5 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -96,7 +96,7 @@ class ActivityPub::Activity
   end
 
   def notify_about_mentions(status)
-    status.mentions.includes(:account).each do |mention|
+    status.active_mentions.includes(:account).each do |mention|
       next unless mention.account.local? && audience_includes?(mention.account)
       NotifyService.new.call(mention.account, mention)
     end
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 978289788..baa05e14c 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -2,7 +2,7 @@
 
 class ActivityPub::Activity::Create < ActivityPub::Activity
   SUPPORTED_TYPES = %w(Note).freeze
-  CONVERTED_TYPES = %w(Image Video Article).freeze
+  CONVERTED_TYPES = %w(Image Video Article Page).freeze
 
   def perform
     return if delete_arrived_first?(object_uri) || unsupported_object_type? || invalid_origin?(@object['id'])
@@ -22,12 +22,17 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   private
 
   def process_status
-    status_params = process_status_params
+    @tags     = []
+    @mentions = []
+    @params   = {}
 
-    ApplicationRecord.transaction do
-      @status = Status.create!(status_params)
+    process_status_params
+    process_tags
+    process_audience
 
-      process_tags(@status)
+    ApplicationRecord.transaction do
+      @status = Status.create!(@params)
+      attach_tags(@status)
     end
 
     resolve_thread(@status)
@@ -42,62 +47,109 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def process_status_params
-    {
-      uri: @object['id'],
-      url: object_url || @object['id'],
-      account: @account,
-      text: text_from_content || '',
-      language: detected_language,
-      spoiler_text: text_from_summary || '',
-      created_at: @object['published'],
-      override_timestamps: @options[:override_timestamps],
-      reply: @object['inReplyTo'].present?,
-      sensitive: @object['sensitive'] || false,
-      visibility: visibility_from_audience,
-      thread: replied_to_status,
-      conversation: conversation_from_uri(@object['conversation']),
-      media_attachment_ids: process_attachments.take(4).map(&:id),
-    }
-  end
-
-  def process_tags(status)
+    @params = begin
+      {
+        uri: @object['id'],
+        url: object_url || @object['id'],
+        account: @account,
+        text: text_from_content || '',
+        language: detected_language,
+        spoiler_text: text_from_summary || '',
+        created_at: @object['published'],
+        override_timestamps: @options[:override_timestamps],
+        reply: @object['inReplyTo'].present?,
+        sensitive: @object['sensitive'] || false,
+        visibility: visibility_from_audience,
+        thread: replied_to_status,
+        conversation: conversation_from_uri(@object['conversation']),
+        media_attachment_ids: process_attachments.take(4).map(&:id),
+      }
+    end
+  end
+
+  def process_audience
+    (as_array(@object['to']) + as_array(@object['cc'])).uniq.each do |audience|
+      next if audience == ActivityPub::TagManager::COLLECTIONS[:public]
+
+      # Unlike with tags, there is no point in resolving accounts we don't already
+      # know here, because silent mentions would only be used for local access
+      # control anyway
+      account = account_from_uri(audience)
+
+      next if account.nil? || @mentions.any? { |mention| mention.account_id == account.id }
+
+      @mentions << Mention.new(account: account, silent: true)
+
+      # If there is at least one silent mention, then the status can be considered
+      # as a limited-audience status, and not strictly a direct message, but only
+      # if we considered a direct message in the first place
+      next unless @params[:visibility] == :direct
+
+      @params[:visibility] = :limited
+    end
+
+    # If the payload was delivered to a specific inbox, the inbox owner must have
+    # access to it, unless they already have access to it anyway
+    return if @options[:delivered_to_account_id].nil? || @mentions.any? { |mention| mention.account_id == @options[:delivered_to_account_id] }
+
+    @mentions << Mention.new(account_id: @options[:delivered_to_account_id], silent: true)
+
+    return unless @params[:visibility] == :direct
+
+    @params[:visibility] = :limited
+  end
+
+  def attach_tags(status)
+    @tags.each do |tag|
+      status.tags << tag
+      TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility?
+    end
+
+    @mentions.each do |mention|
+      mention.status = status
+      mention.save
+    end
+  end
+
+  def process_tags
     return if @object['tag'].nil?
 
     as_array(@object['tag']).each do |tag|
       if equals_or_includes?(tag['type'], 'Hashtag')
-        process_hashtag tag, status
+        process_hashtag tag
       elsif equals_or_includes?(tag['type'], 'Mention')
-        process_mention tag, status
+        process_mention tag
       elsif equals_or_includes?(tag['type'], 'Emoji')
-        process_emoji tag, status
+        process_emoji tag
       end
     end
   end
 
-  def process_hashtag(tag, status)
+  def process_hashtag(tag)
     return if tag['name'].blank?
 
     hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase
-    hashtag = Tag.where(name: hashtag).first_or_create(name: hashtag)
+    hashtag = Tag.where(name: hashtag).first_or_create!(name: hashtag)
 
-    return if status.tags.include?(hashtag)
+    return if @tags.include?(hashtag)
 
-    status.tags << hashtag
-    TrendingTags.record_use!(hashtag, status.account, status.created_at) if status.public_visibility?
+    @tags << hashtag
   rescue ActiveRecord::RecordInvalid
     nil
   end
 
-  def process_mention(tag, status)
+  def process_mention(tag)
     return if tag['href'].blank?
 
     account = account_from_uri(tag['href'])
     account = ::FetchRemoteAccountService.new.call(tag['href'], id: false) if account.nil?
+
     return if account.nil?
-    account.mentions.create(status: status)
+
+    @mentions << Mention.new(account: account, silent: false)
   end
 
-  def process_emoji(tag, _status)
+  def process_emoji(tag)
     return if skip_download?
     return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank?
 
diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb
index 36d3c5730..92e59bb81 100644
--- a/app/lib/activitypub/activity/flag.rb
+++ b/app/lib/activitypub/activity/flag.rb
@@ -2,6 +2,8 @@
 
 class ActivityPub::Activity::Flag < ActivityPub::Activity
   def perform
+    return if skip_reports?
+
     target_accounts            = object_uris.map { |uri| account_from_uri(uri) }.compact.select(&:local?)
     target_statuses_by_account = object_uris.map { |uri| status_from_uri(uri) }.compact.select(&:local?).group_by(&:account_id)
 
@@ -19,6 +21,12 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
     end
   end
 
+  private
+
+  def skip_reports?
+    DomainBlock.find_by(domain: @account.domain)&.reject_reports?
+  end
+
   def object_uris
     @object_uris ||= Array(@object.is_a?(Array) ? @object.map { |item| value_or_id(item) } : value_or_id(@object))
   end
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index 95d1cf9f3..be3a562d0 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -58,8 +58,8 @@ class ActivityPub::TagManager
       [COLLECTIONS[:public]]
     when 'unlisted', 'private'
       [account_followers_url(status.account)]
-    when 'direct'
-      status.mentions.map { |mention| uri_for(mention.account) }
+    when 'direct', 'limited'
+      status.active_mentions.map { |mention| uri_for(mention.account) }
     end
   end
 
@@ -80,7 +80,7 @@ class ActivityPub::TagManager
       cc << COLLECTIONS[:public]
     end
 
-    cc.concat(status.mentions.map { |mention| uri_for(mention.account) }) unless status.direct_visibility?
+    cc.concat(status.active_mentions.map { |mention| uri_for(mention.account) }) unless status.direct_visibility? || status.limited_visibility?
 
     cc
   end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index b10e5dd24..3d7db2721 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -88,7 +88,7 @@ class FeedManager
     end
 
     query.each do |status|
-      next if status.direct_visibility? || filter?(:home, status, into_account)
+      next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
       add_to_feed(:home, into_account.id, status)
     end
 
@@ -156,12 +156,12 @@ class FeedManager
     return true  if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
     return true  if phrase_filtered?(status, receiver_id, :home)
 
-    check_for_blocks = status.mentions.pluck(:account_id)
+    check_for_blocks = status.active_mentions.pluck(:account_id)
     check_for_blocks.concat([status.account_id])
 
     if status.reblog?
       check_for_blocks.concat([status.reblog.account_id])
-      check_for_blocks.concat(status.reblog.mentions.pluck(:account_id))
+      check_for_blocks.concat(status.reblog.active_mentions.pluck(:account_id))
     end
 
     return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home)
@@ -188,7 +188,7 @@ class FeedManager
     # This filter is called from NotifyService, but already after the sender of
     # the notification has been checked for mute/block. Therefore, it's not
     # necessary to check the author of the toot for mute/block again
-    check_for_blocks = status.mentions.pluck(:account_id)
+    check_for_blocks = status.active_mentions.pluck(:account_id)
     check_for_blocks.concat([status.in_reply_to_account]) if status.reply? && !status.in_reply_to_account_id.nil?
 
     should_filter   = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions)                                                         # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted)
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 35d5a09b7..d13884ec8 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -27,7 +27,7 @@ class Formatter
       return html.html_safe # rubocop:disable Rails/OutputSafety
     end
 
-    linkable_accounts = status.mentions.map(&:account)
+    linkable_accounts = status.active_mentions.map(&:account)
     linkable_accounts << status.account
 
     html = raw_content
diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb
index 1a0a635b3..7a181fb40 100644
--- a/app/lib/ostatus/atom_serializer.rb
+++ b/app/lib/ostatus/atom_serializer.rb
@@ -354,7 +354,7 @@ class OStatus::AtomSerializer
     append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text?
     append_element(entry, 'content', Formatter.instance.format(status).to_str || '.', type: 'html', 'xml:lang': status.language)
 
-    status.mentions.sort_by(&:id).each do |mentioned|
+    status.active_mentions.sort_by(&:id).each do |mentioned|
       append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account))
     end
 
diff --git a/app/models/account.rb b/app/models/account.rb
index 1ca27f636..bd3dc9c96 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -59,7 +59,9 @@ class Account < ApplicationRecord
   include Attachmentable
   include Paginable
 
-  MAX_NOTE_LENGTH = 500
+  MAX_DISPLAY_NAME_LENGTH = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
+  MAX_NOTE_LENGTH = (ENV['MAX_BIO_CHARS'] || 500).to_i
+  MAX_FIELDS = (ENV['MAX_PROFILE_FIELDS'] || 4).to_i
 
   enum protocol: [:ostatus, :activitypub]
 
@@ -76,9 +78,9 @@ class Account < ApplicationRecord
   validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
   validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
-  validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
+  validates :display_name, length: { maximum: MAX_DISPLAY_NAME_LENGTH }, if: -> { local? && will_save_change_to_display_name? }
   validate :note_length_does_not_exceed_length_limit, if: -> { local? && will_save_change_to_note? }
-  validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
+  validates :fields, length: { maximum: MAX_FIELDS }, if: -> { local? && will_save_change_to_fields? }
 
   # Timelines
   has_many :stream_entries, inverse_of: :account, dependent: :destroy
@@ -246,14 +248,12 @@ class Account < ApplicationRecord
     self[:fields] = fields
   end
 
-  DEFAULT_FIELDS_SIZE = 4
-
   def build_fields
-    return if fields.size >= DEFAULT_FIELDS_SIZE
+    return if fields.size >= MAX_FIELDS
 
     tmp = self[:fields] || []
 
-    (DEFAULT_FIELDS_SIZE - tmp.size).times do
+    (MAX_FIELDS - tmp.size).times do
       tmp << { name: '', value: '' }
     end
 
diff --git a/app/models/account_conversation.rb b/app/models/account_conversation.rb
index a7205ec1a..cc6b39279 100644
--- a/app/models/account_conversation.rb
+++ b/app/models/account_conversation.rb
@@ -10,6 +10,7 @@
 #  status_ids              :bigint(8)        default([]), not null, is an Array
 #  last_status_id          :bigint(8)
 #  lock_version            :integer          default(0), not null
+#  unread                  :boolean          default(FALSE), not null
 #
 
 class AccountConversation < ApplicationRecord
@@ -57,7 +58,11 @@ class AccountConversation < ApplicationRecord
 
     def add_status(recipient, status)
       conversation = find_or_initialize_by(account: recipient, conversation_id: status.conversation_id, participant_account_ids: participants_from_status(recipient, status))
+
+      return conversation if conversation.status_ids.include?(status.id)
+
       conversation.status_ids << status.id
+      conversation.unread = status.account_id != recipient.id
       conversation.save
       conversation
     rescue ActiveRecord::StaleObjectError
@@ -85,7 +90,7 @@ class AccountConversation < ApplicationRecord
     private
 
     def participants_from_status(recipient, status)
-      ((status.mentions.pluck(:account_id) + [status.account_id]).uniq - [recipient.id]).sort
+      ((status.active_mentions.pluck(:account_id) + [status.account_id]).uniq - [recipient.id]).sort
     end
   end
 
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 93658793b..b828a9d71 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -3,12 +3,13 @@
 #
 # Table name: domain_blocks
 #
-#  id           :bigint(8)        not null, primary key
-#  domain       :string           default(""), not null
-#  created_at   :datetime         not null
-#  updated_at   :datetime         not null
-#  severity     :integer          default("silence")
-#  reject_media :boolean          default(FALSE), not null
+#  id             :bigint(8)        not null, primary key
+#  domain         :string           default(""), not null
+#  created_at     :datetime         not null
+#  updated_at     :datetime         not null
+#  severity       :integer          default("silence")
+#  reject_media   :boolean          default(FALSE), not null
+#  reject_reports :boolean          default(FALSE), not null
 #
 
 class DomainBlock < ApplicationRecord
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 3e3cbdaed..0f787ebc4 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -148,6 +148,7 @@ class MediaAttachment < ApplicationRecord
     "#{x},#{y}"
   end
 
+  after_commit :reset_parent_cache, on: :update
   before_create :prepare_description, unless: :local?
   before_create :set_shortcode
   before_post_process :set_type_and_extension
@@ -252,4 +253,9 @@ class MediaAttachment < ApplicationRecord
       bitrate: movie.bitrate,
     }
   end
+
+  def reset_parent_cache
+    return if status_id.nil?
+    Rails.cache.delete("statuses/#{status_id}")
+  end
 end
diff --git a/app/models/mention.rb b/app/models/mention.rb
index 8ab886b18..d01a88e32 100644
--- a/app/models/mention.rb
+++ b/app/models/mention.rb
@@ -8,6 +8,7 @@
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #  account_id :bigint(8)
+#  silent     :boolean          default(FALSE), not null
 #
 
 class Mention < ApplicationRecord
@@ -18,10 +19,17 @@ class Mention < ApplicationRecord
 
   validates :account, uniqueness: { scope: :status }
 
+  scope :active, -> { where(silent: false) }
+  scope :silent, -> { where(silent: true) }
+
   delegate(
     :username,
     :acct,
     to: :account,
     prefix: true
   )
+
+  def active?
+    !silent?
+  end
 end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index b9bec0808..78b180301 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -24,7 +24,7 @@ class Notification < ApplicationRecord
     favourite:      'Favourite',
   }.freeze
 
-  STATUS_INCLUDES = [:account, :application, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :application, :media_attachments, :tags, mentions: :account]].freeze
+  STATUS_INCLUDES = [:account, :application, :media_attachments, :tags, active_mentions: :account, reblog: [:account, :application, :media_attachments, :tags, active_mentions: :account]].freeze
 
   belongs_to :account, optional: true
   belongs_to :from_account, class_name: 'Account', optional: true
diff --git a/app/models/status.rb b/app/models/status.rb
index ad25cc8df..f67a05b3c 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -39,7 +39,7 @@ class Status < ApplicationRecord
 
   update_index('statuses#status', :proper) if Chewy.enabled?
 
-  enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
+  enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility
 
   belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
 
@@ -54,7 +54,8 @@ class Status < ApplicationRecord
   has_many :bookmarks, inverse_of: :status, dependent: :destroy
   has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
   has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :thread
-  has_many :mentions, dependent: :destroy
+  has_many :mentions, dependent: :destroy, inverse_of: :status
+  has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
   has_many :media_attachments, dependent: :nullify
 
   has_and_belongs_to_many :tags
@@ -93,17 +94,19 @@ class Status < ApplicationRecord
                    :conversation,
                    :status_stat,
                    :tags,
+                   :preview_cards,
                    :stream_entry,
-                   mentions: :account,
+                   active_mentions: :account,
                    reblog: [
                      :account,
                      :application,
                      :stream_entry,
                      :tags,
+                     :preview_cards,
                      :media_attachments,
                      :conversation,
                      :status_stat,
-                     mentions: :account,
+                     active_mentions: :account,
                    ],
                    thread: :account
 
@@ -167,6 +170,10 @@ class Status < ApplicationRecord
     reblog
   end
 
+  def preview_card
+    preview_cards.first
+  end
+
   def title
     if destroyed?
       "#{account.acct} deleted status"
@@ -176,7 +183,11 @@ class Status < ApplicationRecord
   end
 
   def hidden?
-    private_visibility? || direct_visibility?
+    private_visibility? || direct_visibility? || limited_visibility?
+  end
+
+  def distributable?
+    public_visibility? || unlisted_visibility?
   end
 
   def with_media?
@@ -236,8 +247,8 @@ class Status < ApplicationRecord
   before_validation :set_local
 
   class << self
-    def cache_ids
-      left_outer_joins(:status_stat).select('statuses.id, greatest(statuses.updated_at, status_stats.updated_at) AS updated_at')
+    def selectable_visibilities
+      visibilities.keys - %w(direct limited)
     end
 
     def in_chosen_languages(account)
diff --git a/app/models/status_stat.rb b/app/models/status_stat.rb
index 9d358776b..024c467e7 100644
--- a/app/models/status_stat.rb
+++ b/app/models/status_stat.rb
@@ -14,4 +14,12 @@
 
 class StatusStat < ApplicationRecord
   belongs_to :status, inverse_of: :status_stat
+
+  after_commit :reset_parent_cache
+
+  private
+
+  def reset_parent_cache
+    Rails.cache.delete("statuses/#{status_id}")
+  end
 end
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
index dd383eb81..edd30487e 100644
--- a/app/models/stream_entry.rb
+++ b/app/models/stream_entry.rb
@@ -48,7 +48,7 @@ class StreamEntry < ApplicationRecord
   end
 
   def mentions
-    orphaned? ? [] : status.mentions.map(&:account)
+    orphaned? ? [] : status.active_mentions.map(&:account)
   end
 
   private
diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb
index fcf19db62..5e3282681 100644
--- a/app/policies/status_policy.rb
+++ b/app/policies/status_policy.rb
@@ -14,7 +14,7 @@ class StatusPolicy < ApplicationPolicy
   def show?
     return false if local_only? && (current_account.nil? || !current_account.local?)
 
-    if direct?
+    if requires_mention?
       owned? || mention_exists?
     elsif private?
       owned? || following_author? || mention_exists?
@@ -24,7 +24,7 @@ class StatusPolicy < ApplicationPolicy
   end
 
   def reblog?
-    !direct? && (!private? || owned?) && show? && !blocking_author?
+    !requires_mention? && (!private? || owned?) && show? && !blocking_author?
   end
 
   def favourite?
@@ -43,8 +43,8 @@ class StatusPolicy < ApplicationPolicy
 
   private
 
-  def direct?
-    record.direct_visibility?
+  def requires_mention?
+    record.direct_visibility? || record.limited_visibility?
   end
 
   def owned?
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 82b7ffe95..c9d23e25f 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -68,7 +68,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
   end
 
   def virtual_tags
-    object.mentions.to_a.sort_by(&:id) + object.tags + object.emojis
+    object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis
   end
 
   def atom_uri
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index ac08a3f1e..5b88cb9a8 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -21,6 +21,7 @@ class InitialStateSerializer < ActiveModel::Serializer
       search_enabled: Chewy.enabled?,
       version: Mastodon::Version.to_s,
       invites_enabled: Setting.min_invite_role == 'user',
+      mascot: instance_presenter.mascot&.file&.url,
     }
 
     if object.current_account
@@ -63,4 +64,10 @@ class InitialStateSerializer < ActiveModel::Serializer
   def media_attachments
     { accept_content_types: MediaAttachment::IMAGE_FILE_EXTENSIONS + MediaAttachment::VIDEO_FILE_EXTENSIONS + MediaAttachment::AUDIO_FILE_EXTENSIONS + MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES }
   end
+
+  private
+
+  def instance_presenter
+    @instance_presenter ||= InstancePresenter.new
+  end
 end
diff --git a/app/serializers/rest/conversation_serializer.rb b/app/serializers/rest/conversation_serializer.rb
index 08cea47d2..b09ca6341 100644
--- a/app/serializers/rest/conversation_serializer.rb
+++ b/app/serializers/rest/conversation_serializer.rb
@@ -1,7 +1,12 @@
 # frozen_string_literal: true
 
 class REST::ConversationSerializer < ActiveModel::Serializer
-  attribute :id
+  attributes :id, :unread
+
   has_many :participant_accounts, key: :accounts, serializer: REST::AccountSerializer
   has_one :last_status, serializer: REST::StatusSerializer
+
+  def id
+    object.id.to_s
+  end
 end
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 89776b1fc..8a61c1056 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -21,6 +21,8 @@ class REST::StatusSerializer < ActiveModel::Serializer
   has_many :tags
   has_many :emojis, serializer: REST::CustomEmojiSerializer
 
+  has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
+
   def id
     object.id.to_s
   end
@@ -37,6 +39,17 @@ class REST::StatusSerializer < ActiveModel::Serializer
     !current_user.nil?
   end
 
+  def visibility
+    # This visibility is masked behind "private"
+    # to avoid API changes because there are no
+    # UX differences
+    if object.limited_visibility?
+      'private'
+    else
+      object.visibility
+    end
+  end
+
   def uri
     OStatus::TagManager.instance.uri_for(object)
   end
@@ -97,7 +110,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
   end
 
   def ordered_mentions
-    object.mentions.to_a.sort_by(&:id)
+    object.active_mentions.to_a.sort_by(&:id)
   end
 
   class ApplicationSerializer < ActiveModel::Serializer
diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb
index 1ec9ee5dd..8430d12d5 100644
--- a/app/services/activitypub/fetch_remote_account_service.rb
+++ b/app/services/activitypub/fetch_remote_account_service.rb
@@ -5,9 +5,10 @@ class ActivityPub::FetchRemoteAccountService < BaseService
 
   SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
 
-  # Should be called when uri has already been checked for locality
   # Does a WebFinger roundtrip on each call
   def call(uri, id: true, prefetched_body: nil, break_on_redirect: false)
+    return ActivityPub::TagManager.instance.uri_to_resource(uri, Account) if ActivityPub::TagManager.instance.local_uri?(uri)
+
     @json = if prefetched_body.nil?
               fetch_resource(uri, id)
             else
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index ebb4034aa..b7296cf7c 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -12,7 +12,7 @@ class BatchedRemoveStatusService < BaseService
   def call(statuses)
     statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a }
 
-    @mentions = statuses.map { |s| [s.id, s.mentions.includes(:account).to_a] }.to_h
+    @mentions = statuses.map { |s| [s.id, s.active_mentions.includes(:account).to_a] }.to_h
     @tags     = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h
 
     @stream_entry_batches  = []
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index ab520276b..63cab8403 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -14,6 +14,8 @@ class FanOutOnWriteService < BaseService
       deliver_to_mentioned_followers(status)
       deliver_to_direct_timelines(status)
       deliver_to_own_conversation(status)
+    elsif status.limited_visibility?
+      deliver_to_mentioned_followers(status)
     else
       deliver_to_followers(status)
       deliver_to_lists(status)
@@ -57,7 +59,7 @@ class FanOutOnWriteService < BaseService
   end
 
   def deliver_to_mentioned_followers(status)
-    Rails.logger.debug "Delivering status #{status.id} to mentioned followers"
+    Rails.logger.debug "Delivering status #{status.id} to limited followers"
 
     status.mentions.includes(:account).each do |mention|
       mentioned_account = mention.account
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 4169c685b..462e5ee13 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -17,7 +17,8 @@ class FetchLinkCardService < BaseService
 
     return if @url.nil? || @status.preview_cards.any?
 
-    @url = @url.to_s
+    @mentions = status.mentions
+    @url      = @url.to_s
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
@@ -62,6 +63,7 @@ class FetchLinkCardService < BaseService
 
   def attach_card
     @status.preview_cards << @card
+    Rails.cache.delete(@status)
   end
 
   def parse_urls
@@ -81,9 +83,16 @@ class FetchLinkCardService < BaseService
     uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme)
   end
 
+  def mention_link?(a)
+    return false if @mentions.nil?
+    @mentions.any? do |mention|
+      a['href'] == TagManager.instance.url_for(mention.target)
+    end
+  end
+
   def skip_link?(a)
     # Avoid links for hashtags and mentions (microformats)
-    a['rel']&.include?('tag') || a['class']&.include?('u-url')
+    a['rel']&.include?('tag') || a['class']&.include?('u-url') || mention_link?(a)
   end
 
   def attempt_oembed
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 63bf8f17a..49022a844 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -59,9 +59,14 @@ class NotifyService < BaseService
     @notification.target_status.in_reply_to_account_id == @recipient.id && @notification.target_status.thread&.direct_visibility?
   end
 
+  def from_staff?
+    @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user.staff?
+  end
+
   def optional_non_following_and_direct?
     direct_message? &&
       @recipient.user.settings.interactions['must_be_following_dm'] &&
+      !from_staff? &&
       !following_sender? &&
       !response_to_recipient?
   end
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 52d49a69e..d7d7770e9 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -21,7 +21,10 @@ class PostStatusService < BaseService
 
     media  = validate_media!(options[:media_ids])
     status = nil
-    text   = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
+    if text.blank? && options[:spoiler_text].present?
+     text = '.'
+     text = media.find(&:video?) ? '📹' : '🖼' if media.size > 0
+    end
 
     ApplicationRecord.transaction do
       status = account.statuses.create!(text: text,
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 1a53093b8..4bee86c8a 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -8,7 +8,7 @@ class RemoveStatusService < BaseService
     @status       = status
     @account      = status.account
     @tags         = status.tags.pluck(:name).to_a
-    @mentions     = status.mentions.includes(:account).to_a
+    @mentions     = status.active_mentions.includes(:account).to_a
     @reblogs      = status.reblogs.to_a
     @stream_entry = status.stream_entry
     @options      = options
diff --git a/app/services/verify_link_service.rb b/app/services/verify_link_service.rb
index 3453b54c5..9f56249c7 100644
--- a/app/services/verify_link_service.rb
+++ b/app/services/verify_link_service.rb
@@ -25,7 +25,7 @@ class VerifyLinkService < BaseService
   end
 
   def link_back_present?
-    return false if @body.empty?
+    return false if @body.blank?
 
     links = Nokogiri::HTML(@body).xpath('//a[contains(concat(" ", normalize-space(@rel), " "), " me ")]|//link[contains(concat(" ", normalize-space(@rel), " "), " me ")]')
 
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index f2c53e3fe..17f1f224d 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -106,7 +106,7 @@
       - if @account.user&.otp_required_for_login?
         = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
       - unless @account.memorial?
-        = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:memorialize, @account)
+        = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
     - else
       = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
 
@@ -114,7 +114,7 @@
     - if @account.silenced?
       = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' if can?(:unsilence, @account)
     - else
-      = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' if can?(:silence, @account)
+      = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button button--destructive' if can?(:silence, @account)
 
     - if @account.local?
       - unless @account.user_confirmed?
@@ -123,7 +123,7 @@
     - if @account.suspended?
       = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' if can?(:unsuspend, @account)
     - else
-      = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_suspension_path(@account.id), class: 'button' if can?(:suspend, @account)
+      = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_suspension_path(@account.id), class: 'button button--destructive' if can?(:suspend, @account)
 
 - if !@account.local? && @account.hub_url.present?
   %hr.spacer/
diff --git a/app/views/admin/domain_blocks/_domain_block.html.haml b/app/views/admin/domain_blocks/_domain_block.html.haml
index 17a3de81c..7bfea3574 100644
--- a/app/views/admin/domain_blocks/_domain_block.html.haml
+++ b/app/views/admin/domain_blocks/_domain_block.html.haml
@@ -1,10 +1,13 @@
 %tr
-  %td.domain
+  %td
     %samp= domain_block.domain
   %td.severity
     = t("admin.domain_blocks.severities.#{domain_block.severity}")
   %td.reject_media
     - if domain_block.reject_media? || domain_block.suspend?
       %i.fa.fa-check
+  %td.reject_reports
+    - if domain_block.reject_reports? || domain_block.suspend?
+      %i.fa.fa-check
   %td
     = table_link_to 'undo', t('admin.domain_blocks.undo'), admin_domain_block_path(domain_block)
diff --git a/app/views/admin/domain_blocks/index.html.haml b/app/views/admin/domain_blocks/index.html.haml
index 42b8ca53f..4c5221c42 100644
--- a/app/views/admin/domain_blocks/index.html.haml
+++ b/app/views/admin/domain_blocks/index.html.haml
@@ -8,6 +8,7 @@
         %th= t('admin.domain_blocks.domain')
         %th= t('admin.domain_blocks.severity')
         %th= t('admin.domain_blocks.reject_media')
+        %th= t('admin.domain_blocks.reject_reports')
         %th
     %tbody
       = render @domain_blocks
diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml
index 6e514f833..3a4963489 100644
--- a/app/views/admin/domain_blocks/new.html.haml
+++ b/app/views/admin/domain_blocks/new.html.haml
@@ -14,5 +14,8 @@
   .fields-group
     = f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
 
+  .fields-group
+    = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
+
   .actions
     = f.button :button, t('.create'), type: :submit
diff --git a/app/views/admin/email_domain_blocks/_email_domain_block.html.haml b/app/views/admin/email_domain_blocks/_email_domain_block.html.haml
index 61cff9395..bf66c9001 100644
--- a/app/views/admin/email_domain_blocks/_email_domain_block.html.haml
+++ b/app/views/admin/email_domain_blocks/_email_domain_block.html.haml
@@ -1,5 +1,5 @@
 %tr
-  %td.domain
+  %td
     %samp= email_domain_block.domain
   %td
     = table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(email_domain_block), method: :delete
diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml
index 6efbbbe60..e36ebae47 100644
--- a/app/views/admin/instances/_instance.html.haml
+++ b/app/views/admin/instances/_instance.html.haml
@@ -1,5 +1,5 @@
 %tr
-  %td.domain
+  %td
     = link_to instance.domain, admin_accounts_path(by_domain: instance.domain)
   %td.count
     = instance.accounts_count
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index ae0bf81f8..133ca4eca 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -4,8 +4,10 @@
 %div{ style: 'overflow: hidden; margin-bottom: 20px' }
   - if @report.unresolved?
     %div{ style: 'float: right' }
-      = link_to t('admin.reports.silence_account'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button'
-      = link_to t('admin.reports.suspend_account'), new_admin_account_suspension_path(@report.target_account_id, report_id: @report.id), class: 'button'
+      - if @report.target_account.local?
+        = link_to t('admin.accounts.disable'), admin_report_path(@report, outcome: 'disable'), method: :put, class: 'button button--destructive'
+      = link_to t('admin.accounts.silence'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button button--destructive'
+      = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_suspension_path(@report.target_account_id, report_id: @report.id), class: 'button button--destructive'
     %div{ style: 'float: left' }
       = link_to t('admin.reports.mark_as_resolved'), admin_report_path(@report, outcome: 'resolve'), method: :put, class: 'button'
   - else
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index b82555534..7c378bf73 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -26,6 +26,8 @@
       = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: t('admin.settings.thumbnail.desc_html')
     .fields-row__column.fields-row__column-6.fields-group
       = f.input :hero, as: :file, wrapper: :with_block_label, label: t('admin.settings.hero.title'), hint: t('admin.settings.hero.desc_html')
+
+  .fields-row
     .fields-row__column.fields-row__column-6.fields-group
       = f.input :mascot, as: :file, wrapper: :with_block_label, label: t('admin.settings.mascot.title'), hint: t('admin.settings.mascot.desc_html')
 
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 66382db50..0e52702dc 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -14,4 +14,4 @@
 
         = yield
 
-= render template: 'layouts/application', locals: { body_classes: 'admin' }
+= render template: 'layouts/application'
diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml
index 2443e3410..33e3714f8 100644
--- a/app/views/layouts/embedded.html.haml
+++ b/app/views/layouts/embedded.html.haml
@@ -4,6 +4,12 @@
     %meta{ charset: 'utf-8' }/
     %meta{ name: 'robots', content: 'noindex' }/
 
+    - if cdn_host?
+      %link{ rel: 'dns-prefetch', href: cdn_host }/
+
+    - if storage_host?
+      %link{ rel: 'dns-prefetch', href: storage_host }/
+
     = javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous'
     - if @theme
       - if @theme[:supported_locales].include? I18n.locale.to_s
@@ -12,4 +18,6 @@
         = javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
     = render partial: 'layouts/theme', object: @core
     = render partial: 'layouts/theme', object: @theme
+
+  %body.embed
     = yield
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index 751a6e50b..45f9fd178 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -22,7 +22,7 @@
   %hr#settings_publishing/
 
   .fields-group
-    = f.input :setting_default_privacy, collection: Status.visibilities.keys - ['direct'], wrapper: :with_floating_label, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), content_tag(:span, I18n.t("statuses.visibilities.#{visibility}_long"), class: 'hint')]) }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+    = f.input :setting_default_privacy, collection: Status.selectable_visibilities, wrapper: :with_floating_label, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), content_tag(:span, I18n.t("statuses.visibilities.#{visibility}_long"), class: 'hint')]) }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 
     = f.input :setting_default_sensitive, as: :boolean, wrapper: :with_label
 
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 6b61fa9c9..6c4a8fdfb 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -6,8 +6,8 @@
 
   .fields-row
     .fields-row__column.fields-group.fields-row__column-6
-      = f.input :display_name, wrapper: :with_label, hint: t('simple_form.hints.defaults.display_name', count: 30 - @account.display_name.size).html_safe
-      = f.input :note, wrapper: :with_label, hint: t('simple_form.hints.defaults.note', count: 500 - @account.note.size).html_safe
+      = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH }, hint: false
+      = f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false
 
   .fields-row
     .fields-row__column.fields-row__column-6
@@ -36,8 +36,8 @@
 
         = f.simple_fields_for :fields do |fields_f|
           .row
-            = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name')
-            = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value')
+            = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name'), input_html: { maxlength: 255 }
+            = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value'), input_html: { maxlength: 255 }
 
     .fields-row__column.fields-group.fields-row__column-6
       %h6= t('verification.verification')
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 6cedfb337..6e6d0eda8 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -1,16 +1,17 @@
 .detailed-status.detailed-status--flex
-  = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do
-    .detailed-status__display-avatar
-      - if current_account&.user&.setting_auto_play_gif || autoplay
-        = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
-      - else
-        = image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
-    %span.display-name
-      %bdi
-        %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
-      %span.display-name__account
-        = acct(status.account)
-        = fa_icon('lock') if status.account.locked?
+  .p-author.h-card
+    = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
+      .detailed-status__display-avatar
+        - if current_account&.user&.setting_auto_play_gif || autoplay
+          = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
+        - else
+          = image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
+      %span.display-name
+        %bdi
+          %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
+        %span.display-name__account
+          = acct(status.account)
+          = fa_icon('lock') if status.account.locked?
 
   = account_action_button(status.account)
 
@@ -50,7 +51,7 @@
     - if status.direct_visibility?
       %span.detailed-status__link<
         = fa_icon('envelope')
-    - elsif status.private_visibility?
+    - elsif status.private_visibility? || status.limited_visibility?
       %span.detailed-status__link<
         = fa_icon('lock')
     - else
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 5d7e2ad82..e996d5052 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -4,19 +4,21 @@
       %time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
     %data.dt-published{ value: status.created_at.to_time.iso8601 }
 
-    = link_to TagManager.instance.url_for(status.account), class: 'status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do
-      .status__avatar
-        %div
-          - if current_account&.user&.setting_auto_play_gif || autoplay
-            = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
-          - else
-            = image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
-      %span.display-name
-        %bdi
-          %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
-        %span.display-name__account
-          = acct(status.account)
-          = fa_icon('lock') if status.account.locked?
+    .p-author.h-card
+      = link_to TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do
+        .status__avatar
+          %div
+            - if current_account&.user&.setting_auto_play_gif || autoplay
+              = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
+            - else
+              = image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
+        %span.display-name
+          %bdi
+            %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
+          &nbsp;
+          %span.display-name__account
+            = acct(status.account)
+            = fa_icon('lock') if status.account.locked?
   .status__content.emojify<
     - if status.spoiler_text?
       %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index 2edc155bf..0e81c4f68 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -12,7 +12,7 @@
   = opengraph 'og:site_name', site_title
   = opengraph 'og:type', 'article'
   = opengraph 'og:title', "#{display_name(@account)} (@#{@account.local_username_and_domain})"
-  = opengraph 'og:url', short_account_status_url(@account, @stream_entry)
+  = opengraph 'og:url', short_account_status_url(@account, @stream_entry.activity)
 
   = render 'stream_entries/og_description', activity: @stream_entry.activity
   = render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
diff --git a/app/workers/activitypub/distribution_worker.rb b/app/workers/activitypub/distribution_worker.rb
index c2bfd4f2f..17c1ef7ff 100644
--- a/app/workers/activitypub/distribution_worker.rb
+++ b/app/workers/activitypub/distribution_worker.rb
@@ -23,7 +23,7 @@ class ActivityPub::DistributionWorker
   private
 
   def skip_distribution?
-    @status.direct_visibility?
+    @status.direct_visibility? || @status.limited_visibility?
   end
 
   def relayable?
diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb
index 0e2e0eddd..a8a3ebf0f 100644
--- a/app/workers/activitypub/processing_worker.rb
+++ b/app/workers/activitypub/processing_worker.rb
@@ -5,7 +5,7 @@ class ActivityPub::ProcessingWorker
 
   sidekiq_options backtrace: true
 
-  def perform(account_id, body)
-    ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true)
+  def perform(account_id, body, delivered_to_account_id = nil)
+    ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id)
   end
 end
diff --git a/app/workers/activitypub/reply_distribution_worker.rb b/app/workers/activitypub/reply_distribution_worker.rb
index fe99fc05f..c0ed3a1f4 100644
--- a/app/workers/activitypub/reply_distribution_worker.rb
+++ b/app/workers/activitypub/reply_distribution_worker.rb
@@ -9,7 +9,7 @@ class ActivityPub::ReplyDistributionWorker
     @status  = Status.find(status_id)
     @account = @status.thread&.account
 
-    return if @account.nil? || skip_distribution?
+    return unless @account.present? && @status.distributable?
 
     ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
       [signed_payload, @status.account_id, inbox_url]
@@ -20,10 +20,6 @@ class ActivityPub::ReplyDistributionWorker
 
   private
 
-  def skip_distribution?
-    @status.private_visibility? || @status.direct_visibility?
-  end
-
   def inboxes
     @inboxes ||= @account.followers.inboxes
   end