about summary refs log tree commit diff
diff options
context:
space:
mode:
authorpluralcafe-docker <docker@plural.cafe>2018-08-30 05:23:58 +0000
committerpluralcafe-docker <docker@plural.cafe>2018-08-30 05:23:58 +0000
commitcc7437e25597e24b9a5f06f7991861506d9abe5c (patch)
treee627d32df29ef7ae30a67607caf3ecdc1ae333a9
parent395164add468b1079669699dfe8eeaab73f69c15 (diff)
parent5ce67276691c37baad149f2f89f765543f70e6f9 (diff)
Merge branch 'glitch'
-rw-r--r--.buildpacks1
-rw-r--r--.env.nanobox4
-rw-r--r--.env.production.sample4
-rw-r--r--.rubocop.yml6
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock7
-rw-r--r--app/controllers/admin/settings_controller.rb3
-rw-r--r--app/controllers/admin/suspensions_controller.rb2
-rw-r--r--app/controllers/api/base_controller.rb2
-rw-r--r--app/controllers/api/v1/lists/accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/lists_controller.rb2
-rw-r--r--app/controllers/api/v1/mutes_controller.rb24
-rw-r--r--app/controllers/application_controller.rb10
-rw-r--r--app/controllers/auth/sessions_controller.rb2
-rw-r--r--app/controllers/custom_css_controller.rb10
-rw-r--r--app/helpers/application_helper.rb15
-rw-r--r--app/helpers/stream_entries_helper.rb6
-rw-r--r--app/javascript/core/admin.js7
-rw-r--r--app/javascript/flavours/glitch/actions/compose.js75
-rw-r--r--app/javascript/flavours/glitch/actions/store.js13
-rw-r--r--app/javascript/flavours/glitch/components/extended_video_player.js1
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js1
-rw-r--r--app/javascript/flavours/glitch/components/status.js2
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js4
-rw-r--r--app/javascript/flavours/glitch/features/composer/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/index.js16
-rw-r--r--app/javascript/flavours/glitch/features/composer/textarea/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js68
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/report/components/status_check_box.js1
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.js1
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js25
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/styles/components/metadata.scss2
-rw-r--r--app/javascript/flavours/glitch/util/hashtag.js8
-rw-r--r--app/javascript/flavours/glitch/util/settings.js1
-rw-r--r--app/javascript/mastodon/.gitkeep0
-rw-r--r--app/javascript/mastodon/actions/compose.js8
-rw-r--r--app/javascript/mastodon/components/column.js5
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js6
-rw-r--r--app/javascript/mastodon/components/extended_video_player.js1
-rw-r--r--app/javascript/mastodon/components/intersection_observer_article.js4
-rw-r--r--app/javascript/mastodon/components/media_gallery.js1
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js9
-rw-r--r--app/javascript/mastodon/components/status.js31
-rw-r--r--app/javascript/mastodon/containers/status_container.js2
-rw-r--r--app/javascript/mastodon/features/account/components/header.js4
-rw-r--r--app/javascript/mastodon/features/blocks/index.js34
-rw-r--r--app/javascript/mastodon/features/community_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_button.js2
-rw-r--r--app/javascript/mastodon/features/compose/index.js3
-rw-r--r--app/javascript/mastodon/features/direct_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/domain_blocks/index.js11
-rw-r--r--app/javascript/mastodon/features/favourited_statuses/index.js7
-rw-r--r--app/javascript/mastodon/features/favourites/index.js19
-rw-r--r--app/javascript/mastodon/features/follow_requests/index.js35
-rw-r--r--app/javascript/mastodon/features/followers/index.js47
-rw-r--r--app/javascript/mastodon/features/following/index.js47
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js3
-rw-r--r--app/javascript/mastodon/features/hashtag_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/home_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/keyboard_shortcuts/index.js48
-rw-r--r--app/javascript/mastodon/features/list_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/lists/index.js18
-rw-r--r--app/javascript/mastodon/features/mutes/index.js34
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js28
-rw-r--r--app/javascript/mastodon/features/notifications/index.js2
-rw-r--r--app/javascript/mastodon/features/public_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/reblogs/index.js19
-rw-r--r--app/javascript/mastodon/features/report/components/status_check_box.js1
-rw-r--r--app/javascript/mastodon/features/standalone/community_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/standalone/public_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js1
-rw-r--r--app/javascript/mastodon/features/status/index.js8
-rw-r--r--app/javascript/mastodon/features/trends/index.js66
-rw-r--r--app/javascript/mastodon/features/ui/index.js2
-rw-r--r--app/javascript/mastodon/features/video/index.js1
-rw-r--r--app/javascript/mastodon/locales/ar.json24
-rw-r--r--app/javascript/mastodon/locales/ast.json24
-rw-r--r--app/javascript/mastodon/locales/bg.json24
-rw-r--r--app/javascript/mastodon/locales/ca.json24
-rw-r--r--app/javascript/mastodon/locales/co.json34
-rw-r--r--app/javascript/mastodon/locales/cs.json30
-rw-r--r--app/javascript/mastodon/locales/da.json42
-rw-r--r--app/javascript/mastodon/locales/de.json24
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json124
-rw-r--r--app/javascript/mastodon/locales/el.json28
-rw-r--r--app/javascript/mastodon/locales/en.json28
-rw-r--r--app/javascript/mastodon/locales/eo.json24
-rw-r--r--app/javascript/mastodon/locales/es.json24
-rw-r--r--app/javascript/mastodon/locales/eu.json24
-rw-r--r--app/javascript/mastodon/locales/fa.json28
-rw-r--r--app/javascript/mastodon/locales/fi.json24
-rw-r--r--app/javascript/mastodon/locales/fr.json76
-rw-r--r--app/javascript/mastodon/locales/gl.json24
-rw-r--r--app/javascript/mastodon/locales/he.json24
-rw-r--r--app/javascript/mastodon/locales/hr.json24
-rw-r--r--app/javascript/mastodon/locales/hu.json24
-rw-r--r--app/javascript/mastodon/locales/hy.json24
-rw-r--r--app/javascript/mastodon/locales/id.json24
-rw-r--r--app/javascript/mastodon/locales/io.json24
-rw-r--r--app/javascript/mastodon/locales/it.json24
-rw-r--r--app/javascript/mastodon/locales/ja.json30
-rw-r--r--app/javascript/mastodon/locales/ka.json24
-rw-r--r--app/javascript/mastodon/locales/ko.json30
-rw-r--r--app/javascript/mastodon/locales/nl.json24
-rw-r--r--app/javascript/mastodon/locales/no.json24
-rw-r--r--app/javascript/mastodon/locales/oc.json32
-rw-r--r--app/javascript/mastodon/locales/pl.json32
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json30
-rw-r--r--app/javascript/mastodon/locales/pt.json24
-rw-r--r--app/javascript/mastodon/locales/ru.json24
-rw-r--r--app/javascript/mastodon/locales/sk.json24
-rw-r--r--app/javascript/mastodon/locales/sl.json24
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json24
-rw-r--r--app/javascript/mastodon/locales/sr.json24
-rw-r--r--app/javascript/mastodon/locales/sv.json24
-rw-r--r--app/javascript/mastodon/locales/te.json24
-rw-r--r--app/javascript/mastodon/locales/th.json24
-rw-r--r--app/javascript/mastodon/locales/tr.json24
-rw-r--r--app/javascript/mastodon/locales/uk.json24
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json24
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json24
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json36
-rw-r--r--app/javascript/mastodon/reducers/compose.js2
-rw-r--r--app/javascript/mastodon/reducers/notifications.js1
-rw-r--r--app/javascript/mastodon/service_worker/web_push_notifications.js20
-rw-r--r--app/javascript/packs/public.js5
-rw-r--r--app/javascript/styles/mastodon-light/diff.scss88
-rw-r--r--app/javascript/styles/mastodon/components.scss2
-rw-r--r--app/javascript/styles/mastodon/forms.scss11
-rw-r--r--app/lib/activitypub/activity.rb4
-rw-r--r--app/lib/activitypub/activity/create.rb18
-rw-r--r--app/lib/activitypub/activity/update.rb2
-rw-r--r--app/lib/activitypub/linked_data_signature.rb5
-rw-r--r--app/lib/feed_manager.rb2
-rw-r--r--app/lib/request.rb5
-rw-r--r--app/lib/settings/scoped_settings.rb16
-rw-r--r--app/mailers/user_mailer.rb2
-rw-r--r--app/models/concerns/expireable.rb2
-rw-r--r--app/models/form/admin_settings.rb6
-rw-r--r--app/models/user.rb8
-rw-r--r--app/policies/user_policy.rb4
-rw-r--r--app/presenters/instance_presenter.rb2
-rw-r--r--app/serializers/activitypub/actor_serializer.rb4
-rw-r--r--app/serializers/web/notification_serializer.rb4
-rw-r--r--app/services/activitypub/process_account_service.rb7
-rw-r--r--app/services/follow_service.rb2
-rw-r--r--app/services/notify_service.rb2
-rw-r--r--app/views/accounts/_bio.html.haml2
-rw-r--r--app/views/accounts/_header.html.haml10
-rw-r--r--app/views/accounts/show.html.haml2
-rw-r--r--app/views/admin/relays/_relay.html.haml10
-rw-r--r--app/views/admin/reports/_status.html.haml2
-rw-r--r--app/views/admin/settings/edit.html.haml3
-rw-r--r--app/views/admin/suspensions/new.html.haml6
-rw-r--r--app/views/auth/shared/_links.html.haml2
-rwxr-xr-xapp/views/layouts/application.html.haml9
-rw-r--r--app/views/layouts/public.html.haml2
-rw-r--r--app/views/settings/exports/show.html.haml4
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml2
-rw-r--r--app/views/stream_entries/_simple_status.html.haml2
-rw-r--r--app/workers/activitypub/delivery_worker.rb5
-rw-r--r--app/workers/activitypub/update_distribution_worker.rb5
-rw-r--r--app/workers/maintenance/destroy_media_worker.rb2
-rw-r--r--app/workers/maintenance/redownload_account_media_worker.rb2
-rw-r--r--app/workers/maintenance/uncache_media_worker.rb2
-rw-r--r--app/workers/scheduler/backup_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/doorkeeper_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/email_scheduler.rb2
-rw-r--r--app/workers/scheduler/feed_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/ip_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/media_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/subscriptions_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/subscriptions_scheduler.rb2
-rw-r--r--app/workers/scheduler/user_cleanup_scheduler.rb2
-rwxr-xr-xbin/tootctl4
-rw-r--r--config/initializers/content_security_policy.rb15
-rw-r--r--config/initializers/paperclip.rb4
-rw-r--r--config/locales/cs.yml17
-rw-r--r--config/locales/devise.fr.yml22
-rw-r--r--config/locales/doorkeeper.da.yml2
-rw-r--r--config/locales/doorkeeper.fr.yml12
-rw-r--r--config/locales/en.yml13
-rw-r--r--config/locales/fr.yml293
-rw-r--r--config/locales/ja.yml23
-rw-r--r--config/locales/ka.yml6
-rw-r--r--config/locales/ko.yml2
-rw-r--r--config/locales/pl.yml17
-rw-r--r--config/locales/simple_form.fr.yml26
-rw-r--r--config/locales/simple_form.pl.yml4
-rw-r--r--config/routes.rb1
-rw-r--r--config/webpack/production.js4
-rw-r--r--lib/cli.rb18
-rw-r--r--lib/mastodon/accounts_cli.rb55
-rw-r--r--lib/mastodon/cli_helper.rb8
-rw-r--r--lib/mastodon/emoji_cli.rb81
-rw-r--r--lib/mastodon/media_cli.rb55
-rw-r--r--lib/mastodon/version.rb6
-rw-r--r--lib/tasks/db.rake2
-rw-r--r--lib/tasks/mastodon.rake80
-rw-r--r--scalingo.json5
-rw-r--r--spec/controllers/api/v1/mutes_controller_spec.rb57
-rw-r--r--spec/controllers/application_controller_spec.rb37
-rw-r--r--spec/fabricators/relay_fabricator.rb2
-rw-r--r--spec/helpers/application_helper_spec.rb10
-rw-r--r--spec/lib/activitypub/activity/accept_spec.rb26
-rw-r--r--spec/lib/activitypub/activity/reject_spec.rb26
-rw-r--r--spec/lib/formatter_spec.rb192
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--streaming/index.js23
212 files changed, 2899 insertions, 808 deletions
diff --git a/.buildpacks b/.buildpacks
index c232c712f..3450683ce 100644
--- a/.buildpacks
+++ b/.buildpacks
@@ -1,3 +1,4 @@
 https://github.com/heroku/heroku-buildpack-apt
+https://github.com/Scalingo/ffmpeg-buildpack
 https://github.com/Scalingo/nodejs-buildpack
 https://github.com/Scalingo/ruby-buildpack
diff --git a/.env.nanobox b/.env.nanobox
index 8e0af6a8a..b60b6ee68 100644
--- a/.env.nanobox
+++ b/.env.nanobox
@@ -136,8 +136,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # Defaults to 60 seconds. Set to 0 to disable
 # SWIFT_CACHE_TTL=
 
-# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
-# S3_CLOUDFRONT_HOST=
+# Optional alias for S3 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare)
+# S3_ALIAS_HOST=
 
 # Streaming API integration
 # STREAMING_API_BASE_URL=
diff --git a/.env.production.sample b/.env.production.sample
index 235b19207..efb5661fd 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -134,8 +134,8 @@ SMTP_FROM_ADDRESS=notifications@example.com
 # Defaults to 60 seconds. Set to 0 to disable
 # SWIFT_CACHE_TTL=
 
-# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
-# S3_CLOUDFRONT_HOST=
+# Optional alias for S3 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare)
+# S3_ALIAS_HOST=
 
 # Streaming API integration
 # STREAMING_API_BASE_URL=
diff --git a/.rubocop.yml b/.rubocop.yml
index 4f9e09b43..4ba5903bd 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -62,6 +62,9 @@ Metrics/ParameterLists:
 Metrics/PerceivedComplexity:
   Max: 20
 
+Naming/MemoizedInstanceVariableName:
+  Enabled: false
+
 Rails:
   Enabled: true
 
@@ -71,6 +74,9 @@ Rails/HasAndBelongsToMany:
 Rails/SkipsModelValidations:
   Enabled: false
 
+Rails/HttpStatus:
+  Enabled: false
+
 Style/ClassAndModuleChildren:
   Enabled: false
 
diff --git a/Gemfile b/Gemfile
index 760ecbc7c..356081dbf 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,7 @@ gem 'pkg-config', '~> 1.3'
 
 gem 'puma', '~> 3.11'
 gem 'rails', '~> 5.2.1'
+gem 'thor', '~> 0.20'
 
 gem 'hamlit-rails', '~> 0.2'
 gem 'pg', '~> 1.0'
@@ -41,7 +42,7 @@ gem 'omniauth-cas', '~> 1.1'
 gem 'omniauth-saml', '~> 1.10'
 gem 'omniauth', '~> 1.2'
 
-gem 'doorkeeper', '~> 4.4'
+gem 'doorkeeper', '~> 5.0'
 gem 'fast_blank', '~> 1.0'
 gem 'fastimage'
 gem 'goldfinger', '~> 2.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4c03ffc5e..8eda89d12 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -174,14 +174,14 @@ GEM
       devise (~> 4.0)
       railties (< 5.3)
       rotp (~> 2.0)
-    devise_pam_authenticatable2 (9.1.0)
+    devise_pam_authenticatable2 (9.1.1)
       devise (>= 4.0.0)
       rpam2 (~> 4.0)
     diff-lcs (1.3)
     docile (1.3.0)
     domain_name (0.5.20180417)
       unf (>= 0.0.5, < 1.0.0)
-    doorkeeper (4.4.2)
+    doorkeeper (5.0.0)
       railties (>= 4.2)
     dotenv (2.2.2)
     dotenv-rails (2.2.2)
@@ -672,7 +672,7 @@ DEPENDENCIES
   devise (~> 4.4)
   devise-two-factor (~> 3.0)
   devise_pam_authenticatable2 (~> 9.1)
-  doorkeeper (~> 4.4)
+  doorkeeper (~> 5.0)
   dotenv-rails (~> 2.2, < 2.3)
   fabrication (~> 2.20)
   faker (~> 1.8)
@@ -755,6 +755,7 @@ DEPENDENCIES
   stoplight (~> 2.1.3)
   streamio-ffmpeg (~> 3.0)
   strong_migrations (~> 0.2)
+  thor (~> 0.20)
   tty-command (~> 0.8)
   tty-prompt (~> 0.16)
   twitter-text (~> 1.14)
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index 3234b194f..c05c4c841 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -16,6 +16,8 @@ module Admin
       timeline_preview
       show_staff_badge
       bootstrap_timeline_accounts
+      flavour
+      skin
       thumbnail
       hero
       min_invite_role
@@ -23,6 +25,7 @@ module Admin
       peers_api_enabled
       show_known_fediverse_at_about_page
       preview_sensitive_media
+      custom_css
     ).freeze
 
     BOOLEAN_SETTINGS = %w(
diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb
index 0c7bdad9e..f9bbf36fb 100644
--- a/app/controllers/admin/suspensions_controller.rb
+++ b/app/controllers/admin/suspensions_controller.rb
@@ -14,7 +14,7 @@ module Admin
       @suspension = Form::AdminSuspensionConfirmation.new(suspension_params)
 
       if suspension_params[:acct] == @account.acct
-        resolve_report! if suspension_params[:report_id]
+        resolve_report! if suspension_params[:report_id].present?
         perform_suspend!
         mark_reports_resolved!
         redirect_to admin_accounts_path
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 770a69921..0b3735087 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -7,6 +7,8 @@ class Api::BaseController < ApplicationController
   include RateLimitHeaders
 
   skip_before_action :store_current_location
+  skip_before_action :check_user_permissions
+
   protect_from_forgery with: :null_session
 
   rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb
index 19de56732..ec4477034 100644
--- a/app/controllers/api/v1/lists/accounts_controller.rb
+++ b/app/controllers/api/v1/lists/accounts_controller.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class Api::V1::Lists::AccountsController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :read, :'read:lists' },    only:  [:show]
+  before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show]
   before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show]
 
   before_action :require_user!
diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb
index b42b8b971..054172bee 100644
--- a/app/controllers/api/v1/lists_controller.rb
+++ b/app/controllers/api/v1/lists_controller.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class Api::V1::ListsController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :read, :'read:lists' },    only:  [:index, :show]
+  before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index, :show]
   before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:index, :show]
 
   before_action :require_user!
diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb
index aea94d553..3b3a39943 100644
--- a/app/controllers/api/v1/mutes_controller.rb
+++ b/app/controllers/api/v1/mutes_controller.rb
@@ -20,11 +20,7 @@ class Api::V1::MutesController < Api::BaseController
   private
 
   def load_accounts
-    default_accounts.merge(paginated_mutes).to_a
-  end
-
-  def default_accounts
-    Account.includes(:muted_by).references(:muted_by)
+    paginated_mutes.map(&:target_account)
   end
 
   def load_mutes
@@ -32,11 +28,13 @@ class Api::V1::MutesController < Api::BaseController
   end
 
   def paginated_mutes
-    Mute.where(account: current_account).paginate_by_max_id(
-      limit_param(DEFAULT_ACCOUNTS_LIMIT),
-      params[:max_id],
-      params[:since_id]
-    )
+    @paginated_mutes ||= Mute.eager_load(:target_account)
+                             .where(account: current_account)
+                             .paginate_by_max_id(
+                               limit_param(DEFAULT_ACCOUNTS_LIMIT),
+                               params[:max_id],
+                               params[:since_id]
+                             )
   end
 
   def insert_pagination_headers
@@ -50,7 +48,7 @@ class Api::V1::MutesController < Api::BaseController
   end
 
   def prev_path
-    unless@data.empty?
+    unless @data.empty?
       url_for pagination_params(since_id: pagination_since_id)
     end
   end
@@ -59,7 +57,7 @@ class Api::V1::MutesController < Api::BaseController
     if params[:action] == "details"
       @mutes.last.id
     else
-      @accounts.last.muted_by_ids.last
+      paginated_mutes.last.id
     end
   end
 
@@ -67,7 +65,7 @@ class Api::V1::MutesController < Api::BaseController
     if params[:action] == "details"
       @mutes.first.id
     else
-      @accounts.first.muted_by_ids.first
+      paginated_mutes.first.id
     end
   end
 
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 27cd0f4f9..8ffc31bb4 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
   rescue_from Mastodon::NotPermittedError, with: :forbidden
 
   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
-  before_action :check_suspension, if: :user_signed_in?
+  before_action :check_user_permissions, if: :user_signed_in?
 
   def raise_not_found
     raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
@@ -49,8 +49,8 @@ class ApplicationController < ActionController::Base
     forbidden unless current_user&.staff?
   end
 
-  def check_suspension
-    forbidden if current_user.account.suspended?
+  def check_user_permissions
+    forbidden if current_user.disabled? || current_user.account.suspended?
   end
 
   def after_sign_out_path_for(_resource_or_scope)
@@ -165,12 +165,12 @@ class ApplicationController < ActionController::Base
   end
 
   def current_flavour
-    return Setting.default_settings['flavour'] unless Themes.instance.flavours.include? current_user&.setting_flavour
+    return Setting.flavour unless Themes.instance.flavours.include? current_user&.setting_flavour
     current_user.setting_flavour
   end
 
   def current_skin
-    return 'default' unless Themes.instance.skins_for(current_flavour).include? current_user&.setting_skin
+    return Setting.skin unless Themes.instance.skins_for(current_flavour).include? current_user&.setting_skin
     current_user.setting_skin
   end
 
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index 4c0d93f5d..7cd46662f 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -6,7 +6,7 @@ class Auth::SessionsController < Devise::SessionsController
   layout 'auth'
 
   skip_before_action :require_no_authentication, only: [:create]
-  skip_before_action :check_suspension, only: [:destroy]
+  skip_before_action :check_user_permissions, only: [:destroy]
   prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
   prepend_before_action :set_pack
   before_action :set_instance_presenter, only: [:new]
diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb
new file mode 100644
index 000000000..31e501609
--- /dev/null
+++ b/app/controllers/custom_css_controller.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class CustomCssController < ApplicationController
+  before_action :set_cache_headers
+
+  def show
+    skip_session!
+    render plain: Setting.custom_css || '', content_type: 'text/css'
+  end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 327901e4e..6b41fd36e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -27,11 +27,6 @@ module ApplicationHelper
     Setting.open_deletion
   end
 
-  def add_rtl_body_class(other_classes)
-    other_classes = "#{other_classes} rtl" if locale_direction == 'rtl'
-    other_classes
-  end
-
   def locale_direction
     if [:ar, :fa, :he].include?(I18n.locale)
       'rtl'
@@ -77,4 +72,14 @@ module ApplicationHelper
   def react_component(name, props = {})
     content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) })
   end
+
+  def body_classes
+    output = (@body_classes || '').split(' ')
+    output << "flavour-#{current_flavour.parameterize}"
+    output << "skin-#{current_skin.parameterize}"
+    output << 'system-font' if current_account&.user&.setting_system_font_ui
+    output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
+    output << 'rtl' if locale_direction == 'rtl'
+    output.reject(&:blank?).join(' ')
+  end
 end
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index 9ded69436..187bfe4a0 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -62,17 +62,17 @@ module StreamEntriesHelper
     prepend_str = [
       [
         number_to_human(account.statuses_count, strip_insignificant_zeros: true),
-        I18n.t('accounts.posts'),
+        I18n.t('accounts.posts', count: account.statuses_count),
       ].join(' '),
 
       [
         number_to_human(account.following_count, strip_insignificant_zeros: true),
-        I18n.t('accounts.following'),
+        I18n.t('accounts.following', count: account.following_count),
       ].join(' '),
 
       [
         number_to_human(account.followers_count, strip_insignificant_zeros: true),
-        I18n.t('accounts.followers'),
+        I18n.t('accounts.followers', count: account.followers_count),
       ].join(' '),
     ].join(', ')
 
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index 28f27fbc6..0c26dc18d 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -41,3 +41,10 @@ delegate(document, '.media-spoiler-hide-button', 'click', () => {
     element.click();
   });
 });
+
+delegate(document, '#domain_block_severity', 'change', ({ target }) => {
+  const rejectMediaDiv = document.querySelector('.input.with_label.domain_block_reject_media');
+  if (rejectMediaDiv) {
+    rejectMediaDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
+  }
+});
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index f6c8086fe..fb311fc0a 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -3,6 +3,8 @@ import { CancelToken } from 'axios';
 import { throttle } from 'lodash';
 import { search as emojiSearch } from 'flavours/glitch/util/emoji/emoji_mart_search_light';
 import { useEmoji } from './emojis';
+import { tagHistory } from 'flavours/glitch/util/settings';
+import { recoverHashtags } from 'flavours/glitch/util/hashtag';
 import resizeImage from 'flavours/glitch/util/resize_image';
 
 import { updateTimeline } from './timelines';
@@ -28,6 +30,9 @@ export const COMPOSE_UPLOAD_UNDO     = 'COMPOSE_UPLOAD_UNDO';
 export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
 export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
 export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
+export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
+
+export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
 
 export const COMPOSE_MOUNT   = 'COMPOSE_MOUNT';
 export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
@@ -136,6 +141,7 @@ export function submitCompose() {
         'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
       },
     }).then(function (response) {
+      dispatch(insertIntoTagHistory(response.data.tags, status));
       dispatch(submitComposeSuccess({ ...response.data }));
 
       //  If the response has no data then we can't do anything else.
@@ -315,12 +321,22 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
   dispatch(readyComposeSuggestionsEmojis(token, results));
 };
 
+const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
+  dispatch(updateSuggestionTags(token));
+};
+
 export function fetchComposeSuggestions(token) {
   return (dispatch, getState) => {
-    if (token[0] === ':') {
+    switch (token[0]) {
+    case ':':
       fetchComposeSuggestionsEmojis(dispatch, getState, token);
-    } else {
+      break;
+    case '#':
+      fetchComposeSuggestionsTags(dispatch, getState, token);
+      break;
+    default:
       fetchComposeSuggestionsAccounts(dispatch, getState, token);
+      break;
     }
   };
 };
@@ -343,10 +359,15 @@ export function readyComposeSuggestionsAccounts(token, accounts) {
 
 export function selectComposeSuggestion(position, token, suggestion) {
   return (dispatch, getState) => {
-    const completion = typeof suggestion === 'object' && suggestion.id ? (
-      dispatch(useEmoji(suggestion)),
-      suggestion.native || suggestion.colons
-    ) : '@' + getState().getIn(['accounts', suggestion, 'acct']);
+    let completion;
+    if (typeof suggestion === 'object' && suggestion.id) {
+      dispatch(useEmoji(suggestion));
+      completion = suggestion.native || suggestion.colons;
+    } else if (suggestion[0] === '#') {
+      completion = suggestion;
+    } else {
+      completion = '@' + getState().getIn(['accounts', suggestion, 'acct']);
+    }
 
     dispatch({
       type: COMPOSE_SUGGESTION_SELECT,
@@ -357,6 +378,48 @@ export function selectComposeSuggestion(position, token, suggestion) {
   };
 };
 
+export function updateSuggestionTags(token) {
+  return {
+    type: COMPOSE_SUGGESTION_TAGS_UPDATE,
+    token,
+  };
+}
+
+export function updateTagHistory(tags) {
+  return {
+    type: COMPOSE_TAG_HISTORY_UPDATE,
+    tags,
+  };
+}
+
+export function hydrateCompose() {
+  return (dispatch, getState) => {
+    const me = getState().getIn(['meta', 'me']);
+    const history = tagHistory.get(me);
+
+    if (history !== null) {
+      dispatch(updateTagHistory(history));
+    }
+  };
+}
+
+function insertIntoTagHistory(recognizedTags, text) {
+  return (dispatch, getState) => {
+    const state = getState();
+    const oldHistory = state.getIn(['compose', 'tagHistory']);
+    const me = state.getIn(['meta', 'me']);
+    const names = recoverHashtags(recognizedTags, text);
+    const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1);
+
+    names.push(...intersectedOldHistory.toJS());
+
+    const newHistory = names.slice(0, 1000);
+
+    tagHistory.set(me, newHistory);
+    dispatch(updateTagHistory(newHistory));
+  };
+}
+
 export function mountCompose() {
   return {
     type: COMPOSE_MOUNT,
diff --git a/app/javascript/flavours/glitch/actions/store.js b/app/javascript/flavours/glitch/actions/store.js
index a1db0fdd5..2dd94a998 100644
--- a/app/javascript/flavours/glitch/actions/store.js
+++ b/app/javascript/flavours/glitch/actions/store.js
@@ -1,4 +1,5 @@
 import { Iterable, fromJS } from 'immutable';
+import { hydrateCompose } from './compose';
 
 export const STORE_HYDRATE = 'STORE_HYDRATE';
 export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@@ -8,10 +9,14 @@ const convertState = rawState =>
     Iterable.isIndexed(v) ? v.toList() : v.toMap());
 
 export function hydrateStore(rawState) {
-  const state = convertState(rawState);
+  return dispatch => {
+    const state = convertState(rawState);
 
-  return {
-    type: STORE_HYDRATE,
-    state,
+    dispatch({
+      type: STORE_HYDRATE,
+      state,
+    });
+
+    dispatch(hydrateCompose());
   };
 };
diff --git a/app/javascript/flavours/glitch/components/extended_video_player.js b/app/javascript/flavours/glitch/components/extended_video_player.js
index 9e2f6835a..009c0d559 100644
--- a/app/javascript/flavours/glitch/components/extended_video_player.js
+++ b/app/javascript/flavours/glitch/components/extended_video_player.js
@@ -50,6 +50,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
           role='button'
           tabIndex='0'
           aria-label={alt}
+          title={alt}
           muted={muted}
           controls={controls}
           loop={!controls}
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index d87be14cc..3faf0b453 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -174,6 +174,7 @@ class Item extends React.PureComponent {
           <video
             className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
             aria-label={attachment.get('description')}
+            title={attachment.get('description')}
             role='application'
             src={attachment.get('url')}
             onClick={this.handleClick}
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index a87721ef8..1ac5a4b3e 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -412,6 +412,7 @@ export default class Status extends ImmutablePureComponent {
             {Component => (<Component
               preview={video.get('preview_url')}
               src={video.get('url')}
+              alt={video.get('description')}
               inline
               sensitive={status.get('sensitive')}
               letterbox={settings.getIn(['media', 'letterbox'])}
@@ -474,6 +475,7 @@ export default class Status extends ImmutablePureComponent {
     const computedClass = classNames('status', `status-${status.get('visibility')}`, {
       collapsed: isCollapsed,
       'has-background': isCollapsed && background,
+      'status__wrapper-reply': !!status.get('in_reply_to_id'),
       muted,
     }, 'focusable');
 
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 174df0cc9..eda0d637e 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -56,7 +56,9 @@ export default class Header extends ImmutablePureComponent {
     }
 
     if (me !== account.get('id')) {
-      if (account.getIn(['relationship', 'requested'])) {
+      if (!account.get('relationship')) { // Wait until the relationship is loaded
+        actionBtn = '';
+      } else if (account.getIn(['relationship', 'requested'])) {
         actionBtn = (
           <div className='account--action-button'>
             <IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
index f312e9d59..cf6f45b34 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/composer/index.js
@@ -51,6 +51,7 @@ import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
 
 //  State mapping.
 function mapStateToProps (state) {
+  const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
   const inReplyTo = state.getIn(['compose', 'in_reply_to']);
   const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
   const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
@@ -85,12 +86,13 @@ function mapStateToProps (state) {
     sideArm: sideArmPrivacy,
     sensitive: state.getIn(['compose', 'sensitive']),
     showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
-    spoiler: state.getIn(['compose', 'spoiler']),
+    spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
     spoilerText: state.getIn(['compose', 'spoiler_text']),
     suggestionToken: state.getIn(['compose', 'suggestion_token']),
     suggestions: state.getIn(['compose', 'suggestions']),
     text: state.getIn(['compose', 'text']),
     anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
+    spoilersAlwaysOn: spoilersAlwaysOn,
   };
 };
 
@@ -376,6 +378,7 @@ class Composer extends React.Component {
       spoilerText,
       suggestions,
       text,
+      spoilersAlwaysOn,
     } = this.props;
 
     let disabledButton = isSubmitting || isUploading || (!!text.length && !text.trim().length && !anyMedia);
@@ -443,7 +446,7 @@ class Composer extends React.Component {
           onDoodleOpen={onOpenDoodleModal}
           onModalClose={onCloseModal}
           onModalOpen={onOpenActionsModal}
-          onToggleSpoiler={onChangeSpoilerness}
+          onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
           onUpload={onUpload}
           privacy={privacy}
           resetFileKey={resetFileKey}
@@ -515,6 +518,7 @@ Composer.propTypes = {
   onUnmount: PropTypes.func,
   onUpload: PropTypes.func,
   anyMedia: PropTypes.bool,
+  spoilersAlwaysOn: PropTypes.bool,
 };
 
 //  Connecting and export.
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js
index c129622bc..05cbe24c9 100644
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/index.js
@@ -285,13 +285,15 @@ export default class ComposerOptions extends React.PureComponent {
           title={intl.formatMessage(messages.change_privacy)}
           value={privacy}
         />
-        <TextIconButton
-          active={spoiler}
-          ariaControls='glitch.composer.spoiler.input'
-          label='CW'
-          onClick={onToggleSpoiler}
-          title={intl.formatMessage(messages.spoiler)}
-        />
+        {onToggleSpoiler && (
+          <TextIconButton
+            active={spoiler}
+            ariaControls='glitch.composer.spoiler.input'
+            label='CW'
+            onClick={onToggleSpoiler}
+            title={intl.formatMessage(messages.spoiler)}
+          />
+        )}
         <Dropdown
           active={advancedOptions && advancedOptions.some(value => !!value)}
           disabled={disabled}
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/index.js b/app/javascript/flavours/glitch/features/composer/textarea/index.js
index 51d44a83b..50e46fc78 100644
--- a/app/javascript/flavours/glitch/features/composer/textarea/index.js
+++ b/app/javascript/flavours/glitch/features/composer/textarea/index.js
@@ -58,7 +58,7 @@ const handlers = {
     const right = value.slice(selectionStart).search(/[\s\u200B]/);
     const token = function () {
       switch (true) {
-      case left < 0 || !/[@:]/.test(value[left]):
+      case left < 0 || !/[@:#]/.test(value[left]):
         return null;
       case right < 0:
         return value.slice(left);
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
index f55640bcf..331692398 100644
--- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
+++ b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
@@ -57,6 +57,42 @@ export default class ComposerTextareaSuggestionsItem extends React.Component {
     } = this.props;
     const computedClass = classNames('composer--textarea--suggestions--item', { selected });
 
+    //  If the suggestion is an object, then we render an emoji.
+    //  Otherwise, we render a hashtag if it starts with #, or an account.
+    let inner;
+    if (typeof suggestion === 'object') {
+      let url;
+      if (suggestion.custom) {
+        url = suggestion.imageUrl;
+      } else {
+        const mapping = unicodeMapping[suggestion.native] || unicodeMapping[suggestion.native.replace(/\uFE0F$/, '')];
+        if (mapping) {
+          url = `${assetHost}/emoji/${mapping.filename}.svg`;
+        }
+      }
+      if (url) {
+        inner = (
+          <div className='emoji'>
+            <img
+              alt={suggestion.native || suggestion.colons}
+              className='emojione'
+              src={url}
+            />
+            {suggestion.colons}
+          </div>
+        );
+      }
+    } else if (suggestion[0] === '#') {
+      inner = suggestion;
+    } else {
+      inner = (
+        <AccountContainer
+          id={suggestion}
+          small
+        />
+      );
+    }
+
     //  The result.
     return (
       <div
@@ -66,37 +102,7 @@ export default class ComposerTextareaSuggestionsItem extends React.Component {
         role='button'
         tabIndex='0'
       >
-        { //  If the suggestion is an object, then we render an emoji.
-          //  Otherwise, we render an account.
-          typeof suggestion === 'object' ? function () {
-            const url = function () {
-              if (suggestion.custom) {
-                return suggestion.imageUrl;
-              } else {
-                const mapping = unicodeMapping[suggestion.native] || unicodeMapping[suggestion.native.replace(/\uFE0F$/, '')];
-                if (!mapping) {
-                  return null;
-                }
-                return `${assetHost}/emoji/${mapping.filename}.svg`;
-              }
-            }();
-            return url ? (
-              <div className='emoji'>
-                <img
-                  alt={suggestion.native || suggestion.colons}
-                  className='emojione'
-                  src={url}
-                />
-                {suggestion.colons}
-              </div>
-            ) : null;
-          }() : (
-            <AccountContainer
-              id={suggestion}
-              small
-            />
-          )
-        }
+        { inner }
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index f88e23c47..ad5c11979 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -77,6 +77,14 @@ export default class LocalSettingsPage extends React.PureComponent {
           <h2><FormattedMessage id='settings.compose_box_opts' defaultMessage='Compose box options' /></h2>
           <LocalSettingsPageItem
             settings={settings}
+            item={['always_show_spoilers_field']}
+            id='mastodon-settings--always_show_spoilers_field'
+            onChange={onChange}
+          >
+            <FormattedMessage id='settings.always_show_spoilers_field' defaultMessage='Always enable the Content Warning field' />
+          </LocalSettingsPageItem>
+          <LocalSettingsPageItem
+            settings={settings}
             item={['side_arm']}
             id='mastodon-settings--side_arm'
             options={[
diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.js b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
index a685132b0..d674eecf9 100644
--- a/app/javascript/flavours/glitch/features/report/components/status_check_box.js
+++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
@@ -36,6 +36,7 @@ export default class StatusCheckBox extends React.PureComponent {
               <Component
                 preview={video.get('preview_url')}
                 src={video.get('url')}
+                alt={video.get('description')}
                 width={239}
                 height={110}
                 inline
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index 5cfc9dfae..ee9eb02c6 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -60,6 +60,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
           <Video
             preview={video.get('preview_url')}
             src={video.get('url')}
+            alt={video.get('description')}
             inline
             sensitive={status.get('sensitive')}
             letterbox={settings.getIn(['media', 'letterbox'])}
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index a578df026..7e284a0bc 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -323,6 +323,7 @@ export default class Video extends React.PureComponent {
           role='button'
           tabIndex='0'
           aria-label={alt}
+          title={alt}
           width={width}
           height={height}
           onClick={this.togglePlay}
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index 8b997bf4d..594d70ee2 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -18,6 +18,8 @@ import {
   COMPOSE_SUGGESTIONS_CLEAR,
   COMPOSE_SUGGESTIONS_READY,
   COMPOSE_SUGGESTION_SELECT,
+  COMPOSE_SUGGESTION_TAGS_UPDATE,
+  COMPOSE_TAG_HISTORY_UPDATE,
   COMPOSE_ADVANCED_OPTIONS_CHANGE,
   COMPOSE_SENSITIVITY_CHANGE,
   COMPOSE_SPOILERNESS_CHANGE,
@@ -39,6 +41,7 @@ import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
 import { me } from 'flavours/glitch/util/initial_state';
 import { overwrite } from 'flavours/glitch/util/js_helpers';
 import { unescapeHTML } from 'flavours/glitch/util/html';
+import { recoverHashtags } from 'flavours/glitch/util/hashtag';
 
 const totalElefriends = 3;
 
@@ -76,6 +79,7 @@ const initialState = ImmutableMap({
   default_sensitive: false,
   resetFileKey: Math.floor((Math.random() * 0x10000)),
   idempotencyKey: null,
+  tagHistory: ImmutableList(),
   doodle: ImmutableMap({
     fg: 'rgb(  0,    0,    0)',
     bg: 'rgb(255,  255,  255)',
@@ -114,8 +118,9 @@ function apiStatusToTextMentions (state, status) {
 }
 
 function apiStatusToTextHashtags (state, status) {
-  return ImmutableOrderedSet([]).union(status.tags.map(
-    ({ name }) => `#${name} `
+  const text = unescapeHTML(status.content);
+  return ImmutableOrderedSet([]).union(recoverHashtags(status.tags, text).map(
+    (name) => `#${name} `
   )).join('');
 }
 
@@ -204,6 +209,18 @@ const insertSuggestion = (state, position, token, completion) => {
   });
 };
 
+const updateSuggestionTags = (state, token) => {
+  const prefix = token.slice(1);
+
+  return state.merge({
+    suggestions: state.get('tagHistory')
+      .filter(tag => tag.toLowerCase().startsWith(prefix.toLowerCase()))
+      .slice(0, 4)
+      .map(tag => '#' + tag),
+    suggestion_token: token,
+  });
+};
+
 const insertEmoji = (state, position, emojiData) => {
   const emoji = emojiData.native;
 
@@ -358,6 +375,10 @@ export default function compose(state = initialState, action) {
     return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
   case COMPOSE_SUGGESTION_SELECT:
     return insertSuggestion(state, action.position, action.token, action.completion);
+  case COMPOSE_SUGGESTION_TAGS_UPDATE:
+    return updateSuggestionTags(state, action.token);
+  case COMPOSE_TAG_HISTORY_UPDATE:
+    return state.set('tagHistory', fromJS(action.tags));
   case TIMELINE_DELETE:
     if (action.id === state.get('in_reply_to')) {
       return state.set('in_reply_to', null);
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 51032f345..1d24f0e9a 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -12,6 +12,7 @@ const initialState = ImmutableMap({
   side_arm  : 'none',
   side_arm_reply_mode : 'keep',
   show_reply_count : false,
+  always_show_spoilers_field: false,
   collapsed : ImmutableMap({
     enabled     : true,
     auto        : ImmutableMap({
diff --git a/app/javascript/flavours/glitch/styles/components/metadata.scss b/app/javascript/flavours/glitch/styles/components/metadata.scss
index 29a6330e9..2efe6cd66 100644
--- a/app/javascript/flavours/glitch/styles/components/metadata.scss
+++ b/app/javascript/flavours/glitch/styles/components/metadata.scss
@@ -21,7 +21,7 @@
   dt,
   dd {
     box-sizing: border-box;
-    padding: 14px 20px;
+    padding: 14px 5px;
     text-align: center;
     max-height: 48px;
     overflow: hidden;
diff --git a/app/javascript/flavours/glitch/util/hashtag.js b/app/javascript/flavours/glitch/util/hashtag.js
new file mode 100644
index 000000000..d5ea57662
--- /dev/null
+++ b/app/javascript/flavours/glitch/util/hashtag.js
@@ -0,0 +1,8 @@
+export function recoverHashtags (recognizedTags, text) {
+  return recognizedTags.map(tag => {
+      const re = new RegExp(`(?:^|[^\/\)\w])#(${tag.name})`, 'i');
+      const matched_hashtag = text.match(re);
+      return matched_hashtag ? matched_hashtag[1] : tag;
+    }
+  );
+}
diff --git a/app/javascript/flavours/glitch/util/settings.js b/app/javascript/flavours/glitch/util/settings.js
index dbd969cb1..7643a508e 100644
--- a/app/javascript/flavours/glitch/util/settings.js
+++ b/app/javascript/flavours/glitch/util/settings.js
@@ -44,3 +44,4 @@ export default class Settings {
 }
 
 export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
+export const tagHistory = new Settings('mastodon_tag_history');
diff --git a/app/javascript/mastodon/.gitkeep b/app/javascript/mastodon/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
--- a/app/javascript/mastodon/.gitkeep
+++ /dev/null
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index fe3e831d5..6d975cd1e 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -130,7 +130,7 @@ export function submitCompose() {
         'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
       },
     }).then(function (response) {
-      dispatch(insertIntoTagHistory(response.data.tags));
+      dispatch(insertIntoTagHistory(response.data.tags, status));
       dispatch(submitComposeSuccess({ ...response.data }));
 
       // To make the app more responsive, immediately get the status into the columns
@@ -390,13 +390,13 @@ export function hydrateCompose() {
   };
 }
 
-function insertIntoTagHistory(tags) {
+function insertIntoTagHistory(recognizedTags, text) {
   return (dispatch, getState) => {
     const state = getState();
     const oldHistory = state.getIn(['compose', 'tagHistory']);
     const me = state.getIn(['meta', 'me']);
-    const names = tags.map(({ name }) => name);
-    const intersectedOldHistory = oldHistory.filter(name => !names.includes(name));
+    const names = recognizedTags.map(tag => text.match(new RegExp(`#${tag.name}`, 'i'))[0].slice(1));
+    const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1);
 
     names.push(...intersectedOldHistory.toJS());
 
diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js
index e81236d26..d45387463 100644
--- a/app/javascript/mastodon/components/column.js
+++ b/app/javascript/mastodon/components/column.js
@@ -7,6 +7,7 @@ export default class Column extends React.PureComponent {
 
   static propTypes = {
     children: PropTypes.node,
+    label: PropTypes.string,
   };
 
   scrollTop () {
@@ -40,10 +41,10 @@ export default class Column extends React.PureComponent {
   }
 
   render () {
-    const { children } = this.props;
+    const { label, children } = this.props;
 
     return (
-      <div role='region' className='column' ref={this.setRef}>
+      <div role='region' aria-label={label} className='column' ref={this.setRef}>
         {children}
       </div>
     );
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index e83f724e9..a5cf6479b 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -226,6 +226,12 @@ export default class Dropdown extends React.PureComponent {
     return this.target;
   }
 
+  componentWillUnmount = () => {
+    if (this.state.id === this.props.openDropdownId) {
+      this.handleClose();
+    }
+  }
+
   render () {
     const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId } = this.props;
     const open = this.state.id === openDropdownId;
diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js
index 9e2f6835a..009c0d559 100644
--- a/app/javascript/mastodon/components/extended_video_player.js
+++ b/app/javascript/mastodon/components/extended_video_player.js
@@ -50,6 +50,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
           role='button'
           tabIndex='0'
           aria-label={alt}
+          title={alt}
           muted={muted}
           controls={controls}
           loop={!controls}
diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js
index e2ce9ec96..de2203a4b 100644
--- a/app/javascript/mastodon/components/intersection_observer_article.js
+++ b/app/javascript/mastodon/components/intersection_observer_article.js
@@ -109,7 +109,7 @@ export default class IntersectionObserverArticle extends React.Component {
       return (
         <article
           ref={this.handleRef}
-          aria-posinset={index}
+          aria-posinset={index + 1}
           aria-setsize={listLength}
           style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
           data-id={id}
@@ -121,7 +121,7 @@ export default class IntersectionObserverArticle extends React.Component {
     }
 
     return (
-      <article ref={this.handleRef} aria-posinset={index} aria-setsize={listLength} data-id={id} tabIndex='0'>
+      <article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex='0'>
         {children && React.cloneElement(children, { hidden: false })}
       </article>
     );
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 63bc4a59b..6e1310cd6 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -154,6 +154,7 @@ class Item extends React.PureComponent {
           <video
             className='media-gallery__item-gifv-thumbnail'
             aria-label={attachment.get('description')}
+            title={attachment.get('description')}
             role='application'
             src={attachment.get('url')}
             onClick={this.handleClick}
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 4b433f32c..5c888650c 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -26,6 +26,7 @@ export default class ScrollableList extends PureComponent {
     hasMore: PropTypes.bool,
     prepend: PropTypes.node,
     alwaysPrepend: PropTypes.bool,
+    alwaysShowScrollbar: PropTypes.bool,
     emptyMessage: PropTypes.node,
     children: PropTypes.node,
   };
@@ -141,7 +142,7 @@ export default class ScrollableList extends PureComponent {
   }
 
   render () {
-    const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
+    const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, alwaysPrepend, alwaysShowScrollbar, emptyMessage, onLoadMore } = this.props;
     const { fullscreen } = this.state;
     const childrenCount = React.Children.count(children);
 
@@ -172,11 +173,13 @@ export default class ScrollableList extends PureComponent {
         </div>
       );
     } else {
+      const scrollable = alwaysShowScrollbar;
+
       scrollableArea = (
-        <div style={{ flex: '1 1 auto', display: 'flex', flexDirection: 'column' }}>
+        <div className={classNames({ scrollable, fullscreen })} ref={this.setRef} style={{ flex: '1 1 auto', display: 'flex', flexDirection: 'column' }}>
           {alwaysPrepend && prepend}
 
-          <div className='empty-column-indicator' ref={this.setRef}>
+          <div className='empty-column-indicator'>
             {emptyMessage}
           </div>
         </div>
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index e653906f1..43647acc0 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -8,7 +8,7 @@ import DisplayName from './display_name';
 import StatusContent from './status_content';
 import StatusActionBar from './status_action_bar';
 import AttachmentList from './attachment_list';
-import { FormattedMessage } from 'react-intl';
+import { injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { MediaGallery, Video } from '../features/ui/util/async-components';
 import { HotKeys } from 'react-hotkeys';
@@ -18,6 +18,24 @@ import classNames from 'classnames';
 // to use the progress bar to show download progress
 import Bundle from '../features/ui/components/bundle';
 
+export const textForScreenReader = (intl, status, rebloggedByText = false) => {
+  const displayName = status.getIn(['account', 'display_name']);
+
+  const values = [
+    displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
+    status.get('spoiler_text') && status.get('hidden') ? status.get('spoiler_text') : status.get('search_index').slice(status.get('spoiler_text').length),
+    intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
+    status.getIn(['account', 'acct']),
+  ];
+
+  if (rebloggedByText) {
+    values.push(rebloggedByText);
+  }
+
+  return values.join(', ');
+};
+
+@injectIntl
 export default class Status extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -138,9 +156,9 @@ export default class Status extends ImmutablePureComponent {
 
   render () {
     let media = null;
-    let statusAvatar, prepend;
+    let statusAvatar, prepend, rebloggedByText;
 
-    const { hidden, featured } = this.props;
+    const { intl, hidden, featured } = this.props;
 
     let { status, account, ...other } = this.props;
 
@@ -189,6 +207,8 @@ export default class Status extends ImmutablePureComponent {
         </div>
       );
 
+      rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) });
+
       account = status.get('account');
       status  = status.get('reblog');
     }
@@ -210,6 +230,7 @@ export default class Status extends ImmutablePureComponent {
               <Component
                 preview={video.get('preview_url')}
                 src={video.get('url')}
+                alt={video.get('description')}
                 width={239}
                 height={110}
                 inline
@@ -248,10 +269,10 @@ export default class Status extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={handlers}>
-        <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null}>
+        <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}>
           {prepend}
 
-          <div className={classNames('status', `status-${status.get('visibility')}`, { muted: this.props.muted })} data-id={status.get('id')}>
+          <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
             <div className='status__info'>
               <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
 
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index ed375c3e5..bbc0d5e96 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -34,7 +34,7 @@ const messages = defineMessages({
   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
-  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
+  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
 });
 
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index dd2cd406b..eb9236aeb 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -104,7 +104,9 @@ export default class Header extends ImmutablePureComponent {
     }
 
     if (me !== account.get('id')) {
-      if (account.getIn(['relationship', 'requested'])) {
+      if (!account.get('relationship')) { // Wait until the relationship is loaded
+        actionBtn = '';
+      } else if (account.getIn(['relationship', 'requested'])) {
         actionBtn = (
           <div className='account--action-button'>
             <IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js
index 0b88e50ae..68661a37c 100644
--- a/app/javascript/mastodon/features/blocks/index.js
+++ b/app/javascript/mastodon/features/blocks/index.js
@@ -1,15 +1,16 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import { debounce } from 'lodash';
 import PropTypes from 'prop-types';
 import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll-4';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import AccountContainer from '../../containers/account_container';
 import { fetchBlocks, expandBlocks } from '../../actions/blocks';
+import ScrollableList from '../../components/scrollable_list';
 
 const messages = defineMessages({
   heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
@@ -35,13 +36,9 @@ export default class Blocks extends ImmutablePureComponent {
     this.props.dispatch(fetchBlocks());
   }
 
-  handleScroll = (e) => {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandBlocks());
-    }
-  }
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandBlocks());
+  }, 300, { leading: true });
 
   render () {
     const { intl, accountIds, shouldUpdateScroll } = this.props;
@@ -54,16 +51,21 @@ export default class Blocks extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
+
     return (
       <Column icon='ban' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
-        <ScrollContainer scrollKey='blocks' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            {accountIds.map(id =>
-              <AccountContainer key={id} id={id} />
-            )}
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='blocks'
+          onLoadMore={this.handleLoadMore}
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountContainer key={id} id={id} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
index 1cd5cf157..48d2b3f68 100644
--- a/app/javascript/mastodon/features/community_timeline/index.js
+++ b/app/javascript/mastodon/features/community_timeline/index.js
@@ -105,7 +105,7 @@ export default class CommunityTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='users'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js
index 70b28a2ba..8b9609138 100644
--- a/app/javascript/mastodon/features/compose/components/upload_button.js
+++ b/app/javascript/mastodon/features/compose/components/upload_button.js
@@ -7,7 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 
 const messages = defineMessages({
-  upload: { id: 'upload_button.label', defaultMessage: 'Add media' },
+  upload: { id: 'upload_button.label', defaultMessage: 'Add media (JPEG, PNG, GIF, WebM, MP4, MOV)' },
 });
 
 const makeMapStateToProps = () => {
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index df1ec4915..b7394a39e 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -22,6 +22,7 @@ const messages = defineMessages({
   community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
+  compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
 });
 
 const mapStateToProps = (state, ownProps) => ({
@@ -95,7 +96,7 @@ export default class Compose extends React.PureComponent {
     }
 
     return (
-      <div className='drawer'>
+      <div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
         {header}
 
         {(multiColumn || isSearchPage) && <SearchContainer /> }
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
index 2181c75b6..dd289ce56 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.js
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -76,7 +76,7 @@ export default class DirectTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='envelope'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js
index e4e2b5239..2c40bf72d 100644
--- a/app/javascript/mastodon/features/domain_blocks/index.js
+++ b/app/javascript/mastodon/features/domain_blocks/index.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -52,10 +52,17 @@ export default class Blocks extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
+
     return (
       <Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
-        <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} shouldUpdateScroll={shouldUpdateScroll}>
+        <ScrollableList
+          scrollKey='domain_blocks'
+          onLoadMore={this.handleLoadMore}
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
           {domains.map(domain =>
             <DomainContainer key={domain} domain={domain} />
           )}
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js
index 3973ed3cb..499530cd9 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.js
+++ b/app/javascript/mastodon/features/favourited_statuses/index.js
@@ -7,7 +7,7 @@ import Column from '../ui/components/column';
 import ColumnHeader from '../../components/column_header';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import StatusList from '../../components/status_list';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { debounce } from 'lodash';
 
@@ -71,8 +71,10 @@ export default class Favourites extends ImmutablePureComponent {
     const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
     const pinned = !!columnId;
 
+    const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
+
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.heading)}>
         <ColumnHeader
           icon='star'
           title={intl.formatMessage(messages.heading)}
@@ -92,6 +94,7 @@ export default class Favourites extends ImmutablePureComponent {
           isLoading={isLoading}
           onLoadMore={this.handleLoadMore}
           shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js
index 40fe6c9a8..74a683ccc 100644
--- a/app/javascript/mastodon/features/favourites/index.js
+++ b/app/javascript/mastodon/features/favourites/index.js
@@ -5,10 +5,11 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
 import { fetchFavourites } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll-4';
+import { FormattedMessage } from 'react-intl';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import ColumnBackButton from '../../components/column_back_button';
+import ScrollableList from '../../components/scrollable_list';
 
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
@@ -45,15 +46,21 @@ export default class Favourites extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
+
     return (
       <Column>
         <ColumnBackButton />
 
-        <ScrollContainer scrollKey='favourites' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable'>
-            {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='favourites'
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountContainer key={id} id={id} withNote={false} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
index 53a394cbc..cb574e08d 100644
--- a/app/javascript/mastodon/features/follow_requests/index.js
+++ b/app/javascript/mastodon/features/follow_requests/index.js
@@ -1,15 +1,16 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import { debounce } from 'lodash';
 import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll-4';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import AccountAuthorizeContainer from './containers/account_authorize_container';
 import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
+import ScrollableList from '../../components/scrollable_list';
 
 const messages = defineMessages({
   heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@@ -35,13 +36,9 @@ export default class FollowRequests extends ImmutablePureComponent {
     this.props.dispatch(fetchFollowRequests());
   }
 
-  handleScroll = (e) => {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandFollowRequests());
-    }
-  }
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandFollowRequests());
+  }, 300, { leading: true });
 
   render () {
     const { intl, shouldUpdateScroll, accountIds } = this.props;
@@ -54,17 +51,21 @@ export default class FollowRequests extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
+
     return (
       <Column icon='users' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
-
-        <ScrollContainer scrollKey='follow_requests' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            {accountIds.map(id =>
-              <AccountAuthorizeContainer key={id} id={id} />
-            )}
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='follow_requests'
+          onLoadMore={this.handleLoadMore}
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountAuthorizeContainer key={id} id={id} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js
index 5bb8fdd6a..97d59cc4a 100644
--- a/app/javascript/mastodon/features/followers/index.js
+++ b/app/javascript/mastodon/features/followers/index.js
@@ -3,18 +3,19 @@ import { connect } from 'react-redux';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import { debounce } from 'lodash';
 import LoadingIndicator from '../../components/loading_indicator';
 import {
   fetchAccount,
   fetchFollowers,
   expandFollowers,
 } from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll-4';
+import { FormattedMessage } from 'react-intl';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
 import ColumnBackButton from '../../components/column_back_button';
+import ScrollableList from '../../components/scrollable_list';
 
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
@@ -44,24 +45,13 @@ export default class Followers extends ImmutablePureComponent {
     }
   }
 
-  handleScroll = (e) => {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight && this.props.hasMore) {
-      this.props.dispatch(expandFollowers(this.props.params.accountId));
-    }
-  }
-
-  handleLoadMore = (e) => {
-    e.preventDefault();
+  handleLoadMore = debounce(() => {
     this.props.dispatch(expandFollowers(this.props.params.accountId));
-  }
+  }, 300, { leading: true });
 
   render () {
     const { shouldUpdateScroll, accountIds, hasMore } = this.props;
 
-    let loadMore = null;
-
     if (!accountIds) {
       return (
         <Column>
@@ -70,23 +60,26 @@ export default class Followers extends ImmutablePureComponent {
       );
     }
 
-    if (hasMore) {
-      loadMore = <LoadMore onClick={this.handleLoadMore} />;
-    }
+    const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
 
     return (
       <Column>
         <ColumnBackButton />
 
-        <ScrollContainer scrollKey='followers' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            <div className='followers'>
-              <HeaderContainer accountId={this.props.params.accountId} hideTabs />
-              {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-              {loadMore}
-            </div>
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='followers'
+          hasMore={hasMore}
+          onLoadMore={this.handleLoadMore}
+          shouldUpdateScroll={shouldUpdateScroll}
+          prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
+          alwaysPrepend
+          alwaysShowScrollbar
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountContainer key={id} id={id} withNote={false} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js
index 97b0a8964..17bc7ec6e 100644
--- a/app/javascript/mastodon/features/following/index.js
+++ b/app/javascript/mastodon/features/following/index.js
@@ -3,18 +3,19 @@ import { connect } from 'react-redux';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import { debounce } from 'lodash';
 import LoadingIndicator from '../../components/loading_indicator';
 import {
   fetchAccount,
   fetchFollowing,
   expandFollowing,
 } from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll-4';
+import { FormattedMessage } from 'react-intl';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
 import ColumnBackButton from '../../components/column_back_button';
+import ScrollableList from '../../components/scrollable_list';
 
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
@@ -44,24 +45,13 @@ export default class Following extends ImmutablePureComponent {
     }
   }
 
-  handleScroll = (e) => {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight && this.props.hasMore) {
-      this.props.dispatch(expandFollowing(this.props.params.accountId));
-    }
-  }
-
-  handleLoadMore = (e) => {
-    e.preventDefault();
+  handleLoadMore = debounce(() => {
     this.props.dispatch(expandFollowing(this.props.params.accountId));
-  }
+  }, 300, { leading: true });
 
   render () {
     const { shouldUpdateScroll, accountIds, hasMore } = this.props;
 
-    let loadMore = null;
-
     if (!accountIds) {
       return (
         <Column>
@@ -70,23 +60,26 @@ export default class Following extends ImmutablePureComponent {
       );
     }
 
-    if (hasMore) {
-      loadMore = <LoadMore onClick={this.handleLoadMore} />;
-    }
+    const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
 
     return (
       <Column>
         <ColumnBackButton />
 
-        <ScrollContainer scrollKey='following' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            <div className='following'>
-              <HeaderContainer accountId={this.props.params.accountId} hideTabs />
-              {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-              {loadMore}
-            </div>
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='following'
+          hasMore={hasMore}
+          onLoadMore={this.handleLoadMore}
+          shouldUpdateScroll={shouldUpdateScroll}
+          prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
+          alwaysPrepend
+          alwaysShowScrollbar
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountContainer key={id} id={id} withNote={false} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index 95af8997e..f34ac6b8a 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -31,6 +31,7 @@ const messages = defineMessages({
   discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
   personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' },
   security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
+  menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
 });
 
 const mapStateToProps = state => ({
@@ -115,7 +116,7 @@ export default class GettingStarted extends ImmutablePureComponent {
     }
 
     return (
-      <Column>
+      <Column label={intl.formatMessage(messages.menu)}>
         {multiColumn && <div className='column-header__wrapper'>
           <h1 className='column-header'>
             <button>
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js
index 15fca9ab4..b67486f07 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@ -89,7 +89,7 @@ export default class HashtagTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={`#${id}`}>
         <ColumnHeader
           icon='hashtag'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index 4e6853c5b..12dab0e44 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -98,7 +98,7 @@ export default class HomeTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='home'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js
index c6fb735eb..2d86953c6 100644
--- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js
+++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js
@@ -93,6 +93,54 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
                 <td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
               </tr>
               <tr>
+                <td><kbd>g</kbd>+<kbd>h</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>n</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>l</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.local' defaultMessage='to open local timeline' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>t</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.federated' defaultMessage='to open federated timeline' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>d</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.direct' defaultMessage='to open direct messages column' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>s</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.start' defaultMessage='to open "get started" column' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>f</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favourites list' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>p</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.pinned' defaultMessage='to open pinned toots list' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>u</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.my_profile' defaultMessage='to open your profile' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>b</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.blocked' defaultMessage='to open blocked users list' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>m</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.muted' defaultMessage='to open muted users list' /></td>
+              </tr>
+              <tr>
+                <td><kbd>g</kbd>+<kbd>r</kbd></td>
+                <td><FormattedMessage id='keyboard_shortcuts.requests' defaultMessage='to open follow requests list' /></td>
+              </tr>
+              <tr>
                 <td><kbd>?</kbd></td>
                 <td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></td>
               </tr>
diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js
index 5c40fb758..164669e89 100644
--- a/app/javascript/mastodon/features/list_timeline/index.js
+++ b/app/javascript/mastodon/features/list_timeline/index.js
@@ -137,7 +137,7 @@ export default class ListTimeline extends React.PureComponent {
     }
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={title}>
         <ColumnHeader
           icon='list-ul'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.js
index 018e5a9e3..127347730 100644
--- a/app/javascript/mastodon/features/lists/index.js
+++ b/app/javascript/mastodon/features/lists/index.js
@@ -6,12 +6,13 @@ import LoadingIndicator from '../../components/loading_indicator';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import { fetchLists } from '../../actions/lists';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import ColumnLink from '../ui/components/column_link';
 import ColumnSubheading from '../ui/components/column_subheading';
 import NewListForm from './components/new_list_form';
 import { createSelector } from 'reselect';
+import ScrollableList from '../../components/scrollable_list';
 
 const messages = defineMessages({
   heading: { id: 'column.lists', defaultMessage: 'Lists' },
@@ -46,7 +47,7 @@ export default class Lists extends ImmutablePureComponent {
   }
 
   render () {
-    const { intl, lists } = this.props;
+    const { intl, shouldUpdateScroll, lists } = this.props;
 
     if (!lists) {
       return (
@@ -56,19 +57,24 @@ export default class Lists extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
+
     return (
       <Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
 
         <NewListForm />
 
-        <div className='scrollable'>
-          <ColumnSubheading text={intl.formatMessage(messages.subheading)} />
-
+        <ColumnSubheading text={intl.formatMessage(messages.subheading)} />
+        <ScrollableList
+          scrollKey='lists'
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
           {lists.map(list =>
             <ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
           )}
-        </div>
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js
index 66fd3796d..7bf9c1464 100644
--- a/app/javascript/mastodon/features/mutes/index.js
+++ b/app/javascript/mastodon/features/mutes/index.js
@@ -1,15 +1,16 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import { debounce } from 'lodash';
 import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll-4';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import AccountContainer from '../../containers/account_container';
 import { fetchMutes, expandMutes } from '../../actions/mutes';
+import ScrollableList from '../../components/scrollable_list';
 
 const messages = defineMessages({
   heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
@@ -35,13 +36,9 @@ export default class Mutes extends ImmutablePureComponent {
     this.props.dispatch(fetchMutes());
   }
 
-  handleScroll = (e) => {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandMutes());
-    }
-  }
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandMutes());
+  }, 300, { leading: true });
 
   render () {
     const { intl, shouldUpdateScroll, accountIds } = this.props;
@@ -54,16 +51,21 @@ export default class Mutes extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
+
     return (
       <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
-        <ScrollContainer scrollKey='mutes' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable mutes' onScroll={this.handleScroll}>
-            {accountIds.map(id =>
-              <AccountContainer key={id} id={id} />
-            )}
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='mutes'
+          onLoadMore={this.handleLoadMore}
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountContainer key={id} id={id} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index f58224a8b..07fec84b2 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -3,11 +3,20 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import StatusContainer from '../../../containers/status_container';
 import AccountContainer from '../../../containers/account_container';
-import { FormattedMessage } from 'react-intl';
+import { injectIntl, FormattedMessage } from 'react-intl';
 import Permalink from '../../../components/permalink';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { HotKeys } from 'react-hotkeys';
 
+const notificationForScreenReader = (intl, message, timestamp) => {
+  const output = [message];
+
+  output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }));
+
+  return output.join(', ');
+};
+
+@injectIntl
 export default class Notification extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -20,6 +29,7 @@ export default class Notification extends ImmutablePureComponent {
     onMoveUp: PropTypes.func.isRequired,
     onMoveDown: PropTypes.func.isRequired,
     onMention: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
   };
 
   handleMoveUp = () => {
@@ -65,10 +75,12 @@ export default class Notification extends ImmutablePureComponent {
     };
   }
 
-  renderFollow (account, link) {
+  renderFollow (notification, account, link) {
+    const { intl } = this.props;
+
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className='notification notification-follow focusable' tabIndex='0'>
+        <div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow', defaultMessage: '{name} followed you' }, { name: account.get('acct') }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <i className='fa fa-fw fa-user-plus' />
@@ -97,9 +109,11 @@ export default class Notification extends ImmutablePureComponent {
   }
 
   renderFavourite (notification, link) {
+    const { intl } = this.props;
+
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className='notification notification-favourite focusable' tabIndex='0'>
+        <div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <i className='fa fa-fw fa-star star-icon' />
@@ -114,9 +128,11 @@ export default class Notification extends ImmutablePureComponent {
   }
 
   renderReblog (notification, link) {
+    const { intl } = this.props;
+
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className='notification notification-reblog focusable' tabIndex='0'>
+        <div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <i className='fa fa-fw fa-retweet' />
@@ -138,7 +154,7 @@ export default class Notification extends ImmutablePureComponent {
 
     switch(notification.get('type')) {
     case 'follow':
-      return this.renderFollow(account, link);
+      return this.renderFollow(notification, account, link);
     case 'mention':
       return this.renderMention(notification);
     case 'favourite':
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 94a46b833..b7d7f361c 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -165,7 +165,7 @@ export default class Notifications extends React.PureComponent {
     );
 
     return (
-      <Column ref={this.setColumnRef}>
+      <Column ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='bell'
           active={isUnread}
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js
index 5f7ac5fc7..6d5c4118d 100644
--- a/app/javascript/mastodon/features/public_timeline/index.js
+++ b/app/javascript/mastodon/features/public_timeline/index.js
@@ -112,7 +112,7 @@ export default class PublicTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='globe'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js
index 367739636..acb9b40f9 100644
--- a/app/javascript/mastodon/features/reblogs/index.js
+++ b/app/javascript/mastodon/features/reblogs/index.js
@@ -5,10 +5,11 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
 import { fetchReblogs } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll-4';
+import { FormattedMessage } from 'react-intl';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import ColumnBackButton from '../../components/column_back_button';
+import ScrollableList from '../../components/scrollable_list';
 
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
@@ -45,15 +46,21 @@ export default class Reblogs extends ImmutablePureComponent {
       );
     }
 
+    const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
+
     return (
       <Column>
         <ColumnBackButton />
 
-        <ScrollContainer scrollKey='reblogs' shouldUpdateScroll={shouldUpdateScroll}>
-          <div className='scrollable reblogs'>
-            {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-          </div>
-        </ScrollContainer>
+        <ScrollableList
+          scrollKey='reblogs'
+          shouldUpdateScroll={shouldUpdateScroll}
+          emptyMessage={emptyMessage}
+        >
+          {accountIds.map(id =>
+            <AccountContainer key={id} id={id} withNote={false} />
+          )}
+        </ScrollableList>
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/report/components/status_check_box.js b/app/javascript/mastodon/features/report/components/status_check_box.js
index 9ff75a082..2552d94d8 100644
--- a/app/javascript/mastodon/features/report/components/status_check_box.js
+++ b/app/javascript/mastodon/features/report/components/status_check_box.js
@@ -36,6 +36,7 @@ export default class StatusCheckBox extends React.PureComponent {
               <Component
                 preview={video.get('preview_url')}
                 src={video.get('url')}
+                alt={video.get('description')}
                 width={239}
                 height={110}
                 inline
diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js
index 629d058a2..c8ae9b304 100644
--- a/app/javascript/mastodon/features/standalone/community_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/community_timeline/index.js
@@ -51,7 +51,7 @@ export default class CommunityTimeline extends React.PureComponent {
     const { intl } = this.props;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='users'
           title={intl.formatMessage(messages.title)}
diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js
index 1236cb927..115c51d85 100644
--- a/app/javascript/mastodon/features/standalone/public_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js
@@ -51,7 +51,7 @@ export default class PublicTimeline extends React.PureComponent {
     const { intl } = this.props;
 
     return (
-      <Column ref={this.setRef}>
+      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='globe'
           title={intl.formatMessage(messages.title)}
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 12ffb7579..b4bbda161 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -60,6 +60,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
           <Video
             preview={video.get('preview_url')}
             src={video.get('url')}
+            alt={video.get('description')}
             width={300}
             height={150}
             inline
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index e506733b4..48931b2d6 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -43,15 +43,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { HotKeys } from 'react-hotkeys';
 import { boostModal, deleteModal } from '../../initial_state';
 import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
+import { textForScreenReader } from '../../components/status';
 
 const messages = defineMessages({
   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
-  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
+  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
   revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
   hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
+  detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
 });
 
 const makeMapStateToProps = () => {
@@ -404,7 +406,7 @@ export default class Status extends ImmutablePureComponent {
     };
 
     return (
-      <Column>
+      <Column label={intl.formatMessage(messages.detailedStatus)}>
         <ColumnHeader
           showBackButton
           extraButton={(
@@ -417,7 +419,7 @@ export default class Status extends ImmutablePureComponent {
             {ancestors}
 
             <HotKeys handlers={handlers}>
-              <div className='focusable' tabIndex='0'>
+              <div className='focusable' tabIndex='0' aria-label={textForScreenReader(intl, status, false, !status.get('hidden'))}>
                 <DetailedStatus
                   status={status}
                   onOpenVideo={this.handleOpenVideo}
diff --git a/app/javascript/mastodon/features/trends/index.js b/app/javascript/mastodon/features/trends/index.js
deleted file mode 100644
index f33af3e2e..000000000
--- a/app/javascript/mastodon/features/trends/index.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { connect } from 'react-redux';
-import { injectIntl, defineMessages } from 'react-intl';
-import Column from '../ui/components/column';
-import ColumnHeader from '../../components/column_header';
-import Hashtag from '../../components/hashtag';
-import classNames from 'classnames';
-import { fetchTrends } from '../../actions/trends';
-
-const messages = defineMessages({
-  title: { id: 'trends.header', defaultMessage: 'Trending now' },
-  refreshTrends: { id: 'trends.refresh', defaultMessage: 'Refresh trends' },
-});
-
-const mapStateToProps = state => ({
-  trends: state.getIn(['trends', 'items']),
-  loading: state.getIn(['trends', 'isLoading']),
-});
-
-const mapDispatchToProps = dispatch => ({
-  fetchTrends: () => dispatch(fetchTrends()),
-});
-
-@connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
-export default class Trends extends ImmutablePureComponent {
-
-  static propTypes = {
-    intl: PropTypes.object.isRequired,
-    trends: ImmutablePropTypes.list,
-    fetchTrends: PropTypes.func.isRequired,
-    loading: PropTypes.bool,
-  };
-
-  componentDidMount () {
-    this.props.fetchTrends();
-  }
-
-  handleRefresh = () => {
-    this.props.fetchTrends();
-  }
-
-  render () {
-    const { trends, loading, intl } = this.props;
-
-    return (
-      <Column>
-        <ColumnHeader
-          icon='fire'
-          title={intl.formatMessage(messages.title)}
-          extraButton={(
-            <button className='column-header__button' title={intl.formatMessage(messages.refreshTrends)} aria-label={intl.formatMessage(messages.refreshTrends)} onClick={this.handleRefresh}><i className={classNames('fa', 'fa-refresh', { 'fa-spin': loading })} /></button>
-          )}
-        />
-
-        <div className='scrollable'>
-          {trends && trends.map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
-        </div>
-      </Column>
-    );
-  }
-
-}
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 34d52a7d2..91eb37900 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -177,7 +177,7 @@ class SwitchingColumnsArea extends React.PureComponent {
           <WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
           <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
           <WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
-          <WrappedRoute path='/lists' component={Lists} content={children} />
+          <WrappedRoute path='/lists' component={Lists} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 
           <WrappedRoute component={GenericNotFound} content={children} />
         </WrappedSwitch>
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 55ea32acb..52b395f88 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -315,6 +315,7 @@ export default class Video extends React.PureComponent {
           role='button'
           tabIndex='0'
           aria-label={alt}
+          title={alt}
           width={width}
           height={height}
           onClick={this.togglePlay}
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index b38dbcf73..5478e2d3e 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -10,7 +10,9 @@
   "account.endorse": "إبرازه على الملف الشخصي",
   "account.follow": "تابِع",
   "account.followers": "المتابعون",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "يتبع",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "يتابعك",
   "account.hide_reblogs": "إخفاء ترقيات @{name}",
   "account.media": "وسائط",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "نتائج البحث",
   "emoji_button.symbols": "رموز",
   "emoji_button.travel": "أماكن و أسفار",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !",
   "empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
   "empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسية فارغ. قم بزيارة {public} أو استخدم حقل البحث لكي تكتشف مستخدمين آخرين.",
   "empty_column.home.public_timeline": "الخيط العام",
   "empty_column.list": "هذه القائمة فارغة مؤقتا و لكن سوف تمتلئ تدريجيا عندما يبدأ الأعضاء المُنتَمين إليها بنشر تبويقات.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
   "empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام",
   "follow_request.authorize": "ترخيص",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "عرض الترقيات",
   "home.column_settings.show_replies": "عرض الردود",
   "keyboard_shortcuts.back": "للعودة",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "للترقية",
   "keyboard_shortcuts.column": "للتركيز على منشور على أحد الأعمدة",
   "keyboard_shortcuts.compose": "للتركيز على نافذة تحرير النصوص",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "للإنتقال إلى أسفل القائمة",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "للإضافة إلى المفضلة",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "مفتاح الإختصار",
   "keyboard_shortcuts.legend": "لعرض هذا المفتاح",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "لذِكر الناشر",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "لفتح رابط الناشر",
   "keyboard_shortcuts.reply": "للردّ",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "للتركيز على البحث",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير",
   "keyboard_shortcuts.toot": "لتحرير تبويق جديد",
   "keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "الحسابات المحجوبة",
   "navigation_bar.community_timeline": "الخيط العام المحلي",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "الرسائل المباشِرة",
   "navigation_bar.discover": "إكتشف",
   "navigation_bar.domain_blocks": "النطاقات المخفية",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "إلغاء الترقية",
   "status.cannot_reblog": "تعذرت ترقية هذا المنشور",
   "status.delete": "إحذف",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "رسالة خاصة إلى @{name}",
   "status.embed": "إدماج",
   "status.favourite": "أضف إلى المفضلة",
@@ -274,6 +297,7 @@
   "status.reblog": "رَقِّي",
   "status.reblog_private": "القيام بالترقية إلى الجمهور الأصلي",
   "status.reblogged_by": "رقّاه {name}",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "إزالة و إعادة الصياغة",
   "status.reply": "ردّ",
   "status.replyAll": "رُد على الخيط",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 96e3a14d9..da1ab62e6 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Follow",
   "account.followers": "Followers",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Follows",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.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. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.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",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favourite",
@@ -274,6 +297,7 @@
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Reply",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index b41045fb8..725bbed41 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Последвай",
   "account.followers": "Последователи",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Следвам",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Твой последовател",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.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.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.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",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Изтриване",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Предпочитани",
@@ -274,6 +297,7 @@
   "status.reblog": "Споделяне",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} сподели",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Отговор",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 7378b703b..b7527d24e 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Segueix",
   "account.followers": "Seguidors",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Seguint",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Et segueix",
   "account.hide_reblogs": "Amaga els impulsos de @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Resultats de la cerca",
   "emoji_button.symbols": "Símbols",
   "emoji_button.travel": "Viatges i Llocs",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!",
   "empty_column.direct": "Encara no tens missatges directes. Quan enviïs o rebis un, es mostrarà aquí.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.",
   "empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.",
   "empty_column.home.public_timeline": "la línia de temps pública",
   "empty_column.list": "Encara no hi ha res en aquesta llista. Quan els membres d'aquesta llista publiquin nous estats, apareixeran aquí.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.",
   "empty_column.public": "No hi ha res aquí! Escriu alguna cosa públicament o segueix manualment usuaris d'altres instàncies per omplir-ho",
   "follow_request.authorize": "Autoritzar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostrar impulsos",
   "home.column_settings.show_replies": "Mostrar respostes",
   "keyboard_shortcuts.back": "navegar enrera",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "impulsar",
   "keyboard_shortcuts.column": "per centrar un estat en una de les columnes",
   "keyboard_shortcuts.compose": "per centrar l'area de composició de text",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "per baixar en la llista",
   "keyboard_shortcuts.enter": "ampliar estat",
   "keyboard_shortcuts.favourite": "afavorir",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Tecla d'accés directe",
   "keyboard_shortcuts.legend": "per a mostrar aquesta llegenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "per esmentar l'autor",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "respondre",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "per centrar la cerca",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "per a mostrar/amagar text sota CW",
   "keyboard_shortcuts.toot": "per a començar un toot nou de trinca",
   "keyboard_shortcuts.unfocus": "descentrar l'area de composició de text/cerca",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Usuaris bloquejats",
   "navigation_bar.community_timeline": "Línia de temps Local",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Missatges directes",
   "navigation_bar.discover": "Descobreix",
   "navigation_bar.domain_blocks": "Dominis ocults",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Desfer l'impuls",
   "status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
   "status.delete": "Esborrar",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Missatge directe @{name}",
   "status.embed": "Incrustar",
   "status.favourite": "Favorit",
@@ -274,6 +297,7 @@
   "status.reblog": "Impuls",
   "status.reblog_private": "Impulsar a l'audiència original",
   "status.reblogged_by": "{name} ha retootejat",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Esborrar i reescriure",
   "status.reply": "Respondre",
   "status.replyAll": "Respondre al tema",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 717397d41..195c2a566 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -7,10 +7,12 @@
   "account.disclaimer_full": "Ghjè pussibule chì l’infurmazione quì sottu ùn rifletta micca u prufile sanu di l’utilizatore.",
   "account.domain_blocked": "Duminiu piattatu",
   "account.edit_profile": "Mudificà u prufile",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Fà figurà nant'à u prufilu",
   "account.follow": "Siguità",
   "account.followers": "Abbunati",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Abbunamenti",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Vi seguita",
   "account.hide_reblogs": "Piattà spartere da @{name}",
   "account.media": "Media",
@@ -27,7 +29,7 @@
   "account.show_reblogs": "Vede spartere da @{name}",
   "account.unblock": "Sbluccà @{name}",
   "account.unblock_domain": "Ùn piattà più {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Ùn fà figurà nant'à u prufilu",
   "account.unfollow": "Ùn siguità più",
   "account.unmute": "Ùn piattà più @{name}",
   "account.unmute_notifications": "Ùn piattà più nutificazione da @{name}",
@@ -87,7 +89,7 @@
   "confirmations.mute.confirm": "Piattà",
   "confirmations.mute.message": "Site sicuru·a che vulete piattà @{name}?",
   "confirmations.redraft.confirm": "Sguassà è riscrive",
-  "confirmations.redraft.message": "Site sicuru·a chè vulete sguassà stu statutu è riscrivelu? Tutti i favuriti, risposte è spartere saranu persi.",
+  "confirmations.redraft.message": "Site sicuru·a chè vulete sguassà stu statutu è riscrivelu? I favuriti è spartere saranu persi, è e risposte diventeranu orfane.",
   "confirmations.unfollow.confirm": "Disabbunassi",
   "confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
   "embed.instructions": "Integrà stu statutu à u vostru situ cù u codice quì sottu.",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Risultati di a cerca",
   "emoji_button.symbols": "Simbuli",
   "emoji_button.travel": "Lochi è Viaghju",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Ùn c'hè nunda indè a linea lucale. Scrivete puru qualcosa!",
   "empty_column.direct": "Ùn avete ancu nisun missaghju direttu. S'è voi mandate o ricevete unu, u vidarete quì.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
   "empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
   "empty_column.home.public_timeline": "a linea pubblica",
   "empty_column.list": "Ùn c'hè ancu nunda quì. Quandu membri di sta lista manderanu novi statuti, i vidarete quì.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
   "empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altre istanze per empie a linea pubblica",
   "follow_request.authorize": "Auturizà",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Vede e spartere",
   "home.column_settings.show_replies": "Vede e risposte",
   "keyboard_shortcuts.back": "rivultà",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "sparte",
   "keyboard_shortcuts.column": "fucalizà un statutu indè una colonna",
   "keyboard_shortcuts.compose": "fucalizà nant'à l'area di ridazzione",
   "keyboard_shortcuts.description": "Descrizzione",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "falà indè a lista",
   "keyboard_shortcuts.enter": "apre u statutu",
   "keyboard_shortcuts.favourite": "aghjunghje à i favuriti",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Accorte cù a tastera",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Accorta",
   "keyboard_shortcuts.legend": "vede a legenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "mintuvà l'autore",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "per apre u prufile di l'autore",
   "keyboard_shortcuts.reply": "risponde",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "fucalizà nant'à l'area di circata",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "vede/piattà u testu daretu à l'avertimentu CW",
   "keyboard_shortcuts.toot": "scrive un novu statutu",
   "keyboard_shortcuts.unfocus": "ùn fucalizà più l'area di testu",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "Micca trovu",
   "missing_indicator.sublabel": "Ùn era micca pussivule di truvà sta risorsa",
   "mute_modal.hide_notifications": "Piattà nutificazione da st'utilizatore?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Applicazione per u telefuninu",
   "navigation_bar.blocks": "Utilizatori bluccati",
   "navigation_bar.community_timeline": "Linea pubblica lucale",
+  "navigation_bar.compose": "Scrive un novu statutu",
   "navigation_bar.direct": "Missaghji diretti",
   "navigation_bar.discover": "Scopre",
   "navigation_bar.domain_blocks": "Duminii piattati",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Ùn sparte più",
   "status.cannot_reblog": "Stu statutu ùn pò micca esse spartutu",
   "status.delete": "Toglie",
+  "status.detailed_status": "Vista in ditagliu di a cunversazione",
   "status.direct": "Mandà un missaghju @{name}",
   "status.embed": "Integrà",
   "status.favourite": "Aghjunghje à i favuriti",
@@ -274,6 +297,7 @@
   "status.reblog": "Sparte",
   "status.reblog_private": "Sparte à l'audienza uriginale",
   "status.reblogged_by": "{name} hà spartutu",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Sguassà è riscrive",
   "status.reply": "Risponde",
   "status.replyAll": "Risponde à tutti",
@@ -295,7 +319,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} parlanu",
   "ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Mastodon.",
   "upload_area.title": "Drag & drop per caricà un fugliale",
-  "upload_button.label": "Aghjunghje un media",
+  "upload_button.label": "Aghjunghje un media (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "Discrive per i malvistosi",
   "upload_form.focus": "Riquatrà",
   "upload_form.undo": "Sguassà",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index b71179ca0..b0f76e834 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -10,7 +10,9 @@
   "account.endorse": "Představit na profilu",
   "account.follow": "Sleduj",
   "account.followers": "Sledovatelé",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Sleduje",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Sleduje vás",
   "account.hide_reblogs": "Skrýt boosty od uživatele @{name}",
   "account.media": "Média",
@@ -87,7 +89,7 @@
   "confirmations.mute.confirm": "Ignorovat",
   "confirmations.mute.message": "Jste si jistý/á, že chcete ignorovat uživatele {name}?",
   "confirmations.redraft.confirm": "Vymazat a přepsat",
-  "confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento příspěvek? Ztratíte všechny jeho odpovědi, boosty a oblíbení.",
+  "confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento příspěvek? Oblíbení a boosty budou ztraceny a odpovědi na původní příspěvek budou opuštěny.",
   "confirmations.unfollow.confirm": "Přestat sledovat",
   "confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?",
   "embed.instructions": "Pro přidání příspěvku na vaši webovou stránku zkopírujte níže uvedený kód.",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Výsledky hledání",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestování a místa",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Místní časová osa je prázdná. Napište něco veřejně a rozhýbejte to tu!",
   "empty_column.direct": "Ještě nemáte žádné přímé zprávy. Pokud nějakou pošlete nebo dostanete, zobrazí se zde.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Pod tímto hashtagem ještě nic není.",
   "empty_column.home": "Vaše domovská časová osa je prázdná! Začněte navštívením {public} nebo použijte hledání a seznamte se s dalšími uživateli.",
   "empty_column.home.public_timeline": "veřejné časové osy",
   "empty_column.list": "V tomto seznamu ještě nic není. Pokud budou členové tohoto seznamu psát nové příspěvky, objeví se zde.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.",
   "empty_column.public": "Tady nic není! Napište něco veřejně, nebo manuálně začněte sledovat uživatele z jiných instancí, aby tu něco přibylo",
   "follow_request.authorize": "Autorizovat",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Zobrazit boosty",
   "home.column_settings.show_replies": "Zobrazit odpovědi",
   "keyboard_shortcuts.back": "k návratu zpět",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "k boostnutí",
   "keyboard_shortcuts.column": "k zaměření na příspěvek v jednom ze sloupců",
   "keyboard_shortcuts.compose": "k zaměření na psací prostor",
   "keyboard_shortcuts.description": "Popis",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "k přesunutí dolů v seznamu",
   "keyboard_shortcuts.enter": "k otevření příspěvku",
   "keyboard_shortcuts.favourite": "k oblíbení",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Klávesové zkratky",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Horká klávesa",
   "keyboard_shortcuts.legend": "k zobrazení této legendy",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "ke zmínění autora",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "k otevření autorova profilu",
   "keyboard_shortcuts.reply": "k odpovězení",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "k zaměření na vyhledávání",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "k zobrazení/skrytí textu za CW",
   "keyboard_shortcuts.toot": "k napsání úplně nového tootu",
   "keyboard_shortcuts.unfocus": "ke zrušení soustředění na psací prostor/hledání",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "Nenalezeno",
   "missing_indicator.sublabel": "Tento zdroj se nepodařilo najít",
   "mute_modal.hide_notifications": "Skrýt oznámení před tímto uživatelem?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Mobilní aplikace",
   "navigation_bar.blocks": "Blokovaní uživatelé",
   "navigation_bar.community_timeline": "Místní časová osa",
+  "navigation_bar.compose": "Vytvořit nový toot",
   "navigation_bar.direct": "Přímé zprávy",
   "navigation_bar.discover": "Objevujte",
   "navigation_bar.domain_blocks": "Skryté domény",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Zrušit boost",
   "status.cannot_reblog": "Tento příspěvek nemůže být boostnutý",
   "status.delete": "Delete",
+  "status.detailed_status": "Detailní zobrazení konverzace",
   "status.direct": "Poslat přímou zprávu uživateli @{name}",
   "status.embed": "Vložit",
   "status.favourite": "Oblíbit",
@@ -274,6 +297,7 @@
   "status.reblog": "Boostnout",
   "status.reblog_private": "Boostnout původnímu publiku",
   "status.reblogged_by": "{name} boostnul/a",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Vymazat a přepsat",
   "status.reply": "Odpovědět",
   "status.replyAll": "Odpovědět na vlákno",
@@ -295,7 +319,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} other {lidí}} diskutuje",
   "ui.beforeunload": "Váš koncept se ztratí, pokud Mastodon opustíte.",
   "upload_area.title": "Přetažením nahrajete",
-  "upload_button.label": "Přidat média",
+  "upload_button.label": "Přidat média (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "Popis pro zrakově postižené",
   "upload_form.focus": "Vystřihnout",
   "upload_form.undo": "Smazat",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index 4c38b8eb2..83f049a5b 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -10,7 +10,9 @@
   "account.endorse": "Fremhæv på profil",
   "account.follow": "Følg",
   "account.followers": "Følgere",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Følger",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Følger dig",
   "account.hide_reblogs": "Skjul fremhævelserne fra @{name}",
   "account.media": "Medie",
@@ -20,7 +22,7 @@
   "account.mute_notifications": "Dæmp notifikationer fra @{name}",
   "account.muted": "Dæmpet",
   "account.posts": "Trut",
-  "account.posts_with_replies": "Trut samt svar",
+  "account.posts_with_replies": "Trut og svar",
   "account.report": "Rapporter @{name}",
   "account.requested": "Afventer godkendelse. Tryk for at annullere følgeanmodning",
   "account.share": "Del @{name}s profil",
@@ -51,7 +53,7 @@
   "column.lists": "Lister",
   "column.mutes": "Dæmpede brugere",
   "column.notifications": "Notifikationer",
-  "column.pins": "Fastgjorte toots",
+  "column.pins": "Fastgjorte trut",
   "column.public": "Fælles tidslinje",
   "column_back_button.label": "Tilbage",
   "column_header.hide_settings": "Skjul indstillinger",
@@ -61,7 +63,7 @@
   "column_header.show_settings": "Vis indstillinger",
   "column_header.unpin": "Fastgør ikke længere",
   "column_subheading.settings": "Indstillinger",
-  "community.column_settings.media_only": "Kun multimedier",
+  "community.column_settings.media_only": "Kun medie",
   "compose_form.direct_message_warning": "Dette trut vil kun blive sendt til de nævnte brugere.",
   "compose_form.direct_message_warning_learn_more": "Lær mere",
   "compose_form.hashtag_warning": "Dette trut vil ikke blive vist under noget hashtag da det ikke er listet. Kun offentlige trut kan blive vist under søgninger med hashtags.",
@@ -70,8 +72,8 @@
   "compose_form.placeholder": "Hvad har du på hjertet?",
   "compose_form.publish": "Trut",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.marked": "Multimedie er markeret som værende følsomt",
-  "compose_form.sensitive.unmarked": "Multimediet er ikke markeret som værende følsomt",
+  "compose_form.sensitive.marked": "Medie er markeret som værende følsomt",
+  "compose_form.sensitive.unmarked": "Mediet er ikke markeret som værende følsomt",
   "compose_form.spoiler.marked": "Teksten er skjult bag en advarsel",
   "compose_form.spoiler.unmarked": "Teksten er ikke skjult",
   "compose_form.spoiler_placeholder": "Skriv din advarsel her",
@@ -87,7 +89,7 @@
   "confirmations.mute.confirm": "Dæmp",
   "confirmations.mute.message": "Er du sikker på, du vil dæmpe {name}?",
   "confirmations.redraft.confirm": "Slet & omskriv",
-  "confirmations.redraft.message": "Er du sikker på, du vil slette denne status og omskrive den? Du vil miste alle svar, fremhævelser og favoritter der medfølger.",
+  "confirmations.redraft.message": "Er du sikker på, du vil slette denne status og omskrive den? Favoritter og fremhævelser vil gå tabt og svar til det oprindelige opslag vil blive forældreløse.",
   "confirmations.unfollow.confirm": "Følg ikke længere",
   "confirmations.unfollow.message": "Er du sikker på, du ikke længere vil følge {name}?",
   "embed.instructions": "Indlejre denne status på din side ved at kopiere nedenstående kode.",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Søgeresultater",
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Rejser & steder",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at starte lavinen!",
   "empty_column.direct": "Du har endnu ingen direkte beskeder. Når du sender eller modtager en, vil den vises her.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Dette hashtag indeholder endnu ikke noget.",
   "empty_column.home": "Din hjemme tidslinje er tom! Besøg {public} eller brug søgningen for at komme igang og møde andre brugere.",
   "empty_column.home.public_timeline": "den offentlige tidslinje",
   "empty_column.list": "Der er endnu intet i denne liste. Når medlemmer af denne liste poster nye statusser, vil de vises her.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du har endnu ingen notifikationer. Tag ud og bland dig med folkemængden for at starte samtalen.",
   "empty_column.public": "Der er ikke noget at se her! Skriv noget offentligt eller start ud med manuelt at følge brugere fra andre instanser for st udfylde tomrummet",
   "follow_request.authorize": "Godkend",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Vis fremhævelser",
   "home.column_settings.show_replies": "Vis svar",
   "keyboard_shortcuts.back": "for at navigere dig tilbage",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "for at fremhæve",
   "keyboard_shortcuts.column": "for at fokusere på en status i en af kolonnerne",
   "keyboard_shortcuts.compose": "for at fokusere på skriveområdet",
   "keyboard_shortcuts.description": "Beskrivelse",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "for at rykke ned ad listen",
   "keyboard_shortcuts.enter": "for at åbne status",
   "keyboard_shortcuts.favourite": "for at favorisere",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Tastaturgenveje",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hurtigtast",
   "keyboard_shortcuts.legend": "for at vise denne legende",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "for at nævne forfatteren",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "til profil af åben forfatter",
   "keyboard_shortcuts.reply": "for at svare",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "for at fokusere søgningen",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "for at vise/skjule tekst bag CW",
   "keyboard_shortcuts.toot": "for at påbegynde et helt nyt trut",
   "keyboard_shortcuts.unfocus": "for at fjerne fokus fra skriveområde/søgning",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "Ikke fundet",
   "missing_indicator.sublabel": "Denne ressource kunne ikke blive fundet",
   "mute_modal.hide_notifications": "Skjul notifikationer fra denne bruger?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Mobil apps",
   "navigation_bar.blocks": "Blokerede brugere",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direkte beskeder",
   "navigation_bar.discover": "Opdag",
   "navigation_bar.domain_blocks": "Skjulte domæner",
@@ -258,12 +280,13 @@
   "status.cancel_reblog_private": "Fremhæv ikke længere",
   "status.cannot_reblog": "Denne post kan ikke fremhæves",
   "status.delete": "Slet",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Send direkte besked til @{name}",
   "status.embed": "Indlejre",
   "status.favourite": "Favorit",
   "status.filtered": "Filtreret",
   "status.load_more": "Indlæs mere",
-  "status.media_hidden": "Multimedia skjult",
+  "status.media_hidden": "Medie skjult",
   "status.mention": "Nævn @{name}",
   "status.more": "Mere",
   "status.mute": "Dæmp @{name}",
@@ -274,6 +297,7 @@
   "status.reblog": "Fremhæv",
   "status.reblog_private": "Fremhæv til oprindeligt publikum",
   "status.reblogged_by": "{name} fremhævede",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Slet og omskriv",
   "status.reply": "Svar",
   "status.replyAll": "Svar samtale",
@@ -295,7 +319,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} snakker",
   "ui.beforeunload": "Din kladde vil gå tabt hvis du forlader Mastodon.",
   "upload_area.title": "Træk og slip for at uploade",
-  "upload_button.label": "Tilføj medie",
+  "upload_button.label": "Tilføj medie (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "Beskriv for de svagtseende",
   "upload_form.focus": "Beskær",
   "upload_form.undo": "Slet",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 3427b322b..e117175cd 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Folgen",
   "account.followers": "Folgende",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Folgt",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Folgt dir",
   "account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
   "account.media": "Medien",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Suchergebnisse",
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Reisen und Orte",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!",
   "empty_column.direct": "Du hast noch keine Direktnachrichten erhalten. Wenn du eine sendest oder empfängst, wird sie hier zu sehen sein.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
   "empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.",
   "empty_column.home.public_timeline": "die öffentliche Zeitleiste",
   "empty_column.list": "Diese Liste ist derzeit leer. Wenn Wesen auf dieser Liste neue Beiträge veröffentlichen werden sie hier erscheinen.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.",
   "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um die Zeitleiste aufzufüllen",
   "follow_request.authorize": "Erlauben",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
   "home.column_settings.show_replies": "Antworten anzeigen",
   "keyboard_shortcuts.back": "zurück navigieren",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "boosten",
   "keyboard_shortcuts.column": "einen Status in einer der Spalten fokussieren",
   "keyboard_shortcuts.compose": "um das Textfeld zu fokussieren",
   "keyboard_shortcuts.description": "Beschreibung",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "sich in der Liste hinunter bewegen",
   "keyboard_shortcuts.enter": "um den Status zu öffnen",
   "keyboard_shortcuts.favourite": "um zu favorisieren",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Tastenkombinationen",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Tastenkürzel",
   "keyboard_shortcuts.legend": "um diese Übersicht anzuzeigen",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "um Autor_in zu erwähnen",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "um Profil des Autors zu öffnen",
   "keyboard_shortcuts.reply": "um zu antworten",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "um die Suche zu fokussieren",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "um den Text hinter einer Inhaltswarnung zu verstecken oder ihn anzuzeigen",
   "keyboard_shortcuts.toot": "um einen neuen Toot zu beginnen",
   "keyboard_shortcuts.unfocus": "um das Textfeld/die Suche nicht mehr zu fokussieren",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blockierte Profile",
   "navigation_bar.community_timeline": "Lokale Zeitleiste",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direktnachrichten",
   "navigation_bar.discover": "Entdecken",
   "navigation_bar.domain_blocks": "Versteckte Domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Nicht mehr teilen",
   "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
   "status.delete": "Löschen",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direktnachricht @{name}",
   "status.embed": "Einbetten",
   "status.favourite": "Favorisieren",
@@ -274,6 +297,7 @@
   "status.reblog": "Teilen",
   "status.reblog_private": "An das eigentliche Publikum teilen",
   "status.reblogged_by": "{name} teilte",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Löschen und neu erstellen",
   "status.reply": "Antworten",
   "status.replyAll": "Auf Thread antworten",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 2d836be90..0c55090f6 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -390,7 +390,7 @@
         "id": "confirmations.redraft.confirm"
       },
       {
-        "defaultMessage": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+        "defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
         "id": "confirmations.redraft.message"
       },
       {
@@ -638,6 +638,10 @@
       {
         "defaultMessage": "Blocked users",
         "id": "column.blocks"
+      },
+      {
+        "defaultMessage": "You haven't blocked any users yet.",
+        "id": "empty_column.blocks"
       }
     ],
     "path": "app/javascript/mastodon/features/blocks/index.json"
@@ -907,7 +911,7 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Add media",
+        "defaultMessage": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
         "id": "upload_button.label"
       }
     ],
@@ -1019,6 +1023,10 @@
       {
         "defaultMessage": "Logout",
         "id": "navigation_bar.logout"
+      },
+      {
+        "defaultMessage": "Compose new toot",
+        "id": "navigation_bar.compose"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/index.json"
@@ -1045,6 +1053,10 @@
       {
         "defaultMessage": "Unhide {domain}",
         "id": "account.unblock_domain"
+      },
+      {
+        "defaultMessage": "There are no hidden domains yet.",
+        "id": "empty_column.domain_blocks"
       }
     ],
     "path": "app/javascript/mastodon/features/domain_blocks/index.json"
@@ -1054,6 +1066,10 @@
       {
         "defaultMessage": "Favourites",
         "id": "column.favourites"
+      },
+      {
+        "defaultMessage": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+        "id": "empty_column.favourited_statuses"
       }
     ],
     "path": "app/javascript/mastodon/features/favourited_statuses/index.json"
@@ -1061,6 +1077,15 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "No one has favourited this toot yet. When someone does, they will show up here.",
+        "id": "empty_column.favourites"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/favourites/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Authorize",
         "id": "follow_request.authorize"
       },
@@ -1076,6 +1101,10 @@
       {
         "defaultMessage": "Follow requests",
         "id": "column.follow_requests"
+      },
+      {
+        "defaultMessage": "You don't have any follow requests yet. When you receive one, it will show up here.",
+        "id": "empty_column.follow_requests"
       }
     ],
     "path": "app/javascript/mastodon/features/follow_requests/index.json"
@@ -1083,6 +1112,24 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "No one follows this user yet.",
+        "id": "account.followers.empty"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/followers/index.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "This user doesn't follow anyone yet.",
+        "id": "account.follows.empty"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/following/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Home",
         "id": "tabs_bar.home"
       },
@@ -1319,6 +1366,54 @@
         "id": "keyboard_shortcuts.unfocus"
       },
       {
+        "defaultMessage": "to open home timeline",
+        "id": "keyboard_shortcuts.home"
+      },
+      {
+        "defaultMessage": "to open notifications column",
+        "id": "keyboard_shortcuts.notifications"
+      },
+      {
+        "defaultMessage": "to open local timeline",
+        "id": "keyboard_shortcuts.local"
+      },
+      {
+        "defaultMessage": "to open federated timeline",
+        "id": "keyboard_shortcuts.federated"
+      },
+      {
+        "defaultMessage": "to open direct messages column",
+        "id": "keyboard_shortcuts.direct"
+      },
+      {
+        "defaultMessage": "to open \"get started\" column",
+        "id": "keyboard_shortcuts.start"
+      },
+      {
+        "defaultMessage": "to open favourites list",
+        "id": "keyboard_shortcuts.favourites"
+      },
+      {
+        "defaultMessage": "to open pinned toots list",
+        "id": "keyboard_shortcuts.pinned"
+      },
+      {
+        "defaultMessage": "to open your profile",
+        "id": "keyboard_shortcuts.my_profile"
+      },
+      {
+        "defaultMessage": "to open blocked users list",
+        "id": "keyboard_shortcuts.blocked"
+      },
+      {
+        "defaultMessage": "to open muted users list",
+        "id": "keyboard_shortcuts.muted"
+      },
+      {
+        "defaultMessage": "to open follow requests list",
+        "id": "keyboard_shortcuts.requests"
+      },
+      {
         "defaultMessage": "to display this legend",
         "id": "keyboard_shortcuts.legend"
       }
@@ -1394,6 +1489,10 @@
       {
         "defaultMessage": "Your lists",
         "id": "lists.subheading"
+      },
+      {
+        "defaultMessage": "You don't have any lists yet. When you create one, it will show up here.",
+        "id": "empty_column.lists"
       }
     ],
     "path": "app/javascript/mastodon/features/lists/index.json"
@@ -1403,6 +1502,10 @@
       {
         "defaultMessage": "Muted users",
         "id": "column.mutes"
+      },
+      {
+        "defaultMessage": "You haven't muted any users yet.",
+        "id": "empty_column.mutes"
       }
     ],
     "path": "app/javascript/mastodon/features/mutes/index.json"
@@ -1525,6 +1628,15 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "No one has boosted this toot yet. When someone does, they will show up here.",
+        "id": "status.reblogs.empty"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/reblogs/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "A look inside...",
         "id": "standalone.public_title"
       }
@@ -1636,7 +1748,7 @@
         "id": "confirmations.redraft.confirm"
       },
       {
-        "defaultMessage": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+        "defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
         "id": "confirmations.redraft.message"
       },
       {
@@ -1652,6 +1764,10 @@
         "id": "status.show_less_all"
       },
       {
+        "defaultMessage": "Detailed conversation view",
+        "id": "status.detailed_status"
+      },
+      {
         "defaultMessage": "Are you sure you want to block {name}?",
         "id": "confirmations.block.message"
       }
@@ -2012,4 +2128,4 @@
     ],
     "path": "app/javascript/mastodon/features/video/index.json"
   }
-]
\ No newline at end of file
+]
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 76a003f65..0ab2d4bac 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Ακολούθησε",
   "account.followers": "Ακόλουθοι",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Ακολουθεί",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Σε ακολουθεί",
   "account.hide_reblogs": "Απόκρυψη προωθήσεων από @{name}",
   "account.media": "Πολυμέσα",
@@ -87,7 +89,7 @@
   "confirmations.mute.confirm": "Αποσιώπηση",
   "confirmations.mute.message": "Σίγουρα θες να αποσιωπήσεις τον/την {name};",
   "confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
-  "confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την κατάσταση και να την γράψεις ξανά; Θα χάσεις όλες τις απαντήσεις, αναφορές και τα αγαπημένα προς αυτή.",
+  "confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την κατάσταση και να την ξαναγράψεις; Οι αναφορές και τα αγαπημένα της θα χαθούν ενώ οι απαντήσεις προς αυτή θα μείνουν ορφανές.",
   "confirmations.unfollow.confirm": "Διακοπή παρακολούθησης",
   "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
   "embed.instructions": "Ενσωματώστε αυτή την κατάσταση στην ιστοσελίδα σας αντιγράφοντας τον παρακάτω κώδικα.",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Αποτελέσματα αναζήτησης",
   "emoji_button.symbols": "Σύμβολα",
   "emoji_button.travel": "Ταξίδια & Τοποθεσίες",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Η τοπική ροή είναι κενή. Γράψε κάτι δημόσιο παραμύθι ν' αρχινίσει!",
   "empty_column.direct": "Δεν έχεις προσωπικά μηνύματα ακόμα. Όταν στείλεις ή λάβεις κανένα, θα εμφανιστεί εδώ.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ταμπέλα.",
   "empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
   "empty_column.home.public_timeline": "η δημόσια ροή",
   "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.",
   "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλα instances για να τη γεμίσεις",
   "follow_request.authorize": "Ενέκρινε",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
   "home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
   "keyboard_shortcuts.back": "για επιστροφή πίσω",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "για προώθηση",
   "keyboard_shortcuts.column": "για εστίαση μιας κατάστασης σε μια από τις στήλες",
   "keyboard_shortcuts.compose": "για εστίαση στην περιοχή κειμένου συγγραφής",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "για κίνηση προς τα κάτω στη λίστα",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "για σημείωση αγαπημένου",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Συντόμευση",
   "keyboard_shortcuts.legend": "για να εμφανίσεις αυτόν τον οδηγό",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "για να αναφέρεις το συγγραφέα",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "για απάντηση",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "για εστίαση αναζήτησης",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "για εμφάνιση/απόκρυψη κειμένου πίσω από την προειδοποίηση",
   "keyboard_shortcuts.toot": "για δημιουργία ολοκαίνουριου τουτ",
   "keyboard_shortcuts.unfocus": "για την απο-εστίαση του πεδίου σύνθεσης/αναζήτησης",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Αποκλεισμένοι χρήστες",
   "navigation_bar.community_timeline": "Τοπική ροή",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Προσωπικά μηνύματα",
   "navigation_bar.discover": "Ανακάλυψη",
   "navigation_bar.domain_blocks": "Κρυμμένοι τομείς",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Ακύρωσε την προώθηση",
   "status.cannot_reblog": "Αυτή η δημοσίευση δεν μπορεί να προωθηθεί",
   "status.delete": "Διαγραφή",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Προσωπικό μήνυμα προς @{name}",
   "status.embed": "Ενσωμάτωσε",
   "status.favourite": "Σημείωσε ως αγαπημένο",
@@ -274,6 +297,7 @@
   "status.reblog": "Προώθησε",
   "status.reblog_private": "Προώθησε στους αρχικούς παραλήπτες",
   "status.reblogged_by": "{name} προώθησε",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Σβήσε & ξαναγράψε",
   "status.reply": "Απάντησε",
   "status.replyAll": "Απάντησε στην συζήτηση",
@@ -295,7 +319,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} μιλάνε",
   "ui.beforeunload": "Το προσχέδιό σου θα χαθεί αν φύγεις από το Mastodon.",
   "upload_area.title": "Drag & drop για να ανεβάσεις",
-  "upload_button.label": "Πρόσθεσε πολυμέσα",
+  "upload_button.label": "Πρόσθεσε πολυμέσα (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "Περιέγραψε για όσους & όσες έχουν προβλήματα όρασης",
   "upload_form.focus": "Περικοπή",
   "upload_form.undo": "Διαγραφή",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 27b2cb45e..def1e0a56 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Follow",
   "account.followers": "Followers",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Follows",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -91,7 +93,7 @@
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
   "confirmations.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.",
@@ -110,12 +112,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.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. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.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",
@@ -132,20 +141,32 @@
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -169,6 +190,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -263,6 +285,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favourite",
@@ -279,6 +302,7 @@
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Reply",
   "status.replyAll": "Reply to thread",
@@ -300,7 +324,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media",
+  "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Crop",
   "upload_form.undo": "Delete",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index f4c316441..ab584840e 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -10,7 +10,9 @@
   "account.endorse": "Montri en profilo",
   "account.follow": "Sekvi",
   "account.followers": "Sekvantoj",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Sekvatoj",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Sekvas vin",
   "account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
   "account.media": "Aŭdovidaĵoj",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Serĉaj rezultoj",
   "emoji_button.symbols": "Simboloj",
   "emoji_button.travel": "Vojaĝoj kaj lokoj",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
   "empty_column.direct": "Vi ankoraŭ ne havas rektan mesaĝon. Kiam vi sendos aŭ ricevos iun, ĝi aperos ĉi tie.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.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 publikan tempolinion",
   "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.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.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",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Montri diskonigojn",
   "home.column_settings.show_replies": "Montri respondojn",
   "keyboard_shortcuts.back": "por reveni",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "por iri suben en la listo",
   "keyboard_shortcuts.enter": "por malfermi mesaĝon",
   "keyboard_shortcuts.favourite": "por stelumi",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Klavaraj mallongigoj",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Rapidklavo",
   "keyboard_shortcuts.legend": "por montri ĉi tiun noton",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "por mencii la aŭtoron",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "por malfermi la profilon de la aŭtoro",
   "keyboard_shortcuts.reply": "por respondi",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "por fokusigi la serĉilon",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "por montri/kaŝi tekston malantaŭ enhava averto",
   "keyboard_shortcuts.toot": "por komenci tute novan mesaĝon",
   "keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokitaj uzantoj",
   "navigation_bar.community_timeline": "Loka tempolinio",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Rektaj mesaĝoj",
   "navigation_bar.discover": "Esplori",
   "navigation_bar.domain_blocks": "Kaŝitaj domajnoj",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Eksdiskonigi",
   "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
   "status.delete": "Forigi",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Rekte mesaĝi @{name}",
   "status.embed": "Enkorpigi",
   "status.favourite": "Stelumi",
@@ -274,6 +297,7 @@
   "status.reblog": "Diskonigi",
   "status.reblog_private": "Diskonigi al la originala atentaro",
   "status.reblogged_by": "{name} diskonigis",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Forigi kaj reskribi",
   "status.reply": "Respondi",
   "status.replyAll": "Respondi al la fadeno",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index b17e1411e..07e110048 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Sigue",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Te sigue",
   "account.hide_reblogs": "Ocultar retoots de @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Resultados de búsqueda",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viajes y lugares",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "No hay nada en este hashtag aún.",
   "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
   "empty_column.home.public_timeline": "la línea de tiempo pública",
   "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
   "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo",
   "follow_request.authorize": "Autorizar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostrar retoots",
   "home.column_settings.show_replies": "Mostrar respuestas",
   "keyboard_shortcuts.back": "volver atrás",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "retootear",
   "keyboard_shortcuts.column": "enfocar un estado en una de las columnas",
   "keyboard_shortcuts.compose": "enfocar el área de texto de redacción",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "mover hacia abajo en la lista",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "añadir a favoritos",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Tecla caliente",
   "keyboard_shortcuts.legend": "para mostrar esta leyenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "para mencionar al autor",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "para responder",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "para poner el foco en la búsqueda",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "para comenzar un nuevo toot",
   "keyboard_shortcuts.unfocus": "para retirar el foco de la caja de redacción/búsqueda",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Usuarios bloqueados",
   "navigation_bar.community_timeline": "Historia local",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Este toot no puede retootearse",
   "status.delete": "Borrar",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Incrustado",
   "status.favourite": "Favorito",
@@ -274,6 +297,7 @@
   "status.reblog": "Retootear",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "Retooteado por {name}",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Responder",
   "status.replyAll": "Responder al hilo",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index ec27fe425..a52024b15 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -10,7 +10,9 @@
   "account.endorse": "Nabarmendu profilean",
   "account.follow": "Jarraitu",
   "account.followers": "Jarraitzaileak",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Jarraitzen",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Jarraitzen zaitu",
   "account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Bilaketaren emaitzak",
   "emoji_button.symbols": "Sinboloak",
   "emoji_button.travel": "Bidaiak eta tokiak",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Denbora-lerro lokala hutsik dago. Idatzi zerbait publikoki pilota biraka jartzeko!",
   "empty_column.direct": "Ez duzu mezu zuzenik oraindik. Baten bat bidali edo jasotzen duzunean, hemen agertuko da.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Ez dago ezer traola honetan oraindik.",
   "empty_column.home": "Zure hasierako denbora-lerroa hutsik dago! Ikusi {public} edo erabili bilaketa lehen urratsak eman eta beste batzuk aurkitzeko.",
   "empty_column.home.public_timeline": "denbora-lerro publikoa",
   "empty_column.list": "Ez dago ezer zerrenda honetan. Zerrenda honetako kideek mezu berriak argitaratzean, hemen agertuko dira.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.",
   "empty_column.public": "Ez dago ezer hemen! Idatzi zerbait publikoki edo jarraitu eskuz beste instantzia batzuetako erabiltzailean hau betetzeko",
   "follow_request.authorize": "Baimendu",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Erakutsi bultzadak",
   "home.column_settings.show_replies": "Erakutsi erantzunak",
   "keyboard_shortcuts.back": "atzera nabigatzeko",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "bultzada ematea",
   "keyboard_shortcuts.column": "mezu bat zutabe batean fokatzea",
   "keyboard_shortcuts.compose": "testua konposatzeko arean fokatzea",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "zerrendan behera mugitzea",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "gogoko egitea",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Laster-tekla",
   "keyboard_shortcuts.legend": "legenda hau bistaratzea",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "egilea aipatzea",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "egilearen profila irekitzeko",
   "keyboard_shortcuts.reply": "erantzutea",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "bilaketan fokua jartzea",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "testua erakustea/ezkutatzea abisu baten atzean",
   "keyboard_shortcuts.toot": "toot berria hastea",
   "keyboard_shortcuts.unfocus": "testua konposatzeko area / bilaketatik fokua kentzea",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokeatutako erabiltzaileak",
   "navigation_bar.community_timeline": "Denbora-lerro lokala",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Mezu zuzenak",
   "navigation_bar.discover": "Aurkitu",
   "navigation_bar.domain_blocks": "Ezkutatutako domeinuak",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Kendu bultzada",
   "status.cannot_reblog": "Mezu honi ezin zaio bultzada eman",
   "status.delete": "Ezabatu",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Mezu zuzena @{name}(r)i",
   "status.embed": "Txertatu",
   "status.favourite": "Gogokoa",
@@ -274,6 +297,7 @@
   "status.reblog": "Bultzada",
   "status.reblog_private": "Bultzada jatorrizko hartzaileei",
   "status.reblogged_by": "{name}(r)en bultzada",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Ezabatu eta berridatzi",
   "status.reply": "Erantzun",
   "status.replyAll": "Erantzun harian",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 03c6bb7ce..f57c1354e 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -10,7 +10,9 @@
   "account.endorse": "نمایش در نمایه",
   "account.follow": "پی بگیرید",
   "account.followers": "پیگیران",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "پی می‌گیرد",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "پیگیر شماست",
   "account.hide_reblogs": "پنهان کردن بازبوق‌های @{name}",
   "account.media": "عکس و ویدیو",
@@ -87,7 +89,7 @@
   "confirmations.mute.confirm": "بی‌صدا کن",
   "confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
   "confirmations.redraft.confirm": "پاک‌کردن و بازنویسی",
-  "confirmations.redraft.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید و آن را از نو بنویسید؟ با این کار همهٔ پاسخ‌ها، بازبوق‌ها، و پسندیده‌شدن‌های آن از دست می‌رود.",
+  "confirmations.redraft.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید و آن را از نو بنویسید؟ با این کار بازبوق‌ها و پسندیده‌شدن‌های آن از دست می‌رود و پاسخ‌ها به آن بی‌مرجع می‌شود.",
   "confirmations.unfollow.confirm": "لغو پیگیری",
   "confirmations.unfollow.message": "آیا واقعاً می‌خواهید به پیگیری از {name} پایان دهید؟",
   "embed.instructions": "برای جاگذاری این نوشته در سایت خودتان، کد زیر را کپی کنید.",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "نتایج جستجو",
   "emoji_button.symbols": "نمادها",
   "emoji_button.travel": "سفر و مکان",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "فهرست نوشته‌های محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
   "empty_column.direct": "شما هیچ پیغام مستقیمی ندارید. اگر چنین پیغامی بگیرید یا بفرستید این‌جا نمایش خواهد یافت.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
   "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
   "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا",
   "empty_column.list": "در این فهرست هنوز چیزی نیست. وقتی اعضای این فهرست چیزی بنویسند، این‌جا ظاهر خواهد شد.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشته‌های دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
   "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا این‌جا پر شود",
   "follow_request.authorize": "اجازه دهید",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "نمایش بازبوق‌ها",
   "home.column_settings.show_replies": "نمایش پاسخ‌ها",
   "keyboard_shortcuts.back": "برای بازگشت",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "برای بازبوقیدن",
   "keyboard_shortcuts.column": "برای برجسته‌کردن یک نوشته در یکی از ستون‌ها",
   "keyboard_shortcuts.compose": "برای فعال‌کردن کادر نوشتهٔ تازه",
   "keyboard_shortcuts.description": "توضیح",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "برای پایین‌رفتن در فهرست",
   "keyboard_shortcuts.enter": "برای گشودن نوشته",
   "keyboard_shortcuts.favourite": "برای پسندیدن",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "میان‌برهای صفحه‌کلید",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "میان‌بر",
   "keyboard_shortcuts.legend": "برای نمایش این راهنما",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "برای نام‌بردن از نویسنده",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
   "keyboard_shortcuts.reply": "برای پاسخ‌دادن",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "برای فعال‌کردن جستجو",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "برای نمایش/نهفتن نوشتهٔ پشت هشدار محتوا",
   "keyboard_shortcuts.toot": "برای آغاز یک بوق تازه",
   "keyboard_shortcuts.unfocus": "برای برداشتن توجه از نوشتن/جستجو",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "کاربران مسدودشده",
   "navigation_bar.community_timeline": "نوشته‌های محلی",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "پیغام‌های خصوصی",
   "navigation_bar.discover": "گشت و گذار",
   "navigation_bar.domain_blocks": "دامین‌های پنهان‌شده",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "حذف بازبوق",
   "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید",
   "status.delete": "پاک‌کردن",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "پیغام مستقیم به @{name}",
   "status.embed": "جاگذاری",
   "status.favourite": "پسندیدن",
@@ -274,6 +297,7 @@
   "status.reblog": "بازبوقیدن",
   "status.reblog_private": "بازبوق به مخاطبان اولیه",
   "status.reblogged_by": "‫{name}‬ بازبوقید",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "پاک‌کردن و بازنویسی",
   "status.reply": "پاسخ",
   "status.replyAll": "به نوشته پاسخ دهید",
@@ -295,7 +319,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {نفر نوشته است} other {نفر نوشته‌اند}}",
   "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.",
   "upload_area.title": "برای بارگذاری به این‌جا بکشید",
-  "upload_button.label": "افزودن تصویر",
+  "upload_button.label": "افزودن عکس و ویدیو (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
   "upload_form.focus": "بریدن لبه‌ها",
   "upload_form.undo": "حذف",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 0861c42fc..fd23e7ff5 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Seuraa",
   "account.followers": "Seuraajia",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Seuraa",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Seuraa sinua",
   "account.hide_reblogs": "Piilota buustaukset käyttäjältä @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Hakutulokset",
   "emoji_button.symbols": "Symbolit",
   "emoji_button.travel": "Matkailu",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!",
   "empty_column.direct": "Sinulla ei ole vielä yhtään viestiä yksittäiselle käyttäjälle. Kun lähetät tai vastaanotat sellaisen, se näkyy täällä.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Tällä hashtagilla ei ole vielä mitään.",
   "empty_column.home": "Kotiaikajanasi on tyhjä! {public} ja hakutoiminto auttavat alkuun ja kohtaamaan muita käyttäjiä.",
   "empty_column.home.public_timeline": "yleinen aikajana",
   "empty_column.list": "Lista on vielä tyhjä. Listan jäsenten julkaisemat tilapäivitykset tulevat tähän näkyviin.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Aloita keskustelu juttelemalla muille.",
   "empty_column.public": "Täällä ei ole mitään! Saat sisältöä, kun kirjoitat jotain julkisesti tai käyt manuaalisesti seuraamassa muiden instanssien käyttäjiä",
   "follow_request.authorize": "Valtuuta",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Näytä buustaukset",
   "home.column_settings.show_replies": "Näytä vastaukset",
   "keyboard_shortcuts.back": "liiku taaksepäin",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "buustaa",
   "keyboard_shortcuts.column": "siirrä fokus tietyn sarakkeen tilapäivitykseen",
   "keyboard_shortcuts.compose": "siirry tekstinsyöttöön",
   "keyboard_shortcuts.description": "Kuvaus",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "siirry listassa alaspäin",
   "keyboard_shortcuts.enter": "avaa tilapäivitys",
   "keyboard_shortcuts.favourite": "tykkää",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Näppäinkomennot",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Pikanäppäin",
   "keyboard_shortcuts.legend": "näytä tämä selite",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "mainitse julkaisija",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "vastaa",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "siirry hakukenttään",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "näytä/piilota sisältövaroituksella merkitty teksti",
   "keyboard_shortcuts.toot": "ala kirjoittaa uutta tuuttausta",
   "keyboard_shortcuts.unfocus": "siirry pois tekstikentästä tai hakukentästä",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Estetyt käyttäjät",
   "navigation_bar.community_timeline": "Paikallinen aikajana",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Viestit",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Piilotetut verkkotunnukset",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Peru buustaus",
   "status.cannot_reblog": "Tätä julkaisua ei voi buustata",
   "status.delete": "Poista",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Viesti käyttäjälle @{name}",
   "status.embed": "Upota",
   "status.favourite": "Tykkää",
@@ -274,6 +297,7 @@
   "status.reblog": "Buustaa",
   "status.reblog_private": "Buustaa alkuperäiselle yleisölle",
   "status.reblogged_by": "{name} buustasi",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Vastaa",
   "status.replyAll": "Vastaa ketjuun",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 79ce01c05..41eaab417 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -10,7 +10,9 @@
   "account.endorse": "Figure sur le profil",
   "account.follow": "Suivre",
   "account.followers": "Abonné⋅e⋅s",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Abonnements",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Vous suit",
   "account.hide_reblogs": "Masquer les partages de @{name}",
   "account.media": "Média",
@@ -22,7 +24,7 @@
   "account.posts": "Pouets",
   "account.posts_with_replies": "Pouets et réponses",
   "account.report": "Signaler",
-  "account.requested": "En attente d'approbation. Cliquez pour annuler la requête",
+  "account.requested": "En attente d’approbation. Cliquez pour annuler la requête",
   "account.share": "Partager le profil de @{name}",
   "account.show_reblogs": "Afficher les partages de @{name}",
   "account.unblock": "Débloquer",
@@ -32,7 +34,7 @@
   "account.unmute": "Ne plus masquer",
   "account.unmute_notifications": "Réactiver les notifications de @{name}",
   "account.view_full_profile": "Afficher le profil complet",
-  "alert.unexpected.message": "Une erreur non-attendue s'est produite.",
+  "alert.unexpected.message": "Une erreur non attendue s’est produite.",
   "alert.unexpected.title": "Oups !",
   "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
   "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.",
@@ -62,9 +64,9 @@
   "column_header.unpin": "Retirer",
   "column_subheading.settings": "Paramètres",
   "community.column_settings.media_only": "Média uniquement",
-  "compose_form.direct_message_warning": "Ce pouet sera uniquement envoyé qu'aux personnes mentionnées. Cependant, l'administration de votre instance et des instances réceptrices pourront inspecter ce message.",
+  "compose_form.direct_message_warning": "Ce pouet sera uniquement envoyé aux personnes mentionnées. Cependant, l’administration de votre instance et des instances réceptrices pourront inspecter ce message.",
   "compose_form.direct_message_warning_learn_more": "En savoir plus",
-  "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.",
+  "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.",
   "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.",
   "compose_form.lock_disclaimer.lock": "verrouillé",
   "compose_form.placeholder": "Qu’avez-vous en tête ?",
@@ -73,7 +75,7 @@
   "compose_form.sensitive.marked": "Média marqué comme sensible",
   "compose_form.sensitive.unmarked": "Média non marqué comme sensible",
   "compose_form.spoiler.marked": "Le texte est caché derrière un avertissement",
-  "compose_form.spoiler.unmarked": "Le texte n'est pas caché",
+  "compose_form.spoiler.unmarked": "Le texte n’est pas caché",
   "compose_form.spoiler_placeholder": "Écrivez ici votre avertissement",
   "confirmation_modal.cancel": "Annuler",
   "confirmations.block.confirm": "Bloquer",
@@ -83,11 +85,11 @@
   "confirmations.delete_list.confirm": "Supprimer",
   "confirmations.delete_list.message": "Êtes-vous sûr de vouloir supprimer définitivement cette liste ?",
   "confirmations.domain_block.confirm": "Masquer le domaine entier",
-  "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables. Vous ne verrez plus de contenu provenant de ce domaine ni dans vos lignes de temps publiques, ni dans vos notifications. Vos suiveurs utilisant ce domaine seront retirés.",
+  "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables. Vous ne verrez plus de contenu provenant de ce domaine, ni dans fils publics, ni dans vos notifications. Vos abonné·e·s utilisant ce domaine seront retiré·e·s.",
   "confirmations.mute.confirm": "Masquer",
   "confirmations.mute.message": "Confirmez-vous le masquage de {name} ?",
   "confirmations.redraft.confirm": "Effacer et ré-écrire",
-  "confirmations.redraft.message": "Êtes vous sûr de vouloir effacer ce statut pour le ré-écrire ? Vous perdrez toutes ses réponses, ses repartages et ses mises en favori.",
+  "confirmations.redraft.message": "Êtes-vous sûr·e de vouloir effacer ce statut pour le ré-écrire ? Ses partages ainsi que ses mises en favori seront perdu·e·s et ses réponses seront orphelines.",
   "confirmations.unfollow.confirm": "Ne plus suivre",
   "confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?",
   "embed.instructions": "Intégrez ce statut à votre site en copiant le code ci-dessous.",
@@ -98,7 +100,7 @@
   "emoji_button.food": "Nourriture & Boisson",
   "emoji_button.label": "Insérer un émoji",
   "emoji_button.nature": "Nature",
-  "emoji_button.not_found": "Pas d'emojis !! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "Pas d’émoji !! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objets",
   "emoji_button.people": "Personnages",
   "emoji_button.recent": "Fréquemment utilisés",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Résultats de la recherche",
   "emoji_button.symbols": "Symboles",
   "emoji_button.travel": "Lieux & Voyages",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
-  "empty_column.direct": "Vous n'avez pas encore de messages directs. Lorsque vous en enverrez ou recevrez un, il s'affichera ici.",
+  "empty_column.direct": "Vous n’avez pas encore de messages directs. Lorsque vous en enverrez ou recevrez un, il s’affichera ici.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
   "empty_column.home": "Vous ne suivez personne. Visitez {public} ou utilisez la recherche pour trouver d’autres personnes à suivre.",
   "empty_column.home.public_timeline": "le fil public",
-  "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.",
+  "empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Dès que des personnes de cette liste publieront de nouveaux statuts, ils apparaîtront ici.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
   "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public",
   "follow_request.authorize": "Accepter",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Afficher les partages",
   "home.column_settings.show_replies": "Afficher les réponses",
   "keyboard_shortcuts.back": "revenir en arrière",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "partager",
-  "keyboard_shortcuts.column": "focaliser un statut dans l'une des colonnes",
+  "keyboard_shortcuts.column": "focaliser un statut dans l’une des colonnes",
   "keyboard_shortcuts.compose": "pour centrer la zone de rédaction",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "pour descendre dans la liste",
   "keyboard_shortcuts.enter": "pour ouvrir le statut",
   "keyboard_shortcuts.favourite": "vers les favoris",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Raccourcis clavier",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Raccourci",
   "keyboard_shortcuts.legend": "pour afficher cette légende",
-  "keyboard_shortcuts.mention": "pour mentionner l'auteur",
-  "keyboard_shortcuts.profile": "pour ouvrir le profil de l'auteur",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "pour mentionner l’auteur·rice",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.profile": "pour ouvrir le profil de l’auteur·rice",
   "keyboard_shortcuts.reply": "pour répondre",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "pour cibler la recherche",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "pour afficher/cacher un texte derrière CW",
   "keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet",
   "keyboard_shortcuts.unfocus": "pour recentrer composer textarea/search",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "Non trouvé",
   "missing_indicator.sublabel": "Ressource introuvable",
   "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Applications mobiles",
   "navigation_bar.blocks": "Comptes bloqués",
   "navigation_bar.community_timeline": "Fil public local",
+  "navigation_bar.compose": "Rédiger un nouveau toot",
   "navigation_bar.direct": "Messages directs",
   "navigation_bar.discover": "Découvrir",
   "navigation_bar.domain_blocks": "Domaines cachés",
@@ -202,12 +224,12 @@
   "onboarding.next": "Suivant",
   "onboarding.page_five.public_timelines": "Le fil public global affiche les messages de toutes les personnes suivies par les membres de {domain}. Le fil public local est identique, mais se limite aux membres de {domain}.",
   "onboarding.page_four.home": "L’accueil affiche les messages des personnes que vous suivez.",
-  "onboarding.page_four.notifications": "La colonne de notification vous avertit lors d'une interaction avec vous.",
+  "onboarding.page_four.notifications": "La colonne de notification vous avertit lors d’une interaction avec vous.",
   "onboarding.page_one.federation": "Mastodon est un réseau de serveurs indépendants qui se joignent pour former un réseau social plus vaste. Nous appelons ces serveurs des instances.",
   "onboarding.page_one.full_handle": "Votre identifiant complet",
-  "onboarding.page_one.handle_hint": "C'est ce que vos amis devront rechercher.",
+  "onboarding.page_one.handle_hint": "C’est ce que vos ami·e·s devront rechercher.",
   "onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
-  "onboarding.page_six.admin": "L’administrateur⋅ice de votre instance est {admin}.",
+  "onboarding.page_six.admin": "Votre instance est administrée par {admin}.",
   "onboarding.page_six.almost_done": "Nous y sommes presque…",
   "onboarding.page_six.appetoot": "Bon appouétit !",
   "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres.",
@@ -220,14 +242,14 @@
   "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.",
   "onboarding.skip": "Passer",
   "privacy.change": "Ajuster la confidentialité du message",
-  "privacy.direct.long": "N'envoyer qu'aux personnes mentionnées",
+  "privacy.direct.long": "N’envoyer qu’aux personnes mentionnées",
   "privacy.direct.short": "Direct",
   "privacy.private.long": "Seul⋅e⋅s vos abonné⋅e⋅s verront vos statuts",
   "privacy.private.short": "Abonné⋅e⋅s uniquement",
   "privacy.public.long": "Afficher dans les fils publics",
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
-  "privacy.unlisted.short": "Non-listé",
+  "privacy.unlisted.short": "Non listé",
   "regeneration_indicator.label": "Chargement…",
   "regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
   "relative_time.days": "{number} j",
@@ -237,8 +259,8 @@
   "relative_time.seconds": "{number} s",
   "reply_indicator.cancel": "Annuler",
   "report.forward": "Transférer à {target}",
-  "report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
-  "report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez le compte ci-dessous :",
+  "report.forward_hint": "Le compte provient d’un autre serveur. Envoyez également une copie anonyme du rapport ?",
+  "report.hint": "Le rapport sera envoyé aux modérateur·rice·s de votre instance. Vous pouvez expliquer pourquoi vous signalez le compte ci-dessous :",
   "report.placeholder": "Commentaires additionnels",
   "report.submit": "Envoyer",
   "report.target": "Signalement",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Dé-booster",
   "status.cannot_reblog": "Cette publication ne peut être boostée",
   "status.delete": "Effacer",
+  "status.detailed_status": "Vue détaillée de la conversation",
   "status.direct": "Envoyer un message direct à @{name}",
   "status.embed": "Intégrer",
   "status.favourite": "Ajouter aux favoris",
@@ -272,8 +295,9 @@
   "status.pin": "Épingler sur le profil",
   "status.pinned": "Pouet épinglé",
   "status.reblog": "Partager",
-  "status.reblog_private": "Booster vers l'audience originale",
+  "status.reblog_private": "Booster vers l’audience originale",
   "status.reblogged_by": "{name} a partagé :",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Effacer et ré-écrire",
   "status.reply": "Répondre",
   "status.replyAll": "Répondre au fil",
@@ -292,16 +316,16 @@
   "tabs_bar.local_timeline": "Fil public local",
   "tabs_bar.notifications": "Notifications",
   "tabs_bar.search": "Chercher",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} discutent",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {personne} other {personnes}} discutent",
   "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
   "upload_area.title": "Glissez et déposez pour envoyer",
-  "upload_button.label": "Joindre un média",
-  "upload_form.description": "Décrire pour les malvoyants",
+  "upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4)",
+  "upload_form.description": "Décrire pour les malvoyant·e·s",
   "upload_form.focus": "Recadrer",
   "upload_form.undo": "Supprimer",
   "upload_progress.label": "Envoi en cours…",
   "video.close": "Fermer la vidéo",
-  "video.exit_fullscreen": "Quitter plein écran",
+  "video.exit_fullscreen": "Quitter le plein écran",
   "video.expand": "Agrandir la vidéo",
   "video.fullscreen": "Plein écran",
   "video.hide": "Masquer la vidéo",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index d83b11d74..26c99e541 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Seguir",
   "account.followers": "Seguidoras",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Seguindo",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Séguena",
   "account.hide_reblogs": "Ocultar repeticións de @{name}",
   "account.media": "Medios",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Resultados da busca",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viaxes e Lugares",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "A liña temporal local está baldeira. Escriba algo de xeito público para que rule!",
   "empty_column.direct": "Aínda non ten mensaxes directas. Cando envíe ou reciba unha, aparecerá aquí.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Aínda non hai nada con esta etiqueta.",
   "empty_column.home": "A súa liña temporal de inicio está baldeira! Visite {public} ou utilice a busca para atopar outras usuarias.",
   "empty_column.home.public_timeline": "a liña temporal pública",
   "empty_column.list": "Aínda non hai nada en esta lista. Cando as usuarias incluídas na lista publiquen mensaxes, aparecerán aquí.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Aínda non ten notificacións. Interactúe con outras para iniciar unha conversa.",
   "empty_column.public": "Nada por aquí! Escriba algo de xeito público, ou siga manualmente usuarias de outras instancias para ir enchéndoa",
   "follow_request.authorize": "Autorizar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostrar repeticións",
   "home.column_settings.show_replies": "Mostrar respostas",
   "keyboard_shortcuts.back": "voltar atrás",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "promover",
   "keyboard_shortcuts.column": "destacar un estado en unha das columnas",
   "keyboard_shortcuts.compose": "Foco no área de escritura",
   "keyboard_shortcuts.description": "Descrición",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "ir hacia abaixo na lista",
   "keyboard_shortcuts.enter": "abrir estado",
   "keyboard_shortcuts.favourite": "marcar como favorito",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Atallos do teclado",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Tecla de acceso directo",
   "keyboard_shortcuts.legend": "para mostrar esta lenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "para mencionar o autor",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "para responder",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "para centrar a busca",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "mostrar/agochar un texto detrás do AC",
   "keyboard_shortcuts.toot": "escribir un toot novo",
   "keyboard_shortcuts.unfocus": "quitar o foco do área de escritura/busca",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Usuarias bloqueadas",
   "navigation_bar.community_timeline": "Liña temporal local",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Mensaxes directas",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Dominios agochados",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Non promover",
   "status.cannot_reblog": "Esta mensaxe non pode ser promovida",
   "status.delete": "Eliminar",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Mensaxe directa @{name}",
   "status.embed": "Incrustar",
   "status.favourite": "Favorita",
@@ -274,6 +297,7 @@
   "status.reblog": "Promover",
   "status.reblog_private": "Promover a audiencia orixinal",
   "status.reblogged_by": "{name} promoveu",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Resposta",
   "status.replyAll": "Resposta a conversa",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index d20adce11..e9a13b02d 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "מעקב",
   "account.followers": "עוקבים",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "נעקבים",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "במעקב אחריך",
   "account.hide_reblogs": "להסתיר הידהודים מאת @{name}",
   "account.media": "מדיה",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "תוצאות חיפוש",
   "emoji_button.symbols": "סמלים",
   "emoji_button.travel": "טיולים ואתרים",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.",
   "empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
   "empty_column.home.public_timeline": "ציר זמן בין-קהילתי",
   "empty_column.list": "אין עדיין מאום ברשימה.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב.",
   "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות",
   "follow_request.authorize": "קבלה",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "הצגת הדהודים",
   "home.column_settings.show_replies": "הצגת תגובות",
   "keyboard_shortcuts.back": "ניווט חזרה",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "להדהד",
   "keyboard_shortcuts.column": "להתמקד בהודעה באחד מהטורים",
   "keyboard_shortcuts.compose": "להתמקד בתיבת חיבור ההודעות",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "לנוע במורד הרשימה",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "לחבב",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "מקש קיצור",
   "keyboard_shortcuts.legend": "להציג את הפירוש",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "לאזכר את המחבר(ת)",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "לענות",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "להתמקד בחלון החיפוש",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש",
   "keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "חסימות",
   "navigation_bar.community_timeline": "ציר זמן מקומי",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "לא ניתן להדהד הודעה זו",
   "status.delete": "מחיקה",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "הטמעה",
   "status.favourite": "חיבוב",
@@ -274,6 +297,7 @@
   "status.reblog": "הדהוד",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "הודהד על ידי {name}",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "תגובה",
   "status.replyAll": "תגובה לכולם",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index e6ce3359d..7da45c3a4 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Slijedi",
   "account.followers": "Sljedbenici",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Slijedi",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "te slijedi",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Putovanja & Mjesta",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
   "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
   "empty_column.home.public_timeline": "javni timeline",
   "empty_column.list": "There is nothing in this list yet.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
   "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
   "follow_request.authorize": "Autoriziraj",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Pokaži boostove",
   "home.column_settings.show_replies": "Pokaži odgovore",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalni timeline",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ovaj post ne može biti boostan",
   "status.delete": "Obriši",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Označi omiljenim",
@@ -274,6 +297,7 @@
   "status.reblog": "Podigni",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} je podigao",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Odgovori",
   "status.replyAll": "Odgovori na temu",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 80c3a1de8..c9d32135a 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Követés",
   "account.followers": "Követők",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Követve",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Követnek téged",
   "account.hide_reblogs": "Rejtsd el a tülkölést @{name}-tól/től",
   "account.media": "Média",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Keresési találatok",
   "emoji_button.symbols": "Szimbólumok",
   "emoji_button.travel": "Utazás és Helyek",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "A helyi idővonal üres. Írj egy publikus stástuszt, hogy elindítsd a labdát!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Jelenleg nem található semmi ezen hashtaggel.",
   "empty_column.home": "A hazai idővonala üres! Látogasd meg a {public} vagy használd a keresőt, hogy ismerj meg más felhasználókat.",
   "empty_column.home.public_timeline": "publikus idővonal",
   "empty_column.list": "A lista jelenleg üres. Mikor a listatagok új státuszt posztolnak itt meg fognak jelenni.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Jelenleg nincsenek értesítései. Lépj kapcsolatba másokkal, hogy indítsd el a beszélgetést.",
   "empty_column.public": "Jelenleg semmi nincs itt! Írj valamit publikusan vagy kövess más szervereken levő felhasználókat, hogy megtöltsd",
   "follow_request.authorize": "Engedélyez",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Ismétlések mutatása",
   "home.column_settings.show_replies": "Válaszok mutatása",
   "keyboard_shortcuts.back": "vissza navigálás",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "ismétlés",
   "keyboard_shortcuts.column": "összpontosítson egy státuszra az egyik oszlopban",
   "keyboard_shortcuts.compose": "fókuszálja a szerkesztési szövegdobozt",
   "keyboard_shortcuts.description": "Leírás",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "lefele navigálás a listában",
   "keyboard_shortcuts.enter": "státusz megnyitása",
   "keyboard_shortcuts.favourite": "kedvenccé tétel",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Billentyű rövidítések",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Gyorsbillentyű",
   "keyboard_shortcuts.legend": "jelmagyarázat megjelenítése",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "szerző megjelenítése",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "válaszolás",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "kereső kiemelése",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "új tülk megkezdése",
   "keyboard_shortcuts.unfocus": "tülk szerkesztés/keresés fókuszpontból való kivétele",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Tiltott felhasználók",
   "navigation_bar.community_timeline": "Helyi idővonal",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ezen státusz nem rebloggolható",
   "status.delete": "Törlés",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Beágyaz",
   "status.favourite": "Kedvenc",
@@ -274,6 +297,7 @@
   "status.reblog": "Reblog",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} reblogolta",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Válasz",
   "status.replyAll": "Válaszolj a beszélgetésre",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index e5ad93fd8..1376f6894 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Հետեւել",
   "account.followers": "Հետեւվողներ",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Հետեւում է",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Հետեւում է քեզ",
   "account.hide_reblogs": "Թաքցնել @{name}֊ի տարածածները",
   "account.media": "Մեդիա",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Որոնման արդյունքներ",
   "emoji_button.symbols": "Նշաններ",
   "emoji_button.travel": "Ուղեւորություն եւ տեղանքներ",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Տեղական հոսքը դատա՛րկ է։ Հրապարակային մի բան գրիր շարժիչը խոդ տալու համար։",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Այս պիտակով դեռ ոչինչ չկա։",
   "empty_column.home": "Քո հիմնական հոսքը դատա՛րկ է։ Այցելի՛ր {public}ը կամ օգտվիր որոնումից՝ այլ մարդկանց հանդիպելու համար։",
   "empty_column.home.public_timeline": "հրապարակային հոսք",
   "empty_column.list": "Այս ցանկում դեռ ոչինչ չկա։ Երբ ցանկի անդամներից որեւէ մեկը նոր թութ գրի, այն կհայտնվի այստեղ։",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Ոչ մի ծանուցում դեռ չունես։ Բզիր մյուսներին՝ խոսակցությունը սկսելու համար։",
   "empty_column.public": "Այստեղ բան չկա՛։ Հրապարակային մի բան գրիր կամ հետեւիր այլ հանգույցներից էակների՝ այն լցնելու համար։",
   "follow_request.authorize": "Վավերացնել",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Ցուցադրել տարածածները",
   "home.column_settings.show_replies": "Ցուցադրել պատասխանները",
   "keyboard_shortcuts.back": "ետ նավարկելու համար",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "տարածելու համար",
   "keyboard_shortcuts.column": "սյուներից մեկի վրա սեւեռվելու համար",
   "keyboard_shortcuts.compose": "շարադրման տիրույթին սեւեռվելու համար",
   "keyboard_shortcuts.description": "Նկարագրություն",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "ցանկով ներքեւ շարժվելու համար",
   "keyboard_shortcuts.enter": "թութը բացելու համար",
   "keyboard_shortcuts.favourite": "հավանելու համար",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Ստեղնաշարի կարճատներ",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Հատուկ ստեղն",
   "keyboard_shortcuts.legend": "այս ձեռնարկը ցուցադրելու համար",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "հեղինակին նշելու համար",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "պատասխանելու համար",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "որոնման դաշտին սեւեռվելու համար",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "թարմ թութ սկսելու համար",
   "keyboard_shortcuts.unfocus": "տեքստի/որոնման տիրույթից ապասեւեռվելու համար",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Արգելափակված օգտատերեր",
   "navigation_bar.community_timeline": "Տեղական հոսք",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Այս թութը չի կարող տարածվել",
   "status.delete": "Ջնջել",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Ներդնել",
   "status.favourite": "Հավանել",
@@ -274,6 +297,7 @@
   "status.reblog": "Տարածել",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} տարածել է",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Պատասխանել",
   "status.replyAll": "Պատասխանել թելին",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 010d35b73..e8b087e9d 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Ikuti",
   "account.followers": "Pengikut",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Mengikuti",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Mengikuti anda",
   "account.hide_reblogs": "Sembunyikan boosts dari @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Hasil pencarian",
   "emoji_button.symbols": "Simbol",
   "emoji_button.travel": "Tempat Wisata",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
   "empty_column.home": "Linimasa anda kosong! Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
   "empty_column.home.public_timeline": "linimasa publik",
   "empty_column.list": "Tidak ada postingan di list ini. Ketika anggota dari list ini memposting status baru, status tersebut akan tampil disini.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
   "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisi ini",
   "follow_request.authorize": "Izinkan",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Tampilkan boost",
   "home.column_settings.show_replies": "Tampilkan balasan",
   "keyboard_shortcuts.back": "untuk kembali",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "untuk menyebarkan",
   "keyboard_shortcuts.column": "untuk fokus kepada sebuah status di sebuah kolom",
   "keyboard_shortcuts.compose": "untuk fokus ke area penulisan",
   "keyboard_shortcuts.description": "Deskripsi",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "untuk pindah ke bawah dalam sebuah daftar",
   "keyboard_shortcuts.enter": "untuk membuka status",
   "keyboard_shortcuts.favourite": "untuk memfavoritkan",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Pintasan keyboard",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "untuk fokus mencari",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Pengguna diblokir",
   "navigation_bar.community_timeline": "Linimasa lokal",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Hapus",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Difavoritkan",
@@ -274,6 +297,7 @@
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "di-boost {name}",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Balas",
   "status.replyAll": "Balas ke semua",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 06e6b02d7..a3ea121e4 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Sequar",
   "account.followers": "Sequanti",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Sequas",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Sequas tu",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
   "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.",
   "empty_column.home.public_timeline": "la publika tempolineo",
   "empty_column.list": "There is nothing in this list yet.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
   "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
   "follow_request.authorize": "Yurizar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Montrar repeti",
   "home.column_settings.show_replies": "Montrar respondi",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokusita uzeri",
   "navigation_bar.community_timeline": "Lokala tempolineo",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Efacar",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favorizar",
@@ -274,6 +297,7 @@
   "status.reblog": "Repetar",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} repetita",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Respondar",
   "status.replyAll": "Respondar a filo",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index c9ac57c74..fa0956fe3 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Segui",
   "account.followers": "Seguaci",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Segue",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Ti segue",
   "account.hide_reblogs": "Nascondi condivisioni da @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Risultati della ricerca",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Viaggi e luoghi",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
   "empty_column.direct": "Non hai ancora nessun messaggio diretto. Quando ne manderai o riceverai qualcuno, apparirà qui.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
   "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
   "empty_column.home.public_timeline": "la timeline pubblica",
   "empty_column.list": "Non c'è niente in questo elenco ancora. Quando i membri di questo elenco postano nuovi stati, questi appariranno qui.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
   "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio",
   "follow_request.authorize": "Autorizza",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostra post condivisi",
   "home.column_settings.show_replies": "Mostra risposte",
   "keyboard_shortcuts.back": "per tornare indietro",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "per condividere",
   "keyboard_shortcuts.column": "per portare il focus su uno status in una delle colonne",
   "keyboard_shortcuts.compose": "per portare il focus nell'area di composizione",
   "keyboard_shortcuts.description": "Descrizione",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "per spostarsi in basso nella lista",
   "keyboard_shortcuts.enter": "per aprire lo status",
   "keyboard_shortcuts.favourite": "per segnare come apprezzato",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Tasti di scelta rapida",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Tasto di scelta rapida",
   "keyboard_shortcuts.legend": "per mostrare questa spiegazione",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "per menzionare l'autore",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "per aprire il profilo dell'autore",
   "keyboard_shortcuts.reply": "per rispondere",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "per spostare il focus sulla ricerca",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "per mostrare/nascondere il testo dei CW",
   "keyboard_shortcuts.toot": "per iniziare a scrivere un toot completamente nuovo",
   "keyboard_shortcuts.unfocus": "per uscire dall'area di composizione o dalla ricerca",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Utenti bloccati",
   "navigation_bar.community_timeline": "Timeline locale",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Messaggi diretti",
   "navigation_bar.discover": "Scopri",
   "navigation_bar.domain_blocks": "Domini nascosti",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Annulla condivisione",
   "status.cannot_reblog": "Questo post non può essere condiviso",
   "status.delete": "Elimina",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Messaggio diretto @{name}",
   "status.embed": "Incorpora",
   "status.favourite": "Apprezzato",
@@ -274,6 +297,7 @@
   "status.reblog": "Condividi",
   "status.reblog_private": "Condividi con i destinatari iniziali",
   "status.reblogged_by": "{name} ha condiviso",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Cancella e riscrivi",
   "status.reply": "Rispondi",
   "status.replyAll": "Rispondi alla conversazione",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index d8ec6e934..c1eada447 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -10,7 +10,9 @@
   "account.endorse": "プロフィールで紹介する",
   "account.follow": "フォロー",
   "account.followers": "フォロワー",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "フォロー",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "フォローされています",
   "account.hide_reblogs": "@{name}さんからのブーストを非表示",
   "account.media": "メディア",
@@ -91,7 +93,7 @@
   "confirmations.mute.confirm": "ミュート",
   "confirmations.mute.message": "本当に{name}さんをミュートしますか?",
   "confirmations.redraft.confirm": "削除して下書きに戻す",
-  "confirmations.redraft.message": "本当にこのトゥートを削除して下書きに戻しますか? このトゥートへの全ての返信やブースト、お気に入り登録を失うことになります。",
+  "confirmations.redraft.message": "本当にこのトゥートを削除して下書きに戻しますか? このトゥートへのお気に入り登録やブーストは失われ、リプライは孤立することになります。",
   "confirmations.unfollow.confirm": "フォロー解除",
   "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?",
   "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
@@ -110,12 +112,19 @@
   "emoji_button.search_results": "検索結果",
   "emoji_button.symbols": "記号",
   "emoji_button.travel": "旅行と場所",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
   "empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
   "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
   "empty_column.home.public_timeline": "連合タイムライン",
   "empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しいトゥートをするとここに表示されます。",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
   "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう",
   "follow_request.authorize": "許可",
@@ -132,20 +141,32 @@
   "home.column_settings.show_reblogs": "ブースト表示",
   "home.column_settings.show_replies": "返信表示",
   "keyboard_shortcuts.back": "戻る",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "ブースト",
   "keyboard_shortcuts.column": "左からn番目のカラム内最新トゥートに移動",
   "keyboard_shortcuts.compose": "トゥート入力欄に移動",
   "keyboard_shortcuts.description": "説明",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "カラム内一つ下に移動",
   "keyboard_shortcuts.enter": "トゥートの詳細を表示",
   "keyboard_shortcuts.favourite": "お気に入り",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "キーボードショートカット",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "ホットキー",
   "keyboard_shortcuts.legend": "この一覧を表示",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "メンション",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "プロフィールを開く",
   "keyboard_shortcuts.reply": "返信",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "検索欄に移動",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "CWで隠れた文を見る/隠す",
   "keyboard_shortcuts.toot": "新規トゥート",
   "keyboard_shortcuts.unfocus": "トゥート入力欄・検索欄から離れる",
@@ -166,9 +187,10 @@
   "missing_indicator.label": "見つかりません",
   "missing_indicator.sublabel": "見つかりませんでした",
   "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "アプリ",
   "navigation_bar.blocks": "ブロックしたユーザー",
   "navigation_bar.community_timeline": "ローカルタイムライン",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "ダイレクトメッセージ",
   "navigation_bar.discover": "見つける",
   "navigation_bar.domain_blocks": "非表示にしたドメイン",
@@ -263,6 +285,7 @@
   "status.cancel_reblog_private": "ブースト解除",
   "status.cannot_reblog": "この投稿はブーストできません",
   "status.delete": "削除",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "@{name}さんにダイレクトメッセージ",
   "status.embed": "埋め込み",
   "status.favourite": "お気に入り",
@@ -279,6 +302,7 @@
   "status.reblog": "ブースト",
   "status.reblog_private": "ブースト",
   "status.reblogged_by": "{name}さんがブースト",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "削除して下書きに戻す",
   "status.reply": "返信",
   "status.replyAll": "全員に返信",
@@ -300,7 +324,7 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {人} other {人}} がトゥート",
   "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
   "upload_area.title": "ドラッグ&ドロップでアップロード",
-  "upload_button.label": "メディアを追加",
+  "upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "視覚障害者のための説明",
   "upload_form.focus": "焦点",
   "upload_form.undo": "削除",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index d2a915c60..6ad129254 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -10,7 +10,9 @@
   "account.endorse": "გამორჩევა პროფილზე",
   "account.follow": "გაყოლა",
   "account.followers": "მიმდევრები",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "მიდევნებები",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "მოგყვებათ",
   "account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან",
   "account.media": "მედია",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "ძებნის შედეგები",
   "emoji_button.symbols": "სიმბოლოები",
   "emoji_button.travel": "მოგზაურობა და ადგილები",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "ლოკალური თაიმლაინი ცარიელია. დაწერეთ რაიმე ღიად ან ქენით რაიმე სხვა!",
   "empty_column.direct": "ჯერ პირდაპირი წერილები არ გაქვთ. როდესაც მიიღებთ ან გააგზავნით, გამოჩნდება აქ.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "ამ ჰეშტეგში ჯერ არაფერია.",
   "empty_column.home": "თქვენი სახლის თაიმლაინი ცარიელია! ესტუმრეთ {public}-ს ან დასაწყისისთვის გამოიყენეთ ძებნა, რომ შეხვდეთ სხვა მომხმარებლებს.",
   "empty_column.home.public_timeline": "ღია თაიმლაინი",
   "empty_column.list": "ამ სიაში ჯერ არაფერია. როდესაც სიის წევრები დაპოსტავენ ახალ სტატუსებს, ისინი გამოჩნდებიან აქ.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "ჯერ შეტყობინებები არ გაქვთ. საუბრის დასაწყებად იურთიერთქმედეთ სხვებთან.",
   "empty_column.public": "აქ არაფერია! შესავსებად, დაწერეთ რაიმე ღიად ან ხელით გაჰყევით მომხმარებლებს სხვა ინსტანციებისგან",
   "follow_request.authorize": "ავტორიზაცია",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "ბუსტების ჩვენება",
   "home.column_settings.show_replies": "პასუხების ჩვენება",
   "keyboard_shortcuts.back": "უკან გადასასვლელად",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "დასაბუსტად",
   "keyboard_shortcuts.column": "ერთ-ერთი სვეტში სტატუსზე ფოკუსირებისთვის",
   "keyboard_shortcuts.compose": "შედგენის ტექსტ-არეაზე ფოკუსირებისთვის",
   "keyboard_shortcuts.description": "აღწერილობა",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "სიაში ქვემოთ გადასაადგილებლად",
   "keyboard_shortcuts.enter": "სტატუსის გასახსნელად",
   "keyboard_shortcuts.favourite": "ფავორიტად ქცევისთვის",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "კლავიატურის სწრაფი ბმულები",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "ცხელი კლავიში",
   "keyboard_shortcuts.legend": "ამ ლეგენდის გამოსაჩენად",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "ავტორის დასახელებლად",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "ავტორის პროფილის გასახსნელად",
   "keyboard_shortcuts.reply": "პასუხისთვის",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "ძიებაზე ფოკუსირებისთვის",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "გაფრთხილების უკან ტექსტის გამოსაჩენად/დასამალვად",
   "keyboard_shortcuts.toot": "ახალი ტუტის დასაწყებად",
   "keyboard_shortcuts.unfocus": "შედგენის ტექსტ-არეაზე ფოკუსის მოსაშორებლად",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "დაბლოკილი მომხმარებლები",
   "navigation_bar.community_timeline": "ლოკალური თაიმლაინი",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "პირდაპირი წერილები",
   "navigation_bar.discover": "აღმოაჩინე",
   "navigation_bar.domain_blocks": "დამალული დომენები",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "ბუსტის მოშორება",
   "status.cannot_reblog": "ეს პოსტი ვერ დაიბუსტება",
   "status.delete": "წაშლა",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "პირდაპირი წერილი @{name}-ს",
   "status.embed": "ჩართვა",
   "status.favourite": "ფავორიტი",
@@ -274,6 +297,7 @@
   "status.reblog": "ბუსტი",
   "status.reblog_private": "დაიბუსტოს საწყის აუდიტორიაზე",
   "status.reblogged_by": "{name} დაიბუსტა",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "გაუქმდეს და გადანაწილდეს",
   "status.reply": "პასუხი",
   "status.replyAll": "უპასუხე თემას",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 18150d17c..c2c25fe50 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -7,10 +7,12 @@
   "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.",
   "account.domain_blocked": "도메인 숨겨짐",
   "account.edit_profile": "프로필 편집",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "프로필에 나타내기",
   "account.follow": "팔로우",
   "account.followers": "팔로워",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "팔로우",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "날 팔로우합니다",
   "account.hide_reblogs": "@{name}의 부스트를 숨기기",
   "account.media": "미디어",
@@ -27,7 +29,7 @@
   "account.show_reblogs": "@{name}의 부스트 보기",
   "account.unblock": "차단 해제",
   "account.unblock_domain": "{domain} 숨김 해제",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "프로필에 나타내지 않기",
   "account.unfollow": "팔로우 해제",
   "account.unmute": "뮤트 해제",
   "account.unmute_notifications": "@{name}의 알림 뮤트 해제",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "검색 결과",
   "emoji_button.symbols": "기호",
   "emoji_button.travel": "여행과 장소",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!",
   "empty_column.direct": "아직 다이렉트 메시지가 없습니다. 다이렉트 메시지를 보내거나 받은 경우, 여기에 표시 됩니다.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
   "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.",
   "empty_column.home.public_timeline": "연합 타임라인",
   "empty_column.list": "리스트에 아직 아무 것도 없습니다.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요.",
   "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스의 유저를 팔로우 해서 채워보세요",
   "follow_request.authorize": "허가",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "부스트 표시",
   "home.column_settings.show_replies": "답글 표시",
   "keyboard_shortcuts.back": "뒤로가기",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "부스트",
   "keyboard_shortcuts.column": "해당 열에 포커스",
   "keyboard_shortcuts.compose": "작성창으로 포커스",
   "keyboard_shortcuts.description": "설명",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "리스트에서 아래로 이동",
   "keyboard_shortcuts.enter": "열기",
   "keyboard_shortcuts.favourite": "관심글 지정",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "키보드 단축키",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "핫키",
   "keyboard_shortcuts.legend": "이 도움말 표시",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "멘션",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "프로필 열기",
   "keyboard_shortcuts.reply": "답장",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "검색창에 포커스",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "CW로 가려진 텍스트를 표시/비표시",
   "keyboard_shortcuts.toot": "새 툿 작성",
   "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "찾을 수 없습니다",
   "missing_indicator.sublabel": "이 리소스를 찾을 수 없었습니다",
   "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "모바일 앱",
   "navigation_bar.blocks": "차단한 사용자",
   "navigation_bar.community_timeline": "로컬 타임라인",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "다이렉트 메시지",
   "navigation_bar.discover": "발견하기",
   "navigation_bar.domain_blocks": "숨겨진 도메인",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "부스트 취소",
   "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다",
   "status.delete": "삭제",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "@{name}에게 다이렉트 메시지",
   "status.embed": "공유하기",
   "status.favourite": "즐겨찾기",
@@ -274,6 +297,7 @@
   "status.reblog": "부스트",
   "status.reblog_private": "원래의 수신자들에게 부스트",
   "status.reblogged_by": "{name}님이 부스트 했습니다",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "지우고 다시 쓰기",
   "status.reply": "답장",
   "status.replyAll": "전원에게 답장",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 72ffbdc6e..2ad2a6928 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -10,7 +10,9 @@
   "account.endorse": "Op profiel weergeven",
   "account.follow": "Volgen",
   "account.followers": "Volgers",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Volgt",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Volgt jou",
   "account.hide_reblogs": "Verberg boosts van @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Zoekresultaten",
   "emoji_button.symbols": "Symbolen",
   "emoji_button.travel": "Reizen en plekken",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!",
   "empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
   "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.",
   "empty_column.home.public_timeline": "de globale tijdlijn",
   "empty_column.list": "Er is nog niks in deze lijst. Wanneer lijstleden nieuwe toots publiceren, zijn deze hier te zien.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.",
   "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen",
   "follow_request.authorize": "Goedkeuren",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Boosts tonen",
   "home.column_settings.show_replies": "Reacties tonen",
   "keyboard_shortcuts.back": "om terug te gaan",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "om te boosten",
   "keyboard_shortcuts.column": "om op een toot te focussen in één van de kolommen",
   "keyboard_shortcuts.compose": "om het tekstvak voor toots te focussen",
   "keyboard_shortcuts.description": "Omschrijving",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "om naar beneden door de lijst te bewegen",
   "keyboard_shortcuts.enter": "om toot volledig te tonen",
   "keyboard_shortcuts.favourite": "om als favoriet te markeren",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Sneltoetsen",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Sneltoets",
   "keyboard_shortcuts.legend": "om deze legenda te tonen",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "om de auteur te vermelden",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "om het gebruikersprofiel te openen",
   "keyboard_shortcuts.reply": "om te reageren",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "om het zoekvak te focussen",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "om tekst achter een waarschuwing (CW) te tonen/verbergen",
   "keyboard_shortcuts.toot": "om een nieuwe toot te starten",
   "keyboard_shortcuts.unfocus": "om het tekst- en zoekvak te ontfocussen",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Geblokkeerde gebruikers",
   "navigation_bar.community_timeline": "Lokale tijdlijn",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Directe berichten",
   "navigation_bar.discover": "Ontdekken",
   "navigation_bar.domain_blocks": "Verborgen domeinen",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Niet langer boosten",
   "status.cannot_reblog": "Deze toot kan niet geboost worden",
   "status.delete": "Verwijderen",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Directe toot @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favoriet",
@@ -274,6 +297,7 @@
   "status.reblog": "Boost",
   "status.reblog_private": "Boost naar oorspronkelijke ontvangers",
   "status.reblogged_by": "{name} boostte",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Verwijderen en herschrijven",
   "status.reply": "Reageren",
   "status.replyAll": "Reageer op iedereen",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 3e9612edb..d533ac315 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Følg",
   "account.followers": "Følgere",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Følger",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Følger deg",
   "account.hide_reblogs": "Skjul fremhevinger fra @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Søkeresultat",
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Reise & steder",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
   "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
   "empty_column.home.public_timeline": "en offentlig tidslinje",
   "empty_column.list": "Det er ingenting i denne listen ennå. Når medlemmene av denne listen legger ut nye statuser vil de dukke opp her.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
   "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
   "follow_request.authorize": "Autorisér",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Vis fremhevinger",
   "home.column_settings.show_replies": "Vis svar",
   "keyboard_shortcuts.back": "for å navigere tilbake",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "å fremheve",
   "keyboard_shortcuts.column": "å fokusere en status i en av kolonnene",
   "keyboard_shortcuts.compose": "å fokusere komponeringsfeltet",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "for å flytte ned i listen",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "for å favorittmarkere",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Lyntast",
   "keyboard_shortcuts.legend": "å vise denne forklaringen",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "å nevne forfatter",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "for å svare",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "å fokusere søk",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "å starte en helt ny tut",
   "keyboard_shortcuts.unfocus": "å ufokusere komponerings-/søkefeltet",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokkerte brukere",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Denne posten kan ikke fremheves",
   "status.delete": "Slett",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Bygge inn",
   "status.favourite": "Lik",
@@ -274,6 +297,7 @@
   "status.reblog": "Fremhev",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "Fremhevd av {name}",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Svar",
   "status.replyAll": "Svar til samtale",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 6aaf4cc63..06fa058db 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -7,10 +7,12 @@
   "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.",
   "account.domain_blocked": "Domeni amagat",
   "account.edit_profile": "Modificar lo perfil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Mostrar pel perfil",
   "account.follow": "Sègre",
   "account.followers": "Seguidors",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Abonaments",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Vos sèc",
   "account.hide_reblogs": "Rescondre los partatges de @{name}",
   "account.media": "Mèdias",
@@ -27,7 +29,7 @@
   "account.show_reblogs": "Mostrar los partatges de @{name}",
   "account.unblock": "Desblocar @{name}",
   "account.unblock_domain": "Desblocar {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Mostrar pas pel perfil",
   "account.unfollow": "Quitar de sègre",
   "account.unmute": "Quitar de rescondre @{name}",
   "account.unmute_notifications": "Mostrar las notificacions de @{name}",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Resultats de recèrca",
   "emoji_button.symbols": "Simbòls",
   "emoji_button.travel": "Viatges & lòcs",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
   "empty_column.direct": "Avètz pas encara cap de messatges. Quand ne mandatz un o que ne recebètz un, serà mostrat aquí.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.",
   "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
   "empty_column.home.public_timeline": "lo flux public",
   "empty_column.list": "I a pas res dins la lista pel moment. Quand de membres d’aquesta lista publiquen de novèls estatuts los veiretz aquí.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
   "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public",
   "follow_request.authorize": "Acceptar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostrar los partatges",
   "home.column_settings.show_replies": "Mostrar las responsas",
   "keyboard_shortcuts.back": "anar enrèire",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "partejar",
   "keyboard_shortcuts.column": "centrar un estatut a una colomna",
   "keyboard_shortcuts.compose": "anar al camp tèxte",
   "keyboard_shortcuts.description": "Descripcion",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "far davalar dins la lista",
   "keyboard_shortcuts.enter": "dobrir los estatuts",
   "keyboard_shortcuts.favourite": "apondre als favorits",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Acorchis clavièr",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Acorchis",
   "keyboard_shortcuts.legend": "mostrar aquesta legenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "mencionar l’autor",
-  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.profile": "per dobrir lo perfil de l’autor",
   "keyboard_shortcuts.reply": "respondre",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "anar a la recèrca",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "mostrar/amagar lo tèxte dels avertiments",
   "keyboard_shortcuts.toot": "començar un estatut tot novèl",
   "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "Pas trobat",
   "missing_indicator.sublabel": "Aquesta ressorsa es pas estada trobada",
   "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Aplicacions mobil",
   "navigation_bar.blocks": "Personas blocadas",
   "navigation_bar.community_timeline": "Flux public local",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Messatges dirèctes",
   "navigation_bar.discover": "Trobar",
   "navigation_bar.domain_blocks": "Domenis resconduts",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Quitar de partejar",
   "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat",
   "status.delete": "Escafar",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Messatge per @{name}",
   "status.embed": "Embarcar",
   "status.favourite": "Apondre als favorits",
@@ -274,6 +297,7 @@
   "status.reblog": "Partejar",
   "status.reblog_private": "Partejar a l’audiéncia d’origina",
   "status.reblogged_by": "{name} a partejat",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Escafar e tornar formular",
   "status.reply": "Respondre",
   "status.replyAll": "Respondre a la conversacion",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 595e5d57b..0ad3499bc 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -10,7 +10,9 @@
   "account.endorse": "Polecaj na profilu",
   "account.follow": "Śledź",
   "account.followers": "Śledzący",
+  "account.followers.empty": "Nikt jeszcze nie śledzi tego użytkownika.",
   "account.follows": "Śledzeni",
+  "account.follows.empty": "Ten użytkownik nie śledzi jeszcze nikogo.",
   "account.follows_you": "Śledzi Cię",
   "account.hide_reblogs": "Ukryj podbicia od @{name}",
   "account.media": "Zawartość multimedialna",
@@ -91,7 +93,7 @@
   "confirmations.mute.confirm": "Wycisz",
   "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
   "confirmations.redraft.confirm": "Usuń i przeredaguj",
-  "confirmations.redraft.message": "Czy na pewno chcesz usunąć i przeredagować ten wpis? Utracisz wszystkie odpowiedzi, podbicia i polubienia dotyczące go.",
+  "confirmations.redraft.message": "Czy na pewno chcesz usunąć i przeredagować ten wpis? Polubienia i podbicia zostaną utracone, a odpowiedzi do oryginalnego wpisu zostaną osierocone.",
   "confirmations.unfollow.confirm": "Przestań śledzić",
   "confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?",
   "embed.instructions": "Osadź ten wpis na swojej stronie wklejając poniższy kod.",
@@ -110,12 +112,19 @@
   "emoji_button.search_results": "Wyniki wyszukiwania",
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Podróże i miejsca",
+  "empty_column.blocks": "Nie zablokowałeś(-aś) jeszcze żadnego użytkownika.",
   "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
   "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.",
+  "empty_column.domain_blocks": "Brak ukrytych domen.",
+  "empty_column.favourited_statuses": "Nie dodałeś(-aś) żadnego wpisu do ulubionych. Kiedy to zrobisz, pojawi się on tutaj.",
+  "empty_column.favourites": "Nikt nie dodał tego wpisu do ulubionych. Gdy ktoś to zrobi, pojawi się tutaj.",
+  "empty_column.follow_requests": "Nie masz żadnych próśb o możliwość śledzenia. Kiedy ktoś utworzy ją, pojawi się tutaj.",
   "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy(-a)!",
   "empty_column.home": "Nie śledzisz nikogo. Odwiedź globalną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.",
   "empty_column.home.public_timeline": "globalna 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.lists": "Nie masz żadnych list. Kiedy utworzysz jedną, pojawi się tutaj.",
+  "empty_column.mutes": "Nie wyciszyłeś(-aś) jeszcze żadnego użytkownika.",
   "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
   "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić",
   "follow_request.authorize": "Autoryzuj",
@@ -132,20 +141,32 @@
   "home.column_settings.show_reblogs": "Pokazuj podbicia",
   "home.column_settings.show_replies": "Pokazuj odpowiedzi",
   "keyboard_shortcuts.back": "aby cofnąć się",
+  "keyboard_shortcuts.blocked": "aby przejść do listy zablokowanych użytkowników",
   "keyboard_shortcuts.boost": "aby podbić wpis",
   "keyboard_shortcuts.column": "aby przejść do wpisu z jednej z kolumn",
   "keyboard_shortcuts.compose": "aby przejść do pola tworzenia wpisu",
   "keyboard_shortcuts.description": "Opis",
+  "keyboard_shortcuts.direct": "aby otworzyć kolumnę wiadomości bezpośrednich",
   "keyboard_shortcuts.down": "aby przejść na dół listy",
   "keyboard_shortcuts.enter": "aby otworzyć wpis",
   "keyboard_shortcuts.favourite": "aby dodać do ulubionych",
+  "keyboard_shortcuts.favourites": "aby przejść do listy ulubionych wpisów",
+  "keyboard_shortcuts.federated": "aby otworzyć oś czasu federacji",
   "keyboard_shortcuts.heading": "Skróty klawiszowe",
+  "keyboard_shortcuts.home": "aby otworzyć stronę główną",
   "keyboard_shortcuts.hotkey": "Klawisz",
   "keyboard_shortcuts.legend": "aby wyświetlić tę legendę",
+  "keyboard_shortcuts.local": "aby otworzyć lokalną oś czasu",
   "keyboard_shortcuts.mention": "aby wspomnieć o autorze",
+  "keyboard_shortcuts.muted": "aby przejść do listy wyciszonych użytkowników",
+  "keyboard_shortcuts.my_profile": "aby otworzyć własny profil",
+  "keyboard_shortcuts.notifications": "aby otworzyć kolumnę powiadomień",
+  "keyboard_shortcuts.pinned": "aby przejść do listy przypiętych wpisów",
   "keyboard_shortcuts.profile": "aby przejść do profilu autora wpisu",
   "keyboard_shortcuts.reply": "aby odpowiedzieć",
+  "keyboard_shortcuts.requests": "aby przejść do listy próśb o możliwość śledzenia",
   "keyboard_shortcuts.search": "aby przejść do pola wyszukiwania",
+  "keyboard_shortcuts.start": "aby otworzyć kolumnę „Rozpocznij”",
   "keyboard_shortcuts.toggle_hidden": "aby wyświetlić lub ukryć wpis spod CW",
   "keyboard_shortcuts.toot": "aby utworzyć nowy wpis",
   "keyboard_shortcuts.unfocus": "aby opuścić pole wyszukiwania/pisania",
@@ -166,9 +187,10 @@
   "missing_indicator.label": "Nie znaleziono",
   "missing_indicator.sublabel": "Nie można odnaleźć tego zasobu",
   "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Aplikacje mobilne",
   "navigation_bar.blocks": "Zablokowani użytkownicy",
   "navigation_bar.community_timeline": "Lokalna oś czasu",
+  "navigation_bar.compose": "Utwórz nowy wpis",
   "navigation_bar.direct": "Wiadomości bezpośrednie",
   "navigation_bar.discover": "Odkrywaj",
   "navigation_bar.domain_blocks": "Ukryte domeny",
@@ -263,6 +285,7 @@
   "status.cancel_reblog_private": "Cofnij podbicie",
   "status.cannot_reblog": "Ten wpis nie może zostać podbity",
   "status.delete": "Usuń",
+  "status.detailed_status": "Szczegółowy widok konwersacji",
   "status.direct": "Wyślij wiadomość bezpośrednią do @{name}",
   "status.embed": "Osadź",
   "status.favourite": "Dodaj do ulubionych",
@@ -279,6 +302,7 @@
   "status.reblog": "Podbij",
   "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu",
   "status.reblogged_by": "{name} podbił(a)",
+  "status.reblogs.empty": "Nikt nie podbił jeszcze tego wpisu. Gdy ktoś to zrobi, pojawi się tutaj.",
   "status.redraft": "Usuń i przeredaguj",
   "status.reply": "Odpowiedz",
   "status.replyAll": "Odpowiedz na wątek",
@@ -300,11 +324,11 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {osoba rozmawia} few {osoby rozmawiają} other {osób rozmawia}} o tym",
   "ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.",
   "upload_area.title": "Przeciągnij i upuść aby wysłać",
-  "upload_button.label": "Dodaj zawartość multimedialną",
+  "upload_button.label": "Dodaj zawartość multimedialną (JPEG, PNG, GIF, WebM, MP4)",
   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
   "upload_form.focus": "Przytnij",
   "upload_form.undo": "Usuń",
-  "upload_progress.label": "Wysyłanie",
+  "upload_progress.label": "Wysyłanie...",
   "video.close": "Zamknij film",
   "video.exit_fullscreen": "Opuść tryb pełnoekranowy",
   "video.expand": "Rozszerz film",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index bdfdd46e5..ed6ea3674 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -7,10 +7,12 @@
   "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.",
   "account.domain_blocked": "Domínio escondido",
   "account.edit_profile": "Editar perfil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Destacar no perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Segue",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Segue você",
   "account.hide_reblogs": "Esconder compartilhamentos de @{name}",
   "account.media": "Mídia",
@@ -27,7 +29,7 @@
   "account.show_reblogs": "Mostra compartilhamentos de @{name}",
   "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Desbloquear {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Não destacar no perfil",
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Não silenciar @{name}",
   "account.unmute_notifications": "Retirar silêncio das notificações vindas de @{name}",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Resultados da busca",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
   "empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.",
   "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
   "empty_column.home.public_timeline": "global",
   "empty_column.list": "Ainda não há nada nesta lista. Quando membros dessa lista fizerem novas postagens, elas aparecerão aqui.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.",
   "empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias",
   "follow_request.authorize": "Autorizar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostrar compartilhamentos",
   "home.column_settings.show_replies": "Mostrar as respostas",
   "keyboard_shortcuts.back": "para navegar de volta",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "para compartilhar",
   "keyboard_shortcuts.column": "Focar um status em uma das colunas",
   "keyboard_shortcuts.compose": "para focar a área de redação",
   "keyboard_shortcuts.description": "Descrição",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "para mover para baixo na lista",
   "keyboard_shortcuts.enter": "para expandir um status",
   "keyboard_shortcuts.favourite": "para adicionar aos favoritos",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Atalhos de teclado",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Atalho",
   "keyboard_shortcuts.legend": "para mostrar essa legenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "para mencionar o autor",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "para abrir o perfil do autor",
   "keyboard_shortcuts.reply": "para responder",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "para focar a pesquisa",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "mostrar/esconder o texto com aviso de conteúdo",
   "keyboard_shortcuts.toot": "para compor um novo toot",
   "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa",
@@ -162,9 +183,10 @@
   "missing_indicator.label": "Não encontrado",
   "missing_indicator.sublabel": "Esse recurso não pôde ser encontrado",
   "mute_modal.hide_notifications": "Esconder notificações deste usuário?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Apps",
   "navigation_bar.blocks": "Usuários bloqueados",
   "navigation_bar.community_timeline": "Local",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Mensagens diretas",
   "navigation_bar.discover": "Descobrir",
   "navigation_bar.domain_blocks": "Domínios escondidos",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Desfazer compartilhamento",
   "status.cannot_reblog": "Esta postagem não pode ser compartilhada",
   "status.delete": "Excluir",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Enviar mensagem direta a @{name}",
   "status.embed": "Incorporar",
   "status.favourite": "Adicionar aos favoritos",
@@ -274,6 +297,7 @@
   "status.reblog": "Compartilhar",
   "status.reblog_private": "Compartilhar com a audiência original",
   "status.reblogged_by": "{name} compartilhou",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Apagar & usar como rascunho",
   "status.reply": "Responder",
   "status.replyAll": "Responder à sequência",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index b3c4c3ad9..2f601cb32 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Segue",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "É teu seguidor",
   "account.hide_reblogs": "Esconder partilhas de @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Resultados da pesquisa",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Ainda não existe conteúdo local para mostrar!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.",
   "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
   "empty_column.home.public_timeline": "global",
   "empty_column.list": "Ainda não existem publicações nesta lista. Quando membros desta lista fizerem novas publicações, elas aparecerão aqui.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
   "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos",
   "follow_request.authorize": "Autorizar",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Mostrar as partilhas",
   "home.column_settings.show_replies": "Mostrar as respostas",
   "keyboard_shortcuts.back": "para voltar",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "para partilhar",
   "keyboard_shortcuts.column": "para focar uma publicação numa das colunas",
   "keyboard_shortcuts.compose": "para focar na área de publicação",
   "keyboard_shortcuts.description": "Descrição",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "para mover para baixo na lista",
   "keyboard_shortcuts.enter": "para expandir uma publicação",
   "keyboard_shortcuts.favourite": "para adicionar aos favoritos",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Atalhos do teclado",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Atalho",
   "keyboard_shortcuts.legend": "para mostrar esta legenda",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "para mencionar o autor",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "para responder",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "para focar na pesquisa",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "para compor um novo post",
   "keyboard_shortcuts.unfocus": "para remover o foco da área de publicação/pesquisa",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Utilizadores bloqueados",
   "navigation_bar.community_timeline": "Local",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Este post não pode ser partilhado",
   "status.delete": "Eliminar",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Incorporar",
   "status.favourite": "Adicionar aos favoritos",
@@ -274,6 +297,7 @@
   "status.reblog": "Partilhar",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} partilhou",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Responder",
   "status.replyAll": "Responder à conversa",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 8ba26a3c8..8add56641 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -10,7 +10,9 @@
   "account.endorse": "Рекомендовать в профиле",
   "account.follow": "Подписаться",
   "account.followers": "Подписаны",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Подписки",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Подписан(а) на Вас",
   "account.hide_reblogs": "Скрыть продвижения от @{name}",
   "account.media": "Медиа",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Результаты поиска",
   "emoji_button.symbols": "Символы",
   "emoji_button.travel": "Путешествия",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
   "empty_column.direct": "У Вас пока нет личных сообщений. Когда Вы начнёте их отправлять или получать, они появятся здесь.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
   "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
   "empty_column.home.public_timeline": "публичные ленты",
   "empty_column.list": "В этом списке пока ничего нет.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
   "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
   "follow_request.authorize": "Авторизовать",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Показывать продвижения",
   "home.column_settings.show_replies": "Показывать ответы",
   "keyboard_shortcuts.back": "перейти назад",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "продвинуть пост",
   "keyboard_shortcuts.column": "фокус на одном из столбцов",
   "keyboard_shortcuts.compose": "фокус на поле ввода",
   "keyboard_shortcuts.description": "Описание",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "вниз по списку",
   "keyboard_shortcuts.enter": "развернуть пост",
   "keyboard_shortcuts.favourite": "в избранное",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Сочетания клавиш",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Гор. клавиша",
   "keyboard_shortcuts.legend": "показать это окно",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "упомянуть автора поста",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "перейти к профилю автора",
   "keyboard_shortcuts.reply": "ответить",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "перейти к поиску",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "показать/скрыть текст за предупреждением",
   "keyboard_shortcuts.toot": "начать писать новый пост",
   "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Список блокировки",
   "navigation_bar.community_timeline": "Локальная лента",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Личные сообщения",
   "navigation_bar.discover": "Изучайте",
   "navigation_bar.domain_blocks": "Скрытые домены",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Не продвигать",
   "status.cannot_reblog": "Этот статус не может быть продвинут",
   "status.delete": "Удалить",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Написать @{name}",
   "status.embed": "Встроить",
   "status.favourite": "Нравится",
@@ -274,6 +297,7 @@
   "status.reblog": "Продвинуть",
   "status.reblog_private": "Продвинуть для своей аудитории",
   "status.reblogged_by": "{name} продвинул(а)",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Удалить и исправить",
   "status.reply": "Ответить",
   "status.replyAll": "Ответить на тред",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 8c36f866d..fb5b1eb14 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Následuj",
   "account.followers": "Sledujúci",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Následuje",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Následuje ťa",
   "account.hide_reblogs": "Skryť povýšenia od @{name}",
   "account.media": "Médiá",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Nájdené",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestovanie a miesta",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Lokálna časová os je prázdna. Napíšte niečo, aby sa to tu začalo hýbať!",
   "empty_column.direct": "Ešte nemáš žiadne súkromné správy. Keď nejakú pošleš, alebo dostaneš, ukáže sa tu.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.",
   "empty_column.home": "Tvoja lokálna osa je zatiaľ prázdna! Pre začiatok navštív {public}, alebo použi vyhľadávanie a nájdi tak aj iných užívateľov.",
   "empty_column.home.public_timeline": "verejná časová os",
   "empty_column.list": "Tento zoznam je ešte prázdny. Keď ale členovia tohoto zoznamu napíšu nové správy, tak tie sa objavia priamo tu.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Nemáš ešte žiadne oznámenia. Zapoj sa s niekym do debaty a komunikuj s ostatnými aby diskusia mohla začať.",
   "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne alebo začnite sledovať užívateľov z iných Mastodon serverov, aby tu tak niečo pribudlo",
   "follow_request.authorize": "Povoľ prístup",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Zobraziť povýšené",
   "home.column_settings.show_replies": "Ukázať odpovede",
   "keyboard_shortcuts.back": "dostať sa naspäť",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "vyzdvihnúť",
   "keyboard_shortcuts.column": "zamerať sa na status v jednom zo stĺpcov",
   "keyboard_shortcuts.compose": "zamerať sa na písaciu plochu",
   "keyboard_shortcuts.description": "Popis",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "posunúť sa dole v zozname",
   "keyboard_shortcuts.enter": "otvoriť správu",
   "keyboard_shortcuts.favourite": "pridať do obľúbených",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Klávesové skratky",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Klávesa",
   "keyboard_shortcuts.legend": "zobraziť túto legendu",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "spomenúť autora",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "odpovedať",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "zamerať sa na vyhľadávanie",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "ukáž/skry text za CW",
   "keyboard_shortcuts.toot": "začať úplne novú hlášku",
   "keyboard_shortcuts.unfocus": "nesústrediť sa na písaciu plochu, alebo hľadanie",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokovaní užívatelia",
   "navigation_bar.community_timeline": "Lokálna časová os",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Súkromné správy",
   "navigation_bar.discover": "Objavuj",
   "navigation_bar.domain_blocks": "Skryté domény",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Nezdieľaj",
   "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
   "status.delete": "Zmazať",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Súkromná správa @{name}",
   "status.embed": "Vložiť",
   "status.favourite": "Páči sa mi",
@@ -274,6 +297,7 @@
   "status.reblog": "Povýšiť",
   "status.reblog_private": "Povýš k pôvodnému publiku",
   "status.reblogged_by": "{name} povýšil/a",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Vymaž a prepíš",
   "status.reply": "Odpovedať",
   "status.replyAll": "Odpovedať na diskusiu",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index 01617f790..8f6c2e3d5 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Sledi",
   "account.followers": "Sledilci",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Sledi",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Ti sledi",
   "account.hide_reblogs": "Skrij napuhke od @{name}",
   "account.media": "Mediji",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Rezultati iskanja",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Potovanja in Kraji",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Lokalna časovnica je prazna. Napišite nekaj javnega, da se bo žoga zakotalila!",
   "empty_column.direct": "Nimate še nobenih neposrednih sporočil. Ko ga pošljete ali prejmete, se prikaže tukaj.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "V tem hashtagu še ni nič.",
   "empty_column.home": "Vaša domača časovnica je prazna! Obiščite {public} ali uporabite iskanje, da se boste srečali druge uporabnike.",
   "empty_column.home.public_timeline": "javna časovnica",
   "empty_column.list": "Na tem seznamu ni ničesar. Ko bodo člani tega seznama objavili nove statuse, se bodo pojavili tukaj.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Nimate še nobenih obvestil. Poveži se z drugimi, da začnete pogovor.",
   "empty_column.public": "Tukaj ni ničesar! Da ga napolnite, napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih vozlišč",
   "follow_request.authorize": "Odobri",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Pokaži sunke",
   "home.column_settings.show_replies": "Pokaži odgovore",
   "keyboard_shortcuts.back": "za krmarjenje nazaj",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "suniti",
   "keyboard_shortcuts.column": "osredotočiti status v enega od stolpcev",
   "keyboard_shortcuts.compose": "osredotočiti na sestavljanje besedila",
   "keyboard_shortcuts.description": "Opis",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "premakniti navzdol po seznamu",
   "keyboard_shortcuts.enter": "odpreti status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Tipkovne bližnjice",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hitra tipka",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "da začnete povsem nov tut",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favourite",
@@ -274,6 +297,7 @@
   "status.reblog": "Suni",
   "status.reblog_private": "Suni v prvotno občinstvo",
   "status.reblogged_by": "{name} sunjen",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Odgovori",
   "status.replyAll": "Odgovori na objavo",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 96b46077a..0f56f642a 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Zaprati",
   "account.followers": "Pratioca",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Prati",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Prati Vas",
   "account.hide_reblogs": "Sakrij podrške koje daje korisnika @{name}",
   "account.media": "Mediji",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Rezultati pretrage",
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Putovanja & mesta",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Lokalna lajna je prazna. Napišite nešto javno da lajna produva!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Trenutno nema ništa na ovom heštegu.",
   "empty_column.home": "Vaša lajna je prazna! Posetite {public} ili koristite pretragu da počnete i upoznajete nove ljude.",
   "empty_column.home.public_timeline": "javna lajna",
   "empty_column.list": "U ovoj listi još nema ničega. Kada članovi liste objave nove statuse, oni će se pojavljivati ovde.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Trenutno nemate obaveštenja. Družite se malo da započnete razgovore.",
   "empty_column.public": "Ovde nema ničega! Napišite nešto javno, ili nađite korisnike sa drugih instanci koje ćete zapratiti da popunite ovu prazninu",
   "follow_request.authorize": "Odobri",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Prikaži i podržavanja",
   "home.column_settings.show_replies": "Prikaži odgovore",
   "keyboard_shortcuts.back": "da odete nazad",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "da podržite",
   "keyboard_shortcuts.column": "da se prebacite na status u jednoj od kolona",
   "keyboard_shortcuts.compose": "da se prebacite na pisanje novog tuta",
   "keyboard_shortcuts.description": "Opis",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "da se pomerite na dole u listi",
   "keyboard_shortcuts.enter": "da otvorite status",
   "keyboard_shortcuts.favourite": "da označite kao omiljeno",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Prečice na tastaturi",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Prečica",
   "keyboard_shortcuts.legend": "da prikažete ovaj podsetnik",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "da pomenete autora",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "da odgovorite",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "da se prebacite na pretragu",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "da započnete skroz novi tut",
   "keyboard_shortcuts.unfocus": "da ne budete više na pretrazi/pravljenju novog tuta",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalna lajna",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ovaj status ne može da se podrži",
   "status.delete": "Obriši",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Ugradi na sajt",
   "status.favourite": "Omiljeno",
@@ -274,6 +297,7 @@
   "status.reblog": "Podrži",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} podržao(la)",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Odgovori",
   "status.replyAll": "Odgovori na diskusiju",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 681337947..e727a06a7 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Запрати",
   "account.followers": "Пратиоца",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Прати",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Прати Вас",
   "account.hide_reblogs": "Сакриј подршке које даје корисника @{name}",
   "account.media": "Медији",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Резултати претраге",
   "emoji_button.symbols": "Симболи",
   "emoji_button.travel": "Путовања & места",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Локална лајна је празна. Напишите нешто јавно да лајна продува!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Тренутно нема ништа на овом хештегу.",
   "empty_column.home": "Ваша лајна је празна! Посетите {public} или користите претрагу да почнете и упознајете нове људе.",
   "empty_column.home.public_timeline": "јавна лајна",
   "empty_column.list": "У овој листи још нема ничега. Када чланови листе објаве нове статусе, они ће се појављивати овде.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Тренутно немате обавештења. Дружите се мало да започнете разговоре.",
   "empty_column.public": "Овде нема ничега! Напишите нешто јавно, или нађите кориснике са других инстанци које ћете запратити да попуните ову празнину",
   "follow_request.authorize": "Одобри",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Прикажи и подржавања",
   "home.column_settings.show_replies": "Прикажи одговоре",
   "keyboard_shortcuts.back": "да одете назад",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "да подржите",
   "keyboard_shortcuts.column": "да се пребаците на статус у једној од колона",
   "keyboard_shortcuts.compose": "да се пребаците на писање новог тута",
   "keyboard_shortcuts.description": "Опис",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "да се померите на доле у листи",
   "keyboard_shortcuts.enter": "да отворите статус",
   "keyboard_shortcuts.favourite": "да означите као омиљено",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Пречице на тастатури",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Пречица",
   "keyboard_shortcuts.legend": "да прикажете овај подсетник",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "да поменете аутора",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "да одговорите",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "да се пребаците на претрагу",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "да започнете скроз нови тут",
   "keyboard_shortcuts.unfocus": "да не будете више на претрази/прављењу новог тута",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Блокирани корисници",
   "navigation_bar.community_timeline": "Локална лајна",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Овај статус не може да се подржи",
   "status.delete": "Обриши",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Угради на сајт",
   "status.favourite": "Омиљено",
@@ -274,6 +297,7 @@
   "status.reblog": "Подржи",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} подржао(ла)",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Одговори",
   "status.replyAll": "Одговори на дискусију",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 5c11e72d9..498f6b411 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Följ",
   "account.followers": "Följare",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Följer",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Följer dig",
   "account.hide_reblogs": "Dölj knuffar från @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Sökresultat",
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Resor & Platser",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!",
   "empty_column.direct": "Du har inga direktmeddelanden än. När du skickar eller tar emot kommer den att dyka upp här.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
   "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.",
   "empty_column.home.public_timeline": "den publika tidslinjen",
   "empty_column.list": "Det finns inget i denna lista än. När medlemmar i denna lista lägger till nya statusar kommer de att visas här.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.",
   "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det",
   "follow_request.authorize": "Godkänn",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Visa knuffar",
   "home.column_settings.show_replies": "Visa svar",
   "keyboard_shortcuts.back": "att navigera tillbaka",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "att knuffa",
   "keyboard_shortcuts.column": "att fokusera en status i en av kolumnerna",
   "keyboard_shortcuts.compose": "att fokusera komponera text fältet",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "att flytta ner i listan",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "att favorisera",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Snabbvalstangent",
   "keyboard_shortcuts.legend": "att visa denna översikt",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "att nämna författaren",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "att svara",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "att fokusera sökfältet",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "att visa/gömma text bakom CW",
   "keyboard_shortcuts.toot": "att börja en helt ny toot",
   "keyboard_shortcuts.unfocus": "att avfokusera komponera text fält / sökfält",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blockerade användare",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direktmeddelanden",
   "navigation_bar.discover": "Upptäck",
   "navigation_bar.domain_blocks": "Dolda domäner",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Ta bort knuff",
   "status.cannot_reblog": "Detta inlägg kan inte knuffas",
   "status.delete": "Ta bort",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direktmeddela @{name}",
   "status.embed": "Bädda in",
   "status.favourite": "Favorit",
@@ -274,6 +297,7 @@
   "status.reblog": "Knuff",
   "status.reblog_private": "Knuffa till de ursprungliga åhörarna",
   "status.reblogged_by": "{name} knuffade",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Radera & gör om",
   "status.reply": "Svara",
   "status.replyAll": "Svara på tråden",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index 8aeebd048..bbe575fde 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "అనుసరించు",
   "account.followers": "అనుచరులు",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "అనుసరిస్తున్నవి",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "మిమ్మల్ని అనుసరిస్తున్నారు",
   "account.hide_reblogs": "@{name} నుంచి బూస్ట్ లను దాచిపెట్టు",
   "account.media": "మీడియా",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "శోధన ఫలితాలు",
   "emoji_button.symbols": "చిహ్నాలు",
   "emoji_button.travel": "ప్రయాణం & ప్రదేశాలు",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "స్థానిక కాలక్రమం ఖాళీగా ఉంది. మొదలుపెట్టడానికి బహిరంగంగా ఏదో ఒకటి వ్రాయండి!",
   "empty_column.direct": "మీకు ఇంకా ఏ ప్రత్యక్ష సందేశాలు లేవు. మీరు ఒకదాన్ని పంపినప్పుడు లేదా స్వీకరించినప్పుడు, అది ఇక్కడ చూపబడుతుంది.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "ఇంకా హాష్ ట్యాగ్లో ఏమీ లేదు.",
   "empty_column.home": "మీ హోమ్ కాలక్రమం ఖాళీగా ఉంది! {Public} ను సందర్శించండి లేదా ఇతర వినియోగదారులను కలుసుకోవడానికి మరియు అన్వేషణ కోసం శోధనను ఉపయోగించండి.",
   "empty_column.home.public_timeline": "ప్రజా కాలక్రమం",
   "empty_column.list": "ఇంకా ఈ జాబితాలో ఏదీ లేదు. ఈ జాబితాలోని సభ్యులు కొత్త స్టేటస్ లను పోస్ట్ చేసినప్పుడు, అవి ఇక్కడ కనిపిస్తాయి.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "మీకు ఇంకా ఏ నోటిఫికేషన్లు లేవు. సంభాషణను ప్రారంభించడానికి ఇతరులతో ప్రతిస్పందించండి.",
   "empty_column.public": "ఇక్కడ ఏమీ లేదు! దీన్ని నింపడానికి బహిరంగంగా ఏదైనా వ్రాయండి, లేదా ఇతర దృష్టాంతాల్లోని వినియోగదారులను అనుసరించండి",
   "follow_request.authorize": "అనుమతించు",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "బూస్ట్ లను చూపించు",
   "home.column_settings.show_replies": "ప్రత్యుత్తరాలను చూపించు",
   "keyboard_shortcuts.back": "వెనక్కి తిరిగి వెళ్ళడానికి",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "బూస్ట్ చేయడానికి",
   "keyboard_shortcuts.column": "నిలువు వరుసలలో ఒకదానిపై దృష్టి పెట్టడానికి",
   "keyboard_shortcuts.compose": "కంపోజ్ టెక్స్ట్ఏరియా పై దృష్టి పెట్టడానికి",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "జాబితాలో క్రిందికి వెళ్ళడానికి",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "ఇష్టపడడానికి",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "కీబోర్డ్ సత్వరమార్గాలు",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "హాట్ కీ",
   "keyboard_shortcuts.legend": "ఈ లెజెండ్ ప్రదర్శించడానికి",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "రచయితను ప్రస్తావించడానికి",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "రచయిత ప్రొఫైల్ ను తెరవాలంటే",
   "keyboard_shortcuts.reply": "ప్రత్యుత్తరం ఇవ్వడానికి",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "శోధనపై దృష్టి పెట్టండి",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "CW వెనుక ఉన్న పాఠ్యాన్ని చూపడానికి / దాచడానికి",
   "keyboard_shortcuts.toot": "ఒక సరికొత్త టూట్ను ప్రారంభించడానికి",
   "keyboard_shortcuts.unfocus": "పాఠ్యం వ్రాసే ఏరియా/శోధన పట్టిక నుండి బయటకు రావడానికి",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "బ్లాక్ చేయబడిన వినియోగదారులు",
   "navigation_bar.community_timeline": "స్థానిక కాలక్రమం",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "ప్రత్యక్ష సందేశాలు",
   "navigation_bar.discover": "కనుగొను",
   "navigation_bar.domain_blocks": "దాచిన డొమైన్లు",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "బూస్ట్ను తొలగించు",
   "status.cannot_reblog": "ఈ పోస్ట్ను బూస్ట్ చేయడం సాధ్యం కాదు",
   "status.delete": "తొలగించు",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "@{name}కు నేరుగా సందేశం పంపు",
   "status.embed": "ఎంబెడ్",
   "status.favourite": "ఇష్టపడు",
@@ -274,6 +297,7 @@
   "status.reblog": "బూస్ట్",
   "status.reblog_private": "అసలు ప్రేక్షకులకు బూస్ట్ చేయి",
   "status.reblogged_by": "{name} బూస్ట్ చేసారు",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "తొలగించు & తిరగరాయు",
   "status.reply": "ప్రత్యుత్తరం",
   "status.replyAll": "సంభాషణకు ప్రత్యుత్తరం ఇవ్వండి",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 5e4995b71..5340fda92 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Follow",
   "account.followers": "Followers",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Follows",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.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.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.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",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favourite",
@@ -274,6 +297,7 @@
   "status.reblog": "Boost",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Reply",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index be7ad47e4..b388b0b6f 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Takip et",
   "account.followers": "Takipçiler",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Takip ettikleri",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Seni takip ediyor",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.media": "Media",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Search results",
   "emoji_button.symbols": "Semboller",
   "emoji_button.travel": "Seyahat ve Yerler",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Yerel zaman tüneliniz boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın.",
   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Henüz bu hashtag’e sahip hiçbir gönderi yok.",
   "empty_column.home": "Henüz kimseyi takip etmiyorsunuz. {public} ziyaret edebilir veya arama kısmını kullanarak diğer kullanıcılarla iletişime geçebilirsiniz.",
   "empty_column.home.public_timeline": "herkese açık zaman tüneli",
   "empty_column.list": "There is nothing in this list yet.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.",
   "empty_column.public": "Burada hiçbir gönderi yok! Herkese açık bir şeyler yazın, veya diğer sunucudaki insanları takip ederek bu alanın dolmasını sağlayın",
   "follow_request.authorize": "Yetkilendir",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Boost edilenleri göster",
   "home.column_settings.show_replies": "Cevapları göster",
   "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "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",
   "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "to move down in the list",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Hotkey",
   "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Engellenen kullanıcılar",
   "navigation_bar.community_timeline": "Yerel zaman tüneli",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Direct messages",
   "navigation_bar.discover": "Discover",
   "navigation_bar.domain_blocks": "Hidden domains",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Bu gönderi boost edilemez",
   "status.delete": "Sil",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Favorilere ekle",
@@ -274,6 +297,7 @@
   "status.reblog": "Boost'la",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boost etti",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Cevapla",
   "status.replyAll": "Konuşmayı cevapla",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 5f7e5c266..9a2c18e36 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "Підписатися",
   "account.followers": "Підписники",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Підписки",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Підписаний(-а) на Вас",
   "account.hide_reblogs": "Сховати передмухи від @{name}",
   "account.media": "Медіа",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "Результати пошуку",
   "emoji_button.symbols": "Символи",
   "emoji_button.travel": "Подорожі",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "Локальна стрічка пуста. Напишіть щось, щоб розігріти народ!",
   "empty_column.direct": "У вас ще немає прямих повідомлень. Коли ви відправите чи отримаєте якесь, воно з'явиться тут.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "Дописів з цим хештегом поки не існує.",
   "empty_column.home": "Ви поки ні на кого не підписані. Погортайте {public}, або скористуйтесь пошуком, щоб освоїтися та познайомитися з іншими користувачами.",
   "empty_column.home.public_timeline": "публічні стрічки",
   "empty_column.list": "Немає нічого в цьому списку. Коли його учасники дмухнуть нові статуси, вони з'являться тут.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "У вас ще немає сповіщень. Переписуйтесь з іншими користувачами, щоб почати розмову.",
   "empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших інстанцій, щоб заповнити стрічку",
   "follow_request.authorize": "Авторизувати",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "Показувати передмухи",
   "home.column_settings.show_replies": "Показувати відповіді",
   "keyboard_shortcuts.back": "переходити назад",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "передмухувати",
   "keyboard_shortcuts.column": "фокусуватися на одній з колонок",
   "keyboard_shortcuts.compose": "фокусуватися на полі введення",
   "keyboard_shortcuts.description": "Опис",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "рухатися вниз стрічкою",
   "keyboard_shortcuts.enter": "відкрити статус",
   "keyboard_shortcuts.favourite": "вподобати",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "Гарячі клавіші",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "Гаряча клавіша",
   "keyboard_shortcuts.legend": "показати підказку",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "згадати автора",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "відкрити профіль автора",
   "keyboard_shortcuts.reply": "відповісти",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "сфокусуватися на пошуку",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "показати/приховати прихований текст",
   "keyboard_shortcuts.toot": "почати писати новий дмух",
   "keyboard_shortcuts.unfocus": "розфокусуватися з нового допису чи пошуку",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "Заблоковані користувачі",
   "navigation_bar.community_timeline": "Локальна стрічка",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "Прямі повідомлення",
   "navigation_bar.discover": "Знайти",
   "navigation_bar.domain_blocks": "Приховані домени",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Цей допис не може бути передмухнутий",
   "status.delete": "Видалити",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
   "status.favourite": "Подобається",
@@ -274,6 +297,7 @@
   "status.reblog": "Передмухнути",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} передмухнув(-ла)",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.reply": "Відповісти",
   "status.replyAll": "Відповісти на тред",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 378a1a45b..cf2e1a640 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "关注",
   "account.followers": "关注者",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "正在关注",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "关注了你",
   "account.hide_reblogs": "隐藏来自 @{name} 的转嘟",
   "account.media": "媒体",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "搜索结果",
   "emoji_button.symbols": "符号",
   "emoji_button.travel": "旅行和地点",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!",
   "empty_column.direct": "你还没有使用过私信。当你发出或者收到私信时,它会在这里显示。",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "这个话题标签下暂时没有内容。",
   "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
   "empty_column.home.public_timeline": "公共时间轴",
   "empty_column.list": "这个列表中暂时没有内容。列表中用户所发送的的新嘟文将会在这里显示。",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "你还没有收到过任何通知,快向其他用户搭讪吧。",
   "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户后,这里就会有嘟文出现了哦!",
   "follow_request.authorize": "同意",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "显示转嘟",
   "home.column_settings.show_replies": "显示回复",
   "keyboard_shortcuts.back": "返回上一页",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "转嘟",
   "keyboard_shortcuts.column": "选择第 X 栏中的嘟文",
   "keyboard_shortcuts.compose": "选择嘟文撰写框",
   "keyboard_shortcuts.description": "说明",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "在列表中让光标下移",
   "keyboard_shortcuts.enter": "展开嘟文",
   "keyboard_shortcuts.favourite": "收藏嘟文",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "快捷键列表",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "快捷键",
   "keyboard_shortcuts.legend": "显示此列表",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "提及嘟文作者",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "回复嘟文",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "选择搜索框",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "显示或隐藏被折叠的正文",
   "keyboard_shortcuts.toot": "发送新嘟文",
   "keyboard_shortcuts.unfocus": "取消输入",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "已屏蔽的用户",
   "navigation_bar.community_timeline": "本站时间轴",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "私信",
   "navigation_bar.discover": "发现",
   "navigation_bar.domain_blocks": "已屏蔽的网站",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "取消转嘟",
   "status.cannot_reblog": "无法转嘟这条嘟文",
   "status.delete": "删除",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "发送私信给 @{name}",
   "status.embed": "嵌入",
   "status.favourite": "收藏",
@@ -274,6 +297,7 @@
   "status.reblog": "转嘟",
   "status.reblog_private": "转嘟给原有关注者",
   "status.reblogged_by": "{name} 转嘟了",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "删除并重新编辑",
   "status.reply": "回复",
   "status.replyAll": "回复所有人",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index f0718468a..dbad69191 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "關注",
   "account.followers": "關注的人",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "正關注",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "關注你",
   "account.hide_reblogs": "隱藏 @{name} 的轉推",
   "account.media": "媒體",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "搜尋結果",
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅遊景物",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "本站時間軸暫時未有內容,快寫一點東西來搶頭香啊!",
   "empty_column.direct": "你沒有個人訊息。當你發出或接收個人訊息,就會在這裡出現。",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "這個標籤暫時未有內容。",
   "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
   "empty_column.home.public_timeline": "公共時間軸",
   "empty_column.list": "這個列表暫時未有內容。",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
   "empty_column.public": "跨站時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
   "follow_request.authorize": "批准",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "顯示被轉推的文章",
   "home.column_settings.show_replies": "顯示回應文章",
   "keyboard_shortcuts.back": "後退",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "轉推",
   "keyboard_shortcuts.column": "把標示移動到其中一列",
   "keyboard_shortcuts.compose": "把標示移動到文字輸入區",
   "keyboard_shortcuts.description": "描述",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "在列表往下移動",
   "keyboard_shortcuts.enter": "打開文章",
   "keyboard_shortcuts.favourite": "收藏",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "鍵盤快速鍵",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "快速鍵",
   "keyboard_shortcuts.legend": "顯示這個說明",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "提及作者",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "回覆",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "把標示移動到搜索",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "顯示或隱藏被標為敏感的文字",
   "keyboard_shortcuts.toot": "新的推文",
   "keyboard_shortcuts.unfocus": "把標示移離文字輸入和搜索",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "被你封鎖的用戶",
   "navigation_bar.community_timeline": "本站時間軸",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "個人訊息",
   "navigation_bar.discover": "探索",
   "navigation_bar.domain_blocks": "隱藏的服務站",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "取消轉推",
   "status.cannot_reblog": "這篇文章無法被轉推",
   "status.delete": "刪除",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "私訊 @{name}",
   "status.embed": "鑲嵌",
   "status.favourite": "收藏",
@@ -274,6 +297,7 @@
   "status.reblog": "轉推",
   "status.reblog_private": "轉推到原讀者",
   "status.reblogged_by": "{name} 轉推",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "刪除並編輯",
   "status.reply": "回應",
   "status.replyAll": "回應所有人",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index ca5919e30..09e9150d0 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -1,7 +1,7 @@
 {
   "account.badges.bot": "機器人",
   "account.block": "封鎖 @{name}",
-  "account.block_domain": "隱藏來自 {domain} 的一切貼文",
+  "account.block_domain": "隱藏來自 {domain} 的一切嘟文",
   "account.blocked": "已被封鎖的",
   "account.direct": "發送私訊給 @{name}",
   "account.disclaimer_full": "下列資料不一定完整。",
@@ -10,7 +10,9 @@
   "account.endorse": "Feature on profile",
   "account.follow": "關注",
   "account.followers": "關注者",
+  "account.followers.empty": "No one follows this user yet.",
   "account.follows": "正在關注",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "關注你",
   "account.hide_reblogs": "隱藏來自 @{name} 的轉推",
   "account.media": "媒體",
@@ -65,10 +67,10 @@
   "compose_form.direct_message_warning": "這條嘟文僅對有被提及的使用者才能看到。",
   "compose_form.direct_message_warning_learn_more": "了解更多",
   "compose_form.hashtag_warning": "此則推文將不會在任何主題標籤中看見,只有公開的推文可以用主題標籤來搜尋。",
-  "compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的貼文。",
+  "compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的嘟文。",
   "compose_form.lock_disclaimer.lock": "上鎖",
   "compose_form.placeholder": "在想些什麼?",
-  "compose_form.publish": "貼掉",
+  "compose_form.publish": "嘟掉",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "此媒體已被標註為敏感的",
   "compose_form.sensitive.unmarked": "此媒體未被標註為敏感的",
@@ -90,7 +92,7 @@
   "confirmations.redraft.message": "你確定要刪除這條嘟文並重新編輯它嗎? 所有相關的回覆、轉嘟與最愛都會被刪除。",
   "confirmations.unfollow.confirm": "取消關注",
   "confirmations.unfollow.message": "真的不要繼續關注 {name} 了嗎?",
-  "embed.instructions": "要內嵌此貼文,請將以下代碼貼進你的網站。",
+  "embed.instructions": "要內嵌此嘟文,請將以下代碼貼進你的網站。",
   "embed.preview": "看上去會變成這樣:",
   "emoji_button.activity": "活動",
   "emoji_button.custom": "自訂",
@@ -106,12 +108,19 @@
   "emoji_button.search_results": "搜尋結果",
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅遊與地點",
+  "empty_column.blocks": "You haven't blocked any users yet.",
   "empty_column.community": "本地時間軸是空的。公開寫點什麼吧!",
   "empty_column.direct": "你還沒有使用過私訊。當你發出或著收到私訊時,它會在這裡顯示。",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
   "empty_column.hashtag": "這個主題標籤下什麼都沒有。",
   "empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用者。",
   "empty_column.home.public_timeline": "公開時間軸",
-  "empty_column.list": "此份清單尚未有東西。當此清單的成員貼出了新的狀態時,它們就會出現在這裡。",
+  "empty_column.list": "此份清單尚未有東西。當此清單的成員嘟出了新的狀態時,它們就會出現在這裡。",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "還沒有任何通知。和別的使用者互動來開始對話。",
   "empty_column.public": "這裡什麼都沒有! 寫一些公開的嘟文,或著關注其他站點的使用者後,這裡就會有嘟文出現了",
   "follow_request.authorize": "授權",
@@ -128,20 +137,32 @@
   "home.column_settings.show_reblogs": "顯示轉推",
   "home.column_settings.show_replies": "顯示回應",
   "keyboard_shortcuts.back": "回到上一個",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
   "keyboard_shortcuts.boost": "到轉推",
   "keyboard_shortcuts.column": "選擇第 X 欄中的嘟文",
   "keyboard_shortcuts.compose": "焦點移至撰寫文字區塊",
   "keyboard_shortcuts.description": "描述",
+  "keyboard_shortcuts.direct": "to open direct messages column",
   "keyboard_shortcuts.down": "在列表往下移動",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "收藏",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
   "keyboard_shortcuts.heading": "鍵盤快速鍵",
+  "keyboard_shortcuts.home": "to open home timeline",
   "keyboard_shortcuts.hotkey": "快速鍵",
   "keyboard_shortcuts.legend": "顯示這個說明",
+  "keyboard_shortcuts.local": "to open local timeline",
   "keyboard_shortcuts.mention": "到提到的作者",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
   "keyboard_shortcuts.profile": "to open author's profile",
   "keyboard_shortcuts.reply": "到回應",
+  "keyboard_shortcuts.requests": "to open follow requests list",
   "keyboard_shortcuts.search": "把滑鼠移動到搜尋",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
   "keyboard_shortcuts.toggle_hidden": "顯示或隱藏被標為敏感的嘟文",
   "keyboard_shortcuts.toot": "新的嘟文",
   "keyboard_shortcuts.unfocus": "取消輸入",
@@ -165,6 +186,7 @@
   "navigation_bar.apps": "Mobile apps",
   "navigation_bar.blocks": "封鎖的使用者",
   "navigation_bar.community_timeline": "本地時間軸",
+  "navigation_bar.compose": "Compose new toot",
   "navigation_bar.direct": "私訊",
   "navigation_bar.discover": "探索",
   "navigation_bar.domain_blocks": "隱藏的站點",
@@ -224,7 +246,7 @@
   "privacy.direct.short": "私訊",
   "privacy.private.long": "只有關注你的使用者能看到",
   "privacy.private.short": "僅關注者",
-  "privacy.public.long": "貼到公開時間軸",
+  "privacy.public.long": "嘟到公開時間軸",
   "privacy.public.short": "公開",
   "privacy.unlisted.long": "公開,但不會顯示在公開時間軸",
   "privacy.unlisted.short": "不公開",
@@ -258,6 +280,7 @@
   "status.cancel_reblog_private": "取消轉嘟",
   "status.cannot_reblog": "這篇嘟文無法被轉嘟",
   "status.delete": "刪除",
+  "status.detailed_status": "Detailed conversation view",
   "status.direct": "發送私訊給 @{name}",
   "status.embed": "嵌入",
   "status.favourite": "最愛",
@@ -274,6 +297,7 @@
   "status.reblog": "轉嘟",
   "status.reblog_private": "轉嘟給原有關注者",
   "status.reblogged_by": "{name} 轉嘟了",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "刪除 & 編輯",
   "status.reply": "回覆",
   "status.replyAll": "回覆所有人",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 552f659c9..67d55f66f 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -131,7 +131,7 @@ const updateSuggestionTags = (state, token) => {
 
   return state.merge({
     suggestions: state.get('tagHistory')
-      .filter(tag => tag.startsWith(prefix))
+      .filter(tag => tag.toLowerCase().startsWith(prefix.toLowerCase()))
       .slice(0, 4)
       .map(tag => '#' + tag),
     suggestion_token: token,
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index 84d4fc698..0b29f19fa 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -26,6 +26,7 @@ const notificationToMap = notification => ImmutableMap({
   id: notification.id,
   type: notification.type,
   account: notification.account.id,
+  created_at: notification.created_at,
   status: notification.status ? notification.status.id : null,
 });
 
diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js
index 3318bbadc..d61d916b1 100644
--- a/app/javascript/mastodon/service_worker/web_push_notifications.js
+++ b/app/javascript/mastodon/service_worker/web_push_notifications.js
@@ -80,15 +80,7 @@ const handlePush = (event) => {
 
   // Placeholder until more information can be loaded
   event.waitUntil(
-    notify({
-      title,
-      body,
-      icon,
-      tag: notification_id,
-      timestamp: new Date(),
-      badge: '/badge.png',
-      data: { access_token, preferred_locale, url: '/web/notifications' },
-    }).then(() => fetchFromApi(`/api/v1/notifications/${notification_id}`, 'get', access_token)).then(notification => {
+    fetchFromApi(`/api/v1/notifications/${notification_id}`, 'get', access_token).then(notification => {
       const options = {};
 
       options.title     = formatMessage(`notification.${notification.type}`, preferred_locale, { name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
@@ -112,6 +104,16 @@ const handlePush = (event) => {
       }
 
       return notify(options);
+    }).catch(() => {
+      return notify({
+        title,
+        body,
+        icon,
+        tag: notification_id,
+        timestamp: new Date(),
+        badge: '/badge.png',
+        data: { access_token, preferred_locale, url: '/web/notifications' },
+      });
     })
   );
 };
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index e5fdc7f83..1cb491e17 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -63,7 +63,10 @@ function main() {
         .catch(error => console.error(error));
     }
 
-    new Rellax('.parallax', { speed: -1 });
+    const parallaxComponents = document.querySelectorAll('.parallax');
+    if (parallaxComponents.length > 0 ) {
+      new Rellax('.parallax', { speed: -1 });
+    }
 
     const history = createHistory();
     const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 84ccd326e..ac161a004 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -169,6 +169,10 @@
   color: $white;
 }
 
+.dropdown-menu__separator {
+  border-bottom-color: lighten($ui-base-color, 12%);
+}
+
 // Change the background colors of modals
 .actions-modal,
 .boost-modal,
@@ -281,3 +285,87 @@
     }
   }
 }
+
+.flash-message {
+  box-shadow: none;
+
+  &.notice {
+    background: rgba($success-green, 0.5);
+    color: lighten($success-green, 12%);
+  }
+
+  &.alert {
+    background: rgba($error-red, 0.5);
+    color: lighten($error-red, 12%);
+  }
+}
+
+.simple_form,
+.table-form {
+  .warning {
+    box-shadow: none;
+    background: rgba($error-red, 0.5);
+    text-shadow: none;
+  }
+}
+
+.status__content,
+.reply-indicator__content {
+  a {
+    color: $highlight-text-color;
+  }
+}
+
+.button.logo-button {
+  color: $white;
+
+  svg path:first-child {
+    fill: $white;
+  }
+}
+
+.public-layout {
+  .header,
+  .public-account-header,
+  .public-account-bio {
+    box-shadow: none;
+  }
+
+  .header {
+    background: lighten($ui-base-color, 12%);
+  }
+
+  .public-account-header {
+    &__image {
+      background: lighten($ui-base-color, 12%);
+
+      &::after {
+        box-shadow: none;
+      }
+    }
+
+    &__tabs {
+      &__name {
+        h1,
+        h1 small {
+          color: $white;
+        }
+      }
+    }
+  }
+}
+
+.account__section-headline a.active::after {
+  border-color: transparent transparent $white;
+}
+
+.hero-widget,
+.box-widget,
+.contact-widget,
+.landing-page__information.contact-widget,
+.moved-account-widget,
+.memoriam-widget,
+.activity-stream,
+.nothing-here {
+  box-shadow: none;
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 6073f9c0e..8f279e138 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -5367,7 +5367,7 @@ noscript {
   dt,
   dd {
     box-sizing: border-box;
-    padding: 14px 20px;
+    padding: 14px 5px;
     text-align: center;
     max-height: 48px;
     overflow: hidden;
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 020be5ad2..144b4a519 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -621,3 +621,14 @@ code {
 .scope-danger {
   color: $warning-red;
 }
+
+.form_admin_settings_site_short_description,
+.form_admin_settings_site_description,
+.form_admin_settings_site_extended_description,
+.form_admin_settings_site_terms,
+.form_admin_settings_custom_css,
+.form_admin_settings_closed_registrations_message {
+  textarea {
+    font-family: 'mastodon-font-monospace', monospace;
+  }
+}
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 03476920b..3a39b723e 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -104,7 +104,9 @@ class ActivityPub::Activity
 
   def crawl_links(status)
     return if status.spoiler_text?
-    LinkCrawlWorker.perform_async(status.id)
+
+    # Spread out crawling randomly to avoid DDoSing the link
+    LinkCrawlWorker.perform_in(rand(1..59).seconds, status.id)
   end
 
   def distribute_to_followers(status)
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 00479fd9a..f40e1fa3e 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -48,7 +48,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       account: @account,
       text: text_from_content || '',
       language: detected_language,
-      spoiler_text: @object['summary'] || '',
+      spoiler_text: text_from_summary || '',
       created_at: @object['published'],
       override_timestamps: @options[:override_timestamps],
       reply: @object['inReplyTo'].present?,
@@ -107,7 +107,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     updated   = tag['updated']
     emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain)
 
-    return unless emoji.nil? || emoji.updated_at >= updated
+    return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && emoji.updated_at >= updated)
 
     emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri)
     emoji.image_remote_url = image_url
@@ -193,6 +193,14 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     end
   end
 
+  def text_from_summary
+    if @object['summary'].present?
+      @object['summary']
+    elsif summary_language_map?
+      @object['summaryMap'].values.first
+    end
+  end
+
   def text_from_name
     if @object['name'].present?
       @object['name']
@@ -206,6 +214,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       @object['contentMap'].keys.first
     elsif name_language_map?
       @object['nameMap'].keys.first
+    elsif summary_language_map?
+      @object['summaryMap'].keys.first
     elsif supported_object_type?
       LanguageDetector.instance.detect(text_from_content, @account)
     end
@@ -223,6 +233,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     end
   end
 
+  def summary_language_map?
+    @object['summaryMap'].is_a?(Hash) && !@object['summaryMap'].empty?
+  end
+
   def content_language_map?
     @object['contentMap'].is_a?(Hash) && !@object['contentMap'].empty?
   end
diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb
index aa5907f03..6eebc3b5c 100644
--- a/app/lib/activitypub/activity/update.rb
+++ b/app/lib/activitypub/activity/update.rb
@@ -11,6 +11,6 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
 
   def update_account
     return if @account.uri != object_uri
-    ActivityPub::ProcessAccountService.new.call(@account.username, @account.domain, @object)
+    ActivityPub::ProcessAccountService.new.call(@account.username, @account.domain, @object, signed_with_known_key: true)
   end
 end
diff --git a/app/lib/activitypub/linked_data_signature.rb b/app/lib/activitypub/linked_data_signature.rb
index 16142a6ff..f52a8f406 100644
--- a/app/lib/activitypub/linked_data_signature.rb
+++ b/app/lib/activitypub/linked_data_signature.rb
@@ -32,7 +32,7 @@ class ActivityPub::LinkedDataSignature
     end
   end
 
-  def sign!(creator)
+  def sign!(creator, sign_with: nil)
     options = {
       'type'    => 'RsaSignature2017',
       'creator' => [ActivityPub::TagManager.instance.uri_for(creator), '#main-key'].join,
@@ -42,8 +42,9 @@ class ActivityPub::LinkedDataSignature
     options_hash  = hash(options.without('type', 'id', 'signatureValue').merge('@context' => CONTEXT))
     document_hash = hash(@json.without('signature'))
     to_be_signed  = options_hash + document_hash
+    keypair       = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : creator.keypair
 
-    signature = Base64.strict_encode64(creator.keypair.sign(OpenSSL::Digest::SHA256.new, to_be_signed))
+    signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest::SHA256.new, to_be_signed))
 
     @json.merge('signature' => options.merge('signatureValue' => signature))
   end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 14cba70dc..b59a9f1cd 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -288,7 +288,7 @@ class FeedManager
       # remains in the set. We could pick a random element, but this
       # set should generally be small, and it seems ideal to show the
       # oldest potential such reblog.
-      other_reblog = redis.smembers(reblog_set_key).map(&:to_i).sort.first
+      other_reblog = redis.smembers(reblog_set_key).map(&:to_i).min
 
       redis.zadd(timeline_key, other_reblog, other_reblog) if other_reblog
 
diff --git a/app/lib/request.rb b/app/lib/request.rb
index 576ed23ca..21bdaa700 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -22,10 +22,11 @@ class Request
     set_digest! if options.key?(:body)
   end
 
-  def on_behalf_of(account, key_id_format = :acct)
+  def on_behalf_of(account, key_id_format = :acct, sign_with: nil)
     raise ArgumentError unless account.local?
 
     @account       = account
+    @keypair       = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : @account.keypair
     @key_id_format = key_id_format
 
     self
@@ -70,7 +71,7 @@ class Request
 
   def signature
     algorithm = 'rsa-sha256'
-    signature = Base64.strict_encode64(@account.keypair.sign(OpenSSL::Digest::SHA256.new, signed_string))
+    signature = Base64.strict_encode64(@keypair.sign(OpenSSL::Digest::SHA256.new, signed_string))
 
     "keyId=\"#{key_id}\",algorithm=\"#{algorithm}\",headers=\"#{signed_headers}\",signature=\"#{signature}\""
   end
diff --git a/app/lib/settings/scoped_settings.rb b/app/lib/settings/scoped_settings.rb
index de4af3009..70de7b792 100644
--- a/app/lib/settings/scoped_settings.rb
+++ b/app/lib/settings/scoped_settings.rb
@@ -2,6 +2,11 @@
 
 module Settings
   class ScopedSettings
+    DEFAULTING_TO_UNSCOPED = %w(
+      flavour
+      skin
+    ).freeze
+
     def initialize(object)
       @object = object
     end
@@ -50,15 +55,22 @@ module Settings
       Rails.cache.fetch(Setting.cache_key(key, @object)) do
         db_val = thing_scoped.find_by(var: key.to_s)
         if db_val
-          default_value = Setting.default_settings[key]
+          default_value = ScopedSettings.default_settings[key]
           return default_value.with_indifferent_access.merge!(db_val.value) if default_value.is_a?(Hash)
           db_val.value
         else
-          Setting.default_settings[key]
+          ScopedSettings.default_settings[key]
         end
       end
     end
 
+    class << self
+      def default_settings
+        defaulting = DEFAULTING_TO_UNSCOPED.map { |k| [k, Setting[k]] }.to_h
+        Setting.default_settings.merge!(defaulting)
+      end
+    end
+
     protected
 
     def thing_scoped
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 9848c34a2..aa76b4dfe 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -16,7 +16,7 @@ class UserMailer < Devise::Mailer
     return if @resource.disabled?
 
     I18n.with_locale(@resource.locale || I18n.default_locale) do
-      mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email,
+      mail to: @resource.unconfirmed_email.presence || @resource.email,
            subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance),
            template_name: @resource.pending_reconfirmation? ? 'reconfirmation_instructions' : 'confirmation_instructions'
     end
diff --git a/app/models/concerns/expireable.rb b/app/models/concerns/expireable.rb
index 444ccdfdb..2c0631476 100644
--- a/app/models/concerns/expireable.rb
+++ b/app/models/concerns/expireable.rb
@@ -9,7 +9,7 @@ module Expireable
     attr_reader :expires_in
 
     def expires_in=(interval)
-      self.expires_at = interval.to_i.seconds.from_now unless interval.blank?
+      self.expires_at = interval.to_i.seconds.from_now if interval.present?
       @expires_in     = interval
     end
 
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 010cf7fc3..9ea4ed322 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -30,6 +30,10 @@ class Form::AdminSettings
     :show_staff_badge=,
     :bootstrap_timeline_accounts,
     :bootstrap_timeline_accounts=,
+    :flavour,
+    :flavour=,
+    :skin,
+    :skin=,
     :min_invite_role,
     :min_invite_role=,
     :activity_api_enabled,
@@ -40,6 +44,8 @@ class Form::AdminSettings
     :show_known_fediverse_at_about_page=,
     :preview_sensitive_media,
     :preview_sensitive_media=,
+    :custom_css,
+    :custom_css=,
     to: Setting
   )
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8b65a900c..9e529019c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -98,7 +98,7 @@ class User < ApplicationRecord
            :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_sensitive_media, :hide_network,
            :default_language, to: :settings, prefix: :setting, allow_nil: false
 
-  attr_accessor :invite_code
+  attr_reader :invite_code
 
   def pam_conflict(_)
     # block pam login tries on traditional account
@@ -216,10 +216,6 @@ class User < ApplicationRecord
     save!
   end
 
-  def active_for_authentication?
-    super && !disabled?
-  end
-
   def setting_default_privacy
     settings.default_privacy || (account.locked? ? 'private' : 'public')
   end
@@ -262,7 +258,7 @@ class User < ApplicationRecord
   end
 
   def invite_code=(code)
-    self.invite  = Invite.find_by(code: code) unless code.blank?
+    self.invite  = Invite.find_by(code: code) if code.present?
     @invite_code = code
   end
 
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index dabdf707a..57af5c61c 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -18,11 +18,11 @@ class UserPolicy < ApplicationPolicy
   end
 
   def enable?
-    admin?
+    staff?
   end
 
   def disable?
-    admin? && !record.admin?
+    staff? && !record.admin?
   end
 
   def promote?
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index d14836b9d..0249c134f 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -14,7 +14,7 @@ class InstancePresenter
   )
 
   def contact_account
-    Account.find_local(Setting.site_contact_username)
+    Account.find_local(Setting.site_contact_username.gsub(/\A@/, ''))
   end
 
   def user_count
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 41c9aa44e..5054bd683 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -93,11 +93,11 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
   end
 
   def avatar_exists?
-    object.avatar.exists?
+    object.avatar?
   end
 
   def header_exists?
-    object.header.exists?
+    object.header?
   end
 
   def manually_approves_followers
diff --git a/app/serializers/web/notification_serializer.rb b/app/serializers/web/notification_serializer.rb
index 43ba4d92a..ee83ec8b2 100644
--- a/app/serializers/web/notification_serializer.rb
+++ b/app/serializers/web/notification_serializer.rb
@@ -33,7 +33,7 @@ class Web::NotificationSerializer < ActiveModel::Serializer
   end
 
   def body
-    str = truncate(strip_tags(object.target_status&.spoiler_text&.presence || object.target_status&.text || object.from_account.note), length: 140)
-    HTMLEntities.new.decode(str.to_str) # Do not encode entities, since this value will not be used in HTML
+    str = strip_tags(object.target_status&.spoiler_text&.presence || object.target_status&.text || object.from_account.note)
+    truncate(HTMLEntities.new.decode(str.to_str), length: 140) # Do not encode entities, since this value will not be used in HTML
   end
 end
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 7f95678b0..670a0e4d6 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -5,9 +5,10 @@ class ActivityPub::ProcessAccountService < BaseService
 
   # Should be called with confirmed valid JSON
   # and WebFinger-resolved username and domain
-  def call(username, domain, json)
+  def call(username, domain, json, options = {})
     return if json['inbox'].blank? || unsupported_uri_scheme?(json['id'])
 
+    @options     = options
     @json        = json
     @uri         = @json['id']
     @username    = username
@@ -31,7 +32,7 @@ class ActivityPub::ProcessAccountService < BaseService
     return if @account.nil?
 
     after_protocol_change! if protocol_changed?
-    after_key_change! if key_changed?
+    after_key_change! if key_changed? && !@options[:signed_with_known_key]
     check_featured_collection! if @account.featured_collection_url.present?
 
     @account
@@ -226,7 +227,7 @@ class ActivityPub::ProcessAccountService < BaseService
     updated   = tag['updated']
     emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain)
 
-    return unless emoji.nil? || emoji.updated_at >= updated
+    return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && emoji.updated_at >= updated)
 
     emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri)
     emoji.image_remote_url = image_url
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 60a389afd..f6888a68d 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -27,6 +27,8 @@ class FollowService < BaseService
       return
     end
 
+    ActivityTracker.increment('activity:interactions')
+
     if target_account.locked? || target_account.activitypub?
       request_follow(source_account, target_account, reblogs: reblogs)
     else
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 6490d2735..7d0dcc7ad 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -123,7 +123,7 @@ class NotifyService < BaseService
 
   def send_email
     return if @notification.activity.nil?
-    NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later
+    NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes)
   end
 
   def email_enabled?
diff --git a/app/views/accounts/_bio.html.haml b/app/views/accounts/_bio.html.haml
index c9a4d8f1b..4e674beff 100644
--- a/app/views/accounts/_bio.html.haml
+++ b/app/views/accounts/_bio.html.haml
@@ -3,7 +3,7 @@
     .account__header__fields
       - account.fields.each do |field|
         %dl
-          %dt.emojify{ title: field.name }= field.name
+          %dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
           %dd.emojify{ title: field.value }= Formatter.instance.format_field(account, field.value, custom_emojify: true)
 
   = account_badge(account)
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index caf03bd7c..f09beff98 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -16,17 +16,17 @@
           .counter{ class: active_nav_class(short_account_url(account)) }
             = link_to short_account_url(account), class: 'u-url u-uid', title: number_with_delimiter(account.statuses_count) do
               %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
-              %span.counter-label= t('accounts.posts')
+              %span.counter-label= t('accounts.posts', count: account.statuses_count)
 
           .counter{ class: active_nav_class(account_following_index_url(account)) }
             = link_to account_following_index_url(account), title: number_with_delimiter(account.following_count) do
               %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
-              %span.counter-label= t('accounts.following')
+              %span.counter-label= t('accounts.following', count: account.following_count)
 
           .counter{ class: active_nav_class(account_followers_url(account)) }
             = link_to account_followers_url(account), title: number_with_delimiter(account.followers_count) do
               %span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true
-              %span.counter-label= t('accounts.followers')
+              %span.counter-label= t('accounts.followers', count: account.followers_count)
         .spacer
         .public-account-header__tabs__tabs__buttons
           = account_action_button(account)
@@ -37,7 +37,7 @@
       .public-account-header__extra__links
         = link_to account_following_index_url(account) do
           %strong= number_to_human account.following_count, strip_insignificant_zeros: true
-          = t('accounts.following')
+          = t('accounts.following', count: account.following_count)
         = link_to account_followers_url(account) do
           %strong= number_to_human account.followers_count, strip_insignificant_zeros: true
-          = t('accounts.followers')
+          = t('accounts.followers', count: account.followers_count)
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index e398fc29b..b825b82cb 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -29,7 +29,7 @@
       %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
 
       .account__section-headline
-        = active_link_to t('accounts.posts'), short_account_url(@account)
+        = active_link_to t('accounts.posts_tab_heading'), short_account_url(@account)
         = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
         = active_link_to t('accounts.media'), short_account_media_url(@account)
 
diff --git a/app/views/admin/relays/_relay.html.haml b/app/views/admin/relays/_relay.html.haml
index d974c80a6..667821894 100644
--- a/app/views/admin/relays/_relay.html.haml
+++ b/app/views/admin/relays/_relay.html.haml
@@ -2,20 +2,24 @@
   %td
     %samp= relay.inbox_url
   %td
-    - if relay.enabled?
+    - if relay.accepted?
       %span.positive-hint
         = fa_icon('check')
         = ' '
         = t 'admin.relays.enabled'
+    - elsif relay.pending?
+      = fa_icon('hourglass')
+      = ' '
+      = t 'admin.relays.pending'
     - else
       %span.negative-hint
         = fa_icon('times')
         = ' '
         = t 'admin.relays.disabled'
   %td
-    - if relay.enabled?
+    - if relay.accepted?
       = table_link_to 'power-off', t('admin.relays.disable'), disable_admin_relay_path(relay), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
-    - else
+    - elsif !relay.pending?
       = table_link_to 'power-off', t('admin.relays.enable'), enable_admin_relay_path(relay), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
 
     = table_link_to 'times', t('admin.relays.delete'), admin_relay_path(relay), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml
index 5b410ec84..4d557b071 100644
--- a/app/views/admin/reports/_status.html.haml
+++ b/app/views/admin/reports/_status.html.haml
@@ -14,7 +14,7 @@
     - unless status.proper.media_attachments.empty?
       - if status.proper.media_attachments.first.video?
         - video = status.proper.media_attachments.first
-        = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
+        = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true, alt: video.description
       - else
         = react_component :media_gallery, height: 343, sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
 
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index fda6b00f4..abe7ecf32 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -15,6 +15,7 @@
   %hr/
 
   .fields-group
+    = f.input :flavour, collection: Themes.instance.flavours, label_method: lambda { |flavour| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, include_blank: false
     = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: t('admin.settings.thumbnail.desc_html')
     = f.input :hero, as: :file, wrapper: :with_block_label, label: t('admin.settings.hero.title'), hint: t('admin.settings.hero.desc_html')
 
@@ -48,7 +49,7 @@
   .fields-group
     = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 }
     = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
-
+    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
   %hr/
 
   .fields-group
diff --git a/app/views/admin/suspensions/new.html.haml b/app/views/admin/suspensions/new.html.haml
index 5ffbbbe46..f03ecacc3 100644
--- a/app/views/admin/suspensions/new.html.haml
+++ b/app/views/admin/suspensions/new.html.haml
@@ -8,13 +8,13 @@
     %ul
       %li.negative-hint
         = number_to_human @account.statuses_count, strip_insignificant_zeros: true
-        = t('accounts.posts')
+        = t('accounts.posts', count: @account.statuses_count)
       %li.negative-hint
         = number_to_human @account.following_count, strip_insignificant_zeros: true
-        = t('accounts.following')
+        = t('accounts.following', count: @account.following_count)
       %li.negative-hint
         = number_to_human @account.followers_count, strip_insignificant_zeros: true
-        = t('accounts.followers')
+        = t('accounts.followers', count: @account.followers_count)
 
   %p.hint= t('admin.suspensions.hint_html', value: content_tag(:code, @account.acct))
 
diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml
index 08b385092..516c625a6 100644
--- a/app/views/auth/shared/_links.html.haml
+++ b/app/views/auth/shared/_links.html.haml
@@ -3,7 +3,7 @@
     %li= link_to t('auth.login'), new_session_path(resource_name)
 
   - if devise_mapping.registerable? && controller_name != 'registrations'
-    %li= link_to t('auth.register'), new_registration_path(resource_name)
+    %li= link_to t('auth.register'), open_registrations? ? new_registration_path(resource_name) : 'https://joinmastodon.org/#getting-started'
 
   - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
     %li= link_to t('auth.forgot_password'), new_password_path(resource_name)
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 9ede598b3..36b4e9cae 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -21,15 +21,14 @@
         = javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
     = csrf_meta_tags
 
+    - if Setting.custom_css.present?
+      = stylesheet_link_tag custom_css_path, media: 'all'
+
     = yield :header_tags
 
     -#  These must come after :header_tags to ensure our initial state has been defined.
     = render partial: 'layouts/theme', object: @core
     = render partial: 'layouts/theme', object: @theme
 
-  - body_classes ||= @body_classes || ''
-  - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui
-  - body_classes += current_account&.user&.setting_reduce_motion ? ' reduce-motion' : ' no-reduce-motion'
-
-  %body{ class: add_rtl_body_class(body_classes) }
+  %body{ class: body_classes }
     = content_for?(:content) ? yield(:content) : yield
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index 24911bb1e..bfa385f58 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -11,7 +11,7 @@
             = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'
           - else
             = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn nav-link nav-button'
-            = link_to t('auth.register'), new_user_registration_path, class: 'webapp-btn nav-link nav-button'
+            = link_to t('auth.register'), open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started', class: 'webapp-btn nav-link nav-button'
 
     .container= yield
 
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index ef2d2b894..792dccd9e 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -9,7 +9,7 @@
         %td= number_to_human_size @export.total_storage
         %td
       %tr
-        %th= t('accounts.statuses')
+        %th= t('accounts.statuses', count: @export.total_statuses)
         %td= number_with_delimiter @export.total_statuses
         %td
       %tr
@@ -17,7 +17,7 @@
         %td= number_with_delimiter @export.total_follows
         %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
       %tr
-        %th= t('accounts.followers')
+        %th= t('accounts.followers', count: @export.total_followers)
         %td= number_with_delimiter @export.total_followers
         %td
       %tr
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index a7c767816..7843005c1 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -21,7 +21,7 @@
   - if !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
-      = react_component :video, 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
+      = react_component :video, 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, alt: video.description
     - else
       = react_component :media_gallery, 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 1d61684ab..effd4826e 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -25,7 +25,7 @@
   - unless status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
-      = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
+      = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true, alt: video.description
     - else
       = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
 
diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb
index 323a9f85b..adbb496d9 100644
--- a/app/workers/activitypub/delivery_worker.rb
+++ b/app/workers/activitypub/delivery_worker.rb
@@ -10,7 +10,8 @@ class ActivityPub::DeliveryWorker
 
   HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
 
-  def perform(json, source_account_id, inbox_url)
+  def perform(json, source_account_id, inbox_url, options = {})
+    @options        = options.with_indifferent_access
     @json           = json
     @source_account = Account.find(source_account_id)
     @inbox_url      = inbox_url
@@ -27,7 +28,7 @@ class ActivityPub::DeliveryWorker
 
   def build_request
     request = Request.new(:post, @inbox_url, body: @json)
-    request.on_behalf_of(@source_account, :uri)
+    request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with])
     request.add_headers(HEADERS)
   end
 
diff --git a/app/workers/activitypub/update_distribution_worker.rb b/app/workers/activitypub/update_distribution_worker.rb
index bbda69305..b9e5ff064 100644
--- a/app/workers/activitypub/update_distribution_worker.rb
+++ b/app/workers/activitypub/update_distribution_worker.rb
@@ -5,7 +5,8 @@ class ActivityPub::UpdateDistributionWorker
 
   sidekiq_options queue: 'push'
 
-  def perform(account_id)
+  def perform(account_id, options = {})
+    @options = options.with_indifferent_access
     @account = Account.find(account_id)
 
     ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
@@ -26,7 +27,7 @@ class ActivityPub::UpdateDistributionWorker
   end
 
   def signed_payload
-    @signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
+    @signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account, sign_with: @options[:sign_with]))
   end
 
   def payload
diff --git a/app/workers/maintenance/destroy_media_worker.rb b/app/workers/maintenance/destroy_media_worker.rb
index 5f052983b..cde33d6d7 100644
--- a/app/workers/maintenance/destroy_media_worker.rb
+++ b/app/workers/maintenance/destroy_media_worker.rb
@@ -6,7 +6,7 @@ class Maintenance::DestroyMediaWorker
   sidekiq_options queue: 'pull'
 
   def perform(media_attachment_id)
-    media = MediaAttachment.find(media_attachment_id)
+    media = media_attachment_id.is_a?(MediaAttachment) ? media_attachment_id : MediaAttachment.find(media_attachment_id)
     media.destroy
   rescue ActiveRecord::RecordNotFound
     true
diff --git a/app/workers/maintenance/redownload_account_media_worker.rb b/app/workers/maintenance/redownload_account_media_worker.rb
index fc26815f2..6afbe6e19 100644
--- a/app/workers/maintenance/redownload_account_media_worker.rb
+++ b/app/workers/maintenance/redownload_account_media_worker.rb
@@ -6,7 +6,7 @@ class Maintenance::RedownloadAccountMediaWorker
   sidekiq_options queue: 'pull', retry: false
 
   def perform(account_id)
-    account = Account.find(account_id)
+    account = account_id.is_a?(Account) ? account_id : Account.find(account_id)
     account.reset_avatar!
     account.reset_header!
     account.save
diff --git a/app/workers/maintenance/uncache_media_worker.rb b/app/workers/maintenance/uncache_media_worker.rb
index 2d1a670a7..4bc62ef75 100644
--- a/app/workers/maintenance/uncache_media_worker.rb
+++ b/app/workers/maintenance/uncache_media_worker.rb
@@ -6,7 +6,7 @@ class Maintenance::UncacheMediaWorker
   sidekiq_options queue: 'pull'
 
   def perform(media_attachment_id)
-    media = MediaAttachment.find(media_attachment_id)
+    media = media_attachment_id.is_a?(MediaAttachment) ? media_attachment_id : MediaAttachment.find(media_attachment_id)
 
     return if media.file.blank?
 
diff --git a/app/workers/scheduler/backup_cleanup_scheduler.rb b/app/workers/scheduler/backup_cleanup_scheduler.rb
index 023a77307..d43660699 100644
--- a/app/workers/scheduler/backup_cleanup_scheduler.rb
+++ b/app/workers/scheduler/backup_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::BackupCleanupScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     old_backups.reorder(nil).find_each(&:destroy!)
diff --git a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
index fec08c6bc..e5e5f6bc4 100644
--- a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
+++ b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::DoorkeeperCleanupScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
diff --git a/app/workers/scheduler/email_scheduler.rb b/app/workers/scheduler/email_scheduler.rb
index 24117e424..24ec89b29 100644
--- a/app/workers/scheduler/email_scheduler.rb
+++ b/app/workers/scheduler/email_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::EmailScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     eligible_users.reorder(nil).find_each do |user|
diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb
index b02bac883..cd2273418 100644
--- a/app/workers/scheduler/feed_cleanup_scheduler.rb
+++ b/app/workers/scheduler/feed_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::FeedCleanupScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     clean_home_feeds!
diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb
index 6bb93df7d..42620332e 100644
--- a/app/workers/scheduler/ip_cleanup_scheduler.rb
+++ b/app/workers/scheduler/ip_cleanup_scheduler.rb
@@ -5,7 +5,7 @@ class Scheduler::IpCleanupScheduler
 
   RETENTION_PERIOD = 1.year
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     time_ago = RETENTION_PERIOD.ago
diff --git a/app/workers/scheduler/media_cleanup_scheduler.rb b/app/workers/scheduler/media_cleanup_scheduler.rb
index a27e02953..fb01aa70c 100644
--- a/app/workers/scheduler/media_cleanup_scheduler.rb
+++ b/app/workers/scheduler/media_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::MediaCleanupScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     unattached_media.find_each(&:destroy)
diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
index 06ba66205..5fba120f6 100644
--- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::SubscriptionsCleanupScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     Subscription.expired.in_batches.delete_all
diff --git a/app/workers/scheduler/subscriptions_scheduler.rb b/app/workers/scheduler/subscriptions_scheduler.rb
index 4b0959af2..d5873bccb 100644
--- a/app/workers/scheduler/subscriptions_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::SubscriptionsScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     Pubsubhubbub::SubscribeWorker.push_bulk(expiring_accounts.pluck(:id))
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index 626fb1652..881b911be 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
 class Scheduler::UserCleanupScheduler
   include Sidekiq::Worker
 
-  sidekiq_options unique: :until_executed
+  sidekiq_options unique: :until_executed, retry: 0
 
   def perform
     User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch|
diff --git a/bin/tootctl b/bin/tootctl
new file mode 100755
index 000000000..2fe02523a
--- /dev/null
+++ b/bin/tootctl
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+APP_PATH = File.expand_path('../config/application', __dir__)
+require_relative '../lib/cli'
+Mastodon::CLI.start(ARGV)
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 6a649b772..6d7666c48 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -4,6 +4,16 @@
 
 if Rails.env.production?
   assets_host = Rails.configuration.action_controller.asset_host || "https://#{ENV['WEB_DOMAIN'] || ENV['LOCAL_DOMAIN']}"
+  data_hosts = [assets_host]
+
+  if ENV['S3_ENABLED'] == 'true'
+    attachments_host = ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST'] || ENV['S3_HOSTNAME'] || "s3-#{ENV['S3_REGION'] || 'us-east-1'}.amazonaws.com"
+  elsif ENV['SWIFT_ENABLED'] == 'true'
+    attachments_host = ENV['SWIFT_OBJECT_URL']
+  else
+    attachments_host = nil
+  end
+  data_hosts << attachments_host unless attachments_host.nil?
 
   Rails.application.config.content_security_policy do |p|
     p.base_uri        :none
@@ -13,9 +23,10 @@ if Rails.env.production?
     p.font_src        :self, assets_host
     p.img_src         :self, :https, :data, :blob
     p.style_src       :self, :unsafe_inline, assets_host
-    p.media_src       :self, :data, assets_host
+    p.media_src       :self, :data, *data_hosts
     p.frame_src       :self, :https
-    p.connect_src     :self, assets_host, Rails.configuration.x.streaming_api_base_url
+    p.worker_src      :self, assets_host
+    p.connect_src     :self, :blob, Rails.configuration.x.streaming_api_base_url, *data_hosts
   end
 end
 
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index 59ab9b9a1..df0205879 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -47,10 +47,10 @@ if ENV['S3_ENABLED'] == 'true'
     Paperclip::Attachment.default_options[:url] = ':s3_path_url'
   end
 
-  if ENV.has_key?('S3_CLOUDFRONT_HOST')
+  if ENV.has_key?('S3_ALIAS_HOST') || ENV.has_key?('S3_CLOUDFRONT_HOST')
     Paperclip::Attachment.default_options.merge!(
       url: ':s3_alias_url',
-      s3_host_alias: ENV['S3_CLOUDFRONT_HOST']
+      s3_host_alias: ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST']
     )
   end
 elsif ENV['SWIFT_ENABLED'] == 'true'
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 9ae9c259c..9768177c3 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -30,10 +30,14 @@ cs:
     other_instances: Seznam instancí
     privacy_policy: Zásady soukromí
     source_code: Zdrojový kód
-    status_count_after: příspěvků
+    status_count_after:
+      one: příspěvek
+      other: příspěvků
     status_count_before: Kteří napsali
     terms: Podmínky používání
-    user_count_after: uživatelů
+    user_count_after:
+      one: uživatele
+      other: uživatelů
     user_count_before: Domov
     what_is_mastodon: Co je Mastodon?
   accounts:
@@ -346,6 +350,9 @@ cs:
       contact_information:
         email: Pracovní e-mail
         username: Uživatelské jméno kontaktu
+      custom_css:
+        desc_html: Pozměnit vzhled pomocí šablony CSS načtené na každé stránce
+        title: Vlastní CSS
       hero:
         desc_html: Zobrazuje se na hlavní stránce. Doporučuje se rozlišení alespoň 600x100px. Pokud toto není nastavené, bude zobrazena miniatura instance
         title: Hlavní obrázek
@@ -414,6 +421,12 @@ cs:
       last_delivery: Poslední doručení
       title: WebSub
       topic: Téma
+    suspensions:
+      bad_acct_msg: Hodnota pro potvrzení neodpovídá. Suspendujete správný účet?
+      hint_html: 'Pro potvrzení suspenzace účtu prosím zadejte do pole níže %{value}:'
+      proceed: Pokračovat
+      title: Suspendovat účet %{acct}
+      warning_html: 'Suspenzace tohoto účtu <strong>nenávratně</strong> smaže z tohoto účtu data, včetně:'
     title: Administrace
   admin_mailer:
     new_report:
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index 7e10f83b4..e9c98a63f 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -17,30 +17,30 @@ fr:
       unconfirmed: Vous devez valider votre compte pour continuer.
     mailer:
       confirmation_instructions:
-        action: Vérifier l'adresse courriel
-        explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de l'activer. Si ce n'était pas vous, veuillez ignorer ce courriel.
-        extra_html: S'il vous plaît, consultez également <a href="%{terms_path}"> 1les règles de l'instance</a> 2 et <a href="%{policy_path}">3nos termes de service</a> 4.
+        action: Vérifier l’adresse courriel
+        explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de l’activer. Si ce n’était pas vous, veuillez ignorer ce courriel.
+        extra_html: Merci de consultez également <a href="%{terms_path}">les règles de l’instance</a> et <a href="%{policy_path}">nos conditions d’utilisation</a>.
         subject: Merci de confirmer votre inscription sur %{instance}
-        title: Vérifier l'adresse courriel
+        title: Vérifier l’adresse courriel
       email_changed:
-        explanation: 'L''adresse courriel de votre compte est en cours de modification pour devenir :'
-        extra: Si vous n'avez pas changé votre adresse courriel, il est probable que quelqu'un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l'administrateur de l'instance si vous êtes bloqué hors de votre compte.
+        explanation: 'L’adresse courriel de votre compte est en cours de modification pour devenir :'
+        extra: Si vous n’avez pas changé votre adresse courriel, il est probable que quelqu’un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l’administrateur·rice de l’instance si vous êtes bloqué·e hors de votre compte.
         subject: 'Mastodon : Courriel modifié'
         title: Nouvelle adresse courriel
       password_change:
         explanation: Le mot de passe de votre compte a été changé.
-        extra: Si vous n'avez pas changé votre mot de passe, il est probable que quelqu'un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l'administrateur de l'instance si vous êtes bloqué hors de votre compte.
+        extra: Si vous n’avez pas changé votre mot de passe, il est probable que quelqu’un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l’administrateur·rice de l’instance si vous êtes bloqué·e hors de votre compte.
         subject: Votre mot de passe a été modifié avec succès
         title: Mot de passe modifié
       reconfirmation_instructions:
         explanation: Confirmez la nouvelle adresse pour changer votre courriel.
-        extra: Si ce changement n' a pas été initié par vous, veuillez ignorer ce courriel. L'adresse courriel du compte Mastodon ne changera pas tant que vous n'aurez pas cliqué sur le lien ci-dessus.
-        subject: 'Mastodon : Confirmez l''email pour %{instance}'
-        title: Vérifier l'adresse courriel
+        extra: Si ce changement n’a pas été initié par vous, veuillez ignorer ce courriel. L’adresse courriel du compte Mastodon ne changera pas tant que vous n’aurez pas cliqué sur le lien ci-dessus.
+        subject: 'Mastodon : Confirmez l’adresse pour %{instance}'
+        title: Vérifier l’adresse courriel
       reset_password_instructions:
         action: Modifier le mot de passe
         explanation: Vous avez demandé un nouveau mot de passe pour votre compte.
-        extra: Si vous ne l'avez pas demandé, veuillez ignorer ce courriel. Votre mot de passe ne changera pas tant que vous n'aurez pas cliqué sur le lien ci-dessus et que vous n'en aurez pas créé un nouveau.
+        extra: Si vous ne l’avez pas demandé, veuillez ignorer ce courriel. Votre mot de passe ne changera pas tant que vous n’aurez pas cliqué sur le lien ci-dessus et que vous n’en aurez pas créé un nouveau.
         subject: Instructions pour changer votre mot de passe
         title: Réinitialisation du mot de passe
       unlock_instructions:
diff --git a/config/locales/doorkeeper.da.yml b/config/locales/doorkeeper.da.yml
index dd45a5078..df964e4b1 100644
--- a/config/locales/doorkeeper.da.yml
+++ b/config/locales/doorkeeper.da.yml
@@ -91,7 +91,9 @@ da:
           unknown: Adgangs-beviset er ugyldigt
         resource_owner_authenticator_not_configured: Ressource ejeren kunne ikke blive fundet grundet Doorkeeper.configure.resource_owner_authenticator ikke er konfigureret.
         server_error: Autoriserings serveren blev mødt med en uventet betingelse der forhindrede den i at færdiggøre anmodningen.
+        temporarily_unavailable: Autoriserings serveren er på nuværende tidspunkt ikke i stand til at håndtere anmodningen grundet midlertidig overlast eller serveren er ved at blive opdateret.
         unauthorized_client: Klienten er ikke godkendt til at udføre denne anmodning ved at bruge denne metode.
+        unsupported_grant_type: Autoriserings typen understøttes ikke af autoriserings serveren.
         unsupported_response_type: Godkendelses serveren understøtter ikke denne type respons.
     flash:
       applications:
diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml
index e8668ba82..eae691659 100644
--- a/config/locales/doorkeeper.fr.yml
+++ b/config/locales/doorkeeper.fr.yml
@@ -7,7 +7,7 @@ fr:
         redirect_uri: L’URL de redirection
         scope: Portée
         scopes: Étendues
-        website: Site web de l'application
+        website: Site web de l’application
     errors:
       models:
         doorkeeper/application:
@@ -64,7 +64,7 @@ fr:
         prompt: Autoriser %{client_name} à utiliser votre compte ?
         title: Autorisation requise
       show:
-        title: Copiez ce code d'autorisation et collez-le dans l'application.
+        title: Copiez ce code d’autorisation et collez-le dans l’application.
     authorized_applications:
       buttons:
         revoke: Annuler
@@ -119,12 +119,12 @@ fr:
       push: recevoir vos notifications
       read: lire toutes les données de votre compte
       read:accounts: voir les informations du compte
-      read:blocks: voir vos bloqués
+      read:blocks: voir vos bloquages
       read:favourites: voir vos favoris
       read:filters: voir vos filtres
       read:follows: voir vos suivis
       read:lists: voir vos listes
-      read:mutes: voir vos silenciés
+      read:mutes: voir vos masquages
       read:notifications: voir vos notifications
       read:reports: voir vos rapports
       read:search: rechercher en votre nom
@@ -137,7 +137,7 @@ fr:
       write:follows: suivre les gens
       write:lists: créer des listes
       write:media: téléverser des fichiers-média
-      write:mutes: silencier des gens et des conversations
+      write:mutes: masquer des gens et des conversations
       write:notifications: nettoyer vos notifications
-      write:reports: rapporter d'autres personnes
+      write:reports: rapporter d’autres personnes
       write:statuses: publier des statuts
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 7e062781c..38a6df8f7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -43,7 +43,9 @@ en:
   accounts:
     choices_html: "%{name}'s choices:"
     follow: Follow
-    followers: Followers
+    followers:
+      one: Follower
+      other: Followers
     following: Following
     joined: Joined %{date}
     media: Media
@@ -54,7 +56,10 @@ en:
     people_who_follow: People who follow %{name}
     pin_errors:
       following: You must be already following the person you want to endorse
-    posts: Toots
+    posts:
+      one: Toot
+      other: Toots
+    posts_tab_heading: Toots
     posts_with_replies: Toots and replies
     reserved_username: The username is reserved
     roles:
@@ -298,6 +303,7 @@ en:
       description_html: A <strong>federation relay</strong> is an intermediary server that exchanges large volumes of public toots between servers that subscribe and publish to it. <strong>It can help small and medium servers discover content from the fediverse</strong>, which would otherwise require local users manually following other people on remote servers.
       enable_hint: Once enabled, your server will subscribe to all public toots from this relay, and will begin sending this server's public toots to it.
       inbox_url: Relay URL
+      pending: Waiting for relay's approval
       setup: Setup a relay connection
       status: Status
       title: Relays
@@ -350,6 +356,9 @@ en:
       contact_information:
         email: Business e-mail
         username: Contact username
+      custom_css:
+        desc_html: Modify the look with CSS loaded on every page
+        title: Custom CSS
       hero:
         desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to instance thumbnail
         title: Hero image
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index b563405bd..cf8ceb247 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -4,7 +4,7 @@ fr:
     about_hashtag_html: Figurent ci-dessous les pouets tagués avec <strong>#%{hashtag}</strong>. Vous pouvez interagir avec eux si vous avez un compte n’importe où dans le Fediverse.
     about_mastodon_html: Mastodon est un réseau social utilisant des formats ouverts et des logiciels libres. Comme le courriel, il est décentralisé.
     about_this: À propos
-    administered_by: 'Administré par :'
+    administered_by: 'Administrée par :'
     api: API
     apps: Applications mobiles
     closed_registrations: Les inscriptions sont actuellement fermées sur cette instance. Cependant, vous pouvez trouver une autre instance sur laquelle vous créer un compte et à partir de laquelle vous pourrez accéder au même réseau.
@@ -20,7 +20,7 @@ fr:
       humane_approach_title: Une approche plus humaine
       not_a_product_body: Mastodon n’est pas un réseau commercial. Ici, pas de publicités, pas de prospection de données et pas d’environnements fermés. Il n’y existe aucune autorité centrale.
       not_a_product_title: Vous êtes une personne, pas un produit
-      real_conversation_body: Avec 500 caractères à votre disposition, une grande granularité en termes de diffusion et la possibilité de masquer vos messages derrières des avertissements, vous êtes libre de vous exprimer de la manière qui vous plaît.
+      real_conversation_body: Avec 500 caractères à votre disposition, une grande granularité en termes de diffusion et la possibilité de masquer vos messages derrière des avertissements, vous êtes libre de vous exprimer de la manière qui vous plaît.
       real_conversation_title: Construit pour de vraies conversations
       within_reach_body: Grâce à l’existence d’un environnement API accueillant pour les développeur·se·s, de multiples applications pour iOS, Android et d’autres plateformes vous permettent de rester en contact avec vos ami·e·s où que vous soyez.
       within_reach_title: Toujours à portée de main
@@ -37,16 +37,19 @@ fr:
     user_count_before: Abrite
     what_is_mastodon: Qu’est-ce que Mastodon ?
   accounts:
-    choices_html: 'Sélection de %{name} :'
+    choices_html: "%{name} recommande :"
     follow: Suivre
     followers: Abonné⋅e⋅s
     following: Abonnements
+    joined: Inscrit·e en %{date}
     media: Médias
-    moved_html: "%{name} a changé de compte pour %{new_profile_link} :"
+    moved_html: "%{name} a changé de compte pour %{new_profile_link} :"
     network_hidden: Cette information n’est pas disponible
     nothing_here: Rien à voir ici !
     people_followed_by: Personnes suivies par %{name}
     people_who_follow: Personnes qui suivent %{name}
+    pin_errors:
+      following: Vous devez être déjà abonné·e à la personne que vous désirez recommander
     posts: Statuts
     posts_with_replies: Statuts & réponses
     reserved_username: Ce nom d’utilisateur⋅ice est réservé
@@ -66,7 +69,7 @@ fr:
       avatar: Avatar
       by_domain: Domaine
       change_email:
-        changed_msg: Courriel du compte modifié avec succès !
+        changed_msg: Courriel du compte modifié avec succès !
         current_email: Courriel actuel
         label: Modifier le courriel
         new_email: Nouveau courriel
@@ -121,11 +124,11 @@ fr:
       public: Publique
       push_subscription_expires: Expiration de l’abonnement PuSH
       redownload: Rafraîchir les avatars
-      remove_avatar: Supprimer l'avatar
+      remove_avatar: Supprimer l’avatar
       resend_confirmation:
-        already_confirmed: Cet utilisateur est déjà confirmé
+        already_confirmed: Cet·te utilisateur·ice est déjà confirmé·e
         send: Renvoyer un courriel de confirmation
-        success: Email de confirmation envoyé avec succès !
+        success: Courriel de confirmation envoyé avec succès !
       reset: Réinitialiser
       reset_password: Réinitialiser le mot de passe
       resubscribe: Se réabonner
@@ -154,35 +157,36 @@ fr:
       web: Web
     action_logs:
       actions:
-        assigned_to_self_report: "%{name} s'est assigné le signalement de %{target} à eux-même"
-        change_email_user: "%{name} a modifié l'adresse de courriel de l'utilisateur %{target}"
-        confirm_user: "%{name} adresse courriel confirmée de l'utilisateur %{target}"
-        create_custom_emoji: "%{name} a importé de nouveaux emoji %{target}"
+        assigned_to_self_report: "%{name} s’est assigné·e le signalement de %{target}"
+        change_email_user: "%{name} a modifié l’adresse de courriel de l’utilisateur·rice %{target}"
+        confirm_user: "%{name} adresse courriel confirmée de l’utilisateur·ice %{target}"
+        create_custom_emoji: "%{name} a importé de nouveaux émojis %{target}"
         create_domain_block: "%{name} a bloqué le domaine %{target}"
         create_email_domain_block: "%{name} a mis le domaine du courriel %{target} sur liste noire"
-        demote_user: "%{name} a rétrogradé l'utilisateur %{target}"
+        demote_user: "%{name} a rétrogradé l’utilisateur·ice %{target}"
         destroy_domain_block: "%{name} a débloqué le domaine %{target}"
         destroy_email_domain_block: "%{name} a mis le domaine du courriel %{target} sur liste blanche"
         destroy_status: "%{name} a enlevé le statut de %{target}"
-        disable_2fa_user: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur %{target}"
-        disable_custom_emoji: "%{name} a désactivé l'emoji %{target}"
-        disable_user: "%{name} a désactivé le login pour l'utilisateur %{target}"
-        enable_custom_emoji: "%{name} a activé l'emoji %{target}"
-        enable_user: "%{name} a activé le login pour l'utilisateur %{target}"
+        disable_2fa_user: "%{name} a désactivé l’authentification à deux facteurs pour l’utilisateur·ice %{target}"
+        disable_custom_emoji: "%{name} a désactivé l’émoji %{target}"
+        disable_user: "%{name} a désactivé le login pour l’utilisateur·ice %{target}"
+        enable_custom_emoji: "%{name} a activé l’émoji %{target}"
+        enable_user: "%{name} a activé le login pour l’utilisateur·ice %{target}"
         memorialize_account: "%{name} a transformé le compte de %{target} en une page de mémorial"
-        promote_user: "%{name} a promu l'utilisateur %{target}"
-        remove_avatar_user: "%{name} a supprimé l'avatar de %{target}'s"
-        reopen_report: "%{name} a ré-ouvert le signalement %{target}"
+        promote_user: "%{name} a promu l’utilisateur·ice %{target}"
+        remove_avatar_user: "%{name} a supprimé l’avatar de %{target}"
+        reopen_report: "%{name} a rouvert le signalement %{target}"
         reset_password_user: "%{name} a réinitialisé le mot de passe de %{target}"
         resolve_report: "%{name} a résolu la dénonciation de %{target}"
         silence_account: "%{name} a mis le compte %{target} en mode silence"
         suspend_account: "%{name} a suspendu le compte %{target}"
-        unassigned_report: "%{name} a dés-assigné le signalement %{target}"
+        unassigned_report: "%{name} a désassigné le signalement %{target}"
         unsilence_account: "%{name} a mis fin au mode silence de %{target}"
         unsuspend_account: "%{name} a réactivé le compte de %{target}"
-        update_custom_emoji: "%{name} a mis à jour l'emoji %{target}"
+        update_custom_emoji: "%{name} a mis à jour l’émoji %{target}"
         update_status: "%{name} a mis à jour le statut de %{target}"
-      title: Journal d'audit
+      deleted_status: "(statut supprimé)"
+      title: Journal d’audit
     custom_emojis:
       by_domain: Domaine
       copied_msg: Copie locale de l’émoji créée avec succès !
@@ -203,29 +207,32 @@ fr:
       overwrite: Réécrire
       shortcode: Raccourci
       shortcode_hint: Au moins deux caractères, seulement des caractères alphanumériques ou des tirets bas
-      title: Émoji personnalisés
+      title: Émojis personnalisés
       unlisted: Délisté
-      update_failed_msg: N'a pas pu mettre à jour cet emoji
-      updated_msg: Emoji mis à jour avec succès !
+      update_failed_msg: N’a pas pu mettre à jour cet émoji
+      updated_msg: Émoji mis à jour avec succès !
       upload: Téléverser
     dashboard:
+      backlog: tâches en attente
       config: Configuration
-      feature_invites: Liens d'invitation
+      feature_deletions: Suppressions de comptes
+      feature_invites: Liens d’invitation
       feature_registrations: Inscriptions
       feature_relay: Relais de fédération
       features: Fonctionnalités
       hidden_service: Fédération avec des services cachés
-      recent_users: Utilisateurs récents
-      search: Recherche texte-plein
-      single_user_mode: Mode utilisateur unique
+      open_reports: signalements non résolus
+      recent_users: Utilisateur·rice·s récent·e·s
+      search: Recherche plein texte
+      single_user_mode: Mode utilisateur·ice unique
       software: Logiciel
-      space: Utilisation d'espace
+      space: Espace utilisé
       title: Tableau de bord
-      total_users: utilisateurs au total
+      total_users: utilisateur·rice·s au total
       trends: Tendances
       week_interactions: interactions cette semaine
-      week_users_active: actif cette semaine
-      week_users_new: utilisateurs cette semaine
+      week_users_active: actif·ve·s cette semaine
+      week_users_new: utilisateur·rice·s cette semaine
     domain_blocks:
       add_new: Ajouter
       created_msg: Le blocage de domaine est désormais activé
@@ -284,20 +291,22 @@ fr:
       title: Invitations
     relays:
       add_new: Ajouter un nouveau relais
+      description_html: Un <strong>relai de fédération</strong> est un serveur intermédiaire qui échange de grandes quantités de pouets entre les serveurs qui publient dessus et ceux qui y sont abonnés. <strong>Il peut aider les petites et moyennes instances à découvrir du contenu sur le fediverse</strong>, ce qui normalement nécessiterait que les membres locaux suivent des gens inscrits sur des serveurs distants.
+      enable_hint: Une fois activé, votre serveur souscrira à tous les pouets publics présents sur ce relais et y enverra ses propres pouets publics.
       inbox_url: URL de relais
       setup: Paramétrer une connexion de relais
       status: Statut
       title: Relais
     report_notes:
-      created_msg: Note de signalement créée avec succès !
-      destroyed_msg: Note de signalement effacée avec succès !
+      created_msg: Note de signalement créée avec succès !
+      destroyed_msg: Note de signalement effacée avec succès !
     reports:
       account:
         note: note
         report: signaler
       action_taken_by: Intervention de
       are_you_sure: Êtes vous certain⋅e ?
-      assign_to_self: Me l'assigner
+      assign_to_self: Me l’assigner
       assigned: Modérateur assigné
       comment:
         none: Aucun
@@ -317,7 +326,7 @@ fr:
       reported_account: Compte signalé
       reported_by: Signalé par
       resolved: Résolus
-      resolved_msg: Signalement résolu avec succès !
+      resolved_msg: Signalement résolu avec succès !
       silence_account: Masquer le compte
       status: Statut
       suspend_account: Suspendre le compte
@@ -329,20 +338,23 @@ fr:
       view: Voir
     settings:
       activity_api_enabled:
-        desc_html: Nombre de statuts affichés localement, d'utilisateurs actifs et de nouveaux enregistrements dans les registres hebdomadaires
-        title: Publier des statistiques agrégées sur l'activité des utilisateurs
+        desc_html: Nombre de statuts affichés localement, d’utilisateur·ice·s actif·ve·s et de nouveaux enregistrements dans les registres hebdomadaires
+        title: Publier des statistiques agrégées sur l’activité des utilisateur·ice·s
       bootstrap_timeline_accounts:
-        desc_html: Séparez les noms d’utilisateur·ice par des virgules. Ne fonctionne qu’avec des comptes locaux et non-verrouillés. Si laissé vide, tous les administrateur⋅ice⋅s locaux sont sélectionné⋅e⋅s.
+        desc_html: Séparez les noms d’utilisateur·ice par des virgules. Ne fonctionne qu’avec des comptes locaux et non verrouillés. Si laissé vide, tous les administrateur⋅ice⋅s locaux sont sélectionné⋅e⋅s.
         title: Abonnements par défaut pour les nouveaux·elles utilisateur·ice·s
       contact_information:
         email: Entrez une adresse courriel publique
         username: Entrez un nom d’utilisateur⋅ice
       hero:
-        desc_html: Affichée sur la page d'accueil. Au moins 600x100px recommandé. Lorsqu'elle n'est pas définie, se rabat sur la vignette de l'instance
-        title: Image d'en-tête
+        desc_html: Affichée sur la page d’accueil. Au moins 600x100px recommandé. Lorsqu’elle n’est pas définie, se rabat sur la vignette de l’instance
+        title: Image d’en-tête
       peers_api_enabled:
         desc_html: Noms des domaines que cette instance a découvert dans le fediverse
         title: Publier la liste des instances découvertes
+      preview_sensitive_media:
+        desc_html: Les liens de prévisualisation sur les autres sites web afficheront une vignette même si le média est sensible
+        title: Afficher les médias sensibles dans les prévisualisations OpenGraph
       registrations:
         closed_message:
           desc_html: Affiché sur la page d’accueil lorsque les inscriptions sont fermées<br>Vous pouvez utiliser des balises HTML
@@ -357,19 +369,20 @@ fr:
           desc_html: Autoriser tout le monde à créer un compte
           title: Ouvrir les inscriptions
       show_known_fediverse_at_about_page:
-        desc_html: Lorsque l'option est activée, les pouets provenant de toutes les instances connues sont affichés dans la prévisualisation. Si non, seuls les pouets locaux sont affichés.
+        desc_html: Lorsque l’option est activée, les pouets provenant de toutes les instances connues sont affichés dans la prévisualisation. Sinon, seuls les pouets locaux sont affichés.
         title: Afficher le fediverse connu dans la prévisualisation du fil
       show_staff_badge:
-        desc_html: Montrer un badge de responsable sur une page utilisateur
+        desc_html: Montrer un badge de responsable sur une page utilisateur·ice
         title: Montrer un badge de responsable
       site_description:
-        desc_html: Paragraphe introductif sur la page d'accueil. Décrivez ce qui rend spécifique ce serveur Mastodon et toute autre chose importante. Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>.
+        desc_html: Paragraphe introductif sur la page d’accueil. Décrivez ce qui rend spécifique ce serveur Mastodon et toute autre chose importante. Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>.
         title: Description du site
       site_description_extended:
         desc_html: Affichée sur la page d’informations complémentaires du site<br>Vous pouvez utiliser des balises HTML
         title: Description étendue du site
       site_short_description:
-        title: Description courte de l'instance
+        desc_html: Affichée dans la barre latérale et dans les méta-tags. Décrivez ce qui rend spécifique ce serveur Mastodon en un seul paragraphe. Si laissée vide, la description de l’instance sera affiché par défaut.
+        title: Description courte de l’instance
       site_terms:
         desc_html: Affichée sur la page des conditions d’utilisation du site<br>Vous pouvez utiliser des balises HTML
         title: Politique de confidentialité
@@ -391,6 +404,7 @@ fr:
       media:
         title: Médias
       no_media: Aucun média
+      no_status_selected: Aucun statut n’a été modifié car aucun n’a été sélectionné
       title: État du compte
       with_media: avec médias
     subscriptions:
@@ -404,13 +418,13 @@ fr:
   admin_mailer:
     new_report:
       body: "%{reporter} a signalé %{target}"
-      body_remote: Quelqu'un de %{domain} a signalé %{target}
+      body_remote: Quelqu’un de %{domain} a signalé %{target}
       subject: Nouveau signalement sur %{instance} (#%{id})
   application_mailer:
     notification_preferences: Modifier les préférences de courriel
     salutation: "%{name},"
     settings: 'Changer les préférences courriel : %{link}'
-    view: 'Voir :'
+    view: 'Voir :'
     view_profile: Voir le profil
     view_status: Afficher le statut
   applications:
@@ -440,7 +454,7 @@ fr:
       cas: CAS
       saml: SAML
     register: S’inscrire
-    register_elsewhere: S'inscrire sur un autre serveur
+    register_elsewhere: S’inscrire sur un autre serveur
     resend_confirmation: Envoyer à nouveau les consignes de confirmation
     reset_password: Réinitialiser le mot de passe
     security: Sécurité
@@ -449,8 +463,8 @@ fr:
     already_following: Vous suivez déjà ce compte
     error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant
     follow: Suivre
-    follow_request: 'Vous avez demandé à suivre :'
-    following: 'Youpi ! Vous suivez  :'
+    follow_request: 'Vous avez demandé à suivre :'
+    following: 'Youpi ! Vous suivez  :'
     post_follow:
       close: Ou bien, vous pouvez fermer cette fenêtre.
       return: Afficher le profil de l’utilisateur⋅ice
@@ -476,7 +490,7 @@ fr:
     description_html: Cela va supprimer votre compte et le désactiver de manière <strong>permanente et irréversible</strong>. Votre nom d’utilisateur⋅ice restera réservé afin d’éviter la confusion.
     proceed: Supprimer compte
     success_msg: Votre compte a été supprimé avec succès
-    warning_html: Seule la suppression du contenu depuis cette instance est garantie. Le contenu qui a été partagé est susceptible de laisser des traces. Les serveurs hors-lignes ainsi que ceux n’étant plus abonnés à vos publications ne mettront pas leur base de données à jour.
+    warning_html: Seule la suppression du contenu depuis cette instance est garantie. Le contenu qui a été partagé est susceptible de laisser des traces. Les serveurs hors-ligne ainsi que ceux n’étant plus abonnés à vos publications ne mettront pas leur base de données à jour.
     warning_title: Disponibilité du contenu disséminé
   errors:
     '403': Vous n’avez pas accès à cette page.
@@ -489,13 +503,13 @@ fr:
     '500':
       content: Nous sommes désolé·e·s, mais quelque chose s’est mal passé de notre côté.
       title: Cette page n’est pas correcte
-    noscript_html: Pour utiliser Mastodon, veuillez activer JavaScript. Sinon, essayez l'une des <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">applications natives</a> pour Mastodon pour votre plate-forme.
+    noscript_html: Pour utiliser Mastodon, veuillez activer JavaScript. Sinon, essayez l’une des <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">applications natives</a> pour Mastodon pour votre plate-forme.
   exports:
     archive_takeout:
       date: Date
       download: Télécharger votre archive
       hint_html: Vous pouvez demander une archive de vos  <strong>pouets et médias téléversés</strong>. Les données exportées seront au format ActivityPub, lisible par tout logiciel compatible. Vous pouvez demander une archive tous les 7 jours.
-      in_progress: Élaboration de votre archive....
+      in_progress: Création de votre archive…
       request: Demandez vos archives
       size: Taille
     blocks: Vous bloquez
@@ -505,12 +519,15 @@ fr:
     storage: Médias stockés
   filters:
     contexts:
-      home: Ligne de temps personnelle
+      home: Accueil
       notifications: Notifications
-      public: Lignes de temps public
+      public: Fils publics
       thread: Conversations
     edit:
-      title: Filtre d'édition
+      title: Éditer le filtre
+    errors:
+      invalid_context: Contexte invalide ou insuffisant
+      invalid_irreversible: Le filtrage irréversible ne fonctionne que pour l’accueil et les notifications
     index:
       delete: Effacer
       title: Filtres
@@ -559,7 +576,7 @@ fr:
       '86400': 1 jour
     expires_in_prompt: Jamais
     generate: Générer
-    invited_by: 'Vous avez été invité par :'
+    invited_by: 'Vous avez été invité·e par :'
     max_uses:
       one: 1 usage
       other: "%{count} usages"
@@ -577,26 +594,26 @@ fr:
       images_and_video: Impossible de joindre une vidéo à un statut contenant déjà des images
       too_many: Impossible de joindre plus de 4 fichiers
   migrations:
-    acct: utilisateur@domaine du nouveau compte
-    currently_redirecting: 'Votre profile va être redirigé vers :'
+    acct: profil@domaine du nouveau compte
+    currently_redirecting: 'Votre profile va être redirigé vers :'
     proceed: Enregistrer
-    updated_msg: Les paramètres de votre migration de compte ont été mis à jour avec succès !
+    updated_msg: Les paramètres de votre migration de compte ont été mis à jour avec succès !
   moderation:
     title: Modération
   notification_mailer:
     digest:
       action: Voir toutes les notifications
-      body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite le %{since} :'
-      mention: "%{name} vous a mentionné⋅e dans :"
+      body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite le %{since} :'
+      mention: "%{name} vous a mentionné⋅e dans :"
       new_followers_summary:
         one: Vous avez un⋅e nouvel⋅le abonné⋅e ! Youpi !
         other: Vous avez %{count} nouveaux⋅elles abonné⋅e·s ! Incroyable !
       subject:
         one: "Une nouvelle notification depuis votre dernière visite \U0001F418"
         other: "%{count} nouvelles notifications depuis votre dernière visite \U0001F418"
-      title: Pendant votre absence...
+      title: Pendant votre absence…
     favourite:
-      body: "%{name} a ajouté votre post à ses favoris :"
+      body: "%{name} a ajouté votre post à ses favoris :"
       subject: "%{name} a ajouté votre post à ses favoris"
       title: Nouveau favori
     follow:
@@ -604,17 +621,17 @@ fr:
       subject: "%{name} vous suit"
       title: Nouvel·le abonné·e
     follow_request:
-      action: Gérer les demandes d'abonnement
+      action: Gérer les demandes d’abonnement
       body: "%{name} a demandé à vous suivre"
       subject: 'Abonné⋅es en attente : %{name}'
-      title: Nouvelle demande d'abonnement
+      title: Nouvelle demande d’abonnement
     mention:
       action: Répondre
-      body: "%{name} vous a mentionné⋅e dans :"
+      body: "%{name} vous a mentionné⋅e dans :"
       subject: "%{name} vous a mentionné·e"
       title: Nouvelle mention
     reblog:
-      body: "%{name} a partagé votre statut :"
+      body: "%{name} a partagé votre statut :"
       subject: "%{name} a partagé votre statut"
       title: Nouveau partage
   number:
@@ -640,10 +657,14 @@ fr:
     publishing: Publication
     web: Web
   remote_follow:
-    acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre cet·te utilisateur⋅ice
+    acct: Entrez l’adresse profil@instance depuis laquelle vous voulez vous abonner
     missing_resource: L’URL de redirection n’a pas pu être trouvée
-    proceed: Continuez pour suivre
-    prompt: 'Vous allez suivre :'
+    no_account_html: Vous n’avez pas de compte ? Vous pouvez <a href='%{sign_up_path}' target='_blank'>vous inscrire ici</a>
+    proceed: Confirmer l’abonnement
+    prompt: 'Vous allez suivre :'
+  remote_interaction:
+    proceed: Confirmer l’interaction
+    prompt: 'Vous désirez interagir avec ce pouet :'
   remote_unfollow:
     error: Erreur
     title: Titre
@@ -706,7 +727,7 @@ fr:
     your_apps: Vos applications
   statuses:
     attached:
-      description: 'Attaché : %{attached}'
+      description: 'Attaché : %{attached}'
       image:
         one: "%{count} image"
         other: "%{count} images"
@@ -714,10 +735,10 @@ fr:
         one: "%{count} vidéo"
         other: "%{count} vidéos"
     boosted_from_html: Repartagé depuis %{acct_link}
-    content_warning: 'Attention au contenu : %{warning}'
+    content_warning: 'Avertissement sur le contenu : %{warning}'
     disallowed_hashtags:
-      one: 'contient un hashtag désactivé : %{tags}'
-      other: 'contient les hashtag désactivés : %{tags}'
+      one: 'contient un hashtag désactivé : %{tags}'
+      other: 'contient les hashtags désactivés : %{tags}'
     language_detection: Détecter automatiquement la langue
     open_in_web: Ouvrir sur le web
     over_character_limit: limite de caractères dépassée de %{max} caractères
@@ -727,6 +748,7 @@ fr:
       private: Les statuts non-publics ne peuvent pas être épinglés
       reblog: Un partage ne peut pas être épinglé
     show_more: Afficher plus
+    sign_in_to_participate: Inscrivez-vous pour prendre part à la conversation
     title: '%{name} : "%{quote}"'
     visibilities:
       private: Abonné⋅e⋅s uniquement
@@ -740,7 +762,88 @@ fr:
     reblogged: a partagé
     sensitive_content: Contenu sensible
   terms:
-    title: "%{instance} Conditions d’utilisations et politique de confidentialité"
+    body_html: |
+      <h2>Politique de confidentialité</h2>
+      <h3 id="collect">Quelles informations collectons-nous ?</h3>
+
+      <ul>
+        <li><em>Informations de base sur votre compte</em> : Si vous vous inscrivez sur ce serveur, il vous sera demandé de rentrer un identifiant, une adresse électronique et un mot de passe. Vous pourrez également ajouter des informations additionnelles sur votre profil, telles qu’un nom public et une biographie, ainsi que téléverser une image de profil et une image d’en-tête. Vos identifiant, nom public, biographie, image de profil et image d’en-tête seront toujours affichés publiquement.</li>
+      <li><em>Posts, liste d’abonnements et autres informations publiques</em> : La liste de vos abonnements ainsi que la liste de vos abonné·e·s sont publiques. Quand vous postez un message, la date et l’heure d’envoi ainsi que le nom de l’application utilisée pour sa transmission sont enregistré·e·s. Des médias, tels que des images ou des vidéos, peuvent être joints aux messages. Les posts publics et non listés sont affichés publiquement. Quand vous mettez en avant un post sur votre profil, ce post est également affiché publiquement. Vos messages sont délivrés à vos abonné·e·s, ce qui, dans certains cas, signifie qu’ils sont délivrés à des serveurs tiers et que ces derniers en stockent une copie. Quand vous supprimer un post, il est probable que vos abonné·e·s en soient informé·e·s. Partager un message ou le marquer comme favori est toujours une action publique.</li>
+        <li><em>Posts directs et abonné·e·s uniquement</em> : Tous les posts sont stockés et traités par le serveur. Les messages abonné·e·s uniquement ne sont transmis qu’à vos abonné·e·s et aux personnes mentionnées dans le corps du message, tandis que les messages directs ne sont transmis qu’aux personnes mentionnées. Dans certains cas, cela signifie qu’ils sont délivrés à des serveurs tiers et que ces derniers en stockent une copie. Nous faisons un effort de bonne fois pour en limiter l’accès uniquement aux personnes autorisées, mais ce n’est pas nécessairement le cas des autres serveurs. Il est donc très important que vous vérifiiez les serveurs auxquels appartiennent vos abonné·e·s. Il vous est possible d’activer une option dans les paramètres afin d’approuver et de rejeter manuellement les nouveaux·lles abonné·e·s. <em>Gardez s’il-vous-plaît en mémoire que les opérateur·rice·s du serveur ainsi que celles et ceux de n’importe quel serveur récepteur peuvent voir ces messages</em> et qu’il est possible pour les destinataires de faire des captures d’écran, de copier et plus généralement de repartager ces messages. <em>Ne partager aucune information sensible à l’aide de Mastodon.</em></li>
+        <li><em>IP et autres métadonnées</em> : Quand vous vous connectez, nous enregistrons votre adresse IP ainsi que le nom de votre navigateur web. Toutes les sessions enregistrées peuvent être consultées dans les paramètres, afin que vous puissiez les surveiller et éventuellement les révoquer. La dernière adresse IP utilisée est conservée pour une durée de 12 mois. Nous sommes également susceptibles de conserver les journaux du serveur, ce qui inclut l’adresse IP de chaque requête reçue.</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="use">Que faisons-nous des informations que nous collectons ?</h3>
+
+      <p>Toutes les informations que nous collectons sur vous peuvent être utilisées d’une des manières suivantes :</p>
+
+      <ul>
+        <li>Pour vous fournir les fonctionnalités de base de Mastodon. Vous ne pouvez interagir avec le contenu des autres et poster votre propre contenu que lorsque vous êtes connecté·e. Par exemple, vous pouvez vous abonner à plusieurs autres comptes pour voir l’ensemble de leurs posts dans votre fil d’accueil personnalisé.</li>
+        <li>Pour aider à la modération de la communauté, par exemple, comparer votre adresse IP à d’autres afin de déterminer si un bannissement a été contourné ou si une autre violation aux règles a été commise.</li>
+        <li>L’adresse électronique que vous nous avez fournie peut être utilisée pour vous envoyez des informations, des notifications lorsque d’autres personnes interagissent avec votre contenu ou vous envoient des messages, pour répondre à des demandes de votre part ainsi que pour tout autres requêtes ou questions.</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="protect">Comment protégeons-nous vos informations ?</h3>
+
+      <p>Nous mettons en œuvre une variété de mesures de sécurité afin de garantir la sécurité de vos informations personnelles quand vous les saisissez, les soumettez et les consultez. Entre autres choses, votre session de navigation ainsi que le trafic entre votre application et l’API sont sécurisés à l’aide de TLS tandis que votre mot de passe est haché en utilisant un puissant algorithme à sens unique. Vous pouvez également activer l’authentification à deux facteurs pour sécuriser encore plus l’accès à votre compte.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="data-retention">Quelle est notre politique de conservation des données ?</h3>
+
+      <p>Nous ferons un effort de bonne foi :</p>
+
+      <ul>
+        <li>Pour ne pas conserver plus de 90 jours les journaux systèmes contenant les adresses IP de toutes les requêtes reçues par ce serveur.</li>
+        <li>Pour ne pas conserver plus de 12 mois les adresses IP associées aux utilisateur·ice·s enregistré·e·s.</li>
+      </ul>
+
+      <p>Vous pouvez demander une archive de votre contenu, incluant vos posts, vos médias joints, votre image de profil et votre image d’en-tête.</p>
+
+      <p>Vous pouvez, à n’importe quel moment, supprimer votre compte de manière définitive.</p>
+
+      <hr class="spacer"/>
+
+      <h3 id="cookies">Utilisons-nous des témoins de connexion ?</h3>
+
+      <p>Oui. Les témoins de connexion sont de petits fichiers qu’un site ou un service transféres sur le disque dur de votre ordinateur via votre navigateur web (si vous l’avez autorisé). Ces témoins permettent au site de reconnaître votre navigateur et de, dans le cas où vous possédez un compte, de vous associer avec ce dernier.</p>
+
+      <p>Nous utilisons les témoins de connexion comme un moyen de comprendre et de nous souvenir de vos préférences pour vos prochaines visites.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="disclose">Divulguons-nous des informations à des tierces parties ?</h3>
+
+      <p>Nous ne vendons, n’échangeons ou ne transférons d’une quelque manière que soit des informations permettant de vous identifier personnellement. Cela n’inclut pas les tierces parties de confiance qui nous aident à opérer ce site, à conduire nos activités commerciales ou à vous servir, tant qu’elles acceptent de garder ces informations confidentielles. Nous sommes également susceptibles de partager vos informations quand nous pensons que c’est nécessaire pour nous conformer à la loi, pour appliquer les politiques de notre site ainsi que pour défendre nos droits, notre propriété, notre sécurité et celles et ceux d’autres personnes.</p>
+
+      <p>Votre contenu public peut être téléchargé par d’autres serveurs du réseau. Dans le cas où vos abonné·e·s et vos destinataires résideraient sur des serveurs différents du vôtre, vos posts publics et abonné·e·s uniquement peuvent être délivrés vers les serveurs de vos abonné·e·s tandis que vos messages directs sont délivrés aux serveurs de vos destinataires.</p>
+
+      <p>Quand vous autorisez une application à utiliser votre compte, en fonction de l’étendue des permissions que vous approuvez, il est possible qu’elle puisse accéder aux informations publiques de votre profil, votre liste d’abonnements, votre liste d’abonné·e·s, vos listes, tout vos posts et vos favoris. Les applications ne peuvent en aucun cas accéder à votre adresse électronique et à votre mot de passe.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="children">Utilisation de ce site par les enfants</h3>
+
+      <p>Si ce serveur est situé dans dans l’UE ou l’EEE : Notre site, produits et services sont tous destinés à des personnes âgées de 16 ans ou plus. Si vous avez moins de 16 ans, en application du RGPD (<a href="https://fr.wikipedia.org/wiki/R%C3%A8glement_g%C3%A9n%C3%A9ral_sur_la_protection_des_donn%C3%A9es">Règlement Général sur la Protection des Données</a>), merci de ne pas utiliser ce site.</p>
+
+      <p>Si ce serveur est situé dans aux États-Unis d’Amérique : Notre site, produits et services sont tous destinés à des personnes âgées de 13 ans ou plus. Si vous avez moins de 13 ans, en application du COPPA (<a href="https://fr.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>), merci de ne pas utiliser ce site.</p>
+
+      <p>Les exigences légales peuvent être différentes si ce serveur se trouve dans une autre juridiction.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="changes">Modifications de notre politique de confidentialité</h3>
+
+      <p>Dans le cas où nous déciderions de changer notre politique de confidentialité, nous posterons les modifications sur cette page.</p>
+
+      <p>Ce document est publié sous lincence CC-BY-SA. Il a été mis à jours pour la dernière fois le 7 mars 2018.</p>
+
+      <p>Originellement adapté de la <a href="https://github.com/discourse/discourse">politique de confidentialité de Discourse</a>.</p>
+    title: "%{instance} Conditions d’utilisation et politique de confidentialité"
   themes:
     contrast: Contraste élevé
     default: Mastodon
@@ -758,8 +861,8 @@ fr:
     enabled_success: Identification à deux facteurs activée avec succès
     generate_recovery_codes: Générer les codes de récupération
     instructions_html: "<strong>Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone</strong>. Désormais, cette application génèrera des jetons que vous devrez saisir à chaque connexion."
-    lost_recovery_codes: Les codes de récupération vous permettent de retrouver les accès à votre comptre si vous perdez votre téléphone. Si vous perdez vos codes de récupération, vous pouvez les générer à nouveau ici. Vos anciens codes de récupération seront invalidés.
-    manual_instructions: 'Si vous ne pouvez pas scanner ce QR code et devez l’entrer manuellement, voici le secret en clair :'
+    lost_recovery_codes: Les codes de récupération vous permettent de retrouver les accès à votre compte si vous perdez votre téléphone. Si vous perdez vos codes de récupération, vous pouvez les générer à nouveau ici. Vos anciens codes de récupération seront invalidés.
+    manual_instructions: 'Si vous ne pouvez pas scanner ce QR code et devez l’entrer manuellement, voici le secret en clair :'
     recovery_codes: Codes de récupération
     recovery_codes_regenerated: Codes de récupération régénérés avec succès
     recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver l’accès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
@@ -767,30 +870,30 @@ fr:
     wrong_code: Les codes entrés sont incorrects ! L’heure du serveur et celle de votre appareil sont-elles correctes ?
   user_mailer:
     backup_ready:
-      explanation: Vous avez demandé une sauvegarde complète de votre compte Mastodon. Elle est maintenant prête à être téléchargée !
+      explanation: Vous avez demandé une sauvegarde complète de votre compte Mastodon. Elle est maintenant prête à être téléchargée !
       subject: Votre archive est prête à être téléchargée
-      title: Retrait de l'archive
+      title: Récupération de l’archive
     welcome:
       edit_profile_action: Configuration du profil
-      edit_profile_step: Vous pouvez personnaliser votre profil en téléchargeant un avatar, une image d'en-tête, en changeant votre pseudo et plus encore. Si vous souhaitez examiner les nouveaux abonnés avant qu'ils ne soient autorisés à vous suivre, vous pouvez verrouiller votre compte.
+      edit_profile_step: Vous pouvez personnaliser votre profil en téléchargeant un avatar, une image d’en-tête, en changeant votre pseudo et plus encore. Si vous souhaitez examiner les nouveaux·lles abonné·e·s avant qu’il·elle·s ne soient autorisé·e·s à vous suivre, vous pouvez verrouiller votre compte.
       explanation: Voici quelques conseils pour vous aider à démarrer
       final_action: Commencer à publier
-      final_step: 'Commencez à poster ! Même sans abonné·es, vos messages publics peuvent être vus par d''autres, par exemple sur la chronologie locale et dans les hashtags. Vous pouvez vous présenter sur le hashtag #introductions.'
-      full_handle: Votre pleine maîtrise
-      full_handle_hint: C'est ce que vous diriez à vos amis pour qu'ils puissent vous envoyer un message ou vous suivre à partir d'une autre instance.
+      final_step: 'Commencez à poster ! Même sans abonné·e·s, vos messages publics peuvent être vus par d’autres, par exemple sur le fil public local et dans les hashtags. Vous pouvez vous présenter sur le hashtag #introductions.'
+      full_handle: Votre identifiant complet
+      full_handle_hint: C’est ce que vous diriez à vos ami·e·s pour qu’il·elle·s puissent vous envoyer un message ou vous suivre à partir d’une autre instance.
       review_preferences_action: Modifier les préférences
-      review_preferences_step: Assurez-vous de définir vos préférences, telles que les courriels que vous aimeriez recevoir ou le niveau de confidentialité auquel vous aimeriez que vos messages soient soumis par défaut. Si vous n'avez pas le mal des transports, vous pouvez choisir d'activer la lecture automatique GIF.
+      review_preferences_step: Assurez-vous de définir vos préférences, telles que les courriels que vous aimeriez recevoir ou le niveau de confidentialité auquel vous aimeriez que vos messages soient soumis par défaut. Si vous n’avez pas le mal des transports, vous pouvez choisir d’activer la lecture automatique des GIF.
       subject: Bienvenue sur Mastodon
-      tip_bridge_html: Si vous venez de Twitter, vous pouvez retrouver vos amis sur Mastodon en utilisant le <a href="%{bridge_url}">bridge app</a>. Cela ne fonctionne que s'ils ont aussi utilisé cette application !
-      tip_federated_timeline: La chronologie fédérée est une vue en direct du réseau Mastodon. Mais elle n'inclut que les personnes auxquelles vos voisin·es sont abonné·es, donc elle n'est pas complète.
-      tip_following: Vous suivez les administrateurs et administratrices de votre serveur par défaut. Pour trouver d'autres personnes intéressantes, consultez les chronologies locales et fédérées.
-      tip_local_timeline: La chronologie locale est une vue des personnes sur %{instance}. Ce sont vos voisines et voisins immédiats !
-      tip_mobile_webapp: Si votre navigateur mobile vous propose d'ajouter Mastodon à votre écran d'accueil, vous pouvez recevoir des notifications. Il agit comme une application native de bien des façons !
+      tip_bridge_html: Si vous venez de Twitter, vous pouvez retrouver vos ami·e·s sur Mastodon en utilisant l’<a href="%{bridge_url}">application de mise en relation</a>. Cela ne fonctionne que s’il·elle·s ont aussi utilisé cette application !
+      tip_federated_timeline: La fil public global est une vue en direct du réseau Mastodon. Mais elle n’inclut que les personnes auxquelles vos voisin·es sont abonné·e·s, donc elle n’est pas complète.
+      tip_following: Vous suivez les administrateur·rice·s de votre serveur par défaut. Pour trouver d’autres personnes intéressantes, consultez les fils publics local et global.
+      tip_local_timeline: Le fil public local est une vue des personnes sur %{instance}. Ce sont vos voisines et voisins immédiats !
+      tip_mobile_webapp: Si votre navigateur mobile vous propose d’ajouter Mastodon à votre écran d’accueil, vous pouvez recevoir des notifications. Il agit comme une application native de bien des façons !
       tips: Astuces
-      title: Bienvenue à bord, %{name} !
+      title: Bienvenue à bord, %{name} !
   users:
     invalid_email: L’adresse courriel est invalide
     invalid_otp_token: Le code d’authentification à deux facteurs est invalide
     otp_lost_help_html: Si vous perdez accès aux deux, vous pouvez contacter %{email}
     seamless_external_login: Vous êtes connecté via un service externe, donc les paramètres concernant le mot de passe et le courriel ne sont pas disponibles.
-    signed_in_as: 'Connecté·e en tant que :'
+    signed_in_as: 'Connecté·e en tant que :'
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 47177b809..abf6722b1 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -30,10 +30,14 @@ ja:
     other_instances: 他のインスタンス
     privacy_policy: プライバシーポリシー
     source_code: ソースコード
-    status_count_after: トゥート
+    status_count_after:
+      one: トゥート
+      other: トゥート
     status_count_before: トゥート数
     terms: 利用規約
-    user_count_after: 人
+    user_count_after:
+      one: 人
+      other: 人
     user_count_before: ユーザー数
     what_is_mastodon: Mastodon とは?
   accounts:
@@ -49,7 +53,7 @@ ja:
     people_followed_by: "%{name} さんがフォロー中のアカウント"
     people_who_follow: "%{name} さんをフォロー中のアカウント"
     pin_errors:
-      following: 推薦したい人はあなたが既にフォローしている必要があります
+      following: おすすめしたい人はあなたが既にフォローしている必要があります
     posts: トゥート
     posts_with_replies: トゥートと返信
     reserved_username: このユーザー名は予約されています
@@ -185,7 +189,7 @@ ja:
         unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました"
         update_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を更新しました"
         update_status: "%{name} さんが %{target} さんの投稿を更新しました"
-      deleted_status: "(削除されました)"
+      deleted_status: "(削除済)"
       title: 操作履歴
     custom_emojis:
       by_domain: ドメイン
@@ -346,6 +350,8 @@ ja:
       contact_information:
         email: ビジネスメールアドレス
         username: 連絡先のユーザー名
+      custom_css:
+        title: カスタムCSS
       hero:
         desc_html: フロントページに表示されます。サイズは600x100px以上推奨です。未設定の場合、インスタンスのサムネイルが使用されます
         title: ヒーローイメージ
@@ -414,6 +420,12 @@ ja:
       last_delivery: 最終配送
       title: WebSub
       topic: トピック
+    suspensions:
+      bad_acct_msg: 値が一致しませんでした。停止しようとしているアカウントに間違いはありませんか?
+      hint_html: 'アカウントの停止を確認するには、以下のフィールドに %{value} と入力してください:'
+      proceed: 完全に活動停止させる
+      title: "%{acct} を停止"
+      warning_html: 'このアカウントを停止すると、このアカウントから次のようなデータが<strong>不可逆的に</strong>削除されます:'
     title: 管理
   admin_mailer:
     new_report:
@@ -645,7 +657,6 @@ ja:
           quadrillion: Q
           thousand: K
           trillion: T
-          unit: ''
   pagination:
     newer: 新しいトゥート
     next: 次
@@ -868,7 +879,7 @@ ja:
     recovery_codes_regenerated: リカバリーコードが再生成されました
     recovery_instructions_html: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。<strong>リカバリーコードは大切に保全してください。</strong>たとえば印刷してほかの重要な書類と一緒に保管することができます。
     setup: 初期設定
-    wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
+    wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していますか?
   user_mailer:
     backup_ready:
       explanation: Mastodonアカウントのアーカイブを受け付けました。今すぐダウンロードできます!
diff --git a/config/locales/ka.yml b/config/locales/ka.yml
index 86d982665..c1105b017 100644
--- a/config/locales/ka.yml
+++ b/config/locales/ka.yml
@@ -6,6 +6,7 @@ ka:
     about_this: შესახებ
     administered_by: 'ადმინისტრატორი:'
     api: აპი
+    apps: მობილური აპლიკაციები
     closed_registrations: რეგისტრაციები ამჟამად ინსტანციაზე დახურულია. თუმცა! ანგარიშის შესაქმნელად შეგიძლიათ იპოვოთ სხვა ინსტანცია და იმავე ქსელზე იქონიოთ წვდომა იქიდან.
     contact: კონტაქტი
     contact_missing: არაა დაყენებული
@@ -281,6 +282,7 @@ ka:
       search: ძებნა
       title: ცნობილი ინსტანციები
     invites:
+      deactivate_all: ყველას დეაქტივაცია
       filter:
         all: ყველა
         available: ხელმისაწვდომი
@@ -660,6 +662,9 @@ ka:
     no_account_html: არ გაქვთ ანგარიში? შეგიძლიათ <a href='%{sign_up_path}' target='_blank'>დარეგისტრირდეთ აქ</a>
     proceed: გააგრძელეთ გასაყოლად
     prompt: 'თქვენ გაჰყვებით:'
+  remote_interaction:
+    proceed: გააგრძელეთ ურთიერთქმედება
+    prompt: 'თქვენ გსურთ ურთიერთქმედება ამ ტუტთან:'
   remote_unfollow:
     error: შეცდომა
     title: სათაური
@@ -743,6 +748,7 @@ ka:
       private: არა-საჯარო ტუტი ვერ აიპინება
       reblog: ბუსტი ვერ აიპინება
     show_more: მეტის ჩვენება
+    sign_in_to_participate: საუბარში მონაწილეობისთვის გაიარეთ ავტორიზაცია
     title: '%{name}: "%{quote}"'
     visibilities:
       private: მხოლოდ-მიმდევრები
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index b5da99049..1ae825443 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -640,7 +640,7 @@ ko:
     publishing: 퍼블리싱
     web: 웹
   remote_follow:
-    acct: 아이디@도메인을 입력해 주십시오
+    acct: 당신이 사용하는 아이디@도메인을 입력해 주십시오
     missing_resource: 리디렉션 대상을 찾을 수 없습니다
     no_account_html: 계정이 없나요? <a href='%{sign_up_path}' target='_blank'>여기에서 가입 할 수 있습니다</a>
     proceed: 팔로우 하기
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index f58f32668..c7c74b239 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -30,10 +30,16 @@ pl:
     other_instances: Lista instancji
     privacy_policy: Polityka prywatności
     source_code: Kod źródłowy
-    status_count_after: wpisów
+    status_count_after:
+      few: wpisów
+      many: wpisów
+      one: wpisu
     status_count_before: Są autorami
     terms: Zasady użytkowania
-    user_count_after: użytkowników
+    user_count_after:
+      few: użytkowników
+      many: użytkowników
+      one: użytkownik
     user_count_before: Z serwera korzysta
     what_is_mastodon: Czym jest Mastodon?
   accounts:
@@ -404,6 +410,7 @@ pl:
       media:
         title: Media
       no_media: Bez zawartości multimedialnej
+      no_status_selected: Żaden wpis nie został zmieniony, bo żaden nie został wybrany
       title: Wpisy konta
       with_media: Z zawartością multimedialną
     subscriptions:
@@ -413,6 +420,12 @@ pl:
       last_delivery: Ostatnio doręczono
       title: WebSub
       topic: Temat
+    suspensions:
+      bad_acct_msg: Zawartość potwierdzenia nie zgadza się. Czy próbujesz zawiesić właściwe konto?
+      hint_html: 'Aby potwierdzić zawieszenie konta, wprowadź %{value} w poniższe pole:'
+      proceed: Przejdź
+      title: Zawieś %{acct}
+      warning_html: 'Zawieszenie konta będzie skutkowało <strong>nieodwracalnym</strong> usunięciem danych z tego konta, wliczając:'
     title: Administracja
   admin_mailer:
     new_report:
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index a023faf92..c13cfa50d 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -3,34 +3,34 @@ fr:
   simple_form:
     hints:
       defaults:
-        autofollow: Les personnes qui s'inscrivent grâce à l'invitation vous suivront automatiquement
+        autofollow: Les personnes qui s’inscrivent grâce à l’invitation vous suivront automatiquement
         avatar: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à %{dimensions}px
         bot: Ce compte exécute principalement des actions automatisées et pourrait ne pas être surveillé
-        context: Un ou plusieurs contextes où le filtre devrait s'appliquer
+        context: Un ou plusieurs contextes où le filtre devrait s’appliquer
         digest: Uniquement envoyé après une longue période d’inactivité et uniquement si vous avez reçu des messages personnels pendant votre absence
         display_name:
           one: <span class="name-counter">1</span> caractère restant
           other: <span class="name-counter">%{count}</span> caractères restants
-        fields: Vous pouvez avoir jusqu'à 4 éléments affichés en tant que tableau sur votre profil
+        fields: Vous pouvez avoir jusqu’à 4 éléments affichés en tant que tableau sur votre profil
         header: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à %{dimensions}px
-        inbox_url: Copiez l'URL depuis la page d'accueil du relais que vous souhaitez utiliser
+        inbox_url: Copiez l’URL depuis la page d’accueil du relais que vous souhaitez utiliser
         irreversible: Les pouets filtrés disparaîtront irrémédiablement, même si le filtre est supprimé plus tard
-        locale: La langue de l'interface-utilisateur, des courriels, et des notifications
+        locale: La langue de l’interface, des courriels et des notifications
         locked: Vous devrez approuver chaque abonné⋅e et vos statuts ne s’afficheront qu’à vos abonné⋅es
         note:
           one: <span class="note-counter">1</span> caractère restant
           other: <span class="note-counter">%{count}</span> caractères restants
-        phrase: Sera trouvé sans que la case ou l'avertissement de contenu du pouet soit pris en compte
-        scopes: À quelles APIs l'application sera autorisée à accéder. Si vous sélectionnez un périmètre de haut-niveau, vous n'avez pas besoin de sélectionner les individuels.
-        setting_default_language: La langue de vos pouets peut être détectée automatiquement, mais ça n'est pas toujours pertinent
+        phrase: Sera trouvé sans que la case ou l’avertissement de contenu du pouet soit pris en compte
+        scopes: À quelles APIs l’application sera autorisée à accéder. Si vous sélectionnez un périmètre de haut-niveau, vous n’avez pas besoin de sélectionner les individuels.
+        setting_default_language: La langue de vos pouets peut être détectée automatiquement, mais ça n’est pas toujours pertinent
         setting_hide_network: Ceux que vous suivez et ceux qui vous suivent ne seront pas affichés sur votre profil
         setting_noindex: Affecte votre profil public ainsi que vos statuts
         setting_theme: Affecte l’apparence de Mastodon quand vous êtes connecté·e depuis n’importe quel appareil.
-        whole_word: Lorsque le mot-clef ou la phrase-clef est uniquement alphanumérique, ça sera uniquement appliqué s'il correspond au mot entier
+        whole_word: Lorsque le mot-clef ou la phrase-clef est uniquement alphanumérique, ça sera uniquement appliqué s’il correspond au mot entier
       imports:
         data: Un fichier CSV généré par une autre instance de Mastodon
       sessions:
-        otp: 'Entrez le code d’authentification à deux facteurs généré par votre téléphone ou utilisez un de vos codes de récupération :'
+        otp: 'Entrez le code d’authentification à deux facteurs généré par l''application de votre téléphone ou utilisez un de vos codes de récupération :'
       user:
         chosen_languages: Lorsque coché, seuls les pouets dans les langues sélectionnées seront affichés sur les fils publics
     labels:
@@ -55,9 +55,9 @@ fr:
         header: Image d’en-tête
         inbox_url: URL de la boîte de relais
         irreversible: Supprimer plutôt que de cacher
-        locale: Langue de l'interface
+        locale: Langue de l’interface
         locked: Verrouiller le compte
-        max_uses: Nombre maximum d'utilisations
+        max_uses: Nombre maximum d’utilisations
         new_password: Nouveau mot de passe
         note: Présentation
         otp_attempt: Code d’identification à deux facteurs
@@ -79,7 +79,7 @@ fr:
         severity: Sévérité
         type: Type d’import
         username: Identifiant
-        username_or_email: Nom d'utilisateur ou courriel
+        username_or_email: Nom d’utilisateur·ice ou courriel
         whole_word: Mot entier
       interactions:
         must_be_follower: Masquer les notifications des personnes qui ne vous suivent pas
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
index 9008d2338..32ea32d68 100644
--- a/config/locales/simple_form.pl.yml
+++ b/config/locales/simple_form.pl.yml
@@ -15,6 +15,7 @@ pl:
           other: Pozostało <span class="name-counter">%{count}</span> znaków
         fields: Możesz ustawić maksymalnie 4 niestandardowe pola wyświetlane jako tabela na Twoim profilu
         header: PNG, GIF lub JPG. Maksymalnie %{size}. Zostanie zmniejszony do %{dimensions}px
+        inbox_url: Skopiuj adres ze strony głównej przekaźnika, którego chcesz użyć
         irreversible: Filtrowane wpisy znikną bezpowrotnie, nawet gdy filtr zostanie usunięty
         locale: Język interfejsu, wiadomości e-mail i powiadomieniach push
         locked: Musisz akceptować prośby o śledzenie
@@ -24,10 +25,12 @@ pl:
           one: Pozostał <span class="name-counter">1</span> znak
           other: Pozostało <span class="name-counter">%{count}</span> znaków
         phrase: Zostanie wykryte nawet, gdy znajduje się za ostrzeżeniem o zawartości
+        scopes: Wybór API, do których aplikacja będzie miała dostęp. Jeżeli wybierzesz nadrzędny zakres, nie musisz wybierać jego elementów.
         setting_default_language: Język Twoich wpisów może być wykrywany automatycznie, ale nie zawsze jest to dokładne
         setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne
         setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów
         setting_skin: Zmienia wygląd używanej odmiany Mastodona
+        whole_word: Jeśli słowo lub fraza składa się jedynie z liter lub cyfr, filtr będzie zastosowany tylko do pełnych wystąpień
       imports:
         data: Plik CSV wyeksportowany z innej instancji Mastodona
       sessions:
@@ -54,6 +57,7 @@ pl:
         expires_in: Wygaśnie po
         fields: Metadane profilu
         header: Nagłówek
+        inbox_url: Adres skrzynki przekaźnika
         irreversible: Usuwaj zamiast ukrywać
         locale: Język interfejsu
         locked: Ustaw konto jako prywatne
diff --git a/config/routes.rb b/config/routes.rb
index 37bb4967b..7c8f04c8d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,7 @@ Rails.application.routes.draw do
   get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
   get 'manifest', to: 'manifests#show', defaults: { format: 'json' }
   get 'intent', to: 'intents#show'
+  get 'custom.css', to: 'custom_css#show', as: :custom_css
 
   devise_scope :user do
     get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
diff --git a/config/webpack/production.js b/config/webpack/production.js
index 4966807a1..27a78108b 100644
--- a/config/webpack/production.js
+++ b/config/webpack/production.js
@@ -23,8 +23,8 @@ try {
 let attachmentHost;
 
 if (process.env.S3_ENABLED === 'true') {
-  if (process.env.S3_CLOUDFRONT_HOST) {
-    attachmentHost = process.env.S3_CLOUDFRONT_HOST;
+  if (process.env.S3_ALIAS_HOST || process.env.S3_CLOUDFRONT_HOST) {
+    attachmentHost = process.env.S3_ALIAS_HOST || process.env.S3_CLOUDFRONT_HOST;
   } else {
     attachmentHost = process.env.S3_HOSTNAME || `s3-${process.env.S3_REGION || 'us-east-1'}.amazonaws.com`;
   }
diff --git a/lib/cli.rb b/lib/cli.rb
new file mode 100644
index 000000000..60bff4147
--- /dev/null
+++ b/lib/cli.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'thor'
+require_relative 'mastodon/media_cli'
+require_relative 'mastodon/emoji_cli'
+require_relative 'mastodon/accounts_cli'
+module Mastodon
+  class CLI < Thor
+    desc 'media SUBCOMMAND ...ARGS', 'Manage media files'
+    subcommand 'media', Mastodon::MediaCLI
+
+    desc 'emoji SUBCOMMAND ...ARGS', 'Manage custom emoji'
+    subcommand 'emoji', Mastodon::EmojiCLI
+
+    desc 'accounts SUBCOMMAND ...ARGS', 'Manage accounts'
+    subcommand 'accounts', Mastodon::AccountsCLI
+  end
+end
diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb
new file mode 100644
index 000000000..83b69549d
--- /dev/null
+++ b/lib/mastodon/accounts_cli.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'rubygems/package'
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+  class AccountsCLI < Thor
+    option :all, type: :boolean
+    desc 'rotate [USERNAME]', 'Generate and broadcast new keys'
+    long_desc <<-LONG_DESC
+      Generate and broadcast new RSA keys as part of security
+      maintenance.
+
+      With the --all option, all local accounts will be subject
+      to the rotation. Otherwise, and by default, only a single
+      account specified by the USERNAME argument will be
+      processed.
+    LONG_DESC
+    def rotate(username = nil)
+      if options[:all]
+        processed = 0
+        delay     = 0
+
+        Account.local.without_suspended.find_in_batches do |accounts|
+          accounts.each do |account|
+            rotate_keys_for_account(account, delay)
+            processed += 1
+            say('.', :green, false)
+          end
+
+          delay += 5.minutes
+        end
+
+        say
+        say("OK, rotated keys for #{processed} accounts", :green)
+      elsif username.present?
+        rotate_keys_for_account(Account.find_local(username))
+        say('OK', :green)
+      else
+        say('No account(s) given', :red)
+      end
+    end
+
+    private
+
+    def rotate_keys_for_account(account, delay = 0)
+      old_key = account.private_key
+      new_key = OpenSSL::PKey::RSA.new(2048).to_pem
+      account.update(private_key: new_key)
+      ActivityPub::UpdateDistributionWorker.perform_in(delay, account.id, sign_with: old_key)
+    end
+  end
+end
diff --git a/lib/mastodon/cli_helper.rb b/lib/mastodon/cli_helper.rb
new file mode 100644
index 000000000..8c4d9731c
--- /dev/null
+++ b/lib/mastodon/cli_helper.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+dev_null = Logger.new('/dev/null')
+
+Rails.logger                 = dev_null
+ActiveRecord::Base.logger    = dev_null
+HttpLog.configuration.logger = dev_null
+Paperclip.options[:log]      = false
diff --git a/lib/mastodon/emoji_cli.rb b/lib/mastodon/emoji_cli.rb
new file mode 100644
index 000000000..0a773c771
--- /dev/null
+++ b/lib/mastodon/emoji_cli.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'rubygems/package'
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+# rubocop:disable Rails/Output
+
+module Mastodon
+  class EmojiCLI < Thor
+    option :prefix
+    option :suffix
+    option :overwrite, type: :boolean
+    option :unlisted, type: :boolean
+    desc 'import PATH', 'Import emoji from a TAR archive at PATH'
+    long_desc <<-LONG_DESC
+      Imports custom emoji from a TAR archive specified by PATH.
+
+      Existing emoji will be skipped unless the --overwrite option
+      is provided, in which case they will be overwritten.
+
+      With the --prefix option, a prefix can be added to all
+      generated shortcodes. Likewise, the --suffix option controls
+      the suffix of all shortcodes.
+
+      With the --unlisted option, the processed emoji will not be
+      visible in the emoji picker (but still usable via other means)
+    LONG_DESC
+    def import(path)
+      imported = 0
+      skipped  = 0
+      failed   = 0
+
+      Gem::Package::TarReader.new(Zlib::GzipReader.open(path)) do |tar|
+        tar.each do |entry|
+          next unless entry.file? && entry.full_name.end_with?('.png')
+
+          shortcode    = [options[:prefix], File.basename(entry.full_name, '.*'), options[:suffix]].compact.join
+          custom_emoji = CustomEmoji.local.find_by(shortcode: shortcode)
+
+          if custom_emoji && !options[:overwrite]
+            skipped += 1
+            next
+          end
+
+          custom_emoji ||= CustomEmoji.new(shortcode: shortcode, domain: nil)
+          custom_emoji.image = StringIO.new(entry.read)
+          custom_emoji.image_file_name = File.basename(entry.full_name)
+          custom_emoji.visible_in_picker = !options[:unlisted]
+
+          if custom_emoji.save
+            imported += 1
+          else
+            failed += 1
+            say('Failure/Error: ', :red)
+            say(entry.full_name)
+            say('    ' + custom_emoji.errors[:image].join(', '), :red)
+          end
+        end
+      end
+
+      puts
+      say("Imported #{imported}, skipped #{skipped}, failed to import #{failed}", color(imported, skipped, failed))
+    end
+
+    private
+
+    def color(green, _yellow, red)
+      if !green.zero? && red.zero?
+        :green
+      elsif red.zero?
+        :yellow
+      else
+        :red
+      end
+    end
+  end
+end
+
+# rubocop:enable Rails/Output
diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb
new file mode 100644
index 000000000..ee28270da
--- /dev/null
+++ b/lib/mastodon/media_cli.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+# rubocop:disable Rails/Output
+
+module Mastodon
+  class MediaCLI < Thor
+    option :days, type: :numeric, default: 7
+    option :background, type: :boolean, default: false
+    desc 'remove', 'Remove remote media files'
+    long_desc <<-DESC
+      Removes locally cached copies of media attachments from other servers.
+
+      The --days option specifies how old media attachments have to be before
+      they are removed. It defaults to 7 days.
+
+      With the --background option, instead of deleting the files sequentially,
+      they will be queued into Sidekiq and the command will exit as soon as
+      possible. In Sidekiq they will be processed with higher concurrency, but
+      it may impact other operations of the Mastodon server, and it may overload
+      the underlying file storage.
+    DESC
+    def remove
+      time_ago  = options[:days].days.ago
+      queued    = 0
+      processed = 0
+
+      MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id).reorder(nil).find_in_batches do |media_attachments|
+        if options[:background]
+          queued += media_attachments.size
+          Maintenance::UncacheMediaWorker.push_bulk(media_attachments.map(&:id))
+        else
+          media_attachments.each do |m|
+            Maintenance::UncacheMediaWorker.new.perform(m)
+            say('.', :green, false)
+            processed += 1
+          end
+        end
+      end
+
+      say
+
+      if options[:background]
+        say("Scheduled the deletion of #{queued} media attachments", :green)
+      else
+        say("Removed #{processed} media attachments", :green)
+      end
+    end
+  end
+end
+
+# rubocop:enable Rails/Output
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 1aa9b413e..d07108ad4 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -9,11 +9,11 @@ module Mastodon
     end
 
     def minor
-      4
+      5
     end
 
     def patch
-      3
+      0
     end
 
     def pre
@@ -21,7 +21,7 @@ module Mastodon
     end
 
     def flags
-      ''
+      'rc1'
     end
 
     def to_a
diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake
index 32039c31d..b76e90131 100644
--- a/lib/tasks/db.rake
+++ b/lib/tasks/db.rake
@@ -18,7 +18,7 @@ def each_schema_load_environment
   #    needing to do the same, and we can't even use the same method
   #    to do it.
 
-  if Rails.env == 'development'
+  if Rails.env.development?
     test_conf = ActiveRecord::Base.configurations['test']
 
     if test_conf['database']&.present?
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 191ce634c..649a22a0b 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -222,7 +222,7 @@ namespace :mastodon do
         end
 
         if prompt.yes?('Do you want to access the uploaded files from your own domain?')
-          env['S3_CLOUDFRONT_HOST'] = prompt.ask('Domain for uploaded files:') do |q|
+          env['S3_ALIAS_HOST'] = prompt.ask('Domain for uploaded files:') do |q|
             q.required true
             q.default "files.#{env['LOCAL_DOMAIN']}"
             q.modify :strip
@@ -280,14 +280,14 @@ namespace :mastodon do
 
         begin
           ActionMailer::Base.smtp_settings = {
-            :port                 => env['SMTP_PORT'],
-            :address              => env['SMTP_SERVER'],
-            :user_name            => env['SMTP_LOGIN'].presence,
-            :password             => env['SMTP_PASSWORD'].presence,
-            :domain               => env['LOCAL_DOMAIN'],
-            :authentication       => env['SMTP_AUTH_METHOD'] == 'none' ? nil : env['SMTP_AUTH_METHOD'] || :plain,
-            :openssl_verify_mode  => env['SMTP_OPENSSL_VERIFY_MODE'],
-            :enable_starttls_auto => true,
+            port:                 env['SMTP_PORT'],
+            address:              env['SMTP_SERVER'],
+            user_name:            env['SMTP_LOGIN'].presence,
+            password:             env['SMTP_PASSWORD'].presence,
+            domain:               env['LOCAL_DOMAIN'],
+            authentication:       env['SMTP_AUTH_METHOD'] == 'none' ? nil : env['SMTP_AUTH_METHOD'] || :plain,
+            openssl_verify_mode:  env['SMTP_OPENSSL_VERIFY_MODE'],
+            enable_starttls_auto: true,
           }
 
           ActionMailer::Base.default_options = {
@@ -326,13 +326,11 @@ namespace :mastodon do
 
         if prompt.yes?('Prepare the database now?')
           prompt.say 'Running `RAILS_ENV=production rails db:setup` ...'
-          prompt.say "\n"
+          prompt.say "\n\n"
 
           if cmd.run!({ RAILS_ENV: 'production', SAFETY_ASSURED: 1 }, :rails, 'db:setup').failure?
-            prompt.say "\n"
             prompt.error 'That failed! Perhaps your configuration is not right'
           else
-            prompt.say "\n"
             prompt.ok 'Done!'
           end
         end
@@ -343,13 +341,11 @@ namespace :mastodon do
 
         if prompt.yes?('Compile the assets now?')
           prompt.say 'Running `RAILS_ENV=production rails assets:precompile` ...'
-          prompt.say "\n"
+          prompt.say "\n\n"
 
           if cmd.run!({ RAILS_ENV: 'production' }, :rails, 'assets:precompile').failure?
-            prompt.say "\n"
             prompt.error 'That failed! Maybe you need swap space?'
           else
-            prompt.say "\n"
             prompt.say 'Done!'
           end
         end
@@ -394,12 +390,6 @@ namespace :mastodon do
     end
   end
 
-  desc 'Execute daily tasks (deprecated)'
-  task :daily do
-    # No-op
-    # All of these tasks are now executed via sidekiq-scheduler
-  end
-
   desc 'Turn a user into an admin, identified by the USERNAME environment variable'
   task make_admin: :environment do
     include RoutingHelper
@@ -494,12 +484,6 @@ namespace :mastodon do
   end
 
   namespace :media do
-    desc 'Removes media attachments that have not been assigned to any status for longer than a day (deprecated)'
-    task clear: :environment do
-      # No-op
-      # This task is now executed via sidekiq-scheduler
-    end
-
     desc 'Remove media attachments attributed to silenced accounts'
     task remove_silenced: :environment do
       nb_media_attachments = 0
@@ -512,14 +496,10 @@ namespace :mastodon do
 
     desc 'Remove cached remote media attachments that are older than NUM_DAYS. By default 7 (week)'
     task remove_remote: :environment do
-      time_ago = ENV.fetch('NUM_DAYS') { 7 }.to_i.days.ago
-      nb_media_attachments = 0
-
-      MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id).reorder(nil).find_in_batches do |media_attachments|
-        nb_media_attachments += media_attachments.length
-        Maintenance::UncacheMediaWorker.push_bulk(media_attachments.map(&:id))
-      end
-      puts "Scheduled the deletion of #{nb_media_attachments} media attachments"
+      puts 'Please use `./bin/tootctl media remove --help` directly'.colorize(:yellow)
+      require_relative '../mastodon/media_cli'
+      cli = Mastodon::MediaCLI.new([], days: (ENV['NUM_DAYS'] || 7).to_i)
+      cli.invoke(:remove)
     end
 
     desc 'Set unknown attachment type for remote-only attachments'
@@ -548,21 +528,9 @@ namespace :mastodon do
     task clear: :environment do
       Pubsubhubbub::UnsubscribeWorker.push_bulk(Account.remote.without_followers.where.not(subscription_expires_at: nil).pluck(:id))
     end
-
-    desc 'Re-subscribes to soon expiring PuSH subscriptions (deprecated)'
-    task refresh: :environment do
-      # No-op
-      # This task is now executed via sidekiq-scheduler
-    end
   end
 
   namespace :feeds do
-    desc 'Clear timelines of inactive users (deprecated)'
-    task clear: :environment do
-      # No-op
-      # This task is now executed via sidekiq-scheduler
-    end
-
     desc 'Clear all timelines without regenerating them'
     task clear_all: :environment do
       Redis.current.keys('feed:*').each { |key| Redis.current.del(key) }
@@ -576,21 +544,7 @@ namespace :mastodon do
     end
   end
 
-  namespace :emails do
-    desc 'Send out digest e-mails (deprecated)'
-    task digest: :environment do
-      # No-op
-      # This task is now executed via sidekiq-scheduler
-    end
-  end
-
   namespace :users do
-    desc 'Clear out unconfirmed users (deprecated)'
-    task clear: :environment do
-      # No-op
-      # This task is now executed via sidekiq-scheduler
-    end
-
     desc 'List e-mails of all admin users'
     task admins: :environment do
       puts 'Admin user emails:'
@@ -757,10 +711,10 @@ namespace :mastodon do
       pastel = Pastel.new
 
       duplicate_masters.each do |account|
-        puts pastel.yellow("First of their name: ") + pastel.bold(account.username) + " (#{admin_account_url(account.id)})"
+        puts pastel.yellow('First of their name: ') + pastel.bold(account.username) + " (#{admin_account_url(account.id)})"
 
         Account.where('lower(username) = ?', account.username.downcase).where.not(id: account.id).each do |duplicate|
-          puts "  " + pastel.red("Duplicate: ") + admin_account_url(duplicate.id)
+          puts '  ' + pastel.red('Duplicate: ') + admin_account_url(duplicate.id)
         end
       end
     end
diff --git a/scalingo.json b/scalingo.json
index 873731ac9..dd8fb5530 100644
--- a/scalingo.json
+++ b/scalingo.json
@@ -91,6 +91,11 @@
       "description": "Internal scalingo configuration",
       "required": true,
       "value": "https://github.com/Scalingo/multi-buildpack.git"
+    },
+    "WITH_FFPROBE": {
+      "description": "Internal scalingo configuration to install ffprobe",
+      "required": true,
+      "value": "true"
     }
   },
   "scripts": {
diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb
index 33df32195..95ec17d6f 100644
--- a/spec/controllers/api/v1/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/mutes_controller_spec.rb
@@ -3,24 +3,67 @@ require 'rails_helper'
 RSpec.describe Api::V1::MutesController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:mutes') }
+  let(:user)   { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:scopes) { 'read:mutes' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
-  before do
-    Fabricate(:mute, account: user.account, hide_notifications: false)
-    allow(controller).to receive(:doorkeeper_token) { token }
-  end
+  before { allow(controller).to receive(:doorkeeper_token) { token } }
 
   describe 'GET #index' do
-    it 'returns http success' do
+    it 'limits according to limit parameter' do
+      2.times.map { Fabricate(:mute, account: user.account) }
       get :index, params: { limit: 1 }
+      expect(body_as_json.size).to eq 1
+    end
+
+    it 'queries mutes in range according to max_id' do
+      mutes = 2.times.map { Fabricate(:mute, account: user.account) }
+
+      get :index, params: { max_id: mutes[1] }
 
+      expect(body_as_json.size).to eq 1
+      expect(body_as_json[0][:id]).to eq mutes[0].target_account_id.to_s
+    end
+
+    it 'queries mutes in range according to since_id' do
+      mutes = 2.times.map { Fabricate(:mute, account: user.account) }
+
+      get :index, params: { since_id: mutes[0] }
+
+      expect(body_as_json.size).to eq 1
+      expect(body_as_json[0][:id]).to eq mutes[1].target_account_id.to_s
+    end
+
+    it 'sets pagination header for next path' do
+      mutes = 2.times.map { Fabricate(:mute, account: user.account) }
+      get :index, params: { limit: 1, since_id: mutes[0] }
+      expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq api_v1_mutes_url(limit: 1, max_id: mutes[1])
+    end
+
+    it 'sets pagination header for previous path' do
+      mute = Fabricate(:mute, account: user.account)
+      get :index
+      expect(response.headers['Link'].find_link(['rel', 'prev']).href).to eq api_v1_mutes_url(since_id: mute)
+    end
+
+    it 'returns http success' do
+      get :index
       expect(response).to have_http_status(200)
     end
+
+    context 'with wrong scopes' do
+      let(:scopes) { 'write:mutes' }
+
+      it 'returns http forbidden' do
+        get :index
+        expect(response).to have_http_status(403)
+      end
+    end
   end
 
   describe 'GET #details' do
     before do
+      Fabricate(:mute, account: user.account, hide_notifications: false)
       get :details, params: { limit: 1 }
     end
 
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index c6c78d3f7..2603688be 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -92,6 +92,43 @@ describe ApplicationController, type: :controller do
     end
   end
 
+  describe 'helper_method :current_flavour' do
+    it 'returns "glitch" when theme wasn\'t changed in admin settings' do
+      allow(Setting).to receive(:default_settings).and_return({'skin' => 'default'})
+      allow(Setting).to receive(:default_settings).and_return({'flavour' => 'glitch'})
+
+      expect(controller.view_context.current_flavour).to eq 'glitch'
+    end
+
+    it 'returns instances\'s flavour when user is not signed in' do
+      allow(Setting).to receive(:[]).with('skin').and_return 'default'
+      allow(Setting).to receive(:[]).with('flavour').and_return 'vanilla'
+
+      expect(controller.view_context.current_flavour).to eq 'vanilla'
+    end
+
+    it 'returns instances\'s default flavour when user didn\'t set theme' do
+      current_user = Fabricate(:user)
+      sign_in current_user
+
+      allow(Setting).to receive(:[]).with('skin').and_return 'default'
+      allow(Setting).to receive(:[]).with('flavour').and_return 'vanilla'
+
+      expect(controller.view_context.current_flavour).to eq 'vanilla'
+    end
+
+    it 'returns user\'s flavour when it is set' do
+      current_user = Fabricate(:user)
+      current_user.settings['flavour'] = 'glitch'
+      sign_in current_user
+
+      allow(Setting).to receive(:[]).with('skin').and_return 'default'
+      allow(Setting).to receive(:[]).with('flavour').and_return 'vanilla'
+
+      expect(controller.view_context.current_flavour).to eq 'glitch'
+    end
+  end
+
   context 'ActionController::RoutingError' do
     subject do
       routes.draw { get 'routing_error' => 'anonymous#routing_error' }
diff --git a/spec/fabricators/relay_fabricator.rb b/spec/fabricators/relay_fabricator.rb
index 2c9df4ad3..3f8726f6b 100644
--- a/spec/fabricators/relay_fabricator.rb
+++ b/spec/fabricators/relay_fabricator.rb
@@ -1,4 +1,4 @@
 Fabricator(:relay) do
   inbox_url "https://example.com/inbox"
-  enabled   true
+  state   :idle
 end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index ac54f1f70..3ccd96f44 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -17,7 +17,7 @@ describe ApplicationHelper do
     end
   end
 
-  describe 'add_rtl_body_class' do
+  describe 'locale_direction' do
     around do |example|
       current_locale = I18n.locale
       example.run
@@ -26,22 +26,22 @@ describe ApplicationHelper do
 
     it 'adds rtl body class if locale is Arabic' do
       I18n.locale = :ar
-      expect(helper.add_rtl_body_class('other classes')).to eq 'other classes rtl'
+      expect(helper.locale_direction).to eq 'rtl'
     end
 
     it 'adds rtl body class if locale is Farsi' do
       I18n.locale = :fa
-      expect(helper.add_rtl_body_class('other classes')).to eq 'other classes rtl'
+      expect(helper.locale_direction).to eq 'rtl'
     end
 
     it 'adds rtl if locale is Hebrew' do
       I18n.locale = :he
-      expect(helper.add_rtl_body_class('other classes')).to eq 'other classes rtl'
+      expect(helper.locale_direction).to eq 'rtl'
     end
 
     it 'does not add rtl if locale is Thai' do
       I18n.locale = :th
-      expect(helper.add_rtl_body_class('other classes')).to eq 'other classes'
+      expect(helper.locale_direction).to_not eq 'rtl'
     end
   end
 
diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb
index 6503c83e3..883bab6ac 100644
--- a/spec/lib/activitypub/activity/accept_spec.rb
+++ b/spec/lib/activitypub/activity/accept_spec.rb
@@ -35,4 +35,30 @@ RSpec.describe ActivityPub::Activity::Accept do
       expect(recipient.requested?(sender)).to be false
     end
   end
+
+  context 'given a relay' do
+    let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
+
+    let(:json) do
+      {
+        '@context': 'https://www.w3.org/ns/activitystreams',
+        id: 'foo',
+        type: 'Accept',
+        actor: ActivityPub::TagManager.instance.uri_for(sender),
+        object: {
+          id: 'https://abc-123/456',
+          type: 'Follow',
+          actor: ActivityPub::TagManager.instance.uri_for(recipient),
+          object: ActivityPub::TagManager.instance.uri_for(sender),
+        },
+      }.with_indifferent_access
+    end
+
+    subject { described_class.new(json, sender) }
+
+    it 'marks the relay as accepted' do
+      subject.perform
+      expect(relay.reload.accepted?).to be true
+    end
+  end
 end
diff --git a/spec/lib/activitypub/activity/reject_spec.rb b/spec/lib/activitypub/activity/reject_spec.rb
index 7fd95bcc6..e7205df8d 100644
--- a/spec/lib/activitypub/activity/reject_spec.rb
+++ b/spec/lib/activitypub/activity/reject_spec.rb
@@ -35,4 +35,30 @@ RSpec.describe ActivityPub::Activity::Reject do
       expect(recipient.requested?(sender)).to be false
     end
   end
+
+  context 'given a relay' do
+    let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
+
+    let(:json) do
+      {
+        '@context': 'https://www.w3.org/ns/activitystreams',
+        id: 'foo',
+        type: 'Reject',
+        actor: ActivityPub::TagManager.instance.uri_for(sender),
+        object: {
+          id: 'https://abc-123/456',
+          type: 'Follow',
+          actor: ActivityPub::TagManager.instance.uri_for(recipient),
+          object: ActivityPub::TagManager.instance.uri_for(sender),
+        },
+      }.with_indifferent_access
+    end
+
+    subject { described_class.new(json, sender) }
+
+    it 'marks the relay as rejected' do
+      subject.perform
+      expect(relay.reload.rejected?).to be true
+    end
+  end
 end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index b8683e720..d60d24bf6 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -5,26 +5,26 @@ RSpec.describe Formatter do
   let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
 
   shared_examples 'encode and link URLs' do
-    context 'matches a stand-alone medium URL' do
+    context 'given a stand-alone medium URL' do
       let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
       end
     end
 
-    context 'matches a stand-alone google URL' do
+    context 'given a stand-alone google URL' do
       let(:text) { 'http://google.com' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="http://google.com"'
       end
     end
 
-    context 'matches a stand-alone IDN URL' do
+    context 'given a stand-alone IDN URL' do
       let(:text) { 'https://nic.みんな/' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://nic.みんな/"'
       end
 
@@ -33,138 +33,138 @@ RSpec.describe Formatter do
       end
     end
 
-    context 'matches a URL without trailing period' do
+    context 'given a URL with a trailing period' do
       let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
 
-      it 'has valid URL' do
+      it 'matches the full URL but not the period' do
         is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
       end
     end
 
-    context 'matches a URL without closing paranthesis' do
+    context 'given a URL enclosed with parentheses' do
       let(:text) { '(http://google.com/)' }
 
-      it 'has valid URL' do
+      it 'matches the full URL but not the parentheses' do
         is_expected.to include 'href="http://google.com/"'
       end
     end
 
-    context 'matches a URL without exclamation point' do
+    context 'given a URL with a trailing exclamation point' do
       let(:text) { 'http://www.google.com!' }
 
-      it 'has valid URL' do
+      it 'matches the full URL but not the exclamation point' do
         is_expected.to include 'href="http://www.google.com"'
       end
     end
 
-    context 'matches a URL without single quote' do
+    context 'given a URL with a trailing single quote' do
       let(:text) { "http://www.google.com'" }
 
-      it 'has valid URL' do
+      it 'matches the full URL but not the single quote' do
         is_expected.to include 'href="http://www.google.com"'
       end
     end
 
-    context 'matches a URL without angle brackets' do
+    context 'given a URL with a trailing angle bracket' do
       let(:text) { 'http://www.google.com>' }
 
-      it 'has valid URL' do
+      it 'matches the full URL but not the angle bracket' do
         is_expected.to include 'href="http://www.google.com"'
       end
     end
 
-    context 'matches a URL with a query string' do
+    context 'given a URL with a query string' do
       let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
       end
     end
 
-    context 'matches a URL with parenthesis in it' do
+    context 'given a URL with parentheses in it' do
       let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
       end
     end
 
-    context 'matches a URL with Japanese path string' do
+    context 'given a URL with Japanese path string' do
       let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
       end
     end
 
-    context 'matches a URL with Korean path string' do
+    context 'given a URL with Korean path string' do
       let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
       end
     end
 
-    context 'matches a URL with Simplified Chinese path string' do
+    context 'given a URL with Simplified Chinese path string' do
       let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
       end
     end
 
-    context 'matches a URL with Traditional Chinese path string' do
+    context 'given a URL with Traditional Chinese path string' do
       let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
 
-      it 'has valid URL' do
+      it 'matches the full URL' do
         is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
       end
     end
 
-    context 'contains unsafe URL (XSS attack, visible part)' do
+    context 'given a URL containing unsafe code (XSS attack, visible part)' do
       let(:text) { %q{http://example.com/b<del>b</del>} }
 
-      it 'has escaped HTML' do
+      it 'escapes the HTML in the URL' do
         is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
       end
     end
 
-    context 'contains unsafe URL (XSS attack, invisible part)' do
+    context 'given a URL containing unsafe code (XSS attack, invisible part)' do
       let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
 
-      it 'has escaped HTML' do
+      it 'escapes the HTML in the URL' do
         is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
       end
     end
 
-    context 'contains HTML (script tag)' do
+    context 'given text containing HTML code (script tag)' do
       let(:text) { '<script>alert("Hello")</script>' }
 
-      it 'has escaped HTML' do
+      it 'escapes the HTML' do
         is_expected.to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
       end
     end
 
-    context 'contains HTML (XSS attack)' do
+    context 'given text containing HTML (XSS attack)' do
       let(:text) { %q{<img src="javascript:alert('XSS');">} }
 
-      it 'has escaped HTML' do
+      it 'escapes the HTML' do
         is_expected.to include '<p>&lt;img src=&quot;javascript:alert(&apos;XSS&apos;);&quot;&gt;</p>'
       end
     end
 
-    context 'contains invalid URL' do
+    context 'given an invalid URL' do
       let(:text) { 'http://www\.google\.com' }
 
-      it 'has raw URL' do
+      it 'outputs the raw URL' do
         is_expected.to eq '<p>http://www\.google\.com</p>'
       end
     end
 
-    context 'contains a hashtag' do
+    context 'given text containing a hashtag' do
       let(:text)  { '#hashtag' }
 
-      it 'has a link' do
+      it 'creates a hashtag link' do
         is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
       end
     end
@@ -173,8 +173,8 @@ RSpec.describe Formatter do
   describe '#format' do
     subject { Formatter.instance.format(status) }
 
-    context 'with local status' do
-      context 'with reblog' do
+    context 'given a post with local status' do
+      context 'given a reblogged post' do
         let(:reblog) { Fabricate(:status, account: local_account, text: 'Hello world', uri: nil) }
         let(:status) { Fabricate(:status, reblog: reblog) }
 
@@ -183,15 +183,15 @@ RSpec.describe Formatter do
         end
       end
 
-      context 'contains plain text' do
+      context 'given a post containing plain text' do
         let(:status)  { Fabricate(:status, text: 'text', uri: nil) }
 
-        it 'paragraphizes' do
+        it 'paragraphizes the text' do
           is_expected.to eq '<p>text</p>'
         end
       end
 
-      context 'contains line feeds' do
+      context 'given a post containing line feeds' do
         let(:status)  { Fabricate(:status, text: "line\nfeed", uri: nil) }
 
         it 'removes line feeds' do
@@ -199,18 +199,18 @@ RSpec.describe Formatter do
         end
       end
 
-      context 'contains linkable mentions' do
+      context 'given a post containing linkable mentions' do
         let(:status) { Fabricate(:status, mentions: [ Fabricate(:mention, account: local_account) ], text: '@alice') }
 
-        it 'links' do
+        it 'creates a mention link' do
           is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
         end
       end
 
-      context 'contains unlinkable mentions' do
+      context 'given a post containing unlinkable mentions' do
         let(:status) { Fabricate(:status, text: '@alice', uri: nil) }
 
-        it 'does not link' do
+        it 'does not create a mention link' do
           is_expected.to include '@alice'
         end
       end
@@ -224,29 +224,29 @@ RSpec.describe Formatter do
         include_examples 'encode and link URLs'
       end
 
-      context 'with custom_emojify option' do
+      context 'given a post with custom_emojify option' do
         let!(:emoji) { Fabricate(:custom_emoji) }
         let(:status) { Fabricate(:status, account: local_account, text: text) }
 
         subject { Formatter.instance.format(status, custom_emojify: true) }
 
-        context 'with emoji at the start' do
+        context 'given a post with an emoji shortcode at the start' do
           let(:text) { ':coolcat: Beep boop' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
 
-        context 'with emoji in the middle' do
+        context 'given a post with an emoji shortcode in the middle' do
           let(:text) { 'Beep :coolcat: boop' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
 
-        context 'with concatenated emoji' do
+        context 'given a post with concatenated emoji shortcodes' do
           let(:text) { ':coolcat::coolcat:' }
 
           it 'does not touch the shortcodes' do
@@ -254,46 +254,46 @@ RSpec.describe Formatter do
           end
         end
 
-        context 'with emoji at the end' do
+        context 'given a post with an emoji shortcode at the end' do
           let(:text) { 'Beep boop :coolcat:' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
       end
     end
 
-    context 'with remote status' do
+    context 'given a post with remote status' do
       let(:status) { Fabricate(:status, account: remote_account, text: 'Beep boop') }
 
-      it 'reformats' do
+      it 'reformats the post' do
         is_expected.to eq 'Beep boop'
       end
 
-      context 'with custom_emojify option' do
+      context 'given a post with custom_emojify option' do
         let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
         let(:status) { Fabricate(:status, account: remote_account, text: text) }
 
         subject { Formatter.instance.format(status, custom_emojify: true) }
 
-        context 'with emoji at the start' do
+        context 'given a post with an emoji shortcode at the start' do
           let(:text) { '<p>:coolcat: Beep boop<br />' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
 
-        context 'with emoji in the middle' do
+        context 'given a post with an emoji shortcode in the middle' do
           let(:text) { '<p>Beep :coolcat: boop</p>' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
 
-        context 'with concatenated emoji' do
+        context 'given a post with concatenated emoji' do
           let(:text) { '<p>:coolcat::coolcat:</p>' }
 
           it 'does not touch the shortcodes' do
@@ -301,10 +301,10 @@ RSpec.describe Formatter do
           end
         end
 
-        context 'with emoji at the end' do
+        context 'given a post with an emoji shortcode at the end' do
           let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
@@ -315,26 +315,26 @@ RSpec.describe Formatter do
   describe '#reformat' do
     subject { Formatter.instance.reformat(text) }
 
-    context 'contains plain text' do
+    context 'given a post containing plain text' do
       let(:text) { 'Beep boop' }
 
-      it 'contains plain text' do
+      it 'keeps the plain text' do
         is_expected.to include 'Beep boop'
       end
     end
 
-    context 'contains scripts' do
+    context 'given a post containing script tags' do
       let(:text) { '<script>alert("Hello")</script>' }
 
-      it 'strips scripts' do
+      it 'strips the scripts' do
         is_expected.to_not include '<script>alert("Hello")</script>'
       end
     end
 
-    context 'contains malicious classes' do
+    context 'given a post containing malicious classes' do
       let(:text) { '<span class="mention	status__content__spoiler-link">Show more</span>' }
 
-      it 'strips malicious classes' do
+      it 'strips the malicious classes' do
         is_expected.to_not include 'status__content__spoiler-link'
       end
     end
@@ -343,15 +343,15 @@ RSpec.describe Formatter do
   describe '#plaintext' do
     subject { Formatter.instance.plaintext(status) }
 
-    context 'with local status' do
+    context 'given a post with local status' do
       let(:status)  { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', uri: nil) }
 
-      it 'returns raw text' do
+      it 'returns the raw text' do
         is_expected.to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
       end
     end
 
-    context 'with remote status' do
+    context 'given a post with remote status' do
       let(:status)  { Fabricate(:status, account: remote_account, text: '<script>alert("Hello")</script>') }
 
       it 'returns tag-stripped text' do
@@ -363,60 +363,60 @@ RSpec.describe Formatter do
   describe '#simplified_format' do
     subject { Formatter.instance.simplified_format(account) }
 
-    context 'with local status' do
+    context 'given a post with local status' do
       let(:account) { Fabricate(:account, domain: nil, note: text) }
 
-      context 'contains linkable mentions for local accounts' do
+      context 'given a post containing linkable mentions for local accounts' do
         let(:text) { '@alice' }
 
         before { local_account }
 
-        it 'links' do
+        it 'creates a mention link' do
           is_expected.to eq '<p><span class="h-card"><a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span></p>'
         end
       end
 
-      context 'contains linkable mentions for remote accounts' do
+      context 'given a post containing linkable mentions for remote accounts' do
         let(:text) { '@bob@remote.test' }
 
         before { remote_account }
 
-        it 'links' do
+        it 'creates a mention link' do
           is_expected.to eq '<p><span class="h-card"><a href="https://remote.test/" class="u-url mention">@<span>bob</span></a></span></p>'
         end
       end
 
-      context 'contains unlinkable mentions' do
+      context 'given a post containing unlinkable mentions' do
         let(:text) { '@alice' }
 
-        it 'returns raw mention texts' do
+        it 'does not create a mention link' do
           is_expected.to eq '<p>@alice</p>'
         end
       end
 
-      context 'with custom_emojify option' do
+      context 'given a post with custom_emojify option' do
         let!(:emoji) { Fabricate(:custom_emoji) }
 
         before { account.note = text }
         subject { Formatter.instance.simplified_format(account, custom_emojify: true) }
 
-        context 'with emoji at the start' do
+        context 'given a post with an emoji shortcode at the start' do
           let(:text) { ':coolcat: Beep boop' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
 
-        context 'with emoji in the middle' do
+        context 'given a post with an emoji shortcode in the middle' do
           let(:text) { 'Beep :coolcat: boop' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
 
-        context 'with concatenated emoji' do
+        context 'given a post with concatenated emoji shortcodes' do
           let(:text) { ':coolcat::coolcat:' }
 
           it 'does not touch the shortcodes' do
@@ -424,10 +424,10 @@ RSpec.describe Formatter do
           end
         end
 
-        context 'with emoji at the end' do
+        context 'given a post with an emoji shortcode at the end' do
           let(:text) { 'Beep boop :coolcat:' }
 
-          it 'converts shortcode to image tag' do
+          it 'converts the shortcode to an image tag' do
             is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
           end
         end
@@ -436,7 +436,7 @@ RSpec.describe Formatter do
       include_examples 'encode and link URLs'
     end
 
-    context 'with remote status' do
+    context 'given a post with remote status' do
       let(:text) { '<script>alert("Hello")</script>' }
       let(:account) { Fabricate(:account, domain: 'remote', note: text) }
 
@@ -451,7 +451,7 @@ RSpec.describe Formatter do
 
         subject { Formatter.instance.simplified_format(remote_account, custom_emojify: true) }
 
-        context 'with emoji at the start' do
+        context 'given a post with an emoji shortcode at the start' do
           let(:text) { '<p>:coolcat: Beep boop<br />' }
 
           it 'converts shortcode to image tag' do
@@ -459,7 +459,7 @@ RSpec.describe Formatter do
           end
         end
 
-        context 'with emoji in the middle' do
+        context 'given a post with an emoji shortcode in the middle' do
           let(:text) { '<p>Beep :coolcat: boop</p>' }
 
           it 'converts shortcode to image tag' do
@@ -467,7 +467,7 @@ RSpec.describe Formatter do
           end
         end
 
-        context 'with concatenated emoji' do
+        context 'given a post with concatenated emoji shortcodes' do
           let(:text) { '<p>:coolcat::coolcat:</p>' }
 
           it 'does not touch the shortcodes' do
@@ -475,7 +475,7 @@ RSpec.describe Formatter do
           end
         end
 
-        context 'with emoji at the end' do
+        context 'given a post with an emoji shortcode at the end' do
           let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
 
           it 'converts shortcode to image tag' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 93a6c26fb..015e90edc 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -512,7 +512,7 @@ RSpec.describe User, type: :model do
       context 'when user is confirmed' do
         let(:confirmed_at) { Time.zone.now }
 
-        it { is_expected.to be false }
+        it { is_expected.to be true }
       end
 
       context 'when user is not confirmed' do
diff --git a/streaming/index.js b/streaming/index.js
index d7bfa6542..1c6004b77 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -9,6 +9,7 @@ const log = require('npmlog');
 const url = require('url');
 const WebSocket = require('uws');
 const uuid = require('uuid');
+const fs = require('fs');
 
 const env = process.env.NODE_ENV || 'development';
 
@@ -70,6 +71,9 @@ const redisUrlToClient = (defaultConfig, redisUrl) => {
 const numWorkers = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : Math.max(os.cpus().length - 1, 1));
 
 const startMaster = () => {
+  if (!process.env.SOCKET && process.env.PORT && isNaN(+process.env.PORT)) {
+    log.warn('UNIX domain socket is now supported by using SOCKET. Please migrate from PORT hack.');
+  }
   log.info(`Starting streaming API server master with ${numWorkers} workers`);
 };
 
@@ -448,6 +452,12 @@ const startWorker = (workerId) => {
   app.use(setRequestId);
   app.use(setRemoteAddress);
   app.use(allowCrossDomain);
+
+  app.get('/api/v1/streaming/health', (req, res) => {
+    res.writeHead(200, { 'Content-Type': 'text/plain' });
+    res.end('OK');
+  });
+
   app.use(authenticationMiddleware);
   app.use(errorMiddleware);
 
@@ -574,9 +584,16 @@ const startWorker = (workerId) => {
     });
   }, 30000);
 
-  server.listen(process.env.PORT || 4000, process.env.BIND || '0.0.0.0', () => {
-    log.info(`Worker ${workerId} now listening on ${server.address().address}:${server.address().port}`);
-  });
+  if (process.env.SOCKET || process.env.PORT && isNaN(+process.env.PORT)) {
+    server.listen(process.env.SOCKET || process.env.PORT, () => {
+      fs.chmodSync(server.address(), 0o666);
+      log.info(`Worker ${workerId} now listening on ${server.address()}`);
+    });
+  } else {
+    server.listen(+process.env.PORT || 4000, process.env.BIND || '0.0.0.0', () => {
+      log.info(`Worker ${workerId} now listening on ${server.address().address}:${server.address().port}`);
+    });
+  }
 
   const onExit = () => {
     log.info(`Worker ${workerId} exiting, bye bye`);