about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorbeatrix <beatrix.bitrot@gmail.com>2018-03-03 13:40:00 -0500
committerGitHub <noreply@github.com>2018-03-03 13:40:00 -0500
commit43a9a781a443a6b9296431fbcc4285b3ca6a1a57 (patch)
treed4bf067aeedcebbdc3d160eca6aa1a7c7d1bfa00 /app
parentee00da01d2e4cc455b92f1f4a7c9142c73048433 (diff)
parent65e2a4645e086110072efed5b3d4d1434c933e04 (diff)
Merge pull request #377 from glitch-soc/merge-upstream
hhhhhhhhhnnnnnnnnnnnghh!!!!!
Diffstat (limited to 'app')
-rw-r--r--app/controllers/accounts_controller.rb36
-rw-r--r--app/controllers/api/base_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/search_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb6
-rw-r--r--app/controllers/api/v1/accounts_controller.rb6
-rw-r--r--app/controllers/api/v1/reports_controller.rb12
-rw-r--r--app/controllers/api/v1/search_controller.rb6
-rw-r--r--app/controllers/api/v1/timelines/public_controller.rb14
-rw-r--r--app/controllers/api/v1/timelines/tag_controller.rb14
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/auth/sessions_controller.rb2
-rw-r--r--app/controllers/follower_accounts_controller.rb2
-rw-r--r--app/controllers/following_accounts_controller.rb2
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss367
-rw-r--r--app/javascript/mastodon/actions/reports.js9
-rw-r--r--app/javascript/mastodon/actions/timelines.js4
-rw-r--r--app/javascript/mastodon/components/media_gallery.js5
-rw-r--r--app/javascript/mastodon/components/status.js1
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js4
-rw-r--r--app/javascript/mastodon/features/account_gallery/components/media_item.js16
-rw-r--r--app/javascript/mastodon/features/account_gallery/index.js5
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js8
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js23
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js5
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js1
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle.js11
-rw-r--r--app/javascript/mastodon/features/ui/components/columns_area.js17
-rw-r--r--app/javascript/mastodon/features/ui/components/report_modal.js47
-rw-r--r--app/javascript/mastodon/features/ui/components/tabs_bar.js3
-rw-r--r--app/javascript/mastodon/features/ui/index.js1
-rw-r--r--app/javascript/mastodon/features/ui/util/react_router_helpers.js11
-rw-r--r--app/javascript/mastodon/features/video/index.js25
-rw-r--r--app/javascript/mastodon/initial_state.js1
-rw-r--r--app/javascript/mastodon/locales/ar.json12
-rw-r--r--app/javascript/mastodon/locales/bg.json8
-rw-r--r--app/javascript/mastodon/locales/ca.json76
-rw-r--r--app/javascript/mastodon/locales/de.json8
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json44
-rw-r--r--app/javascript/mastodon/locales/en.json8
-rw-r--r--app/javascript/mastodon/locales/eo.json286
-rw-r--r--app/javascript/mastodon/locales/es.json8
-rw-r--r--app/javascript/mastodon/locales/fa.json8
-rw-r--r--app/javascript/mastodon/locales/fi.json384
-rw-r--r--app/javascript/mastodon/locales/fr.json12
-rw-r--r--app/javascript/mastodon/locales/gl.json10
-rw-r--r--app/javascript/mastodon/locales/he.json8
-rw-r--r--app/javascript/mastodon/locales/hr.json8
-rw-r--r--app/javascript/mastodon/locales/hu.json10
-rw-r--r--app/javascript/mastodon/locales/hy.json8
-rw-r--r--app/javascript/mastodon/locales/id.json8
-rw-r--r--app/javascript/mastodon/locales/io.json8
-rw-r--r--app/javascript/mastodon/locales/it.json8
-rw-r--r--app/javascript/mastodon/locales/ja.json14
-rw-r--r--app/javascript/mastodon/locales/ko.json8
-rw-r--r--app/javascript/mastodon/locales/nl.json10
-rw-r--r--app/javascript/mastodon/locales/no.json8
-rw-r--r--app/javascript/mastodon/locales/oc.json8
-rw-r--r--app/javascript/mastodon/locales/pl.json13
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json10
-rw-r--r--app/javascript/mastodon/locales/pt.json8
-rw-r--r--app/javascript/mastodon/locales/ru.json8
-rw-r--r--app/javascript/mastodon/locales/sk.json42
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json8
-rw-r--r--app/javascript/mastodon/locales/sr.json8
-rw-r--r--app/javascript/mastodon/locales/sv.json8
-rw-r--r--app/javascript/mastodon/locales/th.json8
-rw-r--r--app/javascript/mastodon/locales/tr.json8
-rw-r--r--app/javascript/mastodon/locales/uk.json8
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json8
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json8
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json8
-rw-r--r--app/javascript/mastodon/reducers/reports.js4
-rw-r--r--app/javascript/styles/mastodon/about.scss363
-rw-r--r--app/javascript/styles/mastodon/accounts.scss83
-rw-r--r--app/javascript/styles/mastodon/components.scss215
-rw-r--r--app/javascript/styles/mastodon/forms.scss5
-rw-r--r--app/lib/activitypub/activity.rb2
-rw-r--r--app/lib/activitypub/activity/flag.rb25
-rw-r--r--app/lib/activitypub/activity/reject.rb2
-rw-r--r--app/lib/exceptions.rb1
-rw-r--r--app/lib/request.rb19
-rw-r--r--app/lib/sidekiq_error_handler.rb11
-rw-r--r--app/models/concerns/paginable.rb9
-rw-r--r--app/models/import.rb2
-rw-r--r--app/models/report.rb4
-rw-r--r--app/models/user.rb26
-rw-r--r--app/serializers/activitypub/flag_serializer.rb27
-rw-r--r--app/serializers/initial_state_serializer.rb1
-rw-r--r--app/serializers/rest/instance_serializer.rb11
-rw-r--r--app/services/report_service.rb54
-rw-r--r--app/views/about/show.html.haml148
-rw-r--r--app/views/accounts/_follow_button.html.haml23
-rw-r--r--app/views/accounts/_grid_card.html.haml7
-rw-r--r--app/views/accounts/_header.html.haml19
-rw-r--r--app/views/accounts/show.html.haml7
-rw-r--r--app/views/auth/passwords/edit.html.haml4
-rw-r--r--app/views/auth/registrations/edit.html.haml4
-rw-r--r--app/views/auth/sessions/new.html.haml2
-rw-r--r--app/views/settings/preferences/show.html.haml2
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml2
-rw-r--r--app/views/stream_entries/_simple_status.html.haml1
-rw-r--r--app/views/tags/_features.html.haml25
-rw-r--r--app/views/tags/show.html.haml61
103 files changed, 1941 insertions, 1029 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index c9725ed00..1efaf619b 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class AccountsController < ApplicationController
+  PAGE_SIZE = 20
+
   include AccountControllerConcern
 
   before_action :set_cache_headers
@@ -17,13 +19,16 @@ class AccountsController < ApplicationController
         end
 
         @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
-        @statuses        = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id])
+        @statuses        = filtered_status_page(params)
         @statuses        = cache_collection(@statuses, Status)
-        @next_url        = next_url unless @statuses.empty?
+        unless @statuses.empty?
+          @older_url        = older_url if @statuses.last.id > filtered_statuses.last.id
+          @newer_url        = newer_url if @statuses.first.id < filtered_statuses.first.id
+        end
       end
 
       format.atom do
-        @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
+        @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
         render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
       end
 
@@ -70,13 +75,22 @@ class AccountsController < ApplicationController
     @account = Account.find_local!(params[:username])
   end
 
-  def next_url
+  def older_url
+    ::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
+    pagination_url(max_id: @statuses.last.id)
+  end
+
+  def newer_url
+    pagination_url(min_id: @statuses.first.id)
+  end
+
+  def pagination_url(max_id: nil, min_id: nil)
     if media_requested?
-      short_account_media_url(@account, max_id: @statuses.last.id)
+      short_account_media_url(@account, max_id: max_id, min_id: min_id)
     elsif replies_requested?
-      short_account_with_replies_url(@account, max_id: @statuses.last.id)
+      short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
     else
-      short_account_url(@account, max_id: @statuses.last.id)
+      short_account_url(@account, max_id: max_id, min_id: min_id)
     end
   end
 
@@ -87,4 +101,12 @@ class AccountsController < ApplicationController
   def replies_requested?
     request.path.ends_with?('/with_replies')
   end
+
+  def filtered_status_page(params)
+    if params[:min_id].present?
+      filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
+    else
+      filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
+    end
+  end
 end
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 52e68ab35..7b5168b31 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -51,6 +51,10 @@ class Api::BaseController < ApplicationController
     [params[:limit].to_i.abs, default_limit * 2].min
   end
 
+  def truthy_param?(key)
+    ActiveModel::Type::Boolean.new.cast(params[key])
+  end
+
   def current_resource_owner
     @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
   end
diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb
index 11e647c3c..7649da433 100644
--- a/app/controllers/api/v1/accounts/search_controller.rb
+++ b/app/controllers/api/v1/accounts/search_controller.rb
@@ -22,8 +22,4 @@ class Api::V1::Accounts::SearchController < Api::BaseController
       following: truthy_param?(:following)
     )
   end
-
-  def truthy_param?(key)
-    params[key] == 'true'
-  end
 end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 095f6937b..7261ccd24 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -28,9 +28,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
 
   def account_statuses
     default_statuses.tap do |statuses|
-      statuses.merge!(only_media_scope) if params[:only_media]
-      statuses.merge!(pinned_scope) if params[:pinned]
-      statuses.merge!(no_replies_scope) if params[:exclude_replies]
+      statuses.merge!(only_media_scope) if truthy_param?(:only_media)
+      statuses.merge!(pinned_scope) if truthy_param?(:pinned)
+      statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
     end
   end
 
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 4e73e9e8b..d64325944 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -13,9 +13,9 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def follow
-    FollowService.new.call(current_user.account, @account.acct, reblogs: params[:reblogs])
+    FollowService.new.call(current_user.account, @account.acct, reblogs: truthy_param?(:reblogs))
 
-    options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: params[:reblogs] } }, requested_map: { @account.id => false } }
+    options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
 
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
   end
@@ -26,7 +26,7 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def mute
-    MuteService.new.call(current_user.account, @account, notifications: params[:notifications])
+    MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications))
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
   end
 
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index 22828217d..f5095e073 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -13,14 +13,14 @@ class Api::V1::ReportsController < Api::BaseController
   end
 
   def create
-    @report = current_account.reports.create!(
-      target_account: reported_account,
+    @report = ReportService.new.call(
+      current_account,
+      reported_account,
       status_ids: reported_status_ids,
-      comment: report_params[:comment]
+      comment: report_params[:comment],
+      forward: report_params[:forward]
     )
 
-    User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
-
     render json: @report, serializer: REST::ReportSerializer
   end
 
@@ -39,6 +39,6 @@ class Api::V1::ReportsController < Api::BaseController
   end
 
   def report_params
-    params.permit(:account_id, :comment, status_ids: [])
+    params.permit(:account_id, :comment, :forward, status_ids: [])
   end
 end
diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb
index d1b4e0402..99b635ad9 100644
--- a/app/controllers/api/v1/search_controller.rb
+++ b/app/controllers/api/v1/search_controller.rb
@@ -33,12 +33,8 @@ class Api::V1::SearchController < Api::BaseController
     SearchService.new.call(
       params[:q],
       RESULTS_LIMIT,
-      resolving_search?,
+      truthy_param?(:resolve),
       current_account
     )
   end
-
-  def resolving_search?
-    params[:resolve] == 'true'
-  end
 end
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
index 49887778e..d7d70b94d 100644
--- a/app/controllers/api/v1/timelines/public_controller.rb
+++ b/app/controllers/api/v1/timelines/public_controller.rb
@@ -21,15 +21,23 @@ class Api::V1::Timelines::PublicController < Api::BaseController
   end
 
   def public_statuses
-    public_timeline_statuses.paginate_by_max_id(
+    statuses = public_timeline_statuses.paginate_by_max_id(
       limit_param(DEFAULT_STATUSES_LIMIT),
       params[:max_id],
       params[:since_id]
     )
+
+    if truthy_param?(:only_media)
+      # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
+      status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
+      statuses.where(id: status_ids)
+    else
+      statuses
+    end
   end
 
   def public_timeline_statuses
-    Status.as_public_timeline(current_account, params[:local])
+    Status.as_public_timeline(current_account, truthy_param?(:local))
   end
 
   def insert_pagination_headers
@@ -37,7 +45,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit).merge(core_params)
+    params.permit(:local, :limit, :only_media).merge(core_params)
   end
 
   def next_path
diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb
index 08db04a39..eb32611ad 100644
--- a/app/controllers/api/v1/timelines/tag_controller.rb
+++ b/app/controllers/api/v1/timelines/tag_controller.rb
@@ -29,16 +29,24 @@ class Api::V1::Timelines::TagController < Api::BaseController
     if @tag.nil?
       []
     else
-      tag_timeline_statuses.paginate_by_max_id(
+      statuses = tag_timeline_statuses.paginate_by_max_id(
         limit_param(DEFAULT_STATUSES_LIMIT),
         params[:max_id],
         params[:since_id]
       )
+
+      if truthy_param?(:only_media)
+        # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
+        status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
+        statuses.where(id: status_ids)
+      else
+        statuses
+      end
     end
   end
 
   def tag_timeline_statuses
-    Status.as_tag_timeline(@tag, current_account, params[:local])
+    Status.as_tag_timeline(@tag, current_account, truthy_param?(:local))
   end
 
   def insert_pagination_headers
@@ -46,7 +54,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit).merge(core_params)
+    params.permit(:local, :limit, :only_media).merge(core_params)
   end
 
   def next_path
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a296d96db..fc745eaec 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -15,7 +15,7 @@ class ApplicationController < ActionController::Base
   helper_method :current_flavour
   helper_method :current_skin
   helper_method :single_user_mode?
-  helper_method :use_pam?
+  helper_method :use_seamless_external_login?
 
   rescue_from ActionController::RoutingError, with: :not_found
   rescue_from ActiveRecord::RecordNotFound, with: :not_found
@@ -146,8 +146,8 @@ class ApplicationController < ActionController::Base
     @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
   end
 
-  def use_pam?
-    Devise.pam_authentication
+  def use_seamless_external_login?
+    Devise.pam_authentication || Devise.ldap_authentication
   end
 
   def current_account
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index 475cd540a..c9e507343 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -38,7 +38,7 @@ class Auth::SessionsController < Devise::SessionsController
     if session[:otp_user_id]
       User.find(session[:otp_user_id])
     elsif user_params[:email]
-      if use_pam? && Devise.check_at_sign && user_params[:email].index('@').nil?
+      if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
         User.joins(:account).find_by(accounts: { username: user_params[:email] })
       else
         User.find_for_authentication(email: user_params[:email])
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 080cbde11..c74d3f86d 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -9,6 +9,8 @@ class FollowerAccountsController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+
+        @relationships = AccountRelationshipsPresenter.new(@follows.map(&:account_id), current_user.account_id) if user_signed_in?
       end
 
       format.json do
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 74e83ad81..4c1e3f327 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -9,6 +9,8 @@ class FollowingAccountsController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+
+        @relationships = AccountRelationshipsPresenter.new(@follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
       end
 
       format.json do
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index a25a50739..d97e23696 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -15,117 +15,169 @@ $small-breakpoint: 960px;
   }
 }
 
-.show-xs,
-.show-sm {
-  display: none;
-}
+.landing-page {
+  .grid {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: 1fr 2fr;
+    grid-auto-columns: 25%;
+    grid-auto-rows: max-content;
+
+    .column-0 {
+      display: none;
+    }
 
-.show-m {
-  display: block;
-}
+    .column-1 {
+      grid-column: 1;
+      grid-row: 1;
+    }
 
-@media screen and (max-width: $small-breakpoint) {
-  .hide-sm {
-    display: none !important;
-  }
+    .column-2 {
+      grid-column: 2;
+      grid-row: 1;
+    }
 
-  .show-sm {
-    display: block !important;
-  }
-}
+    .column-3 {
+      grid-column: 3;
+      grid-row: 1 / 3;
+    }
 
-@media screen and (max-width: $column-breakpoint) {
-  .hide-xs {
-    display: none !important;
+    .column-4 {
+      grid-column: 1 / 3;
+      grid-row: 2;
+    }
   }
 
-  .show-xs {
-    display: block !important;
-  }
-}
+  @media screen and (max-width: $small-breakpoint) {
+    .grid {
+      grid-template-columns: 40% 60%;
 
-.row {
-  display: flex;
-  flex-wrap: wrap;
-  margin: 0 -5px;
+      .column-0 {
+        display: none;
+      }
 
-  @for $i from 1 through 15 {
-    .column-#{$i} {
-      box-sizing: border-box;
-      min-height: 1px;
-      flex: 0 0 percentage($i / 15);
-      max-width: percentage($i / 15);
-      padding: 0 5px;
+      .column-1 {
+        grid-column: 1;
+        grid-row: 1;
 
-      @media screen and (max-width: $small-breakpoint) {
-        &-sm {
-          box-sizing: border-box;
-          min-height: 1px;
-          flex: 0 0 percentage($i / 15);
-          max-width: percentage($i / 15);
-          padding: 0 5px;
-
-          @media screen and (max-width: $column-breakpoint) {
-            max-width: 100%;
-            flex: 0 0 100%;
-            margin-bottom: 10px;
-
-            &:last-child {
-              margin-bottom: 0;
-            }
-          }
+        &.non-preview .landing-page__forms {
+          height: 100%;
         }
       }
 
-      @media screen and (max-width: $column-breakpoint) {
-        max-width: 100%;
-        flex: 0 0 100%;
-        margin-bottom: 10px;
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1 / 3;
 
-        &:last-child {
-          margin-bottom: 0;
+        &.non-preview {
+          grid-column: 2;
+          grid-row: 1;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 2 / 4;
+      }
+
+      .column-4 {
+        grid-column: 2;
+        grid-row: 3;
+
+        &.non-preview {
+          grid-column: 1 / 3;
+          grid-row: 2;
         }
       }
     }
   }
-}
 
-.column-flex {
-  display: flex;
-  flex-direction: column;
-}
+  @media screen and (max-width: $column-breakpoint) {
+    .grid {
+      grid-template-columns: auto;
 
-.separator-or {
-  position: relative;
-  margin: 40px 0;
-  text-align: center;
+      .column-0 {
+        display: block;
+        grid-column: 1;
+        grid-row: 1;
+      }
 
-  &::before {
-    content: "";
-    display: block;
-    width: 100%;
-    height: 0;
-    border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
-    position: absolute;
-    top: 50%;
-    left: 0;
+      .column-1 {
+        grid-column: 1;
+        grid-row: 3;
+
+        .brand {
+          display: none;
+        }
+      }
+
+      .column-2 {
+        grid-column: 1;
+        grid-row: 2;
+
+        .landing-page__logo,
+        .landing-page__call-to-action {
+          display: none;
+        }
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 2;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 5;
+      }
+
+      .column-4 {
+        grid-column: 1;
+        grid-row: 4;
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 4;
+        }
+      }
+    }
   }
 
-  span {
-    display: inline-block;
-    background: $ui-base-color;
-    font-size: 12px;
-    font-weight: 500;
-    color: $ui-primary-color;
-    text-transform: uppercase;
+  .column-flex {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .separator-or {
     position: relative;
-    z-index: 1;
-    padding: 0 8px;
-    cursor: default;
+    margin: 40px 0;
+    text-align: center;
+
+    &::before {
+      content: "";
+      display: block;
+      width: 100%;
+      height: 0;
+      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
+      position: absolute;
+      top: 50%;
+      left: 0;
+    }
+
+    span {
+      display: inline-block;
+      background: $ui-base-color;
+      font-size: 12px;
+      font-weight: 500;
+      color: $ui-primary-color;
+      text-transform: uppercase;
+      position: relative;
+      z-index: 1;
+      padding: 0 8px;
+      cursor: default;
+    }
   }
-}
 
-.landing-page {
   p,
   li {
     font-family: 'mastodon-font-sans-serif', sans-serif;
@@ -502,7 +554,7 @@ $small-breakpoint: 960px;
           display: block;
           width: 80px;
           height: 80px;
-          border-radius: $ui-avatar-border-size;
+          border-radius: 48px;
         }
       }
 
@@ -539,6 +591,7 @@ $small-breakpoint: 960px;
 
       img {
         position: static;
+        padding: 10px 0;
       }
 
       @media screen and (max-width: $small-breakpoint) {
@@ -558,18 +611,33 @@ $small-breakpoint: 960px;
   }
 
   &__call-to-action {
-    margin-bottom: 10px;
     background: darken($ui-base-color, 4%);
     border-radius: 4px;
     padding: 25px 40px;
     overflow: hidden;
 
     .row {
+      display: flex;
+      flex-direction: row-reverse;
+      flex-wrap: wrap;
+      justify-content: space-between;
       align-items: center;
     }
 
-    .information-board__section {
-      padding: 0;
+    .row__information-board {
+      display: flex;
+      justify-content: flex-end;
+      align-items: flex-end;
+
+      .information-board__section {
+        flex: 1 0 auto;
+        padding: 0 10px;
+      }
+    }
+
+    .row__mascot {
+      flex: 1;
+      margin: 10px -50px 0 0;
     }
   }
 
@@ -619,6 +687,8 @@ $small-breakpoint: 960px;
 
   &__short-description {
     .row {
+      display: flex;
+      flex-wrap: wrap;
       align-items: center;
       margin-bottom: 40px;
     }
@@ -668,7 +738,6 @@ $small-breakpoint: 960px;
     height: 100%;
 
     @media screen and (max-width: $small-breakpoint) {
-      margin-bottom: 10px;
       height: auto;
     }
 
@@ -717,15 +786,14 @@ $small-breakpoint: 960px;
     width: 100%;
     flex: 1 1 auto;
     overflow: hidden;
+    height: 100%;
 
     .column-header {
       color: inherit;
       font-family: inherit;
       font-size: 16px;
-      padding: 15px;
       line-height: inherit;
       font-weight: inherit;
-      margin: 0;
     }
 
     .column {
@@ -942,93 +1010,54 @@ $small-breakpoint: 960px;
   }
 
   &.tag-page {
-    .features {
-      padding: 30px 0;
-
-      .container-alt {
-        max-width: 820px;
-
-        #mastodon-timeline {
-          margin-right: 0;
-          border-top-right-radius: 0;
-        }
-
-        .about-mastodon {
-          .about-hashtag {
-            background: darken($ui-base-color, 4%);
-            padding: 0 20px 20px 30px;
-            border-radius: 0 5px 5px 0;
-
-            .brand {
-              padding-top: 20px;
-              margin-bottom: 20px;
-
-              img {
-                height: 48px;
-                width: auto;
-              }
-            }
-
-            p {
-              strong {
-                color: $ui-secondary-color;
-                font-weight: 700;
-              }
-            }
+    .grid {
+      @media screen and (min-width: $small-breakpoint) {
+        grid-template-columns: 33% 67%;
+      }
 
-            .cta {
-              margin: 0;
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1;
+      }
+    }
 
-              .button {
-                margin-right: 4px;
-              }
-            }
-          }
+    .brand {
+      text-align: unset;
+      padding: 0;
 
-          .features-list {
-            margin-left: 30px;
-            margin-right: 10px;
-          }
-        }
+      img {
+        height: 48px;
+        width: auto;
       }
     }
 
-    @media screen and (max-width: 675px) {
-      .features {
-        padding: 10px 0;
+    .cta {
+      margin: 0;
 
-        .container-alt {
-          display: flex;
-          flex-direction: column;
-
-          #mastodon-timeline {
-            order: 2;
-            flex: 0 0 auto;
-            height: 60vh;
-            margin-bottom: 20px;
-            border-top-right-radius: 4px;
-          }
+      .button {
+        margin-right: 4px;
+      }
+    }
 
-          .about-mastodon {
-            order: 1;
-            flex: 0 0 auto;
-            max-width: 100%;
+    @media screen and (max-width: $column-breakpoint) {
+      .grid {
+        .column-1 {
+          grid-column: 1;
+          grid-row: 2;
+        }
 
-            .about-hashtag {
-              background: unset;
-              padding: 0;
-              border-radius: 0;
+        .column-2 {
+          grid-column: 1;
+          grid-row: 1;
+        }
+      }
 
-              .cta {
-                margin: 20px 0;
-              }
-            }
+      .brand {
+        margin: 0;
+      }
 
-            .features-list {
-              display: none;
-            }
-          }
-        }
+      .landing-page__features {
+        display: none;
       }
     }
   }
diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js
index b19a07285..afa0c3412 100644
--- a/app/javascript/mastodon/actions/reports.js
+++ b/app/javascript/mastodon/actions/reports.js
@@ -10,6 +10,7 @@ export const REPORT_SUBMIT_FAIL    = 'REPORT_SUBMIT_FAIL';
 
 export const REPORT_STATUS_TOGGLE  = 'REPORT_STATUS_TOGGLE';
 export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
+export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
 
 export function initReport(account, status) {
   return dispatch => {
@@ -45,6 +46,7 @@ export function submitReport() {
       account_id: getState().getIn(['reports', 'new', 'account_id']),
       status_ids: getState().getIn(['reports', 'new', 'status_ids']),
       comment: getState().getIn(['reports', 'new', 'comment']),
+      forward: getState().getIn(['reports', 'new', 'forward']),
     }).then(response => {
       dispatch(closeModal());
       dispatch(submitReportSuccess(response.data));
@@ -78,3 +80,10 @@ export function changeReportComment(comment) {
     comment,
   };
 };
+
+export function changeReportForward(forward) {
+  return {
+    type: REPORT_FORWARD_CHANGE,
+    forward,
+  };
+};
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index df6a36379..858a12b15 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -120,7 +120,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
 export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v1/timelines/home');
 export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public');
 export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
-export const refreshAccountTimeline      = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
+export const refreshAccountTimeline      = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
 export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
 export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
 export const refreshListTimeline         = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
@@ -161,7 +161,7 @@ export function expandTimeline(timelineId, path, params = {}) {
 export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/timelines/home');
 export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public');
 export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
-export const expandAccountTimeline      = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
+export const expandAccountTimeline      = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
 export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
 export const expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
 export const expandListTimeline         = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 9e1bb77c2..3568a8440 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -283,8 +283,9 @@ export default class MediaGallery extends React.PureComponent {
       if (width) {
         style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
       }
+    } else if (width) {
+      style.height = width / (16/9);
     } else {
-      // crop the image
       style.height = height;
     }
 
@@ -309,7 +310,7 @@ export default class MediaGallery extends React.PureComponent {
       if (this.isStandaloneEligible()) {
         children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
       } else {
-        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} containerWidth={width} containerHeight={height} />);
+        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} containerWidth={width} containerHeight={style.height} />);
       }
     }
 
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index c030510a0..c52cd5f09 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -184,6 +184,7 @@ export default class Status extends ImmutablePureComponent {
                 src={video.get('url')}
                 width={239}
                 height={110}
+                inline
                 sensitive={status.get('sensitive')}
                 onOpenVideo={this.handleOpenVideo}
               />
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index b4f812c9a..b538fa5fc 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent {
     let extraInfo = '';
 
     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+
     if ('share' in navigator) {
       menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
     }
-    menu.push(null);
-    menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
+
     menu.push(null);
 
     if (account.get('id') === me) {
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index dda3d4e37..59c805c38 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent {
   render () {
     const { media } = this.props;
     const status = media.get('status');
+    const focusX = media.getIn(['meta', 'focus', 'x']);
+    const focusY = media.getIn(['meta', 'focus', 'y']);
+    const x = ((focusX /  2) + .5) * 100;
+    const y = ((focusY / -2) + .5) * 100;
+    const style = {};
 
-    let content, style;
+    let content;
 
     if (media.get('type') === 'gifv') {
       content = <span className='media-gallery__gifv__label'>GIF</span>;
     }
 
     if (!status.get('sensitive')) {
-      style = { backgroundImage: `url(${media.get('preview_url')})` };
+      style.backgroundImage    = `url(${media.get('preview_url')})`;
+      style.backgroundPosition = `${x}% ${y}%`;
     }
 
     return (
       <div className='account-gallery__item'>
-        <Permalink
-          to={`/statuses/${status.get('id')}`}
-          href={status.get('url')}
-          style={style}
-        >
+        <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
           {content}
         </Permalink>
       </div>
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index ece219a3d..4b408256a 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { getAccountGallery } from '../../selectors';
 import MediaItem from './components/media_item';
 import HeaderContainer from '../account_timeline/containers/header_container';
-import { FormattedMessage } from 'react-intl';
 import { ScrollContainer } from 'react-router-scroll-4';
 import LoadMore from '../../components/load_more';
 
@@ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent {
           <div className='scrollable' onScroll={this.handleScroll}>
             <HeaderContainer accountId={this.props.params.accountId} />
 
-            <div className='account-section-headline'>
-              <FormattedMessage id='account.media' defaultMessage='Media' />
-            </div>
-
             <div className='account-gallery__container'>
               {medias.map(media => (
                 <MediaItem
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index 0ddb6b6c1..5cd4af1d3 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -6,6 +6,8 @@ import ActionBar from '../../account/components/action_bar';
 import MissingIndicator from '../../../components/missing_indicator';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import MovedNote from './moved_note';
+import { FormattedMessage } from 'react-intl';
+import { NavLink } from 'react-router-dom';
 
 export default class Header extends ImmutablePureComponent {
 
@@ -91,6 +93,12 @@ export default class Header extends ImmutablePureComponent {
           onBlockDomain={this.handleBlockDomain}
           onUnblockDomain={this.handleUnblockDomain}
         />
+
+        <div className='account__section-headline'>
+          <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
+          <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
+          <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
+        </div>
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index f8c85c296..aed009ef0 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -12,11 +12,15 @@ import ColumnBackButton from '../../components/column_back_button';
 import { List as ImmutableList } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
-const mapStateToProps = (state, props) => ({
-  statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
-  isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
-  hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
-});
+const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
+  const path = withReplies ? `${accountId}:with_replies` : accountId;
+
+  return {
+    statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
+    isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
+    hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
+  };
+};
 
 @connect(mapStateToProps)
 export default class AccountTimeline extends ImmutablePureComponent {
@@ -27,23 +31,24 @@ export default class AccountTimeline extends ImmutablePureComponent {
     statusIds: ImmutablePropTypes.list,
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
+    withReplies: PropTypes.bool,
   };
 
   componentWillMount () {
     this.props.dispatch(fetchAccount(this.props.params.accountId));
-    this.props.dispatch(refreshAccountTimeline(this.props.params.accountId));
+    this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies));
   }
 
   componentWillReceiveProps (nextProps) {
-    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+    if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
       this.props.dispatch(fetchAccount(nextProps.params.accountId));
-      this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId));
+      this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
     }
   }
 
   handleScrollToBottom = () => {
     if (!this.props.isLoading && this.props.hasMore) {
-      this.props.dispatch(expandAccountTimeline(this.props.params.accountId));
+      this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
     }
   }
 
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 398fc44ce..71c0a203f 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Overlay from 'react-overlays/lib/Overlay';
 import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
+import { searchEnabled } from '../../../initial_state';
 
 const messages = defineMessages({
   placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@@ -17,7 +18,7 @@ class SearchPopout extends React.PureComponent {
 
   render () {
     const { style } = this.props;
-
+    const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
     return (
       <div style={{ ...style, position: 'absolute', width: 285 }}>
         <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
@@ -32,7 +33,7 @@ class SearchPopout extends React.PureComponent {
                 <li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
               </ul>
 
-              <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />
+              {extraInformation}
             </div>
           )}
         </Motion>
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index abdb9a3f6..d4f21fc32 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -57,6 +57,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
             src={video.get('url')}
             width={300}
             height={150}
+            inline
             onOpenVideo={this.handleOpenVideo}
             sensitive={status.get('sensitive')}
           />
diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js
index fc88e0c70..06a6c9cdd 100644
--- a/app/javascript/mastodon/features/ui/components/bundle.js
+++ b/app/javascript/mastodon/features/ui/components/bundle.js
@@ -26,7 +26,7 @@ class Bundle extends React.Component {
     onFetchFail: noop,
   }
 
-  static cache = {}
+  static cache = new Map
 
   state = {
     mod: undefined,
@@ -51,13 +51,12 @@ class Bundle extends React.Component {
 
   load = (props) => {
     const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
+    const cachedMod = Bundle.cache.get(fetchComponent);
 
     onFetch();
 
-    if (Bundle.cache[fetchComponent.name]) {
-      const mod = Bundle.cache[fetchComponent.name];
-
-      this.setState({ mod: mod.default });
+    if (cachedMod) {
+      this.setState({ mod: cachedMod.default });
       onFetchSuccess();
       return Promise.resolve();
     }
@@ -71,7 +70,7 @@ class Bundle extends React.Component {
 
     return fetchComponent()
       .then((mod) => {
-        Bundle.cache[fetchComponent.name] = mod;
+        Bundle.cache.set(fetchComponent, mod);
         this.setState({ mod: mod.default });
         onFetchSuccess();
       })
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index a01e5a390..e82c46402 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -6,6 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 
 import ReactSwipeableViews from 'react-swipeable-views';
 import { links, getIndex, getLink } from './tabs_bar';
+import { Link } from 'react-router-dom';
 
 import BundleContainer from '../containers/bundle_container';
 import ColumnLoading from './column_loading';
@@ -152,11 +153,19 @@ export default class ColumnsArea extends ImmutablePureComponent {
     this.pendingIndex = null;
 
     if (singleColumn) {
-      return columnIndex !== -1 ? (
-        <ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
+      const floatingActionButton = this.context.router.history.location.pathname === '/statuses/new' ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button'><i className='fa fa-pencil' /></Link>;
+
+      return columnIndex !== -1 ? [
+        <ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
           {links.map(this.renderView)}
-        </ReactSwipeableViews>
-      ) : <div className='columns-area'>{children}</div>;
+        </ReactSwipeableViews>,
+
+        floatingActionButton,
+      ] : [
+        <div className='columns-area'>{children}</div>,
+
+        floatingActionButton,
+      ];
     }
 
     return (
diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js
index b5dfa422e..3ae97646f 100644
--- a/app/javascript/mastodon/features/ui/components/report_modal.js
+++ b/app/javascript/mastodon/features/ui/components/report_modal.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { changeReportComment, submitReport } from '../../../actions/reports';
+import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
 import { refreshAccountTimeline } from '../../../actions/timelines';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -10,8 +10,11 @@ import StatusCheckBox from '../../report/containers/status_check_box_container';
 import { OrderedSet } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Button from '../../../components/button';
+import Toggle from 'react-toggle';
+import IconButton from '../../../components/icon_button';
 
 const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
   placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
   submit: { id: 'report.submit', defaultMessage: 'Submit' },
 });
@@ -26,6 +29,7 @@ const makeMapStateToProps = () => {
       isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
       account: getAccount(state, accountId),
       comment: state.getIn(['reports', 'new', 'comment']),
+      forward: state.getIn(['reports', 'new', 'forward']),
       statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
     };
   };
@@ -42,14 +46,19 @@ export default class ReportModal extends ImmutablePureComponent {
     account: ImmutablePropTypes.map,
     statusIds: ImmutablePropTypes.orderedSet.isRequired,
     comment: PropTypes.string.isRequired,
+    forward: PropTypes.bool,
     dispatch: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
-  handleCommentChange = (e) => {
+  handleCommentChange = e => {
     this.props.dispatch(changeReportComment(e.target.value));
   }
 
+  handleForwardChange = e => {
+    this.props.dispatch(changeReportForward(e.target.checked));
+  }
+
   handleSubmit = () => {
     this.props.dispatch(submitReport());
   }
@@ -65,26 +74,25 @@ export default class ReportModal extends ImmutablePureComponent {
   }
 
   render () {
-    const { account, comment, intl, statusIds, isSubmitting } = this.props;
+    const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
 
     if (!account) {
       return null;
     }
 
+    const domain = account.get('acct').split('@')[1];
+
     return (
       <div className='modal-root__modal report-modal'>
         <div className='report-modal__target'>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
           <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
         </div>
 
         <div className='report-modal__container'>
-          <div className='report-modal__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
-
           <div className='report-modal__comment'>
+            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p>
+
             <textarea
               className='setting-text light'
               placeholder={intl.formatMessage(messages.placeholder)}
@@ -92,11 +100,26 @@ export default class ReportModal extends ImmutablePureComponent {
               onChange={this.handleCommentChange}
               disabled={isSubmitting}
             />
+
+            {domain && (
+              <div>
+                <p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
+
+                <div className='setting-toggle'>
+                  <Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
+                  <label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
+                </div>
+              </div>
+            )}
+
+            <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
           </div>
-        </div>
 
-        <div className='report-modal__action-bar'>
-          <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
+          <div className='report-modal__statuses'>
+            <div>
+              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
+            </div>
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js
index 7694e5ab3..77fe5f5e2 100644
--- a/app/javascript/mastodon/features/ui/components/tabs_bar.js
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js
@@ -6,14 +6,13 @@ import { debounce } from 'lodash';
 import { isUserTouching } from '../../../is_mobile';
 
 export const links = [
-  <NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
   <NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
   <NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
 
   <NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
   <NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
 
-  <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
+  <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>,
 ];
 
 export function getIndex (path) {
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 5b0d7246a..ef909136f 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -398,6 +398,7 @@ export default class UI extends React.Component {
               <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
 
               <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
+              <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
               <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
               <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
               <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
index 43007ddc3..32dfe320b 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
@@ -35,14 +35,19 @@ export class WrappedRoute extends React.Component {
     component: PropTypes.func.isRequired,
     content: PropTypes.node,
     multiColumn: PropTypes.bool,
-  }
+    componentParams: PropTypes.object,
+  };
+
+  static defaultProps = {
+    componentParams: {},
+  };
 
   renderComponent = ({ match }) => {
-    const { component, content, multiColumn } = this.props;
+    const { component, content, multiColumn, componentParams } = this.props;
 
     return (
       <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}>
-        {Component => <Component params={match.params} multiColumn={multiColumn}>{content}</Component>}
+        {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>}
       </BundleContainer>
     );
   }
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index c81a5cb5f..98ebcb6f9 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -97,6 +97,7 @@ export default class Video extends React.PureComponent {
     onOpenVideo: PropTypes.func,
     onCloseVideo: PropTypes.func,
     detailed: PropTypes.bool,
+    inline: PropTypes.bool,
     intl: PropTypes.object.isRequired,
   };
 
@@ -105,6 +106,7 @@ export default class Video extends React.PureComponent {
     duration: 0,
     paused: true,
     dragging: false,
+    containerWidth: false,
     fullscreen: false,
     hovered: false,
     muted: false,
@@ -113,6 +115,12 @@ export default class Video extends React.PureComponent {
 
   setPlayerRef = c => {
     this.player = c;
+
+    if (c) {
+      this.setState({
+        containerWidth: c.offsetWidth,
+      });
+    }
   }
 
   setVideoRef = c => {
@@ -246,12 +254,23 @@ export default class Video extends React.PureComponent {
   }
 
   render () {
-    const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
-    const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
+    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
+    const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
     const progress = (currentTime / duration) * 100;
+    const playerStyle = {};
+
+    let { width, height } = this.props;
+
+    if (inline && containerWidth) {
+      width  = containerWidth;
+      height = containerWidth / (16/9);
+
+      playerStyle.width  = width;
+      playerStyle.height = height;
+    }
 
     return (
-      <div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+      <div className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })} style={playerStyle} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
         <video
           ref={this.setVideoRef}
           src={src}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 6f1356324..df310e7e1 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -10,5 +10,6 @@ export const unfollowModal = getMeta('unfollow_modal');
 export const boostModal = getMeta('boost_modal');
 export const deleteModal = getMeta('delete_modal');
 export const me = getMeta('me');
+export const searchEnabled = getMeta('search_enabled');
 
 export default initialState;
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 310c7aa55..b326bc3b1 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -13,7 +13,8 @@
   "account.moved_to": "{name} إنتقل إلى :",
   "account.mute": "أكتم @{name}",
   "account.mute_notifications": "كتم إخطارات @{name}",
-  "account.posts": "المشاركات",
+  "account.posts": "التبويقات",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "أبلغ عن @{name}",
   "account.requested": "في انتظار الموافقة",
   "account.share": "مشاركة @{name}'s profile",
@@ -50,7 +51,7 @@
   "column_header.unpin": "فك التدبيس",
   "column_subheading.navigation": "التصفح",
   "column_subheading.settings": "الإعدادات",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.",
   "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
   "compose_form.lock_disclaimer.lock": "مقفل",
   "compose_form.placeholder": "فيمَ تفكّر؟",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "إلغاء",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "تعليقات إضافية",
   "report.submit": "إرسال",
   "report.target": "إبلاغ",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "حالة",
   "search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
   "search_popout.tips.user": "مستخدِم",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} و {results}}",
   "standalone.public_title": "نظرة على ...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "إسحب ثم أفلت للرفع",
   "upload_button.label": "إضافة وسائط",
   "upload_form.description": "وصف للمعاقين بصريا",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "إلغاء",
   "upload_progress.label": "يرفع...",
   "video.close": "إغلاق الفيديو",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 8188ae2c0..90e394a19 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -14,6 +14,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Публикации",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
   "account.requested": "В очакване на одобрение",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Отказ",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
   "report.submit": "Submit",
   "report.target": "Reporting",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Добави медия",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Отмяна",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index ecaae0847..95b75cfc9 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -1,29 +1,30 @@
 {
-  "account.block": "Bloquejar @{name}",
-  "account.block_domain": "Amagar tot de {domain}",
+  "account.block": "Bloca @{name}",
+  "account.block_domain": "Amaga-ho tot de {domain}",
   "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
-  "account.edit_profile": "Editar perfil",
-  "account.follow": "Seguir",
+  "account.edit_profile": "Edita el perfil",
+  "account.follow": "Segueix",
   "account.followers": "Seguidors",
   "account.follows": "Seguint",
-  "account.follows_you": "et segueix",
+  "account.follows_you": "Et segueix",
   "account.hide_reblogs": "Amaga els impulsos de @{name}",
   "account.media": "Media",
   "account.mention": "Esmentar @{name}",
   "account.moved_to": "{name} s'ha mogut a:",
-  "account.mute": "Silenciar @{name}",
+  "account.mute": "Silencia @{name}",
   "account.mute_notifications": "Notificacions desactivades de @{name}",
-  "account.posts": "Publicacions",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Informe @{name}",
   "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
-  "account.share": "Compartir el perfil de @{name}",
+  "account.share": "Comparteix el perfil de @{name}",
   "account.show_reblogs": "Mostra els impulsos de @{name}",
-  "account.unblock": "Desbloquejar @{name}",
+  "account.unblock": "Desbloca @{name}",
   "account.unblock_domain": "Mostra {domain}",
-  "account.unfollow": "Deixar de seguir",
+  "account.unfollow": "Deixa de seguir",
   "account.unmute": "Treure silenci de @{name}",
   "account.unmute_notifications": "Activar notificacions de @{name}",
-  "account.view_full_profile": "Veure el perfil complet",
+  "account.view_full_profile": "Mostra el perfil complet",
   "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
   "bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
   "bundle_column_error.retry": "Torna-ho a provar",
@@ -31,7 +32,7 @@
   "bundle_modal_error.close": "Tanca",
   "bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
   "bundle_modal_error.retry": "Torna-ho a provar",
-  "column.blocks": "Usuaris bloquejats",
+  "column.blocks": "Usuaris blocats",
   "column.community": "Línia de temps local",
   "column.favourites": "Favorits",
   "column.follow_requests": "Peticions per seguir-te",
@@ -45,46 +46,46 @@
   "column_header.hide_settings": "Amaga la configuració",
   "column_header.moveLeft_settings": "Mou la columna cap a l'esquerra",
   "column_header.moveRight_settings": "Mou la columna cap a la dreta",
-  "column_header.pin": "Fixar",
+  "column_header.pin": "Fixa",
   "column_header.show_settings": "Mostra la configuració",
-  "column_header.unpin": "Deslligar",
+  "column_header.unpin": "No fixis",
   "column_subheading.navigation": "Navegació",
   "column_subheading.settings": "Configuració",
   "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.",
   "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.",
-  "compose_form.lock_disclaimer.lock": "bloquejat",
+  "compose_form.lock_disclaimer.lock": "blocat",
   "compose_form.placeholder": "En què estàs pensant?",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive": "Marcar multimèdia com a sensible",
-  "compose_form.spoiler": "Amagar text darrera l'advertència",
-  "compose_form.spoiler_placeholder": "Escriu l'advertència aquí",
-  "confirmation_modal.cancel": "Cancel·lar",
-  "confirmations.block.confirm": "Bloquejar",
-  "confirmations.block.message": "Estàs segur que vols bloquejar {name}?",
-  "confirmations.delete.confirm": "Esborrar",
-  "confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?",
-  "confirmations.domain_block.confirm": "Amagar tot el domini",
-  "confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.",
-  "confirmations.mute.confirm": "Silenciar",
+  "compose_form.sensitive": "Marca el contingut multimèdia com a sensible",
+  "compose_form.spoiler": "Amaga el text darrera darrere un avís",
+  "compose_form.spoiler_placeholder": "Escriu l'avís aquí",
+  "confirmation_modal.cancel": "Cancel·la",
+  "confirmations.block.confirm": "Bloca",
+  "confirmations.block.message": "Estàs segur que vols blocar {name}?",
+  "confirmations.delete.confirm": "Suprimeix",
+  "confirmations.delete.message": "Estàs segur que vols suprimir aquest estat?",
+  "confirmations.delete_list.confirm": "Suprimeix",
+  "confirmations.delete_list.message": "Estàs segur que vols suprimir permanentment aquesta llista?",
+  "confirmations.domain_block.confirm": "Amaga tot el domini",
+  "confirmations.domain_block.message": "Estàs realment, realment segur que vols blocar totalment {domain}? En la majoria dels casos blocar o silenciar uns pocs objectius és suficient i preferible.",
+  "confirmations.mute.confirm": "Silencia",
   "confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
-  "confirmations.unfollow.confirm": "Deixar de seguir",
+  "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ó.",
   "embed.preview": "Aquí tenim quin aspecte tindrá:",
   "emoji_button.activity": "Activitat",
   "emoji_button.custom": "Personalitzat",
-  "emoji_button.flags": "Marques",
-  "emoji_button.food": "Menjar i Beure",
-  "emoji_button.label": "Inserir emoji",
+  "emoji_button.flags": "Banderes",
+  "emoji_button.food": "Menjar i beure",
+  "emoji_button.label": "Insereix un emoji",
   "emoji_button.nature": "Natura",
   "emoji_button.not_found": "Emojos no!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objectes",
   "emoji_button.people": "Gent",
-  "emoji_button.recent": "Freqüentment utilitzat",
-  "emoji_button.search": "Cercar...",
+  "emoji_button.recent": "Usats freqüentment",
+  "emoji_button.search": "Cerca...",
   "emoji_button.search_results": "Resultats de la cerca",
   "emoji_button.symbols": "Símbols",
   "emoji_button.travel": "Viatges i Llocs",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "fa {number} minuts",
   "relative_time.seconds": "fa {number} segons",
   "reply_indicator.cancel": "Cancel·lar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentaris addicionals",
   "report.submit": "Enviar",
   "report.target": "Informes",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags",
   "search_popout.tips.user": "usuari",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
   "standalone.public_title": "Una mirada a l'interior ...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Arrossega i deixa anar per carregar",
   "upload_button.label": "Afegir multimèdia",
   "upload_form.description": "Descriure els problemes visuals",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Desfer",
   "upload_progress.label": "Pujant...",
   "video.close": "Tancar el vídeo",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index aadcfac97..7f29f83ce 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -14,6 +14,7 @@
   "account.mute": "@{name} stummschalten",
   "account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
   "account.posts": "Beiträge",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "@{name} melden",
   "account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen",
   "account.share": "Profil von @{name} teilen",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Abbrechen",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Zusätzliche Kommentare",
   "report.submit": "Absenden",
   "report.target": "{target} melden",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
   "standalone.public_title": "Ein kleiner Einblick …",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Zum Hochladen hereinziehen",
   "upload_button.label": "Mediendatei hinzufügen",
   "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Entfernen",
   "upload_progress.label": "Wird hochgeladen …",
   "video.close": "Video schließen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index c0841d6f5..52a82ff27 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -318,11 +318,19 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Toots",
+        "id": "account.posts"
+      },
+      {
+        "defaultMessage": "Toots with replies",
+        "id": "account.posts_with_replies"
+      },
+      {
         "defaultMessage": "Media",
         "id": "account.media"
       }
     ],
-    "path": "app/javascript/mastodon/features/account_gallery/index.json"
+    "path": "app/javascript/mastodon/features/account_timeline/components/header.json"
   },
   {
     "descriptors": [
@@ -651,6 +659,18 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "People",
+        "id": "search_results.accounts"
+      },
+      {
+        "defaultMessage": "Toots",
+        "id": "search_results.statuses"
+      },
+      {
+        "defaultMessage": "Hashtags",
+        "id": "search_results.hashtags"
+      },
+      {
         "defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
         "id": "search_results.total"
       }
@@ -707,12 +727,16 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Describe for the visually impaired",
+        "id": "upload_form.description"
+      },
+      {
         "defaultMessage": "Undo",
         "id": "upload_form.undo"
       },
       {
-        "defaultMessage": "Describe for the visually impaired",
-        "id": "upload_form.description"
+        "defaultMessage": "Crop",
+        "id": "upload_form.focus"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/components/upload.json"
@@ -1576,6 +1600,18 @@
       {
         "defaultMessage": "Report {target}",
         "id": "report.target"
+      },
+      {
+        "defaultMessage": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+        "id": "report.hint"
+      },
+      {
+        "defaultMessage": "The account is from another server. Send an anonymized copy of the report there as well?",
+        "id": "report.forward_hint"
+      },
+      {
+        "defaultMessage": "Forward to {target}",
+        "id": "report.forward"
       }
     ],
     "path": "app/javascript/mastodon/features/ui/components/report_modal.json"
@@ -1672,4 +1708,4 @@
     ],
     "path": "app/javascript/mastodon/features/video/index.json"
   }
-]
+]
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index e72c45bf2..d2d8f3995 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -14,6 +14,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Toots",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval. Click to cancel follow request",
   "account.share": "Share @{name}'s profile",
@@ -214,6 +215,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
   "report.submit": "Submit",
   "report.target": "Reporting {target}",
@@ -223,6 +227,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -259,6 +266,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index cf0b4f2ec..b6153145f 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -1,236 +1,243 @@
 {
   "account.block": "Bloki @{name}",
-  "account.block_domain": "Kaŝi ĉion el {domain}",
-  "account.disclaimer_full": "La ĉi-subaj informoj povas ne plene reflekti la profilon de la uzanto.",
-  "account.edit_profile": "Redakti la profilon",
+  "account.block_domain": "Kaŝi ĉion de {domain}",
+  "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
+  "account.edit_profile": "Redakti profilon",
   "account.follow": "Sekvi",
   "account.followers": "Sekvantoj",
   "account.follows": "Sekvatoj",
   "account.follows_you": "Sekvas vin",
-  "account.hide_reblogs": "Maski diskonigitaĵojn de @{name}",
-  "account.media": "Sonbildaĵoj",
+  "account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
+  "account.media": "Aŭdovidaĵoj",
   "account.mention": "Mencii @{name}",
-  "account.moved_to": "{name} movis al:",
+  "account.moved_to": "{name} moviĝis al:",
   "account.mute": "Silentigi @{name}",
   "account.mute_notifications": "Silentigi sciigojn el @{name}",
-  "account.posts": "Mesaĝoj",
+  "account.posts": "Hupoj",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Signali @{name}",
-  "account.requested": "Atendas aprobon",
+  "account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado",
   "account.share": "Diskonigi la profilon de @{name}",
-  "account.show_reblogs": "Montri diskonigaĵojn de @{name}",
+  "account.show_reblogs": "Montri diskonigojn de @{name}",
   "account.unblock": "Malbloki @{name}",
   "account.unblock_domain": "Malkaŝi {domain}",
-  "account.unfollow": "Ne plus sekvi",
+  "account.unfollow": "Ne plu sekvi",
   "account.unmute": "Malsilentigi @{name}",
   "account.unmute_notifications": "Malsilentigi sciigojn de @{name}",
   "account.view_full_profile": "Vidi plenan profilon",
-  "boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi",
-  "bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.",
+  "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
+  "bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
   "bundle_column_error.retry": "Bonvolu reprovi",
   "bundle_column_error.title": "Reta eraro",
   "bundle_modal_error.close": "Fermi",
-  "bundle_modal_error.message": "Io malfunkciis ŝargante tiun ĉi komponanton.",
+  "bundle_modal_error.message": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
   "bundle_modal_error.retry": "Bonvolu reprovi",
   "column.blocks": "Blokitaj uzantoj",
   "column.community": "Loka tempolinio",
-  "column.favourites": "Favoritoj",
-  "column.follow_requests": "Abonpetoj",
+  "column.favourites": "Stelumoj",
+  "column.follow_requests": "Petoj de sekvado",
   "column.home": "Hejmo",
   "column.lists": "Listoj",
   "column.mutes": "Silentigitaj uzantoj",
   "column.notifications": "Sciigoj",
-  "column.pins": "Alpinglitaj pepoj",
+  "column.pins": "Alpinglitaj mesaĝoj",
   "column.public": "Fratara tempolinio",
   "column_back_button.label": "Reveni",
   "column_header.hide_settings": "Kaŝi agordojn",
   "column_header.moveLeft_settings": "Movi kolumnon maldekstren",
   "column_header.moveRight_settings": "Movi kolumnon dekstren",
   "column_header.pin": "Alpingli",
-  "column_header.show_settings": "Malkaŝi agordojn",
+  "column_header.show_settings": "Montri agordojn",
   "column_header.unpin": "Depingli",
   "column_subheading.navigation": "Navigado",
   "column_subheading.settings": "Agordoj",
-  "compose_form.hashtag_warning": "Ĉi tiu pepo ne estos listigita en iu ajn kradvorta listo pro ĝia videbleco estas “eksterlista”. Nur publikaj pepoj povas esti kradvorte trovitaj.",
-  "compose_form.lock_disclaimer": "Via konta ne estas ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.",
+  "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.",
+  "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.",
   "compose_form.lock_disclaimer.lock": "ŝlosita",
   "compose_form.placeholder": "Pri kio vi pensas?",
   "compose_form.publish": "Hup",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive": "Marki ke la enhavo estas tikla",
-  "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
-  "compose_form.spoiler_placeholder": "Skribu tie vian averton",
-  "confirmation_modal.cancel": "Malfari",
+  "compose_form.sensitive": "Marki aŭdovidaĵon tikla",
+  "compose_form.spoiler": "Kaŝi tekston malantaŭ averto",
+  "compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie",
+  "confirmation_modal.cancel": "Nuligi",
   "confirmations.block.confirm": "Bloki",
-  "confirmations.block.message": "Ĉu vi konfirmas la blokadon de {name}?",
-  "confirmations.delete.confirm": "Malaperigi",
-  "confirmations.delete.message": "Ĉu vi konfirmas la malaperigon de tiun pepon?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Ĉu vi certas forviŝi ĉi tiun liston por ĉiam?",
-  "confirmations.domain_block.confirm": "Kaŝi la tutan reton",
-  "confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas bloki {domain} tute? Plej ofte, kelkaj celitaj blokadoj aŭ silentigoj estas sufiĉaj kaj preferindaj.",
+  "confirmations.block.message": "Ĉu vi certas, ke vi volas bloki {name}?",
+  "confirmations.delete.confirm": "Forigi",
+  "confirmations.delete.message": "Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon?",
+  "confirmations.delete_list.confirm": "Forigi",
+  "confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
+  "confirmations.domain_block.confirm": "Kaŝi la tutan domajnon",
+  "confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas tute bloki {domain}? Plej ofte, trafa blokado kaj silentigado sufiĉas kaj preferindas.",
   "confirmations.mute.confirm": "Silentigi",
-  "confirmations.mute.message": "Ĉu vi konfirmas la silentigon de {name}?",
+  "confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
   "confirmations.unfollow.confirm": "Ne plu sekvi",
-  "confirmations.unfollow.message": "Ĉu vi volas ĉesi sekvi {name}?",
-  "embed.instructions": "Enmetu tiun statkonigon ĉe vian retejon kopiante la ĉi-suban kodon.",
+  "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.",
   "embed.preview": "Ĝi aperos tiel:",
-  "emoji_button.activity": "Aktivecoj",
-  "emoji_button.custom": "Personaj",
+  "emoji_button.activity": "Agadoj",
+  "emoji_button.custom": "Propraj",
   "emoji_button.flags": "Flagoj",
   "emoji_button.food": "Manĝi kaj trinki",
-  "emoji_button.label": "Enmeti mieneton",
+  "emoji_button.label": "Enmeti emoĝion",
   "emoji_button.nature": "Naturo",
-  "emoji_button.not_found": "Neniuj mienetoj!! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Objektoj",
+  "emoji_button.not_found": "Neniu emoĝio!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Aĵoj",
   "emoji_button.people": "Homoj",
   "emoji_button.recent": "Ofte uzataj",
   "emoji_button.search": "Serĉo…",
-  "emoji_button.search_results": "Rezultatoj de serĉo",
+  "emoji_button.search_results": "Serĉaj rezultoj",
   "emoji_button.symbols": "Simboloj",
-  "emoji_button.travel": "Vojaĝoj & lokoj",
+  "emoji_button.travel": "Vojaĝoj kaj lokoj",
   "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
-  "empty_column.hashtag": "Ĝise, neniu enhavo estas asociita kun tiu kradvorto.",
+  "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
   "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
   "empty_column.home.public_timeline": "la publika tempolinio",
-  "empty_column.list": "Estas ankoraŭ nenio en ĉi tiu listo. Tuj kiam anoj de ĉi tiu listo publikigos, ties pepoj aperos ĉi tie.",
-  "empty_column.notifications": "Vi dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.",
-  "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj instancoj por plenigi la publikan tempolinion",
-  "follow_request.authorize": "Akcepti",
+  "empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.",
+  "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
+  "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion",
+  "follow_request.authorize": "Rajtigi",
   "follow_request.reject": "Rifuzi",
   "getting_started.appsshort": "Aplikaĵoj",
   "getting_started.faq": "Oftaj demandoj",
   "getting_started.heading": "Por komenci",
-  "getting_started.open_source_notice": "Mastodono estas malfermkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
+  "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
   "getting_started.userguide": "Gvidilo de uzo",
   "home.column_settings.advanced": "Precizaj agordoj",
   "home.column_settings.basic": "Bazaj agordoj",
-  "home.column_settings.filter_regex": "Forfiltri per regulesprimo",
+  "home.column_settings.filter_regex": "Filtri per regulesprimoj",
   "home.column_settings.show_reblogs": "Montri diskonigojn",
   "home.column_settings.show_replies": "Montri respondojn",
-  "home.settings": "Agordoj de la kolumno",
-  "keyboard_shortcuts.back": "reeniri",
-  "keyboard_shortcuts.boost": "diskonigi",
-  "keyboard_shortcuts.column": "fokusigi statuson en unu el la columnoj",
-  "keyboard_shortcuts.compose": "por fokusigi la redaktujon",
-  "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "subenmovi en la listo",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "ŝatitaren",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "home.settings": "Kolumnaj agordoj",
+  "keyboard_shortcuts.back": "por reveni",
+  "keyboard_shortcuts.boost": "por diskonigi",
+  "keyboard_shortcuts.column": "por fokusigi mesaĝon en unu el la kolumnoj",
+  "keyboard_shortcuts.compose": "por fokusigi la tekstujon",
+  "keyboard_shortcuts.description": "Priskribo",
+  "keyboard_shortcuts.down": "por iri suben en la listo",
+  "keyboard_shortcuts.enter": "por malfermi mesaĝon",
+  "keyboard_shortcuts.favourite": "por stelumi",
+  "keyboard_shortcuts.heading": "Klavaraj mallongigoj",
   "keyboard_shortcuts.hotkey": "Rapidklavo",
-  "keyboard_shortcuts.legend": "por montri ĉi tiun legendon",
-  "keyboard_shortcuts.mention": "por sciigi ties aŭtoron",
+  "keyboard_shortcuts.legend": "por montri ĉi tiun noton",
+  "keyboard_shortcuts.mention": "por mencii la aŭtoron",
   "keyboard_shortcuts.reply": "por respondi",
-  "keyboard_shortcuts.search": "por fokusigi la serĉadon",
-  "keyboard_shortcuts.toot": "por ekredakti tute novan pepon",
-  "keyboard_shortcuts.unfocus": "por malfokusigi la redaktujon aŭ la serĉilon",
-  "keyboard_shortcuts.up": "por suprenmovi en la listo",
+  "keyboard_shortcuts.search": "por fokusigi la serĉilon",
+  "keyboard_shortcuts.toot": "por komenci tute novan mesaĝon",
+  "keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon",
+  "keyboard_shortcuts.up": "por iri supren en la listo",
   "lightbox.close": "Fermi",
-  "lightbox.next": "Malantaŭa",
+  "lightbox.next": "Sekva",
   "lightbox.previous": "Antaŭa",
   "lists.account.add": "Aldoni al la listo",
-  "lists.account.remove": "Forviŝi de la listo",
-  "lists.delete": "Delete list",
+  "lists.account.remove": "Forigi de la listo",
+  "lists.delete": "Forigi la liston",
   "lists.edit": "Redakti la liston",
   "lists.new.create": "Aldoni liston",
-  "lists.new.title_placeholder": "Titulo de la nova listo",
-  "lists.search": "Serĉi el la homoj kiujn vi sekvas",
+  "lists.new.title_placeholder": "Titolo de la nova listo",
+  "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
   "lists.subheading": "Viaj listoj",
-  "loading_indicator.label": "Ŝarganta…",
-  "media_gallery.toggle_visible": "Baskuli videblecon",
+  "loading_indicator.label": "Ŝargado…",
+  "media_gallery.toggle_visible": "Baskuligi videblecon",
   "missing_indicator.label": "Ne trovita",
-  "missing_indicator.sublabel": "Ĉi tiu rimedo ne troviĝis",
-  "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el tiu ĉi uzanto?",
+  "missing_indicator.sublabel": "Ĉi tiu rimedo ne estis trovita",
+  "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?",
   "navigation_bar.blocks": "Blokitaj uzantoj",
   "navigation_bar.community_timeline": "Loka tempolinio",
-  "navigation_bar.edit_profile": "Redakti la profilon",
-  "navigation_bar.favourites": "Favoritaj",
-  "navigation_bar.follow_requests": "Abonpetoj",
-  "navigation_bar.info": "Plia informo",
-  "navigation_bar.keyboard_shortcuts": "Klavmallongigo",
+  "navigation_bar.edit_profile": "Redakti profilon",
+  "navigation_bar.favourites": "Stelumoj",
+  "navigation_bar.follow_requests": "Petoj de sekvado",
+  "navigation_bar.info": "Pri ĉiu tiu nodo",
+  "navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
   "navigation_bar.lists": "Listoj",
   "navigation_bar.logout": "Elsaluti",
   "navigation_bar.mutes": "Silentigitaj uzantoj",
-  "navigation_bar.pins": "Alpinglitaj pepoj",
+  "navigation_bar.pins": "Alpinglitaj mesaĝoj",
   "navigation_bar.preferences": "Preferoj",
   "navigation_bar.public_timeline": "Fratara tempolinio",
-  "notification.favourite": "{name} favoris vian mesaĝon",
-  "notification.follow": "{name} sekvis vin",
+  "notification.favourite": "{name} stelumis vian mesaĝon",
+  "notification.follow": "{name} eksekvis vin",
   "notification.mention": "{name} menciis vin",
   "notification.reblog": "{name} diskonigis vian mesaĝon",
-  "notifications.clear": "Forviŝi la sciigojn",
-  "notifications.clear_confirmation": "Ĉu vi certe volas malaperigi ĉiujn viajn sciigojn?",
-  "notifications.column_settings.alert": "Retumilaj atentigoj",
-  "notifications.column_settings.favourite": "Favoritoj:",
+  "notifications.clear": "Forviŝi sciigojn",
+  "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?",
+  "notifications.column_settings.alert": "Retumilaj sciigoj",
+  "notifications.column_settings.favourite": "Stelumoj:",
   "notifications.column_settings.follow": "Novaj sekvantoj:",
   "notifications.column_settings.mention": "Mencioj:",
   "notifications.column_settings.push": "Puŝsciigoj",
-  "notifications.column_settings.push_meta": "Tiu ĉi aparato",
+  "notifications.column_settings.push_meta": "Ĉi tiu aparato",
   "notifications.column_settings.reblog": "Diskonigoj:",
-  "notifications.column_settings.show": "Montri en kolono",
+  "notifications.column_settings.show": "Montri en kolumno",
   "notifications.column_settings.sound": "Eligi sonon",
   "onboarding.done": "Farita",
-  "onboarding.next": "Malantaŭa",
-  "onboarding.page_five.public_timelines": "La loka tempolinio enhavas mesaĝojn de ĉiuj ĉe {domain}. La federacia tempolinio enhavas ĉiujn mesaĝojn de uzantoj, kiujn iu ĉe {domain} sekvas. Ambaŭ tre utilas por trovi novajn kunparolantojn.",
-  "onboarding.page_four.home": "La hejma tempolinio enhavas la mesaĝojn de ĉiuj uzantoj, kiuj vi sekvas.",
-  "onboarding.page_four.notifications": "La sciiga kolumno informas vin kiam iu interagas kun vi.",
-  "onboarding.page_one.federation": "Mastodono estas reto de nedependaj serviloj, unuiĝintaj por krei pligrandan socian retejon. Ni nomas tiujn servilojn instancoj.",
-  "onboarding.page_one.full_handle": "Via tuta uzantnomo",
-  "onboarding.page_one.handle_hint": "Jen kion vi dirintus al viaj amikoj por serĉi.",
-  "onboarding.page_one.welcome": "Bonvenon al Mastodono!",
-  "onboarding.page_six.admin": "Via instancestro estas {admin}.",
-  "onboarding.page_six.almost_done": "Estas preskaŭ finita…",
-  "onboarding.page_six.appetoot": "Bonan a‘pepi’ton!",
-  "onboarding.page_six.apps_available": "{apps} estas elŝuteblaj por iOS, Androido kaj alioj.",
-  "onboarding.page_six.github": "Mastodono estas libera, senpaga kaj malfermkoda programaro. Vi povas signali cimojn, proponi funkciojn aŭ kontribui al gîa kreskado ĉe {github}.",
-  "onboarding.page_six.guidelines": "komunreguloj",
-  "onboarding.page_six.read_guidelines": "Ni petas vin: ne forgesu legi la {guidelines}n de {domain}!",
-  "onboarding.page_six.various_app": "telefon-aplikaĵoj",
-  "onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian avataron, priskribon kaj vian nomon. Vi tie trovos ankoraŭ aliajn agordojn.",
-  "onboarding.page_three.search": "Uzu la serĉokampo por trovi uzantojn kaj esplori kradvortojn tiel ke {illustration} kaj {introductions}. Por trovi iun, kiu ne estas ĉe ĉi tiu instanco, uzu ĝian kompletan uznomon.",
-  "onboarding.page_two.compose": "Skribu pepojn en la verkkolumno. Vi povas aldoni bildojn, ŝanĝi la agordojn de privateco kaj aldoni tiklavertojn (« content warning ») dank' al la piktogramoj malsupre.",
-  "onboarding.skip": "Pasigi",
-  "privacy.change": "Alĝustigi la privateco de la mesaĝo",
-  "privacy.direct.long": "Vidigi nur al la menciitaj personoj",
+  "onboarding.next": "Sekva",
+  "onboarding.page_five.public_timelines": "La loka tempolinio montras publikajn mesaĝojn de ĉiuj en {domain}. La fratara tempolinio montras publikajn mesaĝojn de ĉiuj, kiuj estas sekvataj de homoj en {domain}. Tio estas la publikaj tempolinioj, kio estas bona maniero por malkovri novajn homojn.",
+  "onboarding.page_four.home": "La hejma tempolinio montras mesaĝojn de ĉiuj uzantoj, kiujn vi sekvas.",
+  "onboarding.page_four.notifications": "La sciiga kolumno montras kiam iu interagas kun vi.",
+  "onboarding.page_one.federation": "Mastodon estas reto de sendependaj serviloj, unuiĝintaj por krei pligrandan socian reton. Ni nomas tiujn servilojn nodoj.",
+  "onboarding.page_one.full_handle": "Via kompleta uzantnomo",
+  "onboarding.page_one.handle_hint": "Jen kion vi petus al viaj amikoj serĉi.",
+  "onboarding.page_one.welcome": "Bonvenon en Mastodon!",
+  "onboarding.page_six.admin": "Via noda administranto estas {admin}.",
+  "onboarding.page_six.almost_done": "Preskaŭ finita…",
+  "onboarding.page_six.appetoot": "Saĝan mesaĝadon!",
+  "onboarding.page_six.apps_available": "{apps} estas disponeblaj por iOS, Android kaj aliaj platformoj.",
+  "onboarding.page_six.github": "Mastodon estas libera, senpaga kaj malfermitkoda programo. Vi povas raporti cimojn, proponi funkciojn aŭ kontribui al la kodo en {github}.",
+  "onboarding.page_six.guidelines": "komunumaj gvidlinioj",
+  "onboarding.page_six.read_guidelines": "Bonvolu atenti pri la {guidelines} de {domain}!",
+  "onboarding.page_six.various_app": "telefonaj aplikaĵoj",
+  "onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian profilbildon, priskribon kaj nomon. Vi ankaŭ trovos tie aliajn agordojn.",
+  "onboarding.page_three.search": "Uzu la serĉilon por trovi uzantojn kaj esplori kradvortojn, tiel {illustration} kaj {introductions}. Por trovi iun, kiu ne estas en ĉi tiu nodo, uzu ties kompletan uzantnomon.",
+  "onboarding.page_two.compose": "Skribu mesaĝojn en la skriba kolumno. Vi povas alŝuti bildojn, ŝanĝi privatecajn agordojn, kaj aldoni avertojn pri la enhavo per la subaj bildetoj.",
+  "onboarding.skip": "Preterpasi",
+  "privacy.change": "Agordi mesaĝan privatecon",
+  "privacy.direct.long": "Afiŝi nur al menciitaj uzantoj",
   "privacy.direct.short": "Rekta",
-  "privacy.private.long": "Vidigi nur al viaj sekvantoj",
-  "privacy.private.short": "Nursekvanta",
-  "privacy.public.long": "Vidigi en publikaj tempolinioj",
+  "privacy.private.long": "Afiŝi nur al sekvantoj",
+  "privacy.private.short": "Nur por sekvantoj",
+  "privacy.public.long": "Afiŝi en publikaj tempolinioj",
   "privacy.public.short": "Publika",
-  "privacy.unlisted.long": "Ne vidigi en publikaj tempolinioj",
+  "privacy.unlisted.long": "Ne afiŝi en publikaj tempolinioj",
   "privacy.unlisted.short": "Nelistigita",
-  "regeneration_indicator.label": "Elŝultanta…",
-  "regeneration_indicator.sublabel": "Via ĉefpaĝo estas preparanta!",
+  "regeneration_indicator.label": "Ŝargado…",
+  "regeneration_indicator.sublabel": "Via hejma fluo pretiĝas!",
   "relative_time.days": "{number}t",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "nun",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
-  "reply_indicator.cancel": "Malfari",
+  "reply_indicator.cancel": "Nuligi",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Pliaj komentoj",
   "report.submit": "Sendi",
-  "report.target": "Signalaĵo",
+  "report.target": "Signali {target}",
   "search.placeholder": "Serĉi",
   "search_popout.search_format": "Detala serĉo",
   "search_popout.tips.hashtag": "kradvorto",
-  "search_popout.tips.status": "statkonigo",
-  "search_popout.tips.text": "Simpla teksto eligas la kongruajn afiŝnomojn, uznomojn kaj kradvortojn",
+  "search_popout.tips.status": "mesaĝoj",
+  "search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
   "search_popout.tips.user": "uzanto",
-  "search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}",
-  "standalone.public_title": "Rigardeti…",
-  "status.block": "Block @{name}",
-  "status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
+  "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
+  "standalone.public_title": "Enrigardo…",
+  "status.block": "Bloki @{name}",
+  "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
   "status.delete": "Forigi",
-  "status.embed": "Enmeti",
-  "status.favourite": "Favori",
-  "status.load_more": "Ŝargi plie",
-  "status.media_hidden": "Sonbildaĵo kaŝita",
+  "status.embed": "Enkorpigi",
+  "status.favourite": "Stelumi",
+  "status.load_more": "Ŝargi pli",
+  "status.media_hidden": "Aŭdovidaĵo kaŝita",
   "status.mention": "Mencii @{name}",
   "status.more": "Pli",
   "status.mute": "Silentigi @{name}",
   "status.mute_conversation": "Silentigi konversacion",
-  "status.open": "Disfaldi statkonigon",
-  "status.pin": "Pingli al la profilo",
+  "status.open": "Grandigi ĉi tiun mesaĝon",
+  "status.pin": "Alpingli en la profilo",
   "status.reblog": "Diskonigi",
   "status.reblogged_by": "{name} diskonigis",
   "status.reply": "Respondi",
@@ -239,28 +246,29 @@
   "status.sensitive_toggle": "Alklaki por vidi",
   "status.sensitive_warning": "Tikla enhavo",
   "status.share": "Diskonigi",
-  "status.show_less": "Refaldi",
-  "status.show_more": "Disfaldi",
+  "status.show_less": "Malgrandigi",
+  "status.show_more": "Grandigi",
   "status.unmute_conversation": "Malsilentigi konversacion",
   "status.unpin": "Depingli de profilo",
   "tabs_bar.compose": "Ekskribi",
-  "tabs_bar.federated_timeline": "Federacia tempolinio",
+  "tabs_bar.federated_timeline": "Fratara tempolinio",
   "tabs_bar.home": "Hejmo",
   "tabs_bar.local_timeline": "Loka tempolinio",
   "tabs_bar.notifications": "Sciigoj",
   "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
-  "upload_area.title": "Algliti por alŝuti",
-  "upload_button.label": "Aldoni sonbildaĵon",
-  "upload_form.description": "Priskribi por la misvidantaj",
+  "upload_area.title": "Altreni kaj lasi por alŝuti",
+  "upload_button.label": "Aldoni aŭdovidaĵon",
+  "upload_form.description": "Priskribi por misvidantaj homoj",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Malfari",
-  "upload_progress.label": "Alŝutanta…",
+  "upload_progress.label": "Alŝutado…",
   "video.close": "Fermi videon",
-  "video.exit_fullscreen": "Eliri el plenekrano",
-  "video.expand": "Vastigi videon",
-  "video.fullscreen": "Igi plenekrane",
+  "video.exit_fullscreen": "Eksigi plenekrana",
+  "video.expand": "Grandigi videon",
+  "video.fullscreen": "Igi plenekrana",
   "video.hide": "Kaŝi videon",
   "video.mute": "Silentigi",
   "video.pause": "Paŭzi",
-  "video.play": "Legi",
+  "video.play": "Ekigi",
   "video.unmute": "Malsilentigi"
 }
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 4bb15396c..5f16ec974 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -14,6 +14,7 @@
   "account.mute": "Silenciar a @{name}",
   "account.mute_notifications": "Silenciar notificaciones de @{name}",
   "account.posts": "Publicaciones",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Reportar a @{name}",
   "account.requested": "Esperando aprobación",
   "account.share": "Compartir el perfil de @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentarios adicionales",
   "report.submit": "Publicar",
   "report.target": "Reportando",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
   "search_popout.tips.user": "usuario",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Un pequeño vistazo...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Arrastra y suelta para subir",
   "upload_button.label": "Subir multimedia",
   "upload_form.description": "Describir para los usuarios con dificultad visual",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Deshacer",
   "upload_progress.label": "Subiendo…",
   "video.close": "Cerrar video",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 6846da66d..4fd467a66 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -14,6 +14,7 @@
   "account.mute": "بی‌صدا کردن @{name}",
   "account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
   "account.posts": "نوشته‌ها",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "گزارش @{name}",
   "account.requested": "در انتظار پذیرش",
   "account.share": "هم‌رسانی نمایهٔ @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "لغو",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "توضیح اضافه",
   "report.submit": "بفرست",
   "report.target": "گزارش‌دادن",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "نوشته",
   "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
   "search_popout.tips.user": "کاربر",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
   "standalone.public_title": "نگاهی به کاربران این سرور...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "برای بارگذاری به این‌جا بکشید",
   "upload_button.label": "افزودن تصویر",
   "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "واگردانی",
   "upload_progress.label": "بارگذاری...",
   "video.close": "بستن ویدیو",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index eb81e7eb4..d27ba1834 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -1,266 +1,274 @@
 {
   "account.block": "Estä @{name}",
-  "account.block_domain": "Hide everything from {domain}",
-  "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
+  "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.",
   "account.edit_profile": "Muokkaa",
   "account.follow": "Seuraa",
   "account.followers": "Seuraajia",
   "account.follows": "Seuraa",
   "account.follows_you": "Seuraa sinua",
-  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.hide_reblogs": "Piilota buustaukset käyttäjältä @{name}",
   "account.media": "Media",
   "account.mention": "Mainitse @{name}",
-  "account.moved_to": "{name} has moved to:",
-  "account.mute": "Mute @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
-  "account.posts": "Postit",
+  "account.moved_to": "{name} on muuttanut instanssiin:",
+  "account.mute": "Mykistä @{name}",
+  "account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
+  "account.posts": "Töötit",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
-  "account.requested": "Odottaa hyväksyntää",
-  "account.share": "Share @{name}'s profile",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.requested": "Odottaa hyväksyntää. Klikkaa peruuttaaksesi seurauspyynnön",
+  "account.share": "Jaa käyttäjän @{name} profiili",
+  "account.show_reblogs": "Näytä boostaukset käyttäjältä @{name}",
   "account.unblock": "Salli @{name}",
-  "account.unblock_domain": "Unhide {domain}",
-  "account.unfollow": "Lopeta seuraaminen",
-  "account.unmute": "Unmute @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
-  "account.view_full_profile": "View full profile",
-  "boost_modal.combo": "You can press {combo} to skip this next time",
-  "bundle_column_error.body": "Something went wrong while loading this component.",
-  "bundle_column_error.retry": "Try again",
+  "account.unblock_domain": "Näytä {domain}",
+  "account.unfollow": "Lakkaa seuraamasta",
+  "account.unmute": "Poista mykistys käyttäjältä @{name}",
+  "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta",
+  "account.view_full_profile": "Näytä koko profiili",
+  "boost_modal.combo": "Voit painaa näppäimiä {combo} ohittaaksesi tämän ensi kerralla",
+  "bundle_column_error.body": "Jokin meni vikaan tätä komponenttia ladatessa.",
+  "bundle_column_error.retry": "Yritä uudestaan",
   "bundle_column_error.title": "Network error",
-  "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Something went wrong while loading this component.",
-  "bundle_modal_error.retry": "Try again",
-  "column.blocks": "Blocked users",
+  "bundle_modal_error.close": "Sulje",
+  "bundle_modal_error.message": "Jokin meni vikaan tätä komponenttia ladatessa.",
+  "bundle_modal_error.retry": "Yritä uudestaan",
+  "column.blocks": "Estetyt käyttäjät",
   "column.community": "Paikallinen aikajana",
-  "column.favourites": "Favourites",
-  "column.follow_requests": "Follow requests",
+  "column.favourites": "Suosikit",
+  "column.follow_requests": "Seurauspyynnöt",
   "column.home": "Koti",
-  "column.lists": "Lists",
-  "column.mutes": "Muted users",
+  "column.lists": "Listat",
+  "column.mutes": "Mykistetyt käyttäjät",
   "column.notifications": "Ilmoitukset",
   "column.pins": "Pinned toot",
   "column.public": "Yleinen aikajana",
   "column_back_button.label": "Takaisin",
-  "column_header.hide_settings": "Hide settings",
-  "column_header.moveLeft_settings": "Move column to the left",
-  "column_header.moveRight_settings": "Move column to the right",
-  "column_header.pin": "Pin",
-  "column_header.show_settings": "Show settings",
-  "column_header.unpin": "Unpin",
-  "column_subheading.navigation": "Navigation",
-  "column_subheading.settings": "Settings",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
-  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
-  "compose_form.lock_disclaimer.lock": "locked",
+  "column_header.hide_settings": "Piilota asetukset",
+  "column_header.moveLeft_settings": "Siirrä saraketta vasemmalle",
+  "column_header.moveRight_settings": "Siirrä saraketta oikealle",
+  "column_header.pin": "Kiinnitä",
+  "column_header.show_settings": "Näytä asetukset",
+  "column_header.unpin": "Poista kiinnitys",
+  "column_subheading.navigation": "Navigaatio",
+  "column_subheading.settings": "Asetukset",
+  "compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.",
+  "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.",
+  "compose_form.lock_disclaimer.lock": "lukittu",
   "compose_form.placeholder": "Mitä sinulla on mielessä?",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive": "Merkitse media herkäksi",
   "compose_form.spoiler": "Piiloita teksti varoituksen taakse",
   "compose_form.spoiler_placeholder": "Content warning",
-  "confirmation_modal.cancel": "Cancel",
-  "confirmations.block.confirm": "Block",
-  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmation_modal.cancel": "Peruuta",
+  "confirmations.block.confirm": "Estä",
+  "confirmations.block.message": "Oletko varma, että haluat estää käyttäjän {name}?",
   "confirmations.delete.confirm": "Delete",
-  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete.message": "Oletko varma, että haluat poistaa tämän statuspäivityksen?",
   "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
-  "confirmations.domain_block.confirm": "Hide entire domain",
-  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
-  "confirmations.mute.confirm": "Mute",
-  "confirmations.mute.message": "Are you sure you want to mute {name}?",
-  "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.",
-  "embed.preview": "Here is what it will look like:",
-  "emoji_button.activity": "Activity",
-  "emoji_button.custom": "Custom",
-  "emoji_button.flags": "Flags",
-  "emoji_button.food": "Food & Drink",
-  "emoji_button.label": "Insert emoji",
-  "emoji_button.nature": "Nature",
-  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Objects",
-  "emoji_button.people": "People",
-  "emoji_button.recent": "Frequently used",
-  "emoji_button.search": "Search...",
-  "emoji_button.search_results": "Search results",
-  "emoji_button.symbols": "Symbols",
-  "emoji_button.travel": "Travel & Places",
-  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
-  "empty_column.hashtag": "There is nothing in this hashtag yet.",
-  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
-  "empty_column.home.public_timeline": "the public timeline",
-  "empty_column.list": "There is nothing in this list yet.",
-  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
-  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
-  "follow_request.authorize": "Authorize",
-  "follow_request.reject": "Reject",
-  "getting_started.appsshort": "Apps",
+  "confirmations.delete_list.message": "Oletko varma, että haluat poistaa tämän listan pysyvästi?",
+  "confirmations.domain_block.confirm": "Piilota koko verkko-osoite",
+  "confirmations.domain_block.message": "Oletko aivan oikeasti varma että haluat estää koko verkko-osoitteen {domain}? Useimmissa tapauksissa muutamat kohdistetut estot ja mykistykset ovat riittäviä ja suositeltavampia.",
+  "confirmations.mute.confirm": "Mykistä",
+  "confirmations.mute.message": "Oletko varma että haluat mykistää käyttäjän {name}?",
+  "confirmations.unfollow.confirm": "Lakkaa seuraamasta",
+  "confirmations.unfollow.message": "Oletko varma, että haluat lakata seuraamasta käyttäjää {name}?",
+  "embed.instructions": "Upota tämä statuspäivitys sivullesi kopioimalla alla oleva koodi.",
+  "embed.preview": "Tältä se tulee näyttämään:",
+  "emoji_button.activity": "Aktiviteetit",
+  "emoji_button.custom": "Mukautetut",
+  "emoji_button.flags": "Liput",
+  "emoji_button.food": "Ruoka ja juoma",
+  "emoji_button.label": "Lisää emoji",
+  "emoji_button.nature": "Luonto",
+  "emoji_button.not_found": "Ei emojeja!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objektit",
+  "emoji_button.people": "Ihmiset",
+  "emoji_button.recent": "Usein käytetyt",
+  "emoji_button.search": "Etsi...",
+  "emoji_button.search_results": "Hakutulokset",
+  "emoji_button.symbols": "Symbolit",
+  "emoji_button.travel": "Matkailu",
+  "empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista saadaksesi pyörät pyörimään!",
+  "empty_column.hashtag": "Tässä hashtagissa ei ole vielä mitään.",
+  "empty_column.home": "Kotiaikajanasi on tyhjä! Käy vierailemassa {public}ssa tai käytä hakutoimintoa aloittaaksesi ja tavataksesi muita käyttäjiä.",
+  "empty_column.home.public_timeline": "yleinen aikajana",
+  "empty_column.list": "Tämä lista on vielä tyhjä. Kun listan jäsenet julkaisevat statuspäivityksiä, ne näkyvät tässä.",
+  "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Juttele muille aloittaaksesi keskustelun.",
+  "empty_column.public": "Täällä ei ole mitään! Kirjoita jotain julkisesti, tai käy manuaalisesti seuraamassa käyttäjiä muista instansseista saadaksesi sisältöä",
+  "follow_request.authorize": "Valtuuta",
+  "follow_request.reject": "Hylkää",
+  "getting_started.appsshort": "Sovellukset",
   "getting_started.faq": "FAQ",
   "getting_started.heading": "Aloitus",
-  "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.",
-  "getting_started.userguide": "User Guide",
-  "home.column_settings.advanced": "Advanced",
-  "home.column_settings.basic": "Basic",
-  "home.column_settings.filter_regex": "Filter out by regular expressions",
-  "home.column_settings.show_reblogs": "Show boosts",
-  "home.column_settings.show_replies": "Show replies",
-  "home.settings": "Column settings",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.",
+  "getting_started.userguide": "Käyttöopas",
+  "home.column_settings.advanced": "Tarkemmat asetukset",
+  "home.column_settings.basic": "Perusasetukset",
+  "home.column_settings.filter_regex": "Suodata säännöllisten lauseiden avulla",
+  "home.column_settings.show_reblogs": "Näytä buustaukset",
+  "home.column_settings.show_replies": "Näytä vastaukset",
+  "home.settings": "Sarakeasetukset",
+  "keyboard_shortcuts.back": "liikkuaksesi taaksepäin",
+  "keyboard_shortcuts.boost": "buustataksesi",
+  "keyboard_shortcuts.column": "keskittääksesi statuspäivitykseen yhdessä sarakkeista",
+  "keyboard_shortcuts.compose": "aktivoidaksesi tekstinkirjoitusalueen",
   "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.down": "liikkuaksesi listassa alaspäin",
   "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "keyboard_shortcuts.favourite": "tykätäksesi",
+  "keyboard_shortcuts.heading": "Näppäinoikotiet",
+  "keyboard_shortcuts.hotkey": "Pikanäppäin",
+  "keyboard_shortcuts.legend": "näyttääksesi tämän selitteen",
+  "keyboard_shortcuts.mention": "mainitaksesi julkaisijan",
+  "keyboard_shortcuts.reply": "vastataksesi",
+  "keyboard_shortcuts.search": "aktivoidaksesi hakukentän",
+  "keyboard_shortcuts.toot": "aloittaaksesi uuden töötin kirjoittamisen",
+  "keyboard_shortcuts.unfocus": "poistaaksesi aktivoinnin tekstikentästä/hakukentästä",
+  "keyboard_shortcuts.up": "liikkuaksesi listassa ylöspäin",
   "lightbox.close": "Sulje",
-  "lightbox.next": "Next",
-  "lightbox.previous": "Previous",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
+  "lightbox.next": "Seuraava",
+  "lightbox.previous": "Edellinen",
+  "lists.account.add": "Lisää listaan",
+  "lists.account.remove": "Poista listalta",
   "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lists.edit": "Muokkaa listaa",
+  "lists.new.create": "Lisää lista",
+  "lists.new.title_placeholder": "Uuden listan otsikko",
+  "lists.search": "Etsi seuraamiesi henkilöiden joukosta",
+  "lists.subheading": "Omat listat",
   "loading_indicator.label": "Ladataan...",
-  "media_gallery.toggle_visible": "Toggle visibility",
-  "missing_indicator.label": "Not found",
-  "missing_indicator.sublabel": "This resource could not be found",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
-  "navigation_bar.blocks": "Blocked users",
+  "media_gallery.toggle_visible": "Säädä näkyvyyttä",
+  "missing_indicator.label": "Ei löydetty",
+  "missing_indicator.sublabel": "Tätä resurssia ei löytynyt",
+  "mute_modal.hide_notifications": "Piilota ilmoitukset tältä käyttäjältä?",
+  "navigation_bar.blocks": "Estetyt käyttäjät",
   "navigation_bar.community_timeline": "Paikallinen aikajana",
   "navigation_bar.edit_profile": "Muokkaa profiilia",
-  "navigation_bar.favourites": "Favourites",
-  "navigation_bar.follow_requests": "Follow requests",
-  "navigation_bar.info": "Extended information",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.favourites": "Suosikit",
+  "navigation_bar.follow_requests": "Seurauspyynnöt",
+  "navigation_bar.info": "Tietoa tästä instanssista",
+  "navigation_bar.keyboard_shortcuts": "Näppäinoikotiet",
+  "navigation_bar.lists": "Listat",
   "navigation_bar.logout": "Kirjaudu ulos",
-  "navigation_bar.mutes": "Muted users",
-  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.mutes": "Mykistetyt käyttäjät",
+  "navigation_bar.pins": "Kiinnitetyt töötit",
   "navigation_bar.preferences": "Ominaisuudet",
   "navigation_bar.public_timeline": "Yleinen aikajana",
   "notification.favourite": "{name} tykkäsi statuksestasi",
   "notification.follow": "{name} seurasi sinua",
   "notification.mention": "{name} mainitsi sinut",
   "notification.reblog": "{name} buustasi statustasi",
-  "notifications.clear": "Clear notifications",
-  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.clear": "Tyhjennä ilmoitukset",
+  "notifications.clear_confirmation": "Oletko varma, että haluat lopullisesti tyhjentää kaikki ilmoituksesi?",
   "notifications.column_settings.alert": "Työpöytä ilmoitukset",
   "notifications.column_settings.favourite": "Tykkäyksiä:",
   "notifications.column_settings.follow": "Uusia seuraajia:",
   "notifications.column_settings.mention": "Mainintoja:",
-  "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
+  "notifications.column_settings.push": "Push-ilmoitukset",
+  "notifications.column_settings.push_meta": "Tämä laite",
   "notifications.column_settings.reblog": "Buusteja:",
   "notifications.column_settings.show": "Näytä sarakkeessa",
-  "notifications.column_settings.sound": "Play sound",
-  "onboarding.done": "Done",
-  "onboarding.next": "Next",
-  "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
-  "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
-  "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
-  "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.full_handle": "Your full handle",
+  "notifications.column_settings.sound": "Soita ääni",
+  "onboarding.done": "Valmis",
+  "onboarding.next": "Seuraava",
+  "onboarding.page_five.public_timelines": "Paikallinen aikajana näyttää kaikki julkiset julkaisut kaikilta, jotka ovat verkko-osoitteessa {domain}. Yleinen aikajana näyttää julkiset julkaisut kaikilta niiltä, joita käyttäjät verkko-osoitteessa {domain} seuraavat. Nämä ovat julkiset aikajanat, ja ne ovat hyviä tapoja löytää uusia ihmisiä.",
+  "onboarding.page_four.home": "Kotiaikajana näyttää julkaisut ihmisiltä joita seuraat.",
+  "onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.",
+  "onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.",
+  "onboarding.page_one.full_handle": "Koko käyttäjänimesi",
   "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
-  "onboarding.page_one.welcome": "Welcome to Mastodon!",
-  "onboarding.page_six.admin": "Your instance's admin is {admin}.",
-  "onboarding.page_six.almost_done": "Almost done...",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
-  "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+  "onboarding.page_one.welcome": "Tervetuloa Mastodoniin!",
+  "onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.",
+  "onboarding.page_six.almost_done": "Melkein valmista...",
+  "onboarding.page_six.appetoot": "Bon Appetööt!",
+  "onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.",
   "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
-  "onboarding.page_six.guidelines": "community guidelines",
-  "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
-  "onboarding.page_six.various_app": "mobile apps",
-  "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
-  "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
-  "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
-  "onboarding.skip": "Skip",
-  "privacy.change": "Adjust status privacy",
-  "privacy.direct.long": "Post to mentioned users only",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "Post to followers only",
-  "privacy.private.short": "Followers-only",
-  "privacy.public.long": "Post to public timelines",
-  "privacy.public.short": "Public",
-  "privacy.unlisted.long": "Do not show in public timelines",
-  "privacy.unlisted.short": "Unlisted",
-  "regeneration_indicator.label": "Loading…",
-  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "onboarding.page_six.guidelines": "yhteisön säännöt",
+  "onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!",
+  "onboarding.page_six.various_app": "mobiilisovellukset",
+  "onboarding.page_three.profile": "Muokkaa profiiliasi muuttaaksesi kuvakettasi, esittelyäsi ja nimimerkkiäsi. Löydät sieltä myös muita henkilökohtaisia asetuksia.",
+  "onboarding.page_three.search": "Käytä hakukenttää löytääksesi ihmisiä ja etsiäksesi hashtageja, kuten {illustration} tai {introductions}. Hakeaksesi henkilöä joka on toisessa instanssissa, käytä hänen käyttäjänimeään kokonaisuudessaan.",
+  "onboarding.page_two.compose": "Kirjoita postauksia kirjoita-sarakkeessa. Voit ladata kuvia, vaihtaa yksityisyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.",
+  "onboarding.skip": "Ohita",
+  "privacy.change": "Säädä töötin yksityisyysasetuksia",
+  "privacy.direct.long": "Julkaise vain mainituille käyttäjille",
+  "privacy.direct.short": "Yksityisviesti",
+  "privacy.private.long": "Julkaise vain seuraajille",
+  "privacy.private.short": "Vain seuraajat",
+  "privacy.public.long": "Julkaise julkisille aikajanoille",
+  "privacy.public.short": "Julkinen",
+  "privacy.unlisted.long": "Älä julkaise yleisillä aikajanoilla",
+  "privacy.unlisted.short": "Julkinen, mutta älä näytä julkisella aikajanalla",
+  "regeneration_indicator.label": "Ladataan…",
+  "regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "nyt",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Peruuta",
-  "report.placeholder": "Additional comments",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Lisäkommentit",
   "report.submit": "Submit",
   "report.target": "Reporting",
   "search.placeholder": "Hae",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.search_format": "Tarkennettu haku",
+  "search_popout.tips.hashtag": "hashtagi",
   "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
+  "search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
+  "search_popout.tips.user": "käyttäjä",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "standalone.public_title": "A look inside...",
+  "standalone.public_title": "Kurkistus sisälle...",
   "status.block": "Block @{name}",
-  "status.cannot_reblog": "This post cannot be boosted",
+  "status.cannot_reblog": "Tätä postausta ei voi buustata",
   "status.delete": "Poista",
-  "status.embed": "Embed",
+  "status.embed": "Upota",
   "status.favourite": "Tykkää",
-  "status.load_more": "Load more",
-  "status.media_hidden": "Media hidden",
+  "status.load_more": "Lataa lisää",
+  "status.media_hidden": "Media piilotettu",
   "status.mention": "Mainitse @{name}",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
-  "status.mute_conversation": "Mute conversation",
-  "status.open": "Expand this status",
-  "status.pin": "Pin on profile",
+  "status.more": "Lisää",
+  "status.mute": "Mykistä @{name}",
+  "status.mute_conversation": "Mykistä keskustelu",
+  "status.open": "Laajenna statuspäivitys",
+  "status.pin": "Kiinnitä profiiliin",
   "status.reblog": "Buustaa",
   "status.reblogged_by": "{name} buustasi",
   "status.reply": "Vastaa",
-  "status.replyAll": "Reply to thread",
+  "status.replyAll": "Vastaa ketjuun",
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Klikkaa nähdäksesi",
   "status.sensitive_warning": "Arkaluontoista sisältöä",
-  "status.share": "Share",
-  "status.show_less": "Show less",
-  "status.show_more": "Show more",
-  "status.unmute_conversation": "Unmute conversation",
-  "status.unpin": "Unpin from profile",
+  "status.share": "Jaa",
+  "status.show_less": "Näytä vähemmän",
+  "status.show_more": "Näytä lisää",
+  "status.unmute_conversation": "Poista mykistys keskustelulta",
+  "status.unpin": "Irrota profiilista",
   "tabs_bar.compose": "Luo",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Koti",
-  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.local_timeline": "Paikallinen",
   "tabs_bar.notifications": "Ilmoitukset",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
-  "upload_area.title": "Drag & drop to upload",
+  "ui.beforeunload": "Luonnoksesi menetetään, jos poistut Mastodonista.",
+  "upload_area.title": "Raahaa ja pudota tähän ladataksesi",
   "upload_button.label": "Lisää mediaa",
-  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.description": "Anna kuvaus näkörajoitteisia varten",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Peru",
-  "upload_progress.label": "Uploading...",
-  "video.close": "Close video",
-  "video.exit_fullscreen": "Exit full screen",
-  "video.expand": "Expand video",
+  "upload_progress.label": "Ladataan...",
+  "video.close": "Sulje video",
+  "video.exit_fullscreen": "Poistu koko näytön tilasta",
+  "video.expand": "Laajenna video",
   "video.fullscreen": "Full screen",
-  "video.hide": "Hide video",
-  "video.mute": "Mute sound",
-  "video.pause": "Pause",
-  "video.play": "Play",
-  "video.unmute": "Unmute sound"
+  "video.hide": "Piilota video",
+  "video.mute": "Mykistä ääni",
+  "video.pause": "Keskeytä",
+  "video.play": "Toista",
+  "video.unmute": "Poista mykistys ääneltä"
 }
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 17075f6de..b10187dfd 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -14,6 +14,7 @@
   "account.mute": "Masquer @{name}",
   "account.mute_notifications": "Ignorer les notifications de @{name}",
   "account.posts": "Statuts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Signaler",
   "account.requested": "Invitation envoyée",
   "account.share": "Partager le profil de @{name}",
@@ -163,7 +164,7 @@
   "notifications.column_settings.alert": "Notifications locales",
   "notifications.column_settings.favourite": "Favoris :",
   "notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
-  "notifications.column_settings.mention": "Mentions :",
+  "notifications.column_settings.mention": "Mentions :",
   "notifications.column_settings.push": "Notifications push",
   "notifications.column_settings.push_meta": "Cet appareil",
   "notifications.column_settings.reblog": "Partages :",
@@ -200,13 +201,16 @@
   "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
   "privacy.unlisted.short": "Non-listé",
   "regeneration_indicator.label": "Chargement…",
-  "regeneration_indicator.sublabel": "Votre page principale est en cours de préparation!",
+  "regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
   "relative_time.days": "{number} j",
   "relative_time.hours": "{number} h",
   "relative_time.just_now": "à l’instant",
   "relative_time.minutes": "{number} min",
   "relative_time.seconds": "{number} s",
   "reply_indicator.cancel": "Annuler",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Commentaires additionnels",
   "report.submit": "Envoyer",
   "report.target": "Signalement",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "statuts",
   "search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms d’utilisateur⋅ice et les hashtags correspondants",
   "search_popout.tips.user": "utilisateur⋅ice",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
   "standalone.public_title": "Jeter un coup d’œil…",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Glissez et déposez pour envoyer",
   "upload_button.label": "Joindre un média",
   "upload_form.description": "Décrire pour les malvoyants",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Annuler",
   "upload_progress.label": "Envoi en cours…",
   "video.close": "Fermer la vidéo",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 9e8352ba4..0b91f57ae 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -13,7 +13,8 @@
   "account.moved_to": "{name} marchou a:",
   "account.mute": "Acalar @{name}",
   "account.mute_notifications": "Acalar as notificacións de @{name}",
-  "account.posts": "Publicacións",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Informar sobre @{name}",
   "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento",
   "account.share": "Compartir o perfil de @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentarios adicionais",
   "report.submit": "Enviar",
   "report.target": "Informar {target}",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "estado",
   "search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
   "search_popout.tips.user": "usuaria",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
   "standalone.public_title": "Ollada dentro...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Arrastre e solte para subir",
   "upload_button.label": "Engadir medios",
   "upload_form.description": "Describa para deficientes visuais",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Desfacer",
   "upload_progress.label": "Subindo...",
   "video.close": "Pechar video",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index d6665295f..e430fabf8 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -14,6 +14,7 @@
   "account.mute": "להשתיק את @{name}",
   "account.mute_notifications": "להסתיר התראות מאת @{name}",
   "account.posts": "הודעות",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "לדווח על @{name}",
   "account.requested": "בהמתנה לאישור",
   "account.share": "לשתף את אודות @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "ביטול",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "הערות נוספות",
   "report.submit": "שליחה",
   "report.target": "דיווח",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
   "search_popout.tips.user": "משתמש(ת)",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
   "standalone.public_title": "הצצה פנימה...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "ניתן להעלות על ידי Drag & drop",
   "upload_button.label": "הוספת מדיה",
   "upload_form.description": "תיאור לכבדי ראיה",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "ביטול",
   "upload_progress.label": "עולה...",
   "video.close": "סגירת וידאו",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index c49ae160f..543a739bc 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -14,6 +14,7 @@
   "account.mute": "Utišaj @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Postovi",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Prijavi @{name}",
   "account.requested": "Čeka pristanak",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Otkaži",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Dodatni komentari",
   "report.submit": "Pošalji",
   "report.target": "Prijavljivanje",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Povuci i spusti kako bi uploadao",
   "upload_button.label": "Dodaj media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Poništi",
   "upload_progress.label": "Uploadam...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 316687129..38b3698b6 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -7,13 +7,14 @@
   "account.followers": "Követők",
   "account.follows": "Követve",
   "account.follows_you": "Követnek téged",
-  "account.hide_reblogs": "@{name} kedvenceinek elrejtése",
+  "account.hide_reblogs": "Rejtsd el a tülkölést @{name}-tól/től",
   "account.media": "Média",
   "account.mention": "@{name} említése",
   "account.moved_to": "{name} átköltözött:",
   "account.mute": "@{name} némítása",
   "account.mute_notifications": "@{name} értesítések némítása",
   "account.posts": "Státuszok",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "@{name} jelentése",
   "account.requested": "Engedélyre vár. Kattintson a követési kérés visszavonására",
   "account.share": "@{name} profiljának megosztása",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Mégsem",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "További kommentek",
   "report.submit": "Submit",
   "report.target": "Reporting",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "felhasználó",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "Betekintés...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Húzza ide a feltöltéshez",
   "upload_button.label": "Média hozzáadása",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Mégsem",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index f46db5b00..15d8f22e6 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -14,6 +14,7 @@
   "account.mute": "Լռեցնել @{name}֊ին",
   "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
   "account.posts": "Գրառումներ",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Բողոքել @{name}֊ից",
   "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
   "account.share": "Կիսվել @{name}֊ի էջով",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}ր",
   "relative_time.seconds": "{number}վ",
   "reply_indicator.cancel": "Չեղարկել",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Լրացուցիչ մեկնաբանություններ",
   "report.submit": "Ուղարկել",
   "report.target": "Բողոքել {target}֊ի մասին",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "թութ",
   "search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
   "search_popout.tips.user": "օգտատեր",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "Այս պահին…",
   "status.block": "Արգելափակել @{name}֊ին",
@@ -252,6 +259,7 @@
   "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
   "upload_button.label": "Ավելացնել մեդիա",
   "upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Հետարկել",
   "upload_progress.label": "Վերբեռնվում է…",
   "video.close": "Փակել  տեսագրությունը",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 6edf855d3..ac4aaa1aa 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -14,6 +14,7 @@
   "account.mute": "Bisukan @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Postingan",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Laporkan @{name}",
   "account.requested": "Menunggu persetujuan",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Batal",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Komentar tambahan",
   "report.submit": "Kirim",
   "report.target": "Melaporkan",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Seret & lepaskan untuk mengunggah",
   "upload_button.label": "Tambahkan media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
   "upload_progress.label": "Mengunggah...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 7aa7cb144..d431fac49 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -14,6 +14,7 @@
   "account.mute": "Celar @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Mesaji",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Denuncar @{name}",
   "account.requested": "Vartante aprobo",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Nihiligar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Plusa komenti",
   "report.submit": "Sendar",
   "report.target": "Denuncante",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Tranar faligar por kargar",
   "upload_button.label": "Adjuntar kontenajo",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Desfacar",
   "upload_progress.label": "Kargante...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 61467df16..eec1a7feb 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -14,6 +14,7 @@
   "account.mute": "Silenzia @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Segnala @{name}",
   "account.requested": "In attesa di approvazione",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Annulla",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Commenti aggiuntivi",
   "report.submit": "Invia",
   "report.target": "Invio la segnalazione",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Trascina per caricare",
   "upload_button.label": "Aggiungi file multimediale",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Annulla",
   "upload_progress.label": "Sto caricando...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 4449af52f..f1ad2911d 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -14,11 +14,12 @@
   "account.mute": "@{name}さんをミュート",
   "account.mute_notifications": "@{name}さんからの通知を受け取らない",
   "account.posts": "投稿",
+  "account.posts_with_replies": "トゥートと返信",
   "account.report": "@{name}さんを通報",
   "account.requested": "フォロー承認待ちです。クリックしてキャンセル",
   "account.share": "@{name}さんのプロフィールを共有する",
   "account.show_reblogs": "@{name}さんからのブーストを表示",
-  "account.unblock": "@{name}さんのブロック解除",
+  "account.unblock": "@{name}さんのブロックを解除",
   "account.unblock_domain": "{domain}を表示",
   "account.unfollow": "フォロー解除",
   "account.unmute": "@{name}さんのミュートを解除",
@@ -71,7 +72,7 @@
   "confirmations.delete_list.confirm": "削除",
   "confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
   "confirmations.domain_block.confirm": "ドメイン全体を非表示",
-  "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
+  "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
   "confirmations.mute.confirm": "ミュート",
   "confirmations.mute.message": "本当に{name}さんをミュートしますか?",
   "confirmations.unfollow.confirm": "フォロー解除",
@@ -207,13 +208,16 @@
   "privacy.unlisted.long": "公開TLで表示しない",
   "privacy.unlisted.short": "未収載",
   "regeneration_indicator.label": "読み込み中…",
-  "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
+  "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
   "relative_time.days": "{number}日前",
   "relative_time.hours": "{number}時間前",
   "relative_time.just_now": "今",
   "relative_time.minutes": "{number}分前",
   "relative_time.seconds": "{number}秒前",
   "reply_indicator.cancel": "キャンセル",
+  "report.forward": "{target} に転送する",
+  "report.forward_hint": "このアカウントは別のインスタンスに所属しています。通報内容を匿名で転送しますか?",
+  "report.hint": "通報内容はあなたのインスタンスのモデレーターへ送信されます。通報理由を入力してください。:",
   "report.placeholder": "追加コメント",
   "report.submit": "通報する",
   "report.target": "{target}さんを通報する",
@@ -223,6 +227,9 @@
   "search_popout.tips.status": "トゥート",
   "search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
   "search_popout.tips.user": "ユーザー",
+  "search_results.accounts": "人々",
+  "search_results.hashtags": "ハッシュタグ",
+  "search_results.statuses": "トゥート",
   "search_results.total": "{count, number}件の結果",
   "standalone.public_title": "今こんな話をしています...",
   "status.block": "@{name}さんをブロック",
@@ -259,6 +266,7 @@
   "upload_area.title": "ドラッグ&ドロップでアップロード",
   "upload_button.label": "メディアを追加",
   "upload_form.description": "視覚障害者のための説明",
+  "upload_form.focus": "焦点",
   "upload_form.undo": "やり直す",
   "upload_progress.label": "アップロード中...",
   "video.close": "動画を閉じる",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 02b4b18e2..8e7c82682 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -14,6 +14,7 @@
   "account.mute": "@{name} 뮤트",
   "account.mute_notifications": "@{name}의 알림을 뮤트",
   "account.posts": "게시물",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "@{name} 신고",
   "account.requested": "승인 대기 중. 클릭해서 취소하기",
   "account.share": "@{name}의 프로파일 공유",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}분 전",
   "relative_time.seconds": "{number}초 전",
   "reply_indicator.cancel": "취소",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "코멘트",
   "report.submit": "신고하기",
   "report.target": "문제가 된 사용자",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "툿",
   "search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
   "search_popout.tips.user": "유저",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number}건의 결과",
   "standalone.public_title": "지금 이런 이야기를 하고 있습니다…",
   "status.block": "@{name} 차단",
@@ -252,6 +259,7 @@
   "upload_area.title": "드래그 & 드롭으로 업로드",
   "upload_button.label": "미디어 추가",
   "upload_form.description": "시각장애인을 위한 설명",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "재시도",
   "upload_progress.label": "업로드 중...",
   "video.close": "동영상 닫기",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 11a012de5..315f92120 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -14,6 +14,7 @@
   "account.mute": "Negeer @{name}",
   "account.mute_notifications": "Negeer meldingen van @{name}",
   "account.posts": "Toots",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapporteer @{name}",
   "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
   "account.share": "Profiel van @{name} delen",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Annuleren",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Extra opmerkingen",
   "report.submit": "Verzenden",
   "report.target": "Rapporteer {target}",
@@ -216,9 +220,12 @@
   "search_popout.tips.status": "toot",
   "search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
   "search_popout.tips.user": "gebruiker",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
   "standalone.public_title": "Een kijkje binnenin...",
-  "status.block": "Block @{name}",
+  "status.block": "Blokkeer @{name}",
   "status.cannot_reblog": "Deze toot kan niet geboost worden",
   "status.delete": "Verwijderen",
   "status.embed": "Embed",
@@ -252,6 +259,7 @@
   "upload_area.title": "Hierin slepen om te uploaden",
   "upload_button.label": "Media toevoegen",
   "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Ongedaan maken",
   "upload_progress.label": "Uploaden...",
   "video.close": "Video sluiten",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 21fd50183..b319ea1a5 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -14,6 +14,7 @@
   "account.mute": "Demp @{name}",
   "account.mute_notifications": "Ignorer varsler fra @{name}",
   "account.posts": "Innlegg",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapportér @{name}",
   "account.requested": "Venter på godkjennelse",
   "account.share": "Del @{name}s profil",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Avbryt",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Tilleggskommentarer",
   "report.submit": "Send inn",
   "report.target": "Rapporterer",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
   "search_popout.tips.user": "bruker",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
   "standalone.public_title": "En titt inni...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Dra og slipp for å laste opp",
   "upload_button.label": "Legg til media",
   "upload_form.description": "Beskriv for synshemmede",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Angre",
   "upload_progress.label": "Laster opp...",
   "video.close": "Lukk video",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 3cf99028a..2402e1759 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -14,6 +14,7 @@
   "account.mute": "Rescondre @{name}",
   "account.mute_notifications": "Rescondre las notificacions de @{name}",
   "account.posts": "Estatuts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Senhalar @{name}",
   "account.requested": "Invitacion mandada. Clicatz per anullar",
   "account.share": "Partejar lo perfil a @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "fa {number}min",
   "relative_time.seconds": "fa {number}s",
   "reply_indicator.cancel": "Anullar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentaris addicionals",
   "report.submit": "Mandar",
   "report.target": "Senhalar {target}",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "estatut",
   "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents",
   "search_popout.tips.user": "utilizaire",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
   "standalone.public_title": "Una ulhada dedins…",
   "status.block": "Blocar @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Lisatz e depausatz per mandar",
   "upload_button.label": "Ajustar un mèdia",
   "upload_form.description": "Descripcion pels mal vesents",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Anullar",
   "upload_progress.label": "Mandadís…",
   "video.close": "Tampar la vidèo",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index bcc28b65a..4eccc3655 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -14,6 +14,7 @@
   "account.mute": "Wycisz @{name}",
   "account.mute_notifications": "Wycisz powiadomienia o @{name}",
   "account.posts": "Wpisy",
+  "account.posts_with_replies": "Wpisy z odpowiedziami",
   "account.report": "Zgłoś @{name}",
   "account.requested": "Oczekująca prośba, kliknij aby anulować",
   "account.share": "Udostępnij profil @{name}",
@@ -99,7 +100,7 @@
   "empty_column.home.public_timeline": "publiczna oś czasu",
   "empty_column.list": "Nie ma nic na tej liście. Kiedy członkowie listy dodadzą nowe wpisy, pojawia się one tutaj.",
   "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
-  "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.",
+  "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić",
   "follow_request.authorize": "Autoryzuj",
   "follow_request.reject": "Odrzuć",
   "getting_started.appsshort": "Aplikacje",
@@ -134,11 +135,11 @@
   "lightbox.next": "Następne",
   "lightbox.previous": "Poprzednie",
   "lists.account.add": "Dodaj do listy",
-  "lists.account.remove": "Usuń z listy",
+  "lists.account.remove": "Usunąć z listy",
   "lists.delete": "Usuń listę",
   "lists.edit": "Edytuj listę",
   "lists.new.create": "Utwórz listę",
-  "lists.new.title_placeholder": "Wprowadź tytuł listy…",
+  "lists.new.title_placeholder": "Wprowadź tytuł listy",
   "lists.search": "Szukaj wśród osób które śledzisz",
   "lists.subheading": "Twoje listy",
   "loading_indicator.label": "Ładowanie…",
@@ -206,7 +207,7 @@
   "privacy.public.short": "Publiczny",
   "privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu",
   "privacy.unlisted.short": "Niewidoczny",
-  "regeneration_indicator.label": "Ładowanie…",
+  "regeneration_indicator.label": "Ładuję…",
   "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
   "relative_time.days": "{number} dni",
   "relative_time.hours": "{number} godz.",
@@ -214,6 +215,9 @@
   "relative_time.minutes": "{number} min.",
   "relative_time.seconds": "{number} s.",
   "reply_indicator.cancel": "Anuluj",
+  "report.forward": "Przekaż na {target}",
+  "report.forward_hint": "To konto znajduje się na innej instancji. Czy chcesz wysłać anonimową kopię zgłoszenia rnież na nią?",
+  "report.hint": "Zgłoszenie zostanie wysłane moderatorom Twojej instancji. Poniżej możesz też umieścić wyjaśnieni dlaczego zgłaszasz to konto:",
   "report.placeholder": "Dodatkowe komentarze",
   "report.submit": "Wyślij",
   "report.target": "Zgłaszanie {target}",
@@ -262,6 +266,7 @@
   "upload_area.title": "Przeciągnij i upuść aby wysłać",
   "upload_button.label": "Dodaj zawartość multimedialną",
   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Cofnij",
   "upload_progress.label": "Wysyłanie",
   "video.close": "Zamknij film",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 3d63da850..c07661e92 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -14,6 +14,7 @@
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Denunciar @{name}",
   "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
   "account.share": "Compartilhar perfil de @{name}",
@@ -90,7 +91,7 @@
   "emoji_button.travel": "Viagens & Lugares",
   "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
   "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.",
-  "empty_column.home": "Você ainda não segue usuário algo. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
+  "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.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
   "report.target": "Denunciar",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
   "search_popout.tips.user": "usuário",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Dê uma espiada...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar mídia",
   "upload_form.description": "Descreva a imagem para deficientes visuais",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Desfazer",
   "upload_progress.label": "Salvando...",
   "video.close": "Fechar vídeo",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index 2fd13db15..61233a1b9 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -14,6 +14,7 @@
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
   "account.share": "Partilhar o perfil @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
   "report.target": "Denunciar",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
   "search_popout.tips.user": "utilizador",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Espreitar lá dentro...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar media",
   "upload_form.description": "Descrição da imagem para pessoas com dificuldades visuais",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Anular",
   "upload_progress.label": "A gravar...",
   "video.close": "Fechar vídeo",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 58ffa8d55..76d3fec13 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -14,6 +14,7 @@
   "account.mute": "Заглушить",
   "account.mute_notifications": "Скрыть уведомления от @{name}",
   "account.posts": "Посты",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Пожаловаться",
   "account.requested": "Ожидает подтверждения",
   "account.share": "Поделиться профилем @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}м",
   "relative_time.seconds": "{number}с",
   "reply_indicator.cancel": "Отмена",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Комментарий",
   "report.submit": "Отправить",
   "report.target": "Жалуемся на",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "статус",
   "search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги",
   "search_popout.tips.user": "пользователь",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
   "standalone.public_title": "Прямо сейчас",
   "status.block": "Заблокировать @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Перетащите сюда, чтобы загрузить",
   "upload_button.label": "Добавить медиаконтент",
   "upload_form.description": "Описать для людей с нарушениями зрения",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Отменить",
   "upload_progress.label": "Загрузка...",
   "video.close": "Закрыть видео",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 1a0629708..de273770a 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -4,7 +4,7 @@
   "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.",
   "account.edit_profile": "Upraviť profil",
   "account.follow": "Následovať",
-  "account.followers": "Následovaťelia",
+  "account.followers": "Sledujúci",
   "account.follows": "Sledujete",
   "account.follows_you": "Následuje vás",
   "account.hide_reblogs": "Skryť povýšenia od @{name}",
@@ -13,7 +13,8 @@
   "account.moved_to": "{name} sa presunul/a na:",
   "account.mute": "Ignorovať @{name}",
   "account.mute_notifications": "Stĺmiť notifikácie od @{name}",
-  "account.posts": "Správy",
+  "account.posts": "Hlášky",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Nahlásiť @{name}",
   "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
   "account.share": "Zdieľať @{name} profil",
@@ -72,7 +73,7 @@
   "confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
   "confirmations.unfollow.confirm": "Nesledovať",
   "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
-  "embed.instructions": "Skopírujte kód uvedený nižšie pre pridanie tohto statusu na vašu web stránku.",
+  "embed.instructions": "Umiestnite kód uvedený nižšie pre pridanie tohto statusu na vašu web stránku.",
   "embed.preview": "Tu je ako to bude vyzerať:",
   "emoji_button.activity": "Aktivita",
   "emoji_button.custom": "Vlastné",
@@ -100,7 +101,7 @@
   "getting_started.appsshort": "Aplikácie",
   "getting_started.faq": "FAQ",
   "getting_started.heading": "Začíname",
-  "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispievať vlastným kódom môžeš na GitHube v {github}.",
+  "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispievať vlastným kódom môžete na GitHube v {github}.",
   "getting_started.userguide": "Používateľská príručka",
   "home.column_settings.advanced": "Rozšírené",
   "home.column_settings.basic": "Základné",
@@ -122,11 +123,11 @@
   "keyboard_shortcuts.mention": "spomenúť autora",
   "keyboard_shortcuts.reply": "odpovedať",
   "keyboard_shortcuts.search": "zamerať sa na vyhľadávanie",
-  "keyboard_shortcuts.toot": "začať úplne nový toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.toot": "začať úplne novú hlášku",
+  "keyboard_shortcuts.unfocus": "nesústrediť sa na písaciu plochu, alebo hľadanie",
   "keyboard_shortcuts.up": "posunúť sa vyššie v zozname",
   "lightbox.close": "Zatvoriť",
-  "lightbox.next": "Ďalší",
+  "lightbox.next": "Ďalšie",
   "lightbox.previous": "Predchádzajúci",
   "lists.account.add": "Pridať do zoznamu",
   "lists.account.remove": "Odobrať zo zoznamu",
@@ -159,10 +160,10 @@
   "notification.mention": "{name} vás spomenul",
   "notification.reblog": "{name} re-tootol tvoj status",
   "notifications.clear": "Vyčistiť zoznam notifikácii",
-  "notifications.clear_confirmation": "Naozaj chcete nenávratne vymazať všetky vaše notifikácie?",
+  "notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
   "notifications.column_settings.alert": "Notifikácie na ploche",
   "notifications.column_settings.favourite": "Obľúbené:",
-  "notifications.column_settings.follow": "Nový nasledujúci:",
+  "notifications.column_settings.follow": "Noví následujúci:",
   "notifications.column_settings.mention": "Zmienenia:",
   "notifications.column_settings.push": "Push notifikácie",
   "notifications.column_settings.push_meta": "Toto zariadenie",
@@ -175,7 +176,7 @@
   "onboarding.page_four.home": "Domovská časová os zobrazí správy od ľudí ktorých sledujete.",
   "onboarding.page_four.notifications": "Stĺpec s notifikáciami zobrazí keď budete s niekým komunikovať.",
   "onboarding.page_one.federation": "Mastodon je sieť nezávislých serverov, spojením ktorých vzniká jedna veľká federovaná sociálna sieť.",
-  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.full_handle": "Vaša celá prezývka aj s adresou",
   "onboarding.page_one.handle_hint": "Toto je čo by ste povedali vaším priateľom že majú hľadať.",
   "onboarding.page_one.welcome": "Vitajte na Mastodone!",
   "onboarding.page_six.admin": "Správca tohto servera je {admin}.",
@@ -193,7 +194,7 @@
   "privacy.change": "Zmeňiť viditeľnosť statusu",
   "privacy.direct.long": "Poslať priamo iba spomenutým používateľom",
   "privacy.direct.short": "Súkromne",
-  "privacy.private.long": "Poslať iba sledujúcim",
+  "privacy.private.long": "Poslať iba následovateľom",
   "privacy.private.short": "Iba pre sledujúcich",
   "privacy.public.long": "Poslať všetkým verejne",
   "privacy.public.short": "Verejné",
@@ -207,23 +208,29 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Zrušiť",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Ďalšie komentáre",
   "report.submit": "Poslať",
   "report.target": "Nahlásenie {target}",
   "search.placeholder": "Hľadať",
-  "search_popout.search_format": "Pokročilý tvar vyhľadávania",
-  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.search_format": "Pokročilý formát vyhľadávania",
+  "search_popout.tips.hashtag": "haštag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy",
   "search_popout.tips.user": "používateľ",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}",
   "standalone.public_title": "Pohľad dovnútra...",
   "status.block": "Blokovať @{name}",
   "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
   "status.delete": "Zmazať",
-  "status.embed": "Embed",
+  "status.embed": "Vložiť",
   "status.favourite": "Páči sa mi",
-  "status.load_more": "Zobraziť viac",
+  "status.load_more": "Zobraz viac",
   "status.media_hidden": "Skryté médiá",
   "status.mention": "Napísať @{name}",
   "status.more": "Viac",
@@ -239,8 +246,8 @@
   "status.sensitive_toggle": "Kliknite pre zobrazenie",
   "status.sensitive_warning": "Chúlostivý obsah",
   "status.share": "Zdieľať",
-  "status.show_less": "Zobraziť menej",
-  "status.show_more": "Zobraziť viac",
+  "status.show_less": "Zobraz menej",
+  "status.show_more": "Zobraz viac",
   "status.unmute_conversation": "Prestať ignorovať konverzáciu",
   "status.unpin": "Odopnúť z profilu",
   "tabs_bar.compose": "Napísať",
@@ -252,6 +259,7 @@
   "upload_area.title": "Ťahaj a pusti pre nahratie",
   "upload_button.label": "Pridať médiá",
   "upload_form.description": "Opis pre slabo vidiacich",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Navrátiť",
   "upload_progress.label": "Nahráva sa...",
   "video.close": "Zavrieť video",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index cd48967cb..bf67a52d6 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -14,6 +14,7 @@
   "account.mute": "Ućutkaj korisnika @{name}",
   "account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
   "account.posts": "Statusa",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Prijavi @{name}",
   "account.requested": "Čekam odobrenje. Kliknite da poništite zahtev za praćenje",
   "account.share": "Podeli profil korisnika @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Poništi",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Dodatni komentari",
   "report.submit": "Pošalji",
   "report.target": "Prijavljujem {target}",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Traženjem običnog teksta ćete dobiti sva pronađena imena, sva korisnička imena i sve nađene heštegove",
   "search_popout.tips.user": "korisnik",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}",
   "standalone.public_title": "Pogled iznutra...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Prevucite ovde da otpremite",
   "upload_button.label": "Dodaj multimediju",
   "upload_form.description": "Opiši za slabovide osobe",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Opozovi",
   "upload_progress.label": "Otpremam...",
   "video.close": "Zatvori video",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 595a70ea6..d8d1ebae7 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -14,6 +14,7 @@
   "account.mute": "Ућуткај корисника @{name}",
   "account.mute_notifications": "Искључи обавештења од корисника @{name}",
   "account.posts": "Статуса",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Пријави @{name}",
   "account.requested": "Чекам одобрење. Кликните да поништите захтев за праћење",
   "account.share": "Подели профил корисника @{name}",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Поништи",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Додатни коментари",
   "report.submit": "Пошаљи",
   "report.target": "Пријављујем {target}",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "статус",
   "search_popout.tips.text": "Тражењем обичног текста ћете добити сва пронађена имена, сва корисничка имена и све нађене хештегове",
   "search_popout.tips.user": "корисник",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}",
   "standalone.public_title": "Поглед изнутра...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Превуците овде да отпремите",
   "upload_button.label": "Додај мултимедију",
   "upload_form.description": "Опиши за слабовиде особе",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Опозови",
   "upload_progress.label": "Отпремам...",
   "video.close": "Затвори видео",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 3f25648c2..0bf66c547 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -14,6 +14,7 @@
   "account.mute": "Tysta @{name}",
   "account.mute_notifications": "Stäng av notifieringar från @{name}",
   "account.posts": "Inlägg",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapportera @{name}",
   "account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan",
   "account.share": "Dela @{name}'s profil",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Ångra",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Ytterligare kommentarer",
   "report.submit": "Skicka",
   "report.target": "Rapporterar {target}",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags",
   "search_popout.tips.user": "användare",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}",
   "standalone.public_title": "En titt inuti...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Dra & släpp för att ladda upp",
   "upload_button.label": "Lägg till media",
   "upload_form.description": "Beskriv för synskadade",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Ångra",
   "upload_progress.label": "Laddar upp...",
   "video.close": "Stäng video",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 740fb80e7..d6ccc9412 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -14,6 +14,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
   "report.submit": "Submit",
   "report.target": "Reporting",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 8805e52f4..702c8454d 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -14,6 +14,7 @@
   "account.mute": "Sustur @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Gönderiler",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapor et @{name}",
   "account.requested": "Onay bekleniyor",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "İptal",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Ek yorumlar",
   "report.submit": "Gönder",
   "report.target": "Raporlama",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Upload için sürükle bırak yapınız",
   "upload_button.label": "Görsel ekle",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Geri al",
   "upload_progress.label": "Yükleniyor...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 2cdaba0ac..b5bd88cbb 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -14,6 +14,7 @@
   "account.mute": "Заглушити",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "Пости",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Поскаржитися",
   "account.requested": "Очікує підтвердження",
   "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Відмінити",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Додаткові коментарі",
   "report.submit": "Відправити",
   "report.target": "Скаржимося на",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "Перетягніть сюди, щоб завантажити",
   "upload_button.label": "Додати медіаконтент",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Відмінити",
   "upload_progress.label": "Завантаження...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index a02211b8a..7cc5903cd 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -14,6 +14,7 @@
   "account.mute": "隐藏 @{name}",
   "account.mute_notifications": "隐藏来自 @{name} 的通知",
   "account.posts": "嘟文",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "举报 @{name}",
   "account.requested": "正在等待对方同意。点击以取消发送关注请求",
   "account.share": "分享 @{name} 的个人资料",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}分",
   "relative_time.seconds": "{number}秒",
   "reply_indicator.cancel": "取消",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "附言",
   "report.submit": "提交",
   "report.target": "举报 {target}",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "嘟文",
   "search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签",
   "search_popout.tips.user": "用户",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "共 {count, number} 个结果",
   "standalone.public_title": "大家都在干啥?",
   "status.block": "屏蔽 @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "将文件拖放到此处开始上传",
   "upload_button.label": "上传媒体文件",
   "upload_form.description": "为视觉障碍人士添加文字说明",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "取消上传",
   "upload_progress.label": "上传中…",
   "video.close": "关闭视频",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 0a3ae423d..d21ecc463 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -14,6 +14,7 @@
   "account.mute": "將 @{name} 靜音",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "文章",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "舉報 @{name}",
   "account.requested": "等候審批",
   "account.share": "分享 @{name} 的個人資料",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "取消",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "額外訊息",
   "report.submit": "提交",
   "report.target": "舉報",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} 項結果",
   "standalone.public_title": "站點一瞥…",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "將檔案拖放至此上載",
   "upload_button.label": "上載媒體檔案",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "還原",
   "upload_progress.label": "上載中……",
   "video.close": "關閉影片",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 1201fe3c7..f1ae29283 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -14,6 +14,7 @@
   "account.mute": "消音 @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.posts": "貼文",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "檢舉 @{name}",
   "account.requested": "正在等待許可",
   "account.share": "分享 @{name} 的用者資訊",
@@ -207,6 +208,9 @@
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "取消",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "更多訊息",
   "report.submit": "送出",
   "report.target": "通報中",
@@ -216,6 +220,9 @@
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} 項結果",
   "standalone.public_title": "站點一瞥…",
   "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
   "upload_area.title": "拖放來上傳",
   "upload_button.label": "增加媒體",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "復原",
   "upload_progress.label": "上傳中...",
   "video.close": "關閉影片",
diff --git a/app/javascript/mastodon/reducers/reports.js b/app/javascript/mastodon/reducers/reports.js
index a08bbec38..21ae6f93f 100644
--- a/app/javascript/mastodon/reducers/reports.js
+++ b/app/javascript/mastodon/reducers/reports.js
@@ -6,6 +6,7 @@ import {
   REPORT_CANCEL,
   REPORT_STATUS_TOGGLE,
   REPORT_COMMENT_CHANGE,
+  REPORT_FORWARD_CHANGE,
 } from '../actions/reports';
 import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
 
@@ -15,6 +16,7 @@ const initialState = ImmutableMap({
     account_id: null,
     status_ids: ImmutableSet(),
     comment: '',
+    forward: false,
   }),
 });
 
@@ -42,6 +44,8 @@ export default function reports(state = initialState, action) {
     });
   case REPORT_COMMENT_CHANGE:
     return state.setIn(['new', 'comment'], action.comment);
+  case REPORT_FORWARD_CHANGE:
+    return state.setIn(['new', 'forward'], action.forward);
   case REPORT_SUBMIT_REQUEST:
     return state.setIn(['new', 'isSubmitting'], true);
   case REPORT_SUBMIT_FAIL:
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index a95b75984..9ce83aa9b 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -15,117 +15,169 @@ $small-breakpoint: 960px;
   }
 }
 
-.show-xs,
-.show-sm {
-  display: none;
-}
+.landing-page {
+  .grid {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: 1fr 2fr;
+    grid-auto-columns: 25%;
+    grid-auto-rows: max-content;
+
+    .column-0 {
+      display: none;
+    }
 
-.show-m {
-  display: block;
-}
+    .column-1 {
+      grid-column: 1;
+      grid-row: 1;
+    }
 
-@media screen and (max-width: $small-breakpoint) {
-  .hide-sm {
-    display: none !important;
-  }
+    .column-2 {
+      grid-column: 2;
+      grid-row: 1;
+    }
 
-  .show-sm {
-    display: block !important;
-  }
-}
+    .column-3 {
+      grid-column: 3;
+      grid-row: 1 / 3;
+    }
 
-@media screen and (max-width: $column-breakpoint) {
-  .hide-xs {
-    display: none !important;
+    .column-4 {
+      grid-column: 1 / 3;
+      grid-row: 2;
+    }
   }
 
-  .show-xs {
-    display: block !important;
-  }
-}
+  @media screen and (max-width: $small-breakpoint) {
+    .grid {
+      grid-template-columns: 40% 60%;
 
-.row {
-  display: flex;
-  flex-wrap: wrap;
-  margin: 0 -5px;
+      .column-0 {
+        display: none;
+      }
 
-  @for $i from 1 through 15 {
-    .column-#{$i} {
-      box-sizing: border-box;
-      min-height: 1px;
-      flex: 0 0 percentage($i / 15);
-      max-width: percentage($i / 15);
-      padding: 0 5px;
+      .column-1 {
+        grid-column: 1;
+        grid-row: 1;
 
-      @media screen and (max-width: $small-breakpoint) {
-        &-sm {
-          box-sizing: border-box;
-          min-height: 1px;
-          flex: 0 0 percentage($i / 15);
-          max-width: percentage($i / 15);
-          padding: 0 5px;
-
-          @media screen and (max-width: $column-breakpoint) {
-            max-width: 100%;
-            flex: 0 0 100%;
-            margin-bottom: 10px;
-
-            &:last-child {
-              margin-bottom: 0;
-            }
-          }
+        &.non-preview .landing-page__forms {
+          height: 100%;
         }
       }
 
-      @media screen and (max-width: $column-breakpoint) {
-        max-width: 100%;
-        flex: 0 0 100%;
-        margin-bottom: 10px;
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1 / 3;
 
-        &:last-child {
-          margin-bottom: 0;
+        &.non-preview {
+          grid-column: 2;
+          grid-row: 1;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 2 / 4;
+      }
+
+      .column-4 {
+        grid-column: 2;
+        grid-row: 3;
+
+        &.non-preview {
+          grid-column: 1 / 3;
+          grid-row: 2;
         }
       }
     }
   }
-}
 
-.column-flex {
-  display: flex;
-  flex-direction: column;
-}
+  @media screen and (max-width: $column-breakpoint) {
+    .grid {
+      grid-template-columns: auto;
 
-.separator-or {
-  position: relative;
-  margin: 40px 0;
-  text-align: center;
+      .column-0 {
+        display: block;
+        grid-column: 1;
+        grid-row: 1;
+      }
 
-  &::before {
-    content: "";
-    display: block;
-    width: 100%;
-    height: 0;
-    border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
-    position: absolute;
-    top: 50%;
-    left: 0;
+      .column-1 {
+        grid-column: 1;
+        grid-row: 3;
+
+        .brand {
+          display: none;
+        }
+      }
+
+      .column-2 {
+        grid-column: 1;
+        grid-row: 2;
+
+        .landing-page__logo,
+        .landing-page__call-to-action {
+          display: none;
+        }
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 2;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 5;
+      }
+
+      .column-4 {
+        grid-column: 1;
+        grid-row: 4;
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 4;
+        }
+      }
+    }
   }
 
-  span {
-    display: inline-block;
-    background: $ui-base-color;
-    font-size: 12px;
-    font-weight: 500;
-    color: $ui-primary-color;
-    text-transform: uppercase;
+  .column-flex {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .separator-or {
     position: relative;
-    z-index: 1;
-    padding: 0 8px;
-    cursor: default;
+    margin: 40px 0;
+    text-align: center;
+
+    &::before {
+      content: "";
+      display: block;
+      width: 100%;
+      height: 0;
+      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
+      position: absolute;
+      top: 50%;
+      left: 0;
+    }
+
+    span {
+      display: inline-block;
+      background: $ui-base-color;
+      font-size: 12px;
+      font-weight: 500;
+      color: $ui-primary-color;
+      text-transform: uppercase;
+      position: relative;
+      z-index: 1;
+      padding: 0 8px;
+      cursor: default;
+    }
   }
-}
 
-.landing-page {
   p,
   li {
     font-family: 'mastodon-font-sans-serif', sans-serif;
@@ -539,6 +591,7 @@ $small-breakpoint: 960px;
 
       img {
         position: static;
+        padding: 10px 0;
       }
 
       @media screen and (max-width: $small-breakpoint) {
@@ -558,18 +611,33 @@ $small-breakpoint: 960px;
   }
 
   &__call-to-action {
-    margin-bottom: 10px;
     background: darken($ui-base-color, 4%);
     border-radius: 4px;
     padding: 25px 40px;
     overflow: hidden;
 
     .row {
+      display: flex;
+      flex-direction: row-reverse;
+      flex-wrap: wrap;
+      justify-content: space-between;
       align-items: center;
     }
 
-    .information-board__section {
-      padding: 0;
+    .row__information-board {
+      display: flex;
+      justify-content: flex-end;
+      align-items: flex-end;
+
+      .information-board__section {
+        flex: 1 0 auto;
+        padding: 0 10px;
+      }
+    }
+
+    .row__mascot {
+      flex: 1;
+      margin: 10px -50px 0 0;
     }
   }
 
@@ -619,6 +687,8 @@ $small-breakpoint: 960px;
 
   &__short-description {
     .row {
+      display: flex;
+      flex-wrap: wrap;
       align-items: center;
       margin-bottom: 40px;
     }
@@ -668,7 +738,6 @@ $small-breakpoint: 960px;
     height: 100%;
 
     @media screen and (max-width: $small-breakpoint) {
-      margin-bottom: 10px;
       height: auto;
     }
 
@@ -717,6 +786,7 @@ $small-breakpoint: 960px;
     width: 100%;
     flex: 1 1 auto;
     overflow: hidden;
+    height: 100%;
 
     .column-header {
       color: inherit;
@@ -942,93 +1012,54 @@ $small-breakpoint: 960px;
   }
 
   &.tag-page {
-    .features {
-      padding: 30px 0;
-
-      .container-alt {
-        max-width: 820px;
-
-        #mastodon-timeline {
-          margin-right: 0;
-          border-top-right-radius: 0;
-        }
-
-        .about-mastodon {
-          .about-hashtag {
-            background: darken($ui-base-color, 4%);
-            padding: 0 20px 20px 30px;
-            border-radius: 0 5px 5px 0;
-
-            .brand {
-              padding-top: 20px;
-              margin-bottom: 20px;
-
-              img {
-                height: 48px;
-                width: auto;
-              }
-            }
-
-            p {
-              strong {
-                color: $ui-secondary-color;
-                font-weight: 700;
-              }
-            }
+    .grid {
+      @media screen and (min-width: $small-breakpoint) {
+        grid-template-columns: 33% 67%;
+      }
 
-            .cta {
-              margin: 0;
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1;
+      }
+    }
 
-              .button {
-                margin-right: 4px;
-              }
-            }
-          }
+    .brand {
+      text-align: unset;
+      padding: 0;
 
-          .features-list {
-            margin-left: 30px;
-            margin-right: 10px;
-          }
-        }
+      img {
+        height: 48px;
+        width: auto;
       }
     }
 
-    @media screen and (max-width: 675px) {
-      .features {
-        padding: 10px 0;
+    .cta {
+      margin: 0;
 
-        .container-alt {
-          display: flex;
-          flex-direction: column;
-
-          #mastodon-timeline {
-            order: 2;
-            flex: 0 0 auto;
-            height: 60vh;
-            margin-bottom: 20px;
-            border-top-right-radius: 4px;
-          }
+      .button {
+        margin-right: 4px;
+      }
+    }
 
-          .about-mastodon {
-            order: 1;
-            flex: 0 0 auto;
-            max-width: 100%;
+    @media screen and (max-width: $column-breakpoint) {
+      .grid {
+        .column-1 {
+          grid-column: 1;
+          grid-row: 2;
+        }
 
-            .about-hashtag {
-              background: unset;
-              padding: 0;
-              border-radius: 0;
+        .column-2 {
+          grid-column: 1;
+          grid-row: 1;
+        }
+      }
 
-              .cta {
-                margin: 20px 0;
-              }
-            }
+      .brand {
+        margin: 0;
+      }
 
-            .features-list {
-              display: none;
-            }
-          }
-        }
+      .landing-page__features {
+        display: none;
       }
     }
   }
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index 9015d04cb..873963c90 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -97,32 +97,6 @@
     }
   }
 
-  .controls {
-    position: absolute;
-    top: 15px;
-    left: 15px;
-    z-index: 2;
-
-    .icon-button {
-      color: rgba($white, 0.8);
-      text-decoration: none;
-      font-size: 13px;
-      line-height: 13px;
-      font-weight: 500;
-
-      .fa {
-        font-weight: 400;
-        margin-right: 5px;
-      }
-
-      &:hover,
-      &:active,
-      &:focus {
-        color: $white;
-      }
-    }
-  }
-
   .roles {
     margin-bottom: 30px;
     padding: 0 15px;
@@ -226,6 +200,40 @@
   }
 }
 
+.card,
+.account-grid-card {
+  .controls {
+    position: absolute;
+    top: 15px;
+    left: 15px;
+    z-index: 2;
+
+    .icon-button {
+      color: rgba($white, 0.8);
+      text-decoration: none;
+      font-size: 13px;
+      line-height: 13px;
+      font-weight: 500;
+
+      .fa {
+        font-weight: 400;
+        margin-right: 5px;
+      }
+
+      &:hover,
+      &:active,
+      &:focus {
+        color: $white;
+      }
+    }
+  }
+}
+
+.account-grid-card .controls {
+  left: auto;
+  right: 15px;
+}
+
 .pagination {
   padding: 30px 0;
   text-align: center;
@@ -233,8 +241,8 @@
 
   a,
   .current,
-  .next,
-  .prev,
+  .newer,
+  .older,
   .page,
   .gap {
     font-size: 14px;
@@ -257,13 +265,13 @@
     cursor: default;
   }
 
-  .prev,
-  .next {
+  .older,
+  .newer {
     text-transform: uppercase;
     color: $ui-secondary-color;
   }
 
-  .prev {
+  .older {
     float: left;
     padding-left: 0;
 
@@ -273,7 +281,7 @@
     }
   }
 
-  .next {
+  .newer {
     float: right;
     padding-right: 0;
 
@@ -295,8 +303,8 @@
       display: none;
     }
 
-    .next,
-    .prev {
+    .newer,
+    .older {
       display: inline-block;
     }
   }
@@ -411,13 +419,14 @@
       font-weight: 400;
     }
 
-    .note {
+    .account__header__content {
       padding: 10px 15px;
       padding-top: 15px;
-      box-sizing: border-box;
       color: lighten($ui-base-color, 26%);
       word-wrap: break-word;
-      min-height: 80px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      height: 5.5em;
     }
   }
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 0b5a721a7..d8364ef81 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1878,7 +1878,7 @@
   font-size: 14px;
   font-weight: 500;
   border-bottom: 2px solid lighten($ui-base-color, 8%);
-  transition: all 200ms linear;
+  transition: all 50ms linear;
 
   .fa {
     font-weight: 400;
@@ -1895,7 +1895,6 @@
   &:active {
     @media screen and (min-width: 631px) {
       background: lighten($ui-base-color, 14%);
-      transition: all 100ms linear;
     }
   }
 
@@ -3891,8 +3890,7 @@ a.status-card {
 
 .boost-modal__action-bar,
 .confirmation-modal__action-bar,
-.mute-modal__action-bar,
-.report-modal__action-bar {
+.mute-modal__action-bar {
   display: flex;
   justify-content: space-between;
   background: $ui-secondary-color;
@@ -3936,21 +3934,94 @@ a.status-card {
   vertical-align: middle;
 }
 
+.report-modal {
+  width: 90vw;
+  max-width: 700px;
+}
+
+.report-modal__container {
+  display: flex;
+  border-top: 1px solid $ui-secondary-color;
+
+  @media screen and (max-width: 480px) {
+    flex-wrap: wrap;
+    overflow-y: auto;
+  }
+}
+
 .report-modal__statuses,
 .report-modal__comment {
-  padding: 10px;
+  box-sizing: border-box;
+  width: 50%;
+
+  @media screen and (max-width: 480px) {
+    width: 100%;
+  }
 }
 
 .report-modal__statuses {
+  flex: 1 1 auto;
   min-height: 20vh;
   max-height: 40vh;
   overflow-y: auto;
   overflow-x: hidden;
+
+  @media screen and (max-width: 480px) {
+    max-height: 10vh;
+  }
 }
 
 .report-modal__comment {
+  padding: 20px;
+  border-right: 1px solid $ui-secondary-color;
+  max-width: 320px;
+
+  p {
+    font-size: 14px;
+    line-height: 20px;
+    margin-bottom: 20px;
+  }
+
   .setting-text {
-    margin-top: 10px;
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    margin: 0;
+    color: $ui-base-color;
+    background: $white;
+    padding: 10px;
+    font-family: inherit;
+    font-size: 14px;
+    resize: vertical;
+    border: 0;
+    outline: 0;
+    border-radius: 4px;
+    border: 1px solid $ui-secondary-color;
+    margin-bottom: 20px;
+
+    &:focus {
+      border: 1px solid darken($ui-secondary-color, 8%);
+    }
+  }
+
+  .setting-toggle {
+    margin-top: 20px;
+    margin-bottom: 24px;
+
+    &__label {
+      color: $ui-base-color;
+      font-size: 14px;
+    }
+  }
+
+  @media screen and (max-width: 480px) {
+    padding: 10px;
+    max-width: 100%;
+    order: 2;
+
+    .setting-toggle {
+      margin-bottom: 4px;
+    }
   }
 }
 
@@ -4043,6 +4114,15 @@ a.status-card {
   }
 }
 
+.report-modal__target {
+  padding: 20px;
+
+  .media-modal__close {
+    top: 19px;
+    right: 15px;
+  }
+}
+
 .loading-bar {
   background-color: $ui-highlight-color;
   height: 3px;
@@ -4154,6 +4234,7 @@ a.status-card {
   &.standalone {
     .media-gallery__item-gifv-thumbnail {
       transform: none;
+      top: 0;
     }
   }
 }
@@ -4283,7 +4364,7 @@ a.status-card {
 
   &.inline {
     video {
-      object-fit: cover;
+      object-fit: contain;
       position: relative;
       top: 50%;
       transform: translateY(-50%);
@@ -4503,64 +4584,96 @@ a.status-card {
 /* End Video Player */
 
 .account-gallery__container {
-  margin: -2px;
-  padding: 4px;
   display: flex;
+  justify-content: center;
   flex-wrap: wrap;
+  padding: 2px;
 }
 
 .account-gallery__item {
-  flex: 1 1 auto;
-  width: calc(100% / 3 - 4px);
-  height: 95px;
-  margin: 2px;
+  flex-grow: 1;
+  width: 50%;
+  overflow: hidden;
+  position: relative;
+
+  &::before {
+    content: "";
+    display: block;
+    padding-top: 100%;
+  }
 
   a {
     display: block;
-    width: 100%;
-    height: 100%;
+    width: calc(100% - 4px);
+    height: calc(100% - 4px);
+    margin: 2px;
+    top: 0;
+    left: 0;
     background-color: $base-overlay-background;
     background-size: cover;
     background-position: center;
-    position: relative;
+    position: absolute;
     color: inherit;
     text-decoration: none;
+    border-radius: 4px;
 
     &:hover,
     &:active,
     &:focus {
       outline: 0;
+
+      &::before {
+        content: "";
+        display: block;
+        width: 100%;
+        height: 100%;
+        background: rgba($base-overlay-background, 0.3);
+        border-radius: 4px;
+      }
     }
   }
 }
 
-.account-section-headline {
-  color: $ui-base-lighter-color;
-  background: lighten($ui-base-color, 2%);
-  border-bottom: 1px solid lighten($ui-base-color, 4%);
-  padding: 15px 10px;
-  font-size: 14px;
-  font-weight: 500;
-  position: relative;
+.account__section-headline {
+  background: darken($ui-base-color, 4%);
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: default;
+  display: flex;
 
-  &::before,
-  &::after {
+  a {
     display: block;
-    content: "";
-    position: absolute;
-    bottom: 0;
-    left: 18px;
-    width: 0;
-    height: 0;
-    border-style: solid;
-    border-width: 0 10px 10px;
-    border-color: transparent transparent lighten($ui-base-color, 4%);
-  }
+    flex: 1 1 auto;
+    color: $ui-primary-color;
+    padding: 15px 0;
+    font-size: 14px;
+    font-weight: 500;
+    text-align: center;
+    text-decoration: none;
+    position: relative;
 
-  &::after {
-    bottom: -1px;
-    border-color: transparent transparent $ui-base-color;
+    &.active {
+      color: $ui-secondary-color;
+
+      &::before,
+      &::after {
+        display: block;
+        content: "";
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        width: 0;
+        height: 0;
+        transform: translateX(-50%);
+        border-style: solid;
+        border-width: 0 10px 10px;
+        border-color: transparent transparent lighten($ui-base-color, 8%);
+      }
+
+      &::after {
+        bottom: -1px;
+        border-color: transparent transparent $ui-base-color;
+      }
+    }
   }
 }
 
@@ -4910,3 +5023,27 @@ noscript {
     left: 0;
   }
 }
+
+.floating-action-button {
+  position: fixed;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 3.9375rem;
+  height: 3.9375rem;
+  bottom: 1.3125rem;
+  right: 1.3125rem;
+  background: darken($ui-highlight-color, 3%);
+  color: $white;
+  border-radius: 50%;
+  font-size: 21px;
+  line-height: 21px;
+  text-decoration: none;
+  box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4);
+
+  &:hover,
+  &:focus,
+  &:active {
+    background: lighten($ui-highlight-color, 7%);
+  }
+}
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index dec7d2284..2e38cda4e 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -278,6 +278,11 @@ code {
   .actions {
     margin-top: 30px;
     display: flex;
+
+    &.actions--top {
+      margin-top: 0;
+      margin-bottom: 30px;
+    }
   }
 
   button,
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 4617905c6..6f4a3b491 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -44,6 +44,8 @@ class ActivityPub::Activity
         ActivityPub::Activity::Accept
       when 'Reject'
         ActivityPub::Activity::Reject
+      when 'Flag'
+        ActivityPub::Activity::Flag
       end
     end
   end
diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb
new file mode 100644
index 000000000..36d3c5730
--- /dev/null
+++ b/app/lib/activitypub/activity/flag.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ActivityPub::Activity::Flag < ActivityPub::Activity
+  def perform
+    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)
+
+    target_accounts.each do |target_account|
+      next if Report.where(account: @account, target_account: target_account).exists?
+
+      target_statuses = target_statuses_by_account[target_account.id]
+
+      ReportService.new.call(
+        @account,
+        target_account,
+        status_ids: target_statuses.nil? ? [] : target_statuses.map(&:id),
+        comment: @json['content'] || ''
+      )
+    end
+  end
+
+  def object_uris
+    @object_uris ||= Array(@object.is_a?(Array) ? @object.map { |item| value_or_id(item) } : value_or_id(@object))
+  end
+end
diff --git a/app/lib/activitypub/activity/reject.rb b/app/lib/activitypub/activity/reject.rb
index d815feeb6..28d472883 100644
--- a/app/lib/activitypub/activity/reject.rb
+++ b/app/lib/activitypub/activity/reject.rb
@@ -17,6 +17,8 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity
 
     follow_request = FollowRequest.find_by(account: target_account, target_account: @account)
     follow_request&.reject!
+
+    UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
   end
 
   def target_uri
diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb
index b2489711d..95e3365c2 100644
--- a/app/lib/exceptions.rb
+++ b/app/lib/exceptions.rb
@@ -4,6 +4,7 @@ module Mastodon
   class Error < StandardError; end
   class NotPermittedError < Error; end
   class ValidationError < Error; end
+  class HostValidationError < ValidationError; end
   class RaceConditionError < Error; end
 
   class UnexpectedResponseError < Error
diff --git a/app/lib/request.rb b/app/lib/request.rb
index 7671f4ffc..5776b3d78 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -1,5 +1,8 @@
 # frozen_string_literal: true
 
+require 'ipaddr'
+require 'socket'
+
 class Request
   REQUEST_TARGET = '(request-target)'
 
@@ -8,7 +11,7 @@ class Request
   def initialize(verb, url, **options)
     @verb    = verb
     @url     = Addressable::URI.parse(url).normalize
-    @options = options
+    @options = options.merge(socket_class: Socket)
     @headers = {}
 
     set_common_headers!
@@ -87,4 +90,18 @@ class Request
   def http_client
     HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
   end
+
+  class Socket < TCPSocket
+    class << self
+      def open(host, *args)
+        address = IPSocket.getaddress(host)
+        raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address)
+        super address, *args
+      end
+
+      alias new open
+    end
+  end
+
+  private_constant :Socket
 end
diff --git a/app/lib/sidekiq_error_handler.rb b/app/lib/sidekiq_error_handler.rb
new file mode 100644
index 000000000..23785cf05
--- /dev/null
+++ b/app/lib/sidekiq_error_handler.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class SidekiqErrorHandler
+  def call(*)
+    yield
+  rescue Mastodon::HostValidationError => e
+    Rails.logger.error "#{e.class}: #{e.message}"
+    Rails.logger.error e.backtrace.join("\n")
+    # Do not retry
+  end
+end
diff --git a/app/models/concerns/paginable.rb b/app/models/concerns/paginable.rb
index 6061bf9bd..66695677e 100644
--- a/app/models/concerns/paginable.rb
+++ b/app/models/concerns/paginable.rb
@@ -10,5 +10,14 @@ module Paginable
       query = query.where(arel_table[:id].gt(since_id)) if since_id.present?
       query
     }
+
+    # Differs from :paginate_by_max_id in that it gives the results immediately following min_id,
+    # whereas since_id gives the items with largest id, but with since_id as a cutoff.
+    # Results will be in ascending order by id.
+    scope :paginate_by_min_id, ->(limit, min_id = nil) {
+      query = reorder(arel_table[:id]).limit(limit)
+      query = query.where(arel_table[:id].gt(min_id)) if min_id.present?
+      query
+    }
   end
 end
diff --git a/app/models/import.rb b/app/models/import.rb
index ba88435bf..fdb4c6b80 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -26,7 +26,7 @@ class Import < ApplicationRecord
 
   validates :type, presence: true
 
-  has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET']
+  has_attached_file :data
   validates_attachment_content_type :data, content_type: FILE_TYPES
   validates_attachment_presence :data
 end
diff --git a/app/models/report.rb b/app/models/report.rb
index f55fb6d3e..dd123fc15 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -24,6 +24,10 @@ class Report < ApplicationRecord
 
   validates :comment, length: { maximum: 1000 }
 
+  def object_type
+    :flag
+  end
+
   def statuses
     Status.where(id: status_ids).includes(:account, :media_attachments, :mentions)
   end
diff --git a/app/models/user.rb b/app/models/user.rb
index 197799294..b3e5f9352 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -44,7 +44,7 @@ class User < ApplicationRecord
   ACTIVE_DURATION = 14.days
 
   devise :two_factor_authenticatable,
-         otp_secret_encryption_key: ENV['OTP_SECRET']
+         otp_secret_encryption_key: ENV.fetch('OTP_SECRET')
 
   devise :two_factor_backupable,
          otp_number_of_backup_codes: 10
@@ -52,7 +52,6 @@ class User < ApplicationRecord
   devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
          :confirmable
 
-  devise :pam_authenticatable if Devise.pam_authentication
   devise :omniauthable
 
   belongs_to :account, inverse_of: :user
@@ -117,6 +116,12 @@ class User < ApplicationRecord
     acc.destroy! unless save
   end
 
+  def ldap_setup(_attributes)
+    self.confirmed_at = Time.now.utc
+    self.admin = false
+    save!
+  end
+
   def confirmed?
     confirmed_at.present?
   end
@@ -247,17 +252,17 @@ class User < ApplicationRecord
   end
 
   def password_required?
-    return false if Devise.pam_authentication
+    return false if Devise.pam_authentication || Devise.ldap_authentication
     super
   end
 
   def send_reset_password_instructions
-    return false if encrypted_password.blank? && Devise.pam_authentication
+    return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
     super
   end
 
   def reset_password!(new_password, new_password_confirmation)
-    return false if encrypted_password.blank? && Devise.pam_authentication
+    return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
     super
   end
 
@@ -280,6 +285,17 @@ class User < ApplicationRecord
     end
   end
 
+  def self.ldap_get_user(attributes = {})
+    resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
+
+    if resource.blank?
+      resource = new(email: attributes[:mail].first, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
+      resource.ldap_setup(attributes)
+    end
+
+    resource
+  end
+
   def self.authenticate_with_pam(attributes = {})
     return nil unless Devise.pam_authentication
     super
diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb
new file mode 100644
index 000000000..53e8f726d
--- /dev/null
+++ b/app/serializers/activitypub/flag_serializer.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ActivityPub::FlagSerializer < ActiveModel::Serializer
+  attributes :id, :type, :actor, :content
+  attribute :virtual_object, key: :object
+
+  def id
+    # This is nil for now
+    ActivityPub::TagManager.instance.uri_for(object)
+  end
+
+  def type
+    'Flag'
+  end
+
+  def actor
+    ActivityPub::TagManager.instance.uri_for(instance_options[:account] || object.account)
+  end
+
+  def virtual_object
+    [ActivityPub::TagManager.instance.uri_for(object.target_account)] + object.statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
+  end
+
+  def content
+    object.comment
+  end
+end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 5434f1c56..1d17e2b0a 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -22,6 +22,7 @@ class InitialStateSerializer < ActiveModel::Serializer
       locale: I18n.locale,
       domain: Rails.configuration.x.local_domain,
       admin: object.admin&.id&.to_s,
+      search_enabled: Chewy.enabled?,
     }
 
     if object.current_account
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 65907dad2..0168b18ea 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -4,7 +4,12 @@ class REST::InstanceSerializer < ActiveModel::Serializer
   include RoutingHelper
 
   attributes :uri, :title, :description, :email,
-             :version, :urls, :stats, :thumbnail, :max_toot_chars
+             :version, :urls, :stats, :thumbnail, :max_toot_chars,
+             :languages
+
+  has_one :contact_account, serializer: REST::AccountSerializer
+
+  delegate :contact_account, to: :instance_presenter
 
   def uri
     Rails.configuration.x.local_domain
@@ -46,6 +51,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
     { streaming_api: Rails.configuration.x.streaming_api_base_url }
   end
 
+  def languages
+    [ENV.fetch('DEFAULT_LOCALE', I18n.default_locale)]
+  end
+
   private
 
   def instance_presenter
diff --git a/app/services/report_service.rb b/app/services/report_service.rb
new file mode 100644
index 000000000..c06488a6d
--- /dev/null
+++ b/app/services/report_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class ReportService < BaseService
+  def call(source_account, target_account, options = {})
+    @source_account = source_account
+    @target_account = target_account
+    @status_ids     = options.delete(:status_ids) || []
+    @comment        = options.delete(:comment) || ''
+    @options        = options
+
+    create_report!
+    notify_staff!
+    forward_to_origin! if !@target_account.local? && ActiveModel::Type::Boolean.new.cast(@options[:forward])
+
+    @report
+  end
+
+  private
+
+  def create_report!
+    @report = @source_account.reports.create!(
+      target_account: @target_account,
+      status_ids: @status_ids,
+      comment: @comment
+    )
+  end
+
+  def notify_staff!
+    User.staff.includes(:account).each do |u|
+      AdminMailer.new_report(u.account, @report).deliver_later
+    end
+  end
+
+  def forward_to_origin!
+    ActivityPub::DeliveryWorker.perform_async(
+      payload,
+      some_local_account.id,
+      @target_account.inbox_url
+    )
+  end
+
+  def payload
+    Oj.dump(ActiveModelSerializers::SerializableResource.new(
+      @report,
+      serializer: ActivityPub::FlagSerializer,
+      adapter: ActivityPub::Adapter,
+      account: some_local_account
+    ).as_json)
+  end
+
+  def some_local_account
+    @some_local_account ||= Account.local.where(suspended: false).first
+  end
+end
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index bc357e522..37bfde887 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -7,51 +7,100 @@
 
 .landing-page.alternative
   .container
-    .row
-      .column-4.hide-sm.show-xs.show-m
-        .landing-page__forms
-          .brand
-            = link_to root_url do
-              = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
-
-          .hide-xs
+    .grid
+      .column-0
+        .brand
+          = link_to root_url do
+            = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+
+      - if Setting.timeline_preview
+        .column-1
+          .landing-page__forms
+            .brand
+              = link_to root_url do
+                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+
             = render 'forms'
 
-      .column-7.column-9-sm
-        .landing-page__hero
-          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+      - else
+        .column-1.non-preview
+          .landing-page__forms
+            .brand
+              = link_to root_url do
+                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
 
-        .landing-page__information
-          .landing-page__short-description
+            = render 'forms'
+
+      - if Setting.timeline_preview
+        .column-2
+          .landing-page__hero
+            = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+
+          .landing-page__information
+            .landing-page__short-description
+              .row
+                .landing-page__logo
+                  = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
+
+                %h1
+                  = @instance_presenter.site_title
+                  %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
+
+              %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
+
+          .landing-page__call-to-action
             .row
-              .landing-page__logo.hide-xs
-                = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
+              .row__information-board
+                .information-board__section
+                  %span= t 'about.user_count_before'
+                  %strong= number_with_delimiter @instance_presenter.user_count
+                  %span= t 'about.user_count_after'
+                .information-board__section
+                  %span= t 'about.status_count_before'
+                  %strong= number_with_delimiter @instance_presenter.status_count
+                  %span= t 'about.status_count_after'
+              .row__mascot
+                .landing-page__mascot
+                  = image_tag asset_pack_path('elephant_ui_plane.svg')
 
-              %h1
-                = @instance_presenter.site_title
-                %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
+      - else
+        .column-2.non-preview
+          .landing-page__hero
+            = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
 
-            %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
+          .landing-page__information
+            .landing-page__short-description
+              .row
+                .landing-page__logo
+                  = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
 
-        .show-xs
-          .landing-page__forms
-            = render 'forms'
-        .landing-page__call-to-action.hide-xs
-          .row
-            .column-5
-              .landing-page__mascot
-                = image_tag asset_pack_path('elephant_ui_plane.svg')
-            .column-5
-              .information-board__section
-                %span= t 'about.user_count_before'
-                %strong= number_with_delimiter @instance_presenter.user_count
-                %span= t 'about.user_count_after'
-            .column-5
-              .information-board__section
-                %span= t 'about.status_count_before'
-                %strong= number_with_delimiter @instance_presenter.status_count
-                %span= t 'about.status_count_after'
-        .landing-page__information
+                %h1
+                  = @instance_presenter.site_title
+                  %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
+
+              %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
+
+          .landing-page__call-to-action
+            .row
+              .row__information-board
+                .information-board__section
+                  %span= t 'about.user_count_before'
+                  %strong= number_with_delimiter @instance_presenter.user_count
+                  %span= t 'about.user_count_after'
+                .information-board__section
+                  %span= t 'about.status_count_before'
+                  %strong= number_with_delimiter @instance_presenter.status_count
+                  %span= t 'about.status_count_after'
+              .row__mascot
+                .landing-page__mascot
+                  = image_tag asset_pack_path('elephant_ui_plane.svg')
+
+      - if Setting.timeline_preview
+        .column-3
+          #mastodon-timeline{ data: { props: Oj.dump(default_props) } }
+
+      - if Setting.timeline_preview
+        .column-4.landing-page__information
           .landing-page__features
             %h3= t 'about.what_is_mastodon'
             %p= t 'about.about_mastodon_html'
@@ -66,13 +115,18 @@
               = link_to t('about.source_code'), @instance_presenter.source_url
               = " (#{@instance_presenter.version_number})"
 
-      .column-4.column-6-sm.column-flex
-        .show-sm.hide-xs
-          .landing-page__forms
-            .brand
-              = link_to root_url do
-                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+      - else
+        .column-4.non-preview.landing-page__information
+          .landing-page__features
+            %h3= t 'about.what_is_mastodon'
+            %p= t 'about.about_mastodon_html'
 
-            = render 'forms'
-        - if Setting.timeline_preview
-          #mastodon-timeline{ data: { props: Oj.dump(default_props) } }
+            = render 'features'
+
+            .landing-page__features__action
+              = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-alternative'
+
+          .landing-page__footer
+            %p
+              = link_to t('about.source_code'), @instance_presenter.source_url
+              = " (#{@instance_presenter.version_number})"
diff --git a/app/views/accounts/_follow_button.html.haml b/app/views/accounts/_follow_button.html.haml
new file mode 100644
index 000000000..e476e0aff
--- /dev/null
+++ b/app/views/accounts/_follow_button.html.haml
@@ -0,0 +1,23 @@
+- relationships ||= nil
+
+- unless account.memorial? || account.moved?
+  - if user_signed_in?
+    - requested = relationships ? relationships.requested[account.id].present? : current_account.requested?(account)
+    - following = relationships ? relationships.following[account.id].present? : current_account.following?(account)
+
+  - if user_signed_in? && current_account.id != account.id && !requested
+    .controls
+      - if following
+        = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
+          = fa_icon 'user-times'
+          = t('accounts.unfollow')
+      - else
+        = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
+          = fa_icon 'user-plus'
+          = t('accounts.follow')
+  - elsif !user_signed_in?
+    .controls
+      .remote-follow
+        = link_to account_remote_follow_path(account), class: 'icon-button' do
+          = fa_icon 'user-plus'
+          = t('accounts.remote_follow')
diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml
index 305eb2c44..95acbd581 100644
--- a/app/views/accounts/_grid_card.html.haml
+++ b/app/views/accounts/_grid_card.html.haml
@@ -1,9 +1,12 @@
 .account-grid-card
   .account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" }
+    = render 'accounts/follow_button', account: account, relationships: @relationships
   .account-grid-card__avatar
     .avatar= image_tag account.avatar.url(:original)
   .name
     = link_to TagManager.instance.url_for(account) do
       %span.display_name.emojify= display_name(account)
-      %span.username @#{account.acct}
-  %p.note.emojify= truncate(strip_tags(account.note), length: 150)
+      %span.username
+        @#{account.local? ? account.local_username_and_domain : account.acct}
+        = fa_icon('lock') if account.locked?
+  .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account)
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index b0062752c..74251b923 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -1,24 +1,7 @@
 - processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account
 .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" }
   .card__illustration
-    - unless account.memorial? || account.moved?
-      - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
-        .controls
-          - if current_account.following?(account)
-            = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
-              = fa_icon 'user-times'
-              = t('accounts.unfollow')
-          - else
-            = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
-              = fa_icon 'user-plus'
-              = t('accounts.follow')
-      - elsif !user_signed_in?
-        .controls
-          .remote-follow
-            = link_to account_remote_follow_path(account), class: 'icon-button' do
-              = fa_icon 'user-plus'
-              = t('accounts.remote_follow')
-
+    = render 'accounts/follow_button', account: account
     .avatar= image_tag account.avatar.url(:original), class: 'u-photo'
 
   .card__bio
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index accad5f78..21c585dab 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -39,6 +39,9 @@
 
       = render partial: 'stream_entries/status', collection: @statuses, as: :status
 
-  - if @statuses.size == 20
+  - if @newer_url || @older_url
     .pagination
-      = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), @next_url, class: 'next', rel: 'next'
+      - if @older_url
+        = link_to safe_join([fa_icon('chevron-left'), t('pagination.older')], ' '), @older_url, class: 'older', rel: 'older'
+      - if @newer_url
+        = link_to safe_join([t('pagination.newer'), fa_icon('chevron-right')], ' '), @newer_url, class: 'newer', rel: 'newer'
diff --git a/app/views/auth/passwords/edit.html.haml b/app/views/auth/passwords/edit.html.haml
index 703c821c0..12880c227 100644
--- a/app/views/auth/passwords/edit.html.haml
+++ b/app/views/auth/passwords/edit.html.haml
@@ -4,7 +4,7 @@
 = simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
   = render 'shared/error_messages', object: resource
 
-  - if !use_pam? || resource.encrypted_password.present?
+  - if !use_seamless_external_login?? || resource.encrypted_password.present?
     = f.input :reset_password_token, as: :hidden
 
     = f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
@@ -13,6 +13,6 @@
     .actions
       = f.button :button, t('auth.set_new_password'), type: :submit
   - else
-    = t('simple_form.labels.defaults.pam_account')
+    %p.hint= t('users.seamless_external_login')
 
 .form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index ca18caa56..fac702b38 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -4,7 +4,7 @@
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
   = render 'shared/error_messages', object: resource
 
-  - if !use_pam? || resource.encrypted_password.present?
+  - if !use_seamless_external_login? || resource.encrypted_password.present?
     = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
     = f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
     = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
@@ -13,7 +13,7 @@
     .actions
       = f.button :button, t('generic.save_changes'), type: :submit
   - else
-    = t('simple_form.labels.defaults.pam_account')
+    %p.hint= t('users.seamless_external_login')
 
 %hr/
 
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index 1c3a0b6b4..0c9f9d5fe 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -5,7 +5,7 @@
   = render partial: 'shared/og'
 
 = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
-  - if use_pam?
+  - if use_seamless_external_login?
     = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.username_or_email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }
   - else
     = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index 75dfa027e..102e4d200 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -4,7 +4,7 @@
 = simple_form_for current_user, url: settings_preferences_path, html: { method: :put } do |f|
   = render 'shared/error_messages', object: current_user
 
-  .actions
+  .actions.actions--top
     = f.button :button, t('generic.save_changes'), type: :submit
 
   %h4= t 'preferences.languages'
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 470bff218..e1122d5a2 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -22,7 +22,7 @@
   - if !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
-      %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true) }}
+      %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true, inline: true) }}
     - else
       %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}
   - elsif status.preview_cards.first
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 03d416fd6..2ad1f5120 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -20,7 +20,6 @@
         %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
     .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
       = Formatter.instance.format(status, custom_emojify: true)
-
       - unless status.media_attachments.empty?
         - if status.media_attachments.first.video?
           - video = status.media_attachments.first
diff --git a/app/views/tags/_features.html.haml b/app/views/tags/_features.html.haml
new file mode 100644
index 000000000..8fbc6b760
--- /dev/null
+++ b/app/views/tags/_features.html.haml
@@ -0,0 +1,25 @@
+.features-list
+  .features-list__row
+    .text
+      %h6= t 'about.features.real_conversation_title'
+      = t 'about.features.real_conversation_body'
+    .visual
+      = fa_icon 'fw comments'
+  .features-list__row
+    .text
+      %h6= t 'about.features.not_a_product_title'
+      = t 'about.features.not_a_product_body'
+    .visual
+      = fa_icon 'fw users'
+  .features-list__row
+    .text
+      %h6= t 'about.features.within_reach_title'
+      = t 'about.features.within_reach_body'
+    .visual
+      = fa_icon 'fw mobile'
+  .features-list__row
+    .text
+      %h6= t 'about.features.humane_approach_title'
+      = t 'about.features.humane_approach_body'
+    .visual
+      = fa_icon 'fw leaf'
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 03f19e20a..000aa0c4d 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -5,48 +5,31 @@
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = render 'og'
 
-.landing-page.tag-page
+.landing-page.tag-page.alternative
   .features
     .container
-      #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } }
+      .grid
+        .column-1
+          #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } }
 
-      .about-mastodon
-        .about-hashtag
-          .brand
-            = link_to root_url do
-              = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+        .column-2
+          .about-mastodon
+            .about-hashtag.landing-page__information
+              .brand
+                = link_to root_url do
+                  = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
 
-          %p= t 'about.about_hashtag_html', hashtag: @tag.name
+              %p= t 'about.about_hashtag_html', hashtag: @tag.name
 
-          .cta
-            - if user_signed_in?
-              = link_to t('settings.back'), root_path, class: 'button button-secondary'
-            - else
-              = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary'
-            = link_to t('about.learn_more'), about_path, class: 'button button-alternative'
+              .cta
+                - if user_signed_in?
+                  = link_to t('settings.back'), root_path, class: 'button button-secondary'
+                - else
+                  = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary'
+                = link_to t('about.learn_more'), about_path, class: 'button button-alternative'
 
-        .features-list
-          .features-list__row
-            .text
-              %h6= t 'about.features.real_conversation_title'
-              = t 'about.features.real_conversation_body'
-            .visual
-              = fa_icon 'fw comments'
-          .features-list__row
-            .text
-              %h6= t 'about.features.not_a_product_title'
-              = t 'about.features.not_a_product_body'
-            .visual
-              = fa_icon 'fw users'
-          .features-list__row
-            .text
-              %h6= t 'about.features.within_reach_title'
-              = t 'about.features.within_reach_body'
-            .visual
-              = fa_icon 'fw mobile'
-          .features-list__row
-            .text
-              %h6= t 'about.features.humane_approach_title'
-              = t 'about.features.humane_approach_body'
-            .visual
-              = fa_icon 'fw leaf'
+            .landing-page__features.landing-page__information
+              %h3= t 'about.what_is_mastodon'
+              %p= t 'about.about_mastodon_html'
+
+              = render 'features'