about summary refs log tree commit diff
diff options
context:
space:
mode:
authorreverite <samantha@chalker.io>2019-03-15 05:54:30 -0700
committerreverite <samantha@chalker.io>2019-03-15 05:54:30 -0700
commit75eeb003b09c53d3b4e98046d1c20b0ad8a887bb (patch)
treed2ae669ee583c613a474f8698b7ea718da803819
parent41d1369391d70a9cd25bdf96cfe567975793ef5a (diff)
parentc2fa0f7c40bcc4064e8baaa221665eadd391c001 (diff)
Merge remote-tracking branch 'glitch/master' into production
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock92
-rw-r--r--app/controllers/about_controller.rb27
-rw-r--r--app/controllers/accounts_controller.rb12
-rw-r--r--app/controllers/admin/accounts_controller.rb17
-rw-r--r--app/controllers/admin/dashboard_controller.rb2
-rw-r--r--app/controllers/admin/settings_controller.rb3
-rw-r--r--app/controllers/api/base_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb8
-rw-r--r--app/controllers/api/v1/accounts_controller.rb6
-rw-r--r--app/controllers/api/v1/timelines/tag_controller.rb2
-rw-r--r--app/controllers/auth/registrations_controller.rb2
-rw-r--r--app/controllers/concerns/account_controller_concern.rb14
-rw-r--r--app/controllers/public_timelines_controller.rb39
-rw-r--r--app/controllers/settings/exports_controller.rb18
-rw-r--r--app/controllers/tags_controller.rb18
-rw-r--r--app/helpers/admin/filter_helper.rb2
-rw-r--r--app/helpers/application_helper.rb18
-rw-r--r--app/helpers/home_helper.rb18
-rw-r--r--app/helpers/jsonld_helper.rb9
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js2
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js2
-rw-r--r--app/javascript/flavours/glitch/components/status.js2
-rw-r--r--app/javascript/flavours/glitch/components/status_prepend.js9
-rw-r--r--app/javascript/flavours/glitch/containers/timeline_container.js12
-rw-r--r--app/javascript/flavours/glitch/features/account/components/profile_column_header.js4
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/following/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.js11
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/filter_bar.js8
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notification.js21
-rw-r--r--app/javascript/flavours/glitch/features/standalone/community_timeline/index.js71
-rw-r--r--app/javascript/flavours/glitch/features/standalone/public_timeline/index.js122
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.js4
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js11
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js6
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js5
-rw-r--r--app/javascript/flavours/glitch/reducers/push_notifications.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/settings.js3
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss816
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss8
-rw-r--r--app/javascript/flavours/glitch/styles/forms.scss21
-rw-r--r--app/javascript/flavours/glitch/styles/polls.scss13
-rw-r--r--app/javascript/flavours/glitch/styles/tables.scss4
-rw-r--r--app/javascript/flavours/glitch/styles/widgets.scss5
-rw-r--r--app/javascript/mastodon/actions/notifications.js2
-rw-r--r--app/javascript/mastodon/containers/timeline_container.js12
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js11
-rw-r--r--app/javascript/mastodon/features/notifications/components/filter_bar.js8
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js34
-rw-r--r--app/javascript/mastodon/features/standalone/community_timeline/index.js71
-rw-r--r--app/javascript/mastodon/features/standalone/hashtag_timeline/index.js8
-rw-r--r--app/javascript/mastodon/features/standalone/public_timeline/index.js122
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json72
-rw-r--r--app/javascript/mastodon/locales/en.json12
-rw-r--r--app/javascript/mastodon/locales/ja.json12
-rw-r--r--app/javascript/mastodon/reducers/push_notifications.js1
-rw-r--r--app/javascript/mastodon/reducers/settings.js3
-rw-r--r--app/javascript/mastodon/service_worker/web_push_locales.js1
-rw-r--r--app/javascript/styles/mastodon/about.scss816
-rw-r--r--app/javascript/styles/mastodon/admin.scss8
-rw-r--r--app/javascript/styles/mastodon/forms.scss21
-rw-r--r--app/javascript/styles/mastodon/polls.scss12
-rw-r--r--app/javascript/styles/mastodon/tables.scss4
-rw-r--r--app/javascript/styles/mastodon/widgets.scss5
-rw-r--r--app/lib/activitypub/activity/create.rb18
-rw-r--r--app/lib/activitypub/activity/delete.rb9
-rw-r--r--app/lib/activitypub/activity/update.rb15
-rw-r--r--app/lib/request.rb2
-rw-r--r--app/mailers/admin_mailer.rb10
-rw-r--r--app/models/account.rb7
-rw-r--r--app/models/account_filter.rb4
-rw-r--r--app/models/concerns/expireable.rb6
-rw-r--r--app/models/concerns/ldap_authenticable.rb25
-rw-r--r--app/models/concerns/omniauthable.rb2
-rw-r--r--app/models/concerns/pam_authenticable.rb68
-rw-r--r--app/models/concerns/user_roles.rb54
-rw-r--r--app/models/form/admin_settings.rb4
-rw-r--r--app/models/notification.rb8
-rw-r--r--app/models/poll.rb2
-rw-r--r--app/models/tag.rb8
-rw-r--r--app/models/user.rb166
-rw-r--r--app/policies/user_policy.rb10
-rw-r--r--app/presenters/instance_presenter.rb10
-rw-r--r--app/serializers/activitypub/update_poll_serializer.rb27
-rw-r--r--app/serializers/rest/instance_serializer.rb2
-rw-r--r--app/serializers/rest/notification_serializer.rb2
-rw-r--r--app/services/activitypub/fetch_remote_poll_service.rb51
-rw-r--r--app/services/activitypub/fetch_replies_service.rb9
-rw-r--r--app/services/activitypub/process_poll_service.rb64
-rw-r--r--app/services/app_sign_up_service.rb2
-rw-r--r--app/services/notify_service.rb6
-rw-r--r--app/services/post_status_service.rb3
-rw-r--r--app/services/remove_status_service.rb6
-rw-r--r--app/services/vote_service.rb23
-rw-r--r--app/views/about/_features.html.haml25
-rw-r--r--app/views/about/_forms.html.haml15
-rw-r--r--app/views/about/_links.html.haml16
-rw-r--r--app/views/about/_login.html.haml13
-rw-r--r--app/views/about/_registration.html.haml20
-rw-r--r--app/views/about/show.html.haml207
-rw-r--r--app/views/admin/accounts/_account.html.haml10
-rw-r--r--app/views/admin/accounts/index.html.haml7
-rw-r--r--app/views/admin/accounts/show.html.haml16
-rw-r--r--app/views/admin/invites/_invite.html.haml22
-rw-r--r--app/views/admin/invites/index.html.haml6
-rw-r--r--app/views/admin/settings/edit.html.haml10
-rw-r--r--app/views/admin_mailer/new_pending_account.text.erb8
-rw-r--r--app/views/auth/registrations/new.html.haml2
-rw-r--r--app/views/auth/shared/_links.html.haml2
-rw-r--r--app/views/invites/_invite.html.haml20
-rw-r--r--app/views/invites/index.html.haml19
-rw-r--r--app/views/layouts/public.html.haml32
-rw-r--r--app/views/public_timelines/show.html.haml13
-rw-r--r--app/views/remote_follow/new.html.haml5
-rw-r--r--app/views/remote_interaction/new.html.haml5
-rw-r--r--app/views/tags/show.html.haml1
-rw-r--r--app/views/user_mailer/confirmation_instructions.html.haml2
-rw-r--r--app/views/user_mailer/confirmation_instructions.text.erb2
-rw-r--r--app/workers/activitypub/distribute_poll_update_worker.rb65
-rw-r--r--app/workers/poll_expiration_notify_worker.rb24
-rw-r--r--config/locales/ar.yml16
-rw-r--r--config/locales/ast.yml7
-rw-r--r--config/locales/bg.yml2
-rw-r--r--config/locales/bn.yml6
-rw-r--r--config/locales/ca.yml16
-rw-r--r--config/locales/co.yml16
-rw-r--r--config/locales/cs.yml16
-rw-r--r--config/locales/cy.yml16
-rw-r--r--config/locales/da.yml16
-rw-r--r--config/locales/de.yml16
-rw-r--r--config/locales/devise.en.yml3
-rw-r--r--config/locales/el.yml16
-rw-r--r--config/locales/en.yml44
-rw-r--r--config/locales/eo.yml16
-rw-r--r--config/locales/es.yml16
-rw-r--r--config/locales/eu.yml16
-rw-r--r--config/locales/fa.yml16
-rw-r--r--config/locales/fi.yml16
-rw-r--r--config/locales/fr.yml16
-rw-r--r--config/locales/gl.yml16
-rw-r--r--config/locales/he.yml13
-rw-r--r--config/locales/hr.yml2
-rw-r--r--config/locales/hu.yml14
-rw-r--r--config/locales/id.yml13
-rw-r--r--config/locales/io.yml4
-rw-r--r--config/locales/it.yml16
-rw-r--r--config/locales/ja.yml45
-rw-r--r--config/locales/ka.yml16
-rw-r--r--config/locales/kk.yml16
-rw-r--r--config/locales/ko.yml16
-rw-r--r--config/locales/lt.yml16
-rw-r--r--config/locales/ms.yml11
-rw-r--r--config/locales/nl.yml16
-rw-r--r--config/locales/no.yml14
-rw-r--r--config/locales/oc.yml16
-rw-r--r--config/locales/pl.yml16
-rw-r--r--config/locales/pt-BR.yml16
-rw-r--r--config/locales/pt.yml14
-rw-r--r--config/locales/ro.yml4
-rw-r--r--config/locales/ru.yml16
-rw-r--r--config/locales/sk.yml16
-rw-r--r--config/locales/sl.yml11
-rw-r--r--config/locales/sq.yml16
-rw-r--r--config/locales/sr-Latn.yml14
-rw-r--r--config/locales/sr.yml16
-rw-r--r--config/locales/sv.yml16
-rw-r--r--config/locales/te.yml11
-rw-r--r--config/locales/th.yml4
-rw-r--r--config/locales/tr.yml13
-rw-r--r--config/locales/uk.yml16
-rw-r--r--config/locales/zh-CN.yml16
-rw-r--r--config/locales/zh-HK.yml16
-rw-r--r--config/locales/zh-TW.yml16
-rw-r--r--config/routes.rb3
-rw-r--r--config/settings.yml2
-rw-r--r--db/migrate/20190307234537_add_approved_to_users.rb23
-rw-r--r--db/migrate/20190314181829_migrate_open_registrations_setting.rb15
-rw-r--r--db/schema.rb3
-rw-r--r--db/seeds.rb2
-rw-r--r--lib/cli.rb4
-rw-r--r--lib/mastodon/settings_cli.rb4
-rw-r--r--lib/mastodon/statuses_cli.rb54
-rw-r--r--spec/controllers/accounts_controller_spec.rb2
-rw-r--r--spec/controllers/admin/settings_controller_spec.rb16
-rw-r--r--spec/controllers/auth/registrations_controller_spec.rb20
-rw-r--r--spec/controllers/concerns/account_controller_concern_spec.rb16
-rw-r--r--spec/helpers/application_helper_spec.rb4
-rw-r--r--spec/models/account_spec.rb5
-rw-r--r--spec/presenters/instance_presenter_spec.rb28
-rw-r--r--spec/requests/localization_spec.rb9
-rw-r--r--spec/services/app_sign_up_service_spec.rb4
-rw-r--r--spec/views/about/show.html.haml_spec.rb36
-rw-r--r--streaming/index.js3
198 files changed, 2039 insertions, 2948 deletions
diff --git a/Gemfile b/Gemfile
index 5f40bd310..db2746413 100644
--- a/Gemfile
+++ b/Gemfile
@@ -15,7 +15,7 @@ gem 'makara', '~> 0.4'
 gem 'pghero', '~> 2.2'
 gem 'dotenv-rails', '~> 2.7'
 
-gem 'aws-sdk-s3', '~> 1.30', require: false
+gem 'aws-sdk-s3', '~> 1.31', require: false
 gem 'fog-core', '<= 2.1.0'
 gem 'fog-openstack', '~> 0.3', require: false
 gem 'paperclip', '~> 6.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 684a34c0a..2ebf7c5c3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -15,25 +15,25 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (5.2.2)
-      actionpack (= 5.2.2)
+    actioncable (5.2.2.1)
+      actionpack (= 5.2.2.1)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailer (5.2.2)
-      actionpack (= 5.2.2)
-      actionview (= 5.2.2)
-      activejob (= 5.2.2)
+    actionmailer (5.2.2.1)
+      actionpack (= 5.2.2.1)
+      actionview (= 5.2.2.1)
+      activejob (= 5.2.2.1)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
-    actionpack (5.2.2)
-      actionview (= 5.2.2)
-      activesupport (= 5.2.2)
+    actionpack (5.2.2.1)
+      actionview (= 5.2.2.1)
+      activesupport (= 5.2.2.1)
       rack (~> 2.0)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.0.2)
-    actionview (5.2.2)
-      activesupport (= 5.2.2)
+    actionview (5.2.2.1)
+      activesupport (= 5.2.2.1)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
@@ -43,21 +43,21 @@ GEM
       activemodel (>= 4.1, < 6)
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
-    active_record_query_trace (1.6)
-    activejob (5.2.2)
-      activesupport (= 5.2.2)
+    active_record_query_trace (1.6.2)
+    activejob (5.2.2.1)
+      activesupport (= 5.2.2.1)
       globalid (>= 0.3.6)
-    activemodel (5.2.2)
-      activesupport (= 5.2.2)
-    activerecord (5.2.2)
-      activemodel (= 5.2.2)
-      activesupport (= 5.2.2)
+    activemodel (5.2.2.1)
+      activesupport (= 5.2.2.1)
+    activerecord (5.2.2.1)
+      activemodel (= 5.2.2.1)
+      activesupport (= 5.2.2.1)
       arel (>= 9.0)
-    activestorage (5.2.2)
-      actionpack (= 5.2.2)
-      activerecord (= 5.2.2)
+    activestorage (5.2.2.1)
+      actionpack (= 5.2.2.1)
+      activerecord (= 5.2.2.1)
       marcel (~> 0.3.1)
-    activesupport (5.2.2)
+    activesupport (5.2.2.1)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
@@ -76,8 +76,8 @@ GEM
     av (0.9.0)
       cocaine (~> 0.5.3)
     aws-eventstream (1.0.1)
-    aws-partitions (1.131.0)
-    aws-sdk-core (3.45.0)
+    aws-partitions (1.143.0)
+    aws-sdk-core (3.46.2)
       aws-eventstream (~> 1.0)
       aws-partitions (~> 1.0)
       aws-sigv4 (~> 1.0)
@@ -85,7 +85,7 @@ GEM
     aws-sdk-kms (1.13.0)
       aws-sdk-core (~> 3, >= 3.39.0)
       aws-sigv4 (~> 1.0)
-    aws-sdk-s3 (1.30.1)
+    aws-sdk-s3 (1.31.0)
       aws-sdk-core (~> 3, >= 3.39.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.0)
@@ -148,7 +148,7 @@ GEM
     cocaine (0.5.8)
       climate_control (>= 0.0.3, < 1.0)
     coderay (1.1.2)
-    concurrent-ruby (1.1.4)
+    concurrent-ruby (1.1.5)
     connection_pool (2.2.2)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
@@ -232,7 +232,7 @@ GEM
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
     get_process_mem (0.2.3)
-    globalid (0.4.1)
+    globalid (0.4.2)
       activesupport (>= 4.2.0)
     goldfinger (2.1.0)
       addressable (~> 2.5)
@@ -271,7 +271,7 @@ GEM
     httplog (1.2.1)
       rack (>= 1.0)
       rainbow (>= 2.0.0)
-    i18n (1.5.3)
+    i18n (1.6.0)
       concurrent-ruby (~> 1.0)
     i18n-tasks (0.9.28)
       activesupport (>= 4.0.2)
@@ -343,7 +343,7 @@ GEM
     mime-types (3.2.2)
       mime-types-data (~> 3.2015)
     mime-types-data (3.2018.0812)
-    mimemagic (0.3.2)
+    mimemagic (0.3.3)
     mini_mime (1.0.1)
     mini_portile2 (2.4.0)
     minitest (5.11.3)
@@ -402,7 +402,7 @@ GEM
     pg (1.1.4)
     pghero (2.2.0)
       activerecord
-    pkg-config (1.3.5)
+    pkg-config (1.3.6)
     powerpack (0.1.2)
     premailer (1.11.1)
       addressable
@@ -436,18 +436,18 @@ GEM
       rack
     rack-test (1.1.0)
       rack (>= 1.0, < 3)
-    rails (5.2.2)
-      actioncable (= 5.2.2)
-      actionmailer (= 5.2.2)
-      actionpack (= 5.2.2)
-      actionview (= 5.2.2)
-      activejob (= 5.2.2)
-      activemodel (= 5.2.2)
-      activerecord (= 5.2.2)
-      activestorage (= 5.2.2)
-      activesupport (= 5.2.2)
+    rails (5.2.2.1)
+      actioncable (= 5.2.2.1)
+      actionmailer (= 5.2.2.1)
+      actionpack (= 5.2.2.1)
+      actionview (= 5.2.2.1)
+      activejob (= 5.2.2.1)
+      activemodel (= 5.2.2.1)
+      activerecord (= 5.2.2.1)
+      activestorage (= 5.2.2.1)
+      activesupport (= 5.2.2.1)
       bundler (>= 1.3.0)
-      railties (= 5.2.2)
+      railties (= 5.2.2.1)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.4)
       actionpack (>= 5.0.1.x)
@@ -463,9 +463,9 @@ GEM
       railties (>= 5.0, < 6)
     rails-settings-cached (0.6.6)
       rails (>= 4.2.0)
-    railties (5.2.2)
-      actionpack (= 5.2.2)
-      activesupport (= 5.2.2)
+    railties (5.2.2.1)
+      actionpack (= 5.2.2.1)
+      activesupport (= 5.2.2.1)
       method_source
       rake (>= 0.8.7)
       thor (>= 0.19.0, < 2.0)
@@ -661,7 +661,7 @@ DEPENDENCIES
   active_record_query_trace (~> 1.6)
   addressable (~> 2.6)
   annotate (~> 2.7)
-  aws-sdk-s3 (~> 1.30)
+  aws-sdk-s3 (~> 1.31)
   better_errors (~> 2.5)
   binding_of_caller (~> 0.7)
   bootsnap (~> 1.4)
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index ce1e8293c..f459bab19 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -2,21 +2,17 @@
 
 class AboutController < ApplicationController
   before_action :set_pack
-  before_action :set_body_classes
+  layout 'public'
+
   before_action :set_instance_presenter, only: [:show, :more, :terms]
 
   def show
-    serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
-    @initial_state_json   = serializable_resource.to_json
+    @hide_navbar = true
   end
 
-  def more
-    render layout: 'public'
-  end
+  def more; end
 
-  def terms
-    render layout: 'public'
-  end
+  def terms; end
 
   private
 
@@ -27,21 +23,10 @@ class AboutController < ApplicationController
   helper_method :new_user
 
   def set_pack
-    use_pack action_name == 'show' ? 'about' : 'common'
+    use_pack 'common'
   end
 
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
-
-  def set_body_classes
-    @body_classes = 'with-modals'
-  end
-
-  def initial_state_params
-    {
-      settings: { known_fediverse: Setting.show_known_fediverse_at_about_page },
-      token: current_session&.token,
-    }
-  end
 end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 442e99089..157ea8569 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -81,11 +81,17 @@ class AccountsController < ApplicationController
   end
 
   def hashtag_scope
-    Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id)
+    tag = Tag.find_normalized(params[:tag])
+
+    if tag
+      Status.tagged_with(tag.id)
+    else
+      Status.none
+    end
   end
 
-  def set_account
-    @account = Account.find_local!(params[:username])
+  def username_param
+    params[:username]
   end
 
   def older_url
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 562fba996..e160c603a 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -2,9 +2,9 @@
 
 module Admin
   class AccountsController < BaseController
-    before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize]
+    before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
     before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
-    before_action :require_local_account!, only: [:enable, :memorialize]
+    before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
 
     def index
       authorize :account, :index?
@@ -45,6 +45,18 @@ module Admin
       redirect_to admin_account_path(@account.id)
     end
 
+    def approve
+      authorize @account.user, :approve?
+      @account.user.approve!
+      redirect_to admin_accounts_path(pending: '1')
+    end
+
+    def reject
+      authorize @account.user, :reject?
+      SuspendAccountService.new.call(@account, including_user: true, destroy: true)
+      redirect_to admin_accounts_path(pending: '1')
+    end
+
     def unsilence
       authorize @account, :unsilence?
       @account.unsilence!
@@ -114,6 +126,7 @@ module Admin
         :remote,
         :by_domain,
         :active,
+        :pending,
         :silenced,
         :suspended,
         :username,
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index bb923c185..22bbcec19 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -10,7 +10,7 @@ module Admin
       @interactions_week     = Redis.current.get("activity:interactions:#{current_week}") || 0
       @relay_enabled         = Relay.enabled.exists?
       @single_user_mode      = Rails.configuration.x.single_user_mode
-      @registrations_enabled = Setting.open_registrations
+      @registrations_enabled = Setting.registrations_mode != 'none'
       @deletions_enabled     = Setting.open_deletion
       @invites_enabled       = Setting.min_invite_role == 'user'
       @search_enabled        = Chewy.enabled?
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index 9624df96b..a64e98868 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -10,7 +10,7 @@ module Admin
       site_description
       site_extended_description
       site_terms
-      open_registrations
+      registrations_mode
       closed_registrations_message
       open_deletion
       timeline_preview
@@ -33,7 +33,6 @@ module Admin
     ).freeze
 
     BOOLEAN_SETTINGS = %w(
-      open_registrations
       open_deletion
       timeline_preview
       show_staff_badge
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index a1dd30918..3a92ee4e4 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -73,7 +73,9 @@ class Api::BaseController < ApplicationController
     elsif current_user.disabled?
       render json: { error: 'Your login is currently disabled' }, status: 403
     elsif !current_user.confirmed?
-      render json: { error: 'Email confirmation is not completed' }, status: 403
+      render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
+    elsif !current_user.approved?
+      render json: { error: 'Your login is currently pending approval' }, status: 403
     else
       set_user_activity
     end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index ed10f3f6a..8cd8f8e79 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -69,7 +69,13 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def hashtag_scope
-    Status.tagged_with(Tag.find_by(name: params[:tagged])&.id)
+    tag = Tag.find_normalized(params[:tagged])
+
+    if tag
+      Status.tagged_with(tag.id)
+    else
+      Status.none
+    end
   end
 
   def pagination_params(core_params)
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 2ccbc3cbb..b0c62778e 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -80,6 +80,10 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def check_enabled_registrations
-    forbidden if single_user_mode? || !Setting.open_registrations
+    forbidden if single_user_mode? || !allowed_registrations?
+  end
+
+  def allowed_registrations?
+    Setting.registrations_mode != 'none'
   end
 end
diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb
index 92c32c178..9adc4ad29 100644
--- a/app/controllers/api/v1/timelines/tag_controller.rb
+++ b/app/controllers/api/v1/timelines/tag_controller.rb
@@ -14,7 +14,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
   private
 
   def load_tag
-    @tag = Tag.find_by(name: params[:id].downcase)
+    @tag = Tag.find_normalized(params[:id])
   end
 
   def load_statuses
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index efe29b53f..74dd7ff34 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -66,7 +66,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   end
 
   def allowed_registrations?
-    Setting.open_registrations || @invite&.valid_for_use?
+    Setting.registrations_mode != 'none' || @invite&.valid_for_use?
   end
 
   def invite_code
diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb
index 6c27ef330..8817fd7de 100644
--- a/app/controllers/concerns/account_controller_concern.rb
+++ b/app/controllers/concerns/account_controller_concern.rb
@@ -7,16 +7,18 @@ module AccountControllerConcern
 
   included do
     layout 'public'
+
     before_action :set_account
+    before_action :check_account_approval
+    before_action :check_account_suspension
     before_action :set_instance_presenter
     before_action :set_link_headers
-    before_action :check_account_suspension
   end
 
   private
 
   def set_account
-    @account = Account.find_local!(params[:account_username])
+    @account = Account.find_local!(username_param)
   end
 
   def set_instance_presenter
@@ -33,6 +35,10 @@ module AccountControllerConcern
     )
   end
 
+  def username_param
+    params[:account_username]
+  end
+
   def webfinger_account_link
     [
       webfinger_account_url,
@@ -58,6 +64,10 @@ module AccountControllerConcern
     webfinger_url(resource: @account.to_webfinger_s)
   end
 
+  def check_account_approval
+    not_found if @account.user_pending?
+  end
+
   def check_account_suspension
     gone if @account.suspended?
   end
diff --git a/app/controllers/public_timelines_controller.rb b/app/controllers/public_timelines_controller.rb
new file mode 100644
index 000000000..c5fe789f4
--- /dev/null
+++ b/app/controllers/public_timelines_controller.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class PublicTimelinesController < ApplicationController
+  before_action :set_pack
+  layout 'public'
+
+  before_action :check_enabled
+  before_action :set_body_classes
+  before_action :set_instance_presenter
+
+  def show
+    respond_to do |format|
+      format.html do
+        @initial_state_json = ActiveModelSerializers::SerializableResource.new(
+          InitialStatePresenter.new(settings: { known_fediverse: Setting.show_known_fediverse_at_about_page }, token: current_session&.token),
+          serializer: InitialStateSerializer
+        ).to_json
+      end
+    end
+  end
+
+  private
+
+  def check_enabled
+    raise ActiveRecord::RecordNotFound unless Setting.timeline_preview
+  end
+
+  def set_body_classes
+    @body_classes = 'with-modals'
+  end
+
+  def set_instance_presenter
+    @instance_presenter = InstancePresenter.new
+  end
+
+  def set_pack
+    use_pack 'about'
+  end
+end
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index cf8745576..7f76668d5 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -9,11 +9,25 @@ class Settings::ExportsController < Settings::BaseController
   end
 
   def create
-    authorize :backup, :create?
+    raise Mastodon::NotPermittedError unless user_signed_in?
+
+    backup = nil
+
+    RedisLock.acquire(lock_options) do |lock|
+      if lock.acquired?
+        authorize :backup, :create?
+        backup = current_user.backups.create!
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
 
-    backup = current_user.backups.create!
     BackupWorker.perform_async(backup.id)
 
     redirect_to settings_export_path
   end
+
+  def lock_options
+    { redis: Redis.current, key: "backup:#{current_user.id}" }
+  end
 end
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 186d276c2..5cb048c1a 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -9,13 +9,15 @@ class TagsController < ApplicationController
   before_action :set_instance_presenter
 
   def show
-    @tag = Tag.find_by!(name: params[:id].downcase)
+    @tag = Tag.find_normalized!(params[:id])
 
     respond_to do |format|
       format.html do
         use_pack 'about'
-        serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
-        @initial_state_json   = serializable_resource.to_json
+        @initial_state_json = ActiveModelSerializers::SerializableResource.new(
+          InitialStatePresenter.new(settings: {}, token: current_session&.token),
+          serializer: InitialStateSerializer
+        ).to_json
       end
 
       format.rss do
@@ -26,8 +28,7 @@ class TagsController < ApplicationController
       end
 
       format.json do
-        @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, params[:local])
-                                       .paginate_by_max_id(PAGE_SIZE, params[:max_id])
+        @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id])
         @statuses = cache_collection(@statuses, Status)
 
         render json: collection_presenter,
@@ -56,11 +57,4 @@ class TagsController < ApplicationController
       items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
     )
   end
-
-  def initial_state_params
-    {
-      settings: {},
-      token: current_session&.token,
-    }
-  end
 end
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 275b5f2fe..8f78bf5f8 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module Admin::FilterHelper
-  ACCOUNT_FILTERS      = %i(local remote by_domain active silenced suspended username display_name email ip staff).freeze
+  ACCOUNT_FILTERS      = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze
   REPORT_FILTERS       = %i(resolved account_id target_account_id).freeze
   INVITE_FILTER        = %i(available expired).freeze
   CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index fc006d777..f70c37522 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -20,7 +20,23 @@ module ApplicationHelper
   end
 
   def open_registrations?
-    Setting.open_registrations
+    Setting.registrations_mode == 'open'
+  end
+
+  def approved_registrations?
+    Setting.registrations_mode == 'approved'
+  end
+
+  def closed_registrations?
+    Setting.registrations_mode == 'none'
+  end
+
+  def available_sign_up_path
+    if closed_registrations?
+      'https://joinmastodon.org/#getting-started'
+    else
+      new_user_registration_path
+    end
   end
 
   def open_deletion?
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
index 9b3f1380b..df60b7dd7 100644
--- a/app/helpers/home_helper.rb
+++ b/app/helpers/home_helper.rb
@@ -56,4 +56,22 @@ module HomeHelper
       'emojify'
     end
   end
+
+  def optional_link_to(condition, path, options = {}, &block)
+    if condition
+      link_to(path, options, &block)
+    else
+      content_tag(:div, &block)
+    end
+  end
+
+  def sign_up_message
+    if closed_registrations?
+      t('auth.registration_closed', instance: site_hostname)
+    elsif open_registrations?
+      t('auth.register')
+    elsif approved_registrations?
+      t('auth.apply_for_account')
+    end
+  end
 end
diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb
index f0a19e332..5b4011275 100644
--- a/app/helpers/jsonld_helper.rb
+++ b/app/helpers/jsonld_helper.rb
@@ -47,6 +47,15 @@ module JsonLdHelper
     !uri.start_with?('http://', 'https://')
   end
 
+  def invalid_origin?(url)
+    return true if unsupported_uri_scheme?(url)
+
+    needle   = Addressable::URI.parse(url).host
+    haystack = Addressable::URI.parse(@account.uri).host
+
+    !haystack.casecmp(needle).zero?
+  end
+
   def canonicalize(json)
     graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
     graph.dump(:normalize)
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index f89b4cb36..dd4f5fd44 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -106,7 +106,7 @@ const excludeTypesFromSettings = state => state.getIn(['settings', 'notification
 
 
 const excludeTypesFromFilter = filter => {
-  const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
+  const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']);
   return allTypes.filterNot(item => item === filter).toJS();
 };
 
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 1fa25ee4e..6be2b4700 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -238,7 +238,7 @@ export default class MediaGallery extends React.PureComponent {
   };
 
   componentWillReceiveProps (nextProps) {
-    if (!is(nextProps.media, this.props.media)) {
+    if (!is(nextProps.media, this.props.media) || nextProps.revealed === true) {
       this.setState({ visible: nextProps.revealed === undefined ? (displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all') : nextProps.revealed });
     }
   }
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index 31f4f1ddd..c8bf75f79 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -466,6 +466,7 @@ export default class Status extends ImmutablePureComponent {
               onOpenVideo={this.handleOpenVideo}
               width={this.props.cachedMediaWidth}
               cacheWidth={this.props.cacheMediaWidth}
+              revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined}
             />)}
           </Bundle>
         );
@@ -483,6 +484,7 @@ export default class Status extends ImmutablePureComponent {
                 onOpenMedia={this.props.onOpenMedia}
                 cacheWidth={this.props.cacheMediaWidth}
                 defaultWidth={this.props.cachedMediaWidth}
+                revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined}
               />
             )}
           </Bundle>
diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js
index 4e329f546..481e6644e 100644
--- a/app/javascript/flavours/glitch/components/status_prepend.js
+++ b/app/javascript/flavours/glitch/components/status_prepend.js
@@ -62,6 +62,13 @@ export default class StatusPrepend extends React.PureComponent {
           values={{ name : link }}
         />
       );
+    case 'poll':
+      return (
+        <FormattedMessage
+          id='notification.poll'
+          defaultMessage='A poll you have voted in has ended'
+        />
+      );
     }
     return null;
   }
@@ -75,7 +82,7 @@ export default class StatusPrepend extends React.PureComponent {
         <div className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}>
           <i
             className={`fa fa-fw fa-${
-              type === 'favourite' ? 'star star-icon' : (type === 'featured' ? 'thumb-tack' : 'retweet')
+              type === 'favourite' ? 'star star-icon' : (type === 'featured' ? 'thumb-tack' : (type === 'poll' ? 'tasks' : 'retweet'))
             } status__prepend-icon`}
           />
         </div>
diff --git a/app/javascript/flavours/glitch/containers/timeline_container.js b/app/javascript/flavours/glitch/containers/timeline_container.js
index 5a1f41f7a..6ce556664 100644
--- a/app/javascript/flavours/glitch/containers/timeline_container.js
+++ b/app/javascript/flavours/glitch/containers/timeline_container.js
@@ -7,7 +7,6 @@ import { hydrateStore } from 'flavours/glitch/actions/store';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from 'mastodon/locales';
 import PublicTimeline from 'flavours/glitch/features/standalone/public_timeline';
-import CommunityTimeline from 'flavours/glitch/features/standalone/community_timeline';
 import HashtagTimeline from 'flavours/glitch/features/standalone/hashtag_timeline';
 import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container';
 import initialState from 'flavours/glitch/util/initial_state';
@@ -26,24 +25,22 @@ export default class TimelineContainer extends React.PureComponent {
   static propTypes = {
     locale: PropTypes.string.isRequired,
     hashtag: PropTypes.string,
-    showPublicTimeline: PropTypes.bool.isRequired,
+    local: PropTypes.bool,
   };
 
   static defaultProps = {
-    showPublicTimeline: initialState.settings.known_fediverse,
+    local: !initialState.settings.known_fediverse,
   };
 
   render () {
-    const { locale, hashtag, showPublicTimeline } = this.props;
+    const { locale, hashtag, local } = this.props;
 
     let timeline;
 
     if (hashtag) {
       timeline = <HashtagTimeline hashtag={hashtag} />;
-    } else if (showPublicTimeline) {
-      timeline = <PublicTimeline />;
     } else {
-      timeline = <CommunityTimeline />;
+      timeline = <PublicTimeline local={local} />;
     }
 
     return (
@@ -51,6 +48,7 @@ export default class TimelineContainer extends React.PureComponent {
         <Provider store={store}>
           <Fragment>
             {timeline}
+
             {ReactDOM.createPortal(
               <ModalContainer />,
               document.getElementById('modal-container'),
diff --git a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js b/app/javascript/flavours/glitch/features/account/components/profile_column_header.js
index 1a6abef37..b6d373a2c 100644
--- a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js
+++ b/app/javascript/flavours/glitch/features/account/components/profile_column_header.js
@@ -11,16 +11,18 @@ export default @injectIntl
 class ProfileColumnHeader extends React.PureComponent {
 
   static propTypes = {
+    onClick: PropTypes.func,
     intl: PropTypes.object.isRequired,
   };
 
   render() {
-    const { intl } = this.props;
+    const { onClick, intl } = this.props;
 
     return (
       <ColumnHeader
         icon='user-circle'
         title={intl.formatMessage(messages.profile)}
+        onClick={onClick}
         showBackButton
       />
     );
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index a9ea5088e..63c1b2d86 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -65,6 +65,10 @@ export default class AccountGallery extends ImmutablePureComponent {
     }
   }
 
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
   handleScrollToBottom = () => {
     if (this.props.hasMore) {
       this.handleLoadMore(this.props.medias.size > 0 ? this.props.medias.last().getIn(['status', 'id']) : undefined);
@@ -94,6 +98,10 @@ export default class AccountGallery extends ImmutablePureComponent {
     return !(location.state && location.state.mastodonModalOpen);
   }
 
+  setRef = c => {
+    this.column = c;
+  }
+
   render () {
     const { medias, isLoading, hasMore } = this.props;
 
@@ -112,8 +120,8 @@ export default class AccountGallery extends ImmutablePureComponent {
     }
 
     return (
-      <Column>
-        <ProfileColumnHeader />
+      <Column ref={this.setRef}>
+        <ProfileColumnHeader onClick={this.handleHeaderClick} />
 
         <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={this.shouldUpdateScroll}>
           <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index 415e3be20..d683b8789 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -57,10 +57,18 @@ export default class AccountTimeline extends ImmutablePureComponent {
     }
   }
 
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
   handleLoadMore = maxId => {
     this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies }));
   }
 
+  setRef = c => {
+    this.column = c;
+  }
+
   render () {
     const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
 
@@ -73,8 +81,8 @@ export default class AccountTimeline extends ImmutablePureComponent {
     }
 
     return (
-      <Column name='account'>
-        <ProfileColumnHeader />
+      <Column ref={this.setRef} name='account'>
+        <ProfileColumnHeader onClick={this.handleHeaderClick} />
 
         <StatusList
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js
index 124004cb6..6bb9f60fd 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.js
@@ -43,6 +43,10 @@ export default class Followers extends ImmutablePureComponent {
     }
   }
 
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
   handleScroll = (e) => {
     const { scrollTop, scrollHeight, clientHeight } = e.target;
 
@@ -61,6 +65,10 @@ export default class Followers extends ImmutablePureComponent {
     return !(location.state && location.state.mastodonModalOpen);
   }
 
+  setRef = c => {
+    this.column = c;
+  }
+
   render () {
     const { accountIds, hasMore } = this.props;
 
@@ -79,8 +87,8 @@ export default class Followers extends ImmutablePureComponent {
     }
 
     return (
-      <Column>
-        <ProfileColumnHeader />
+      <Column ref={this.setRef}>
+        <ProfileColumnHeader onClick={this.handleHeaderClick} />
 
         <ScrollContainer scrollKey='followers' shouldUpdateScroll={this.shouldUpdateScroll}>
           <div className='scrollable' onScroll={this.handleScroll}>
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js
index 656100dad..3f2f091a1 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.js
@@ -43,6 +43,10 @@ export default class Following extends ImmutablePureComponent {
     }
   }
 
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
   handleScroll = (e) => {
     const { scrollTop, scrollHeight, clientHeight } = e.target;
 
@@ -61,6 +65,10 @@ export default class Following extends ImmutablePureComponent {
     return !(location.state && location.state.mastodonModalOpen);
   }
 
+  setRef = c => {
+    this.column = c;
+  }
+
   render () {
     const { accountIds, hasMore } = this.props;
 
@@ -79,8 +87,8 @@ export default class Following extends ImmutablePureComponent {
     }
 
     return (
-      <Column>
-        <ProfileColumnHeader />
+      <Column ref={this.setRef}>
+        <ProfileColumnHeader onClick={this.handleHeaderClick} />
 
         <ScrollContainer scrollKey='following' shouldUpdateScroll={this.shouldUpdateScroll}>
           <div className='scrollable' onScroll={this.handleScroll}>
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 4535d9849..bc4ad359c 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -328,6 +328,14 @@ export default class LocalSettingsPage extends React.PureComponent {
         >
           <FormattedMessage id='settings.inline_preview_cards' defaultMessage='Inline preview cards for external links' />
         </LocalSettingsPageItem>
+        <LocalSettingsPageItem
+          settings={settings}
+          item={['media', 'reveal_behind_cw']}
+          id='mastodon-settings--reveal-behind-cw'
+          onChange={onChange}
+        >
+          <FormattedMessage id='settings.media_reveal_behind_cw' defaultMessage='Reveal sensitive media behind a CW by default' />
+        </LocalSettingsPageItem>
       </div>
     ),
   ];
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
index 4e35d5b4e..e29bd61f5 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -90,6 +90,17 @@ export default class ColumnSettings extends React.PureComponent {
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
           </div>
         </div>
+
+        <div role='group' aria-labelledby='notifications-poll'>
+          <span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
+          </div>
+        </div>
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
index f95a2c9de..3457b7598 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
@@ -6,6 +6,7 @@ const tooltips = defineMessages({
   mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
   favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
   boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
+  polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
   follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
 });
 
@@ -79,6 +80,13 @@ class FilterBar extends React.PureComponent {
           <i className='fa fa-fw fa-retweet' />
         </button>
         <button
+          className={selectedFilter === 'poll' ? 'active' : ''}
+          onClick={this.onClick('poll')}
+          title={intl.formatMessage(tooltips.polls)}
+        >
+          <i className='fa fa-fw fa-tasks' />
+        </button>
+        <button
           className={selectedFilter === 'follow' ? 'active' : ''}
           onClick={this.onClick('follow')}
           title={intl.formatMessage(tooltips.follows)}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js
index daafe3507..5c5bbf604 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js
@@ -108,6 +108,27 @@ export default class Notification extends ImmutablePureComponent {
           withDismiss
         />
       );
+    case 'poll':
+      return (
+        <StatusContainer
+          containerId={notification.get('id')}
+          hidden={hidden}
+          id={notification.get('status')}
+          account={notification.get('account')}
+          prepend='poll'
+          muted
+          notification={notification}
+          onMoveDown={onMoveDown}
+          onMoveUp={onMoveUp}
+          onMention={onMention}
+          getScrollPosition={getScrollPosition}
+          updateScrollBottom={updateScrollBottom}
+          cachedMediaWidth={this.props.cachedMediaWidth}
+          cacheMediaWidth={this.props.cacheMediaWidth}
+          onUnmount={this.props.onUnmount}
+          withDismiss
+        />
+      );
     default:
       return null;
     }
diff --git a/app/javascript/flavours/glitch/features/standalone/community_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/community_timeline/index.js
deleted file mode 100644
index 2b67e836a..000000000
--- a/app/javascript/flavours/glitch/features/standalone/community_timeline/index.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
-import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
-import Column from 'flavours/glitch/components/column';
-import ColumnHeader from 'flavours/glitch/components/column_header';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connectCommunityStream } from 'flavours/glitch/actions/streaming';
-
-const messages = defineMessages({
-  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
-});
-
-@connect()
-@injectIntl
-export default class CommunityTimeline extends React.PureComponent {
-
-  static propTypes = {
-    dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  handleHeaderClick = () => {
-    this.column.scrollTop();
-  }
-
-  setRef = c => {
-    this.column = c;
-  }
-
-  componentDidMount () {
-    const { dispatch } = this.props;
-
-    dispatch(expandCommunityTimeline());
-    this.disconnect = dispatch(connectCommunityStream());
-  }
-
-  componentWillUnmount () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
-  }
-
-  handleLoadMore = maxId => {
-    this.props.dispatch(expandCommunityTimeline({ maxId }));
-  }
-
-  render () {
-    const { intl } = this.props;
-
-    return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
-        <ColumnHeader
-          icon='users'
-          title={intl.formatMessage(messages.title)}
-          onClick={this.handleHeaderClick}
-        />
-
-        <StatusListContainer
-          timelineId='community'
-          onLoadMore={this.handleLoadMore}
-          scrollKey='standalone_public_timeline'
-          trackScroll={false}
-        />
-      </Column>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js
index 907da3992..5e2b3fc6d 100644
--- a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js
@@ -1,70 +1,112 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
-import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
-import { expandPublicTimeline } from 'flavours/glitch/actions/timelines';
-import Column from 'flavours/glitch/components/column';
-import ColumnHeader from 'flavours/glitch/components/column_header';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connectPublicStream } from 'flavours/glitch/actions/streaming';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
+import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming';
+import Masonry from 'react-masonry-infinite';
+import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
+import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container';
+import { debounce } from 'lodash';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
 
-const messages = defineMessages({
-  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
-});
+const mapStateToProps = (state, { local }) => {
+  const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
 
-@connect()
-@injectIntl
-export default class PublicTimeline extends React.PureComponent {
+  return {
+    statusIds: timeline.get('items', ImmutableList()),
+    isLoading: timeline.get('isLoading', false),
+    hasMore: timeline.get('hasMore', false),
+  };
+};
+
+export default @connect(mapStateToProps)
+class PublicTimeline extends React.PureComponent {
 
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    isLoading: PropTypes.bool.isRequired,
+    hasMore: PropTypes.bool.isRequired,
+    local: PropTypes.bool,
   };
 
-  handleHeaderClick = () => {
-    this.column.scrollTop();
+  componentDidMount () {
+    this._connect();
   }
 
-  setRef = c => {
-    this.column = c;
+  componentDidUpdate (prevProps) {
+    if (prevProps.local !== this.props.local) {
+      this._disconnect();
+      this._connect();
+    }
   }
 
-  componentDidMount () {
-    const { dispatch } = this.props;
-
-    dispatch(expandPublicTimeline());
-    this.disconnect = dispatch(connectPublicStream());
+  componentWillUnmount () {
+    this._disconnect();
   }
 
-  componentWillUnmount () {
+  _connect () {
+    const { dispatch, local } = this.props;
+
+    dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
+    this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream());
+  }
+ 
+  _disconnect () {
     if (this.disconnect) {
       this.disconnect();
       this.disconnect = null;
     }
   }
 
-  handleLoadMore = maxId => {
-    this.props.dispatch(expandPublicTimeline({ maxId }));
+  handleLoadMore = () => {
+    const { dispatch, statusIds, local } = this.props;
+    const maxId = statusIds.last();
+
+    if (maxId) {
+      dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
+    }
+  }
+
+  setRef = c => {
+    this.masonry = c;
   }
 
+  handleHeightChange = debounce(() => {
+    if (!this.masonry) {
+      return;
+    }
+
+    this.masonry.forcePack();
+  }, 50)
+
   render () {
-    const { intl } = this.props;
+    const { statusIds, hasMore, isLoading } = this.props;
+
+    const sizes = [
+      { columns: 1, gutter: 0 },
+      { mq: '415px', columns: 1, gutter: 10 },
+      { mq: '640px', columns: 2, gutter: 10 },
+      { mq: '960px', columns: 3, gutter: 10 },
+      { mq: '1255px', columns: 3, gutter: 10 },
+    ];
+
+    const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
-        <ColumnHeader
-          icon='globe'
-          title={intl.formatMessage(messages.title)}
-          onClick={this.handleHeaderClick}
-        />
-
-        <StatusListContainer
-          timelineId='public'
-          onLoadMore={this.handleLoadMore}
-          scrollKey='standalone_public_timeline'
-          trackScroll={false}
-        />
-      </Column>
+      <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
+        {statusIds.map(statusId => (
+          <div className='statuses-grid__item' key={statusId}>
+            <DetailedStatusContainer
+              id={statusId}
+              compact
+              measureHeight
+              onHeightChange={this.handleHeightChange}
+            />
+          </div>
+        )).toArray()}
+      </Masonry>
     );
   }
 
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 ad60320ef..e78f16c54 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -23,7 +23,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
   };
 
   static propTypes = {
-    status: ImmutablePropTypes.map.isRequired,
+    status: ImmutablePropTypes.map,
     settings: ImmutablePropTypes.map.isRequired,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
@@ -138,6 +138,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
             preventPlayback={!expanded}
             onOpenVideo={this.handleOpenVideo}
             autoplay
+            revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined}
           />
         );
         mediaIcon = 'video-camera';
@@ -151,6 +152,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
             fullwidth={settings.getIn(['media', 'fullwidth'])}
             hidden={!expanded}
             onOpenMedia={this.props.onOpenMedia}
+            revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined}
           />
         );
         mediaIcon = 'picture-o';
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 880372de5..73d3c7e9e 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -369,6 +369,10 @@ export default class Status extends ImmutablePureComponent {
     }
   }
 
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
   renderChildren (list) {
     return list.map(id => (
       <StatusContainer
@@ -390,6 +394,10 @@ export default class Status extends ImmutablePureComponent {
     this.node = c;
   }
 
+  setColumnRef = c => {
+    this.column = c;
+  }
+
   componentDidUpdate (prevProps) {
     if (this.props.params.statusId && (this.props.params.statusId !== prevProps.params.statusId || prevProps.ancestorsIds.size < this.props.ancestorsIds.size)) {
       const { status, ancestorsIds } = this.props;
@@ -452,10 +460,11 @@ export default class Status extends ImmutablePureComponent {
     };
 
     return (
-      <Column label={intl.formatMessage(messages.detailedStatus)}>
+      <Column ref={this.setColumnRef} label={intl.formatMessage(messages.detailedStatus)}>
         <ColumnHeader
           icon='comment'
           title={intl.formatMessage(messages.tootHeading)}
+          onClick={this.handleHeaderClick}
           showBackButton
           extraButton={(
             <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><i className={`fa fa-${!isExpanded ? 'eye-slash' : 'eye'}`} /></button>
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index cf66536c4..e3ed799c7 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -119,6 +119,12 @@ export default class Video extends React.PureComponent {
     revealed: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed,
   };
 
+  componentWillReceiveProps (nextProps) {
+    if (nextProps.revealed === true) {
+      this.setState({ revealed: true });
+    }
+  }
+
   // hard coded in components.scss
   // any way to get ::before values programatically?
   volWidth = 50;
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 93a404328..ef694d4ea 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -39,8 +39,9 @@ const initialState = ImmutableMap({
     show_action_bar : true,
   }),
   media     : ImmutableMap({
-    letterbox   : true,
-    fullwidth   : true,
+    letterbox        : true,
+    fullwidth        : true,
+    reveal_behind_cw : false,
   }),
   notifications : ImmutableMap({
     favicon_badge : false,
diff --git a/app/javascript/flavours/glitch/reducers/push_notifications.js b/app/javascript/flavours/glitch/reducers/push_notifications.js
index 1b47ca962..e87e8fc1a 100644
--- a/app/javascript/flavours/glitch/reducers/push_notifications.js
+++ b/app/javascript/flavours/glitch/reducers/push_notifications.js
@@ -9,6 +9,7 @@ const initialState = Immutable.Map({
     favourite: false,
     reblog: false,
     mention: false,
+    poll: false,
   }),
   isSubscribed: false,
   browserSupport: false,
diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js
index 384ff2d44..cc86a6b20 100644
--- a/app/javascript/flavours/glitch/reducers/settings.js
+++ b/app/javascript/flavours/glitch/reducers/settings.js
@@ -33,6 +33,7 @@ const initialState = ImmutableMap({
       favourite: true,
       reblog: true,
       mention: true,
+      poll: true,
     }),
 
     quickFilter: ImmutableMap({
@@ -46,6 +47,7 @@ const initialState = ImmutableMap({
       favourite: true,
       reblog: true,
       mention: true,
+      poll: true,
     }),
 
     sounds: ImmutableMap({
@@ -53,6 +55,7 @@ const initialState = ImmutableMap({
       favourite: true,
       reblog: true,
       mention: true,
+      poll: true,
     }),
   }),
 
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index 329482458..7a457600e 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -193,6 +193,7 @@ $small-breakpoint: 960px;
     }
 
     strong {
+      font-family: $font-display, sans-serif;
       font-weight: 500;
       font-size: 32px;
       line-height: 48px;
@@ -282,168 +283,6 @@ $small-breakpoint: 960px;
 }
 
 .landing-page {
-  .grid {
-    display: grid;
-    grid-gap: 10px;
-    grid-template-columns: 1fr 2fr;
-    grid-auto-columns: 25%;
-    grid-auto-rows: max-content;
-
-    .column-0 {
-      display: none;
-    }
-
-    .column-1 {
-      grid-column: 1;
-      grid-row: 1;
-    }
-
-    .column-2 {
-      grid-column: 2;
-      grid-row: 1;
-    }
-
-    .column-3 {
-      grid-column: 3;
-      grid-row: 1 / 3;
-    }
-
-    .column-4 {
-      grid-column: 1 / 3;
-      grid-row: 2;
-    }
-  }
-
-  @media screen and (max-width: $small-breakpoint) {
-    .grid {
-      grid-template-columns: 40% 60%;
-
-      .column-0 {
-        display: none;
-      }
-
-      .column-1 {
-        grid-column: 1;
-        grid-row: 1;
-
-        &.non-preview .landing-page__forms {
-          height: 100%;
-        }
-      }
-
-      .column-2 {
-        grid-column: 2;
-        grid-row: 1 / 3;
-
-        &.non-preview {
-          grid-column: 2;
-          grid-row: 1;
-        }
-      }
-
-      .column-3 {
-        grid-column: 1;
-        grid-row: 2 / 4;
-      }
-
-      .column-4 {
-        grid-column: 2;
-        grid-row: 3;
-
-        &.non-preview {
-          grid-column: 1 / 3;
-          grid-row: 2;
-        }
-      }
-    }
-  }
-
-  @media screen and (max-width: $column-breakpoint) {
-    .grid {
-      grid-template-columns: 100%;
-
-      .column-0 {
-        display: block;
-        grid-column: 1;
-        grid-row: 1;
-      }
-
-      .column-1 {
-        grid-column: 1;
-        grid-row: 3;
-
-        .brand {
-          display: none;
-        }
-      }
-
-      .column-2 {
-        grid-column: 1;
-        grid-row: 2;
-
-        .landing-page__logo,
-        .landing-page__call-to-action {
-          display: none;
-        }
-
-        &.non-preview {
-          grid-column: 1;
-          grid-row: 2;
-        }
-      }
-
-      .column-3 {
-        grid-column: 1;
-        grid-row: 5;
-      }
-
-      .column-4 {
-        grid-column: 1;
-        grid-row: 4;
-
-        &.non-preview {
-          grid-column: 1;
-          grid-row: 4;
-        }
-      }
-    }
-  }
-
-  .column-flex {
-    display: flex;
-    flex-direction: column;
-  }
-
-  .separator-or {
-    position: relative;
-    margin: 40px 0;
-    text-align: center;
-
-    &::before {
-      content: "";
-      display: block;
-      width: 100%;
-      height: 0;
-      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
-      position: absolute;
-      top: 50%;
-      left: 0;
-    }
-
-    span {
-      display: inline-block;
-      background: $ui-base-color;
-      font-size: 12px;
-      font-weight: 500;
-      color: $darker-text-color;
-      text-transform: uppercase;
-      position: relative;
-      z-index: 1;
-      padding: 0 8px;
-      cursor: default;
-    }
-  }
-
   p,
   li {
     font-family: $font-sans-serif, sans-serif;
@@ -460,28 +299,6 @@ $small-breakpoint: 960px;
     }
   }
 
-  .closed-registrations-message {
-    margin-top: 20px;
-
-    &,
-    p {
-      text-align: center;
-      font-size: 12px;
-      line-height: 18px;
-      color: $darker-text-color;
-      margin-bottom: 0;
-
-      a {
-        color: $highlight-text-color;
-        text-decoration: underline;
-      }
-    }
-
-    p:last-child {
-      margin-bottom: 0;
-    }
-  }
-
   em {
     display: inline;
     margin: 0;
@@ -595,187 +412,6 @@ $small-breakpoint: 960px;
     }
   }
 
-  .container-alt {
-    width: 100%;
-    box-sizing: border-box;
-    max-width: 800px;
-    margin: 0 auto;
-    word-wrap: break-word;
-  }
-
-  .header-wrapper {
-    padding-top: 15px;
-    background: $ui-base-color;
-    background: linear-gradient(150deg, lighten($ui-base-color, 8%), $ui-base-color);
-    position: relative;
-
-    &.compact {
-      background: $ui-base-color;
-      padding-bottom: 15px;
-
-      .hero .heading {
-        padding-bottom: 20px;
-        font-family: $font-sans-serif, sans-serif;
-        font-size: 16px;
-        font-weight: 400;
-        font-size: 16px;
-        line-height: 30px;
-        color: $darker-text-color;
-
-        a {
-          color: $highlight-text-color;
-          text-decoration: underline;
-        }
-      }
-    }
-  }
-
-  .brand {
-    a {
-      padding-left: 0;
-      padding-right: 0;
-      color: $white;
-    }
-
-    img {
-      height: 32px;
-      position: relative;
-      top: 4px;
-      left: -10px;
-    }
-  }
-
-  .header {
-    line-height: 30px;
-    overflow: hidden;
-
-    .container-alt {
-      display: flex;
-      justify-content: space-between;
-    }
-
-    .links {
-      position: relative;
-      z-index: 4;
-
-      a {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        color: $darker-text-color;
-        text-decoration: none;
-        padding: 12px 16px;
-        line-height: 32px;
-        font-family: $font-display, sans-serif;
-        font-weight: 500;
-        font-size: 14px;
-
-        &:hover {
-          color: $secondary-text-color;
-        }
-      }
-
-      ul {
-        list-style: none;
-        margin: 0;
-
-        li {
-          display: inline-block;
-          vertical-align: bottom;
-          margin: 0;
-
-          &:first-child a {
-            padding-left: 0;
-          }
-
-          &:last-child a {
-            padding-right: 0;
-          }
-        }
-      }
-    }
-
-    .hero {
-      margin-top: 50px;
-      align-items: center;
-      position: relative;
-
-      .heading {
-        position: relative;
-        z-index: 4;
-        padding-bottom: 150px;
-      }
-
-      .simple_form,
-      .closed-registrations-message {
-        background: darken($ui-base-color, 4%);
-        width: 280px;
-        padding: 15px 20px;
-        border-radius: 4px 4px 0 0;
-        line-height: initial;
-        position: relative;
-        z-index: 4;
-
-        .actions {
-          margin-bottom: 0;
-
-          button,
-          .button,
-          .block-button {
-            margin-bottom: 0;
-          }
-        }
-      }
-
-      .closed-registrations-message {
-        min-height: 330px;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-      }
-    }
-  }
-
-  .about-short {
-    background: darken($ui-base-color, 4%);
-    padding: 50px 0 30px;
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 16px;
-    font-weight: 400;
-    font-size: 16px;
-    line-height: 30px;
-    color: $darker-text-color;
-
-    a {
-      color: $highlight-text-color;
-      text-decoration: underline;
-    }
-  }
-
-  &.alternative {
-    padding: 10px 0;
-
-    .brand {
-      text-align: center;
-      padding: 30px 0;
-      margin-bottom: 10px;
-
-      img {
-        position: static;
-        padding: 10px 0;
-      }
-
-      @media screen and (max-width: $small-breakpoint) {
-        padding: 15px 0;
-      }
-
-      @media screen and (max-width: $column-breakpoint) {
-        padding: 0;
-        margin-bottom: -10px;
-      }
-    }
-  }
-
   &__information,
   &__forms {
     padding: 20px;
@@ -970,353 +606,253 @@ $small-breakpoint: 960px;
     }
   }
 
-  &__forms {
-    height: 100%;
-
-    @media screen and (max-width: $small-breakpoint) {
-      height: auto;
-    }
-
-    @media screen and (max-width: $column-breakpoint) {
-      background: transparent;
-      box-shadow: none;
-      padding: 0 20px;
-      margin-top: 30px;
-      margin-bottom: 40px;
-
-      .separator-or {
-        span {
-          background: darken($ui-base-color, 8%);
-        }
+  @media screen and (max-width: 840px) {
+    .information-board {
+      .container-alt {
+        padding-right: 20px;
       }
-    }
-
-    hr {
-      margin: 40px 0;
-    }
 
-    .button {
-      display: block;
-    }
-
-    .subtle-hint a {
-      text-decoration: none;
+      .panel {
+        position: static;
+        margin-top: 20px;
+        width: 100%;
+        border-radius: 4px;
 
-      &:hover,
-      &:focus,
-      &:active {
-        text-decoration: underline;
+        .panel-header {
+          text-align: center;
+        }
       }
     }
   }
 
-  #mastodon-timeline {
-    display: flex;
-    -webkit-overflow-scrolling: touch;
-    -ms-overflow-style: -ms-autohiding-scrollbar;
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 13px;
-    line-height: 18px;
-    font-weight: 400;
-    color: $primary-text-color;
-    width: 100%;
-    flex: 1 1 auto;
-    overflow: hidden;
-    height: 100%;
-
-    .column-header {
-      color: inherit;
-      font-family: inherit;
-      font-size: 16px;
-      line-height: inherit;
-      font-weight: inherit;
-      margin: 0;
-      padding: 0;
-    }
-
-    .column {
-      padding: 0;
-      border-radius: 4px;
-      overflow: hidden;
-      width: 100%;
-    }
-
-    .scrollable {
-      height: 400px;
-    }
-
-    p {
-      font-size: inherit;
-      line-height: inherit;
-      font-weight: inherit;
-      color: $primary-text-color;
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
+  @media screen and (max-width: 675px) {
+    .header-wrapper {
+      padding-top: 0;
 
-      a {
-        color: $secondary-text-color;
-        text-decoration: none;
+      &.compact {
+        padding-bottom: 0;
       }
-    }
-
-    .attachment-list__list {
-      margin-left: 0;
-      list-style: none;
-
-      li {
-        font-size: inherit;
-        line-height: inherit;
-        font-weight: inherit;
-        margin-bottom: 0;
-
-        a {
-          color: $dark-text-color;
-          text-decoration: none;
 
-          &:hover {
-            text-decoration: underline;
-          }
-        }
+      &.compact .hero .heading {
+        text-align: initial;
       }
     }
 
-    @media screen and (max-width: $column-breakpoint) {
-      display: none;
+    .header .container-alt,
+    .features .container-alt {
+      display: block;
     }
   }
 
-  &__features {
-    & > p {
-      padding-right: 60px;
-    }
-
-    .features-list {
-      margin: 40px 0;
-      margin-top: 30px;
-    }
-
-    &__action {
-      text-align: center;
-    }
+  .cta {
+    margin: 20px;
   }
+}
 
-  .features-list {
-    .features-list__row {
-      display: flex;
-      padding: 10px 0;
-      justify-content: space-between;
-
-      .visual {
-        flex: 0 0 auto;
-        display: flex;
-        align-items: center;
-        margin-left: 15px;
+.landing {
+  margin-bottom: 100px;
 
-        .fa {
-          display: block;
-          color: $darker-text-color;
-          font-size: 48px;
-        }
-      }
+  @media screen and (max-width: 738px) {
+    margin-bottom: 0;
+  }
 
-      .text {
-        font-size: 16px;
-        line-height: 30px;
-        color: $darker-text-color;
+  &__brand {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 100px;
 
-        h6 {
-          font-size: inherit;
-          line-height: inherit;
-          margin-bottom: 0;
-        }
-      }
+    img {
+      height: 52px;
     }
 
-    @media screen and (min-width: $small-breakpoint) {
-      display: grid;
-      grid-gap: 30px;
-      grid-template-columns: 1fr 1fr;
-      grid-auto-columns: 50%;
-      grid-auto-rows: max-content;
+    @media screen and (max-width: $no-gap-breakpoint) {
+      padding: 0;
+      margin-bottom: 30px;
     }
   }
 
-  .footer-links {
-    padding-bottom: 50px;
-    text-align: right;
-    color: $dark-text-color;
+  .directory {
+    margin-top: 30px;
+    background: transparent;
+    box-shadow: none;
+    border-radius: 0;
+  }
 
-    p {
-      font-size: 14px;
-    }
+  .hero-widget {
+    margin-top: 30px;
+    margin-bottom: 0;
 
-    a {
-      color: inherit;
-      text-decoration: underline;
+    h4 {
+      padding: 10px;
+      text-transform: uppercase;
+      font-weight: 700;
+      font-size: 13px;
+      color: $darker-text-color;
     }
-  }
 
-  &__footer {
-    margin-top: 10px;
-    text-align: center;
-    color: $dark-text-color;
+    &__text {
+      border-radius: 0;
+      padding-bottom: 0;
+    }
 
-    p {
-      font-size: 14px;
+    &__footer {
+      background: $ui-base-color;
+      padding: 10px;
+      border-radius: 0 0 4px 4px;
+      display: flex;
 
-      a {
-        color: inherit;
-        text-decoration: underline;
+      &__column {
+        flex: 1 1 50%;
       }
     }
-  }
 
-  @media screen and (max-width: 840px) {
-    .container-alt {
-      padding: 0 20px;
-    }
+    .account {
+      padding: 10px 0;
+      border-bottom: 0;
 
-    .information-board {
-      .container-alt {
-        padding-right: 20px;
+      .account__display-name {
+        display: flex;
+        align-items: center;
       }
 
-      .panel {
-        position: static;
-        margin-top: 20px;
-        width: 100%;
-        border-radius: 4px;
-
-        .panel-header {
-          text-align: center;
-        }
+      .account__avatar {
+        width: 44px;
+        height: 44px;
+        background-size: 44px 44px;
       }
     }
-  }
 
-  @media screen and (max-width: 675px) {
-    .header-wrapper {
-      padding-top: 0;
+    &__counter {
+      padding: 10px;
 
-      &.compact {
-        padding-bottom: 0;
+      strong {
+        font-family: $font-display, sans-serif;
+        font-size: 15px;
+        font-weight: 700;
+        display: block;
       }
 
-      &.compact .hero .heading {
-        text-align: initial;
+      span {
+        font-size: 14px;
+        color: $darker-text-color;
       }
     }
+  }
 
-    .header .container-alt,
-    .features .container-alt {
-      display: block;
-    }
-
-    .header {
-      .links {
-        padding-top: 15px;
-        background: darken($ui-base-color, 4%);
+  .simple_form .user_agreement .label_input > label {
+    font-weight: 400;
+    color: $darker-text-color;
+  }
 
-        a {
-          padding: 12px 8px;
-        }
+  .simple_form p.lead {
+    color: $darker-text-color;
+    font-size: 15px;
+    line-height: 20px;
+    font-weight: 400;
+    margin-bottom: 25px;
+  }
 
-        .nav {
-          display: flex;
-          flex-flow: row wrap;
-          justify-content: space-around;
-        }
+  &__grid {
+    max-width: 960px;
+    margin: 0 auto;
+    display: grid;
+    grid-template-columns: minmax(0, 50%) minmax(0, 50%);
+    grid-gap: 30px;
 
-        .brand img {
-          left: 0;
-          top: 0;
-        }
-      }
+    @media screen and (max-width: 738px) {
+      grid-template-columns: minmax(0, 100%);
+      grid-gap: 10px;
 
-      .hero {
-        margin-top: 30px;
-        padding: 0;
+      &__column-login {
+        grid-row: 1;
+        display: flex;
+        flex-direction: column;
 
-        .heading {
-          padding: 30px 20px;
-          text-align: center;
+        .box-widget {
+          order: 2;
+          flex: 0 0 auto;
         }
 
-        .simple_form,
-        .closed-registrations-message {
-          background: darken($ui-base-color, 8%);
-          width: 100%;
-          border-radius: 0;
-          box-sizing: border-box;
+        .hero-widget {
+          margin-top: 0;
+          margin-bottom: 10px;
+          order: 1;
+          flex: 0 0 auto;
         }
       }
-    }
-  }
-
-  .cta {
-    margin: 20px;
-  }
 
-  &.tag-page {
-    @media screen and (max-width: $column-breakpoint) {
-      padding: 0;
-
-      .container {
-        padding: 0;
+      &__column-registration {
+        grid-row: 2;
       }
 
-      #mastodon-timeline {
-        display: flex;
-        height: 100vh;
-        border-radius: 0;
+      .directory {
+        margin-top: 10px;
       }
     }
 
-    .grid {
-      @media screen and (min-width: $small-breakpoint) {
-        grid-template-columns: 33% 67%;
-      }
-
-      .column-2 {
-        grid-column: 2;
-        grid-row: 1;
-      }
-    }
+    @media screen and (max-width: $no-gap-breakpoint) {
+      grid-gap: 0;
 
-    .brand {
-      text-align: unset;
-      padding: 0;
+      .hero-widget {
+        display: block;
+        margin-bottom: 0;
+        box-shadow: none;
 
-      img {
-        height: 48px;
-        width: auto;
+        &__img,
+        &__img img,
+        &__footer {
+          border-radius: 0;
+        }
       }
-    }
 
-    .cta {
-      margin: 0;
-
-      .button {
-        margin-right: 4px;
+      .hero-widget,
+      .box-widget,
+      .directory__tag {
+        border-bottom: 1px solid lighten($ui-base-color, 8%);
       }
-    }
 
-    @media screen and (max-width: $column-breakpoint) {
-      .grid {
-        grid-gap: 0;
+      .directory {
+        margin-top: 0;
 
-        .column-1 {
-          grid-column: 1;
-          grid-row: 1;
-        }
+        &__tag {
+          margin-bottom: 0;
 
-        .column-2 {
-          display: none;
+          & > a,
+          & > div {
+            border-radius: 0;
+            box-shadow: none;
+          }
+
+          &:last-child {
+            border-bottom: 0;
+          }
         }
       }
     }
   }
 }
+
+.brand {
+  position: relative;
+  text-decoration: none;
+}
+
+.brand__tagline {
+  display: block;
+  position: absolute;
+  bottom: -10px;
+  left: 50px;
+  width: 300px;
+  color: $ui-primary-color;
+  text-decoration: none;
+  font-size: 14px;
+
+  @media screen and (max-width: $no-gap-breakpoint) {
+    position: static;
+    width: auto;
+    margin-top: 20px;
+    color: $dark-text-color;
+  }
+}
+
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 4dbbaa1e8..42f53f525 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -705,3 +705,11 @@ a.name-tag,
   overflow: hidden;
   text-overflow: ellipsis;
 }
+
+.ellipsized-ip {
+  display: inline-block;
+  max-width: 120px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  vertical-align: middle;
+}
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index bab982706..6051c1d00 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -68,6 +68,17 @@ code {
         top: 2px;
         left: 0;
       }
+
+      label a {
+        color: $highlight-text-color;
+        text-decoration: underline;
+
+        &:hover,
+        &:active,
+        &:focus {
+          text-decoration: none;
+        }
+      }
     }
   }
 
@@ -305,7 +316,7 @@ code {
       box-shadow: none;
     }
 
-    &:focus:invalid {
+    &:focus:invalid:not(:placeholder-shown) {
       border-color: lighten($error-red, 12%);
     }
 
@@ -346,6 +357,10 @@ code {
     }
   }
 
+  .input.disabled {
+    opacity: 0.5;
+  }
+
   .actions {
     margin-top: 30px;
     display: flex;
@@ -392,6 +407,10 @@ code {
       background-color: darken($ui-highlight-color, 5%);
     }
 
+    &:disabled:hover {
+      background-color: $ui-primary-color;
+    }
+
     &.negative {
       background: $error-value-color;
 
diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss
index 4f8c94d83..6030e1e98 100644
--- a/app/javascript/flavours/glitch/styles/polls.scss
+++ b/app/javascript/flavours/glitch/styles/polls.scss
@@ -144,6 +144,7 @@
 
     button,
     select {
+      width: 100%;
       flex: 1 1 50%;
     }
   }
@@ -190,3 +191,15 @@
     color: darken($simple-background-color, 14%);
   }
 }
+
+.muted .poll {
+  color: $dark-text-color;
+
+  &__chart {
+    background: rgba(darken($ui-primary-color, 14%), 0.2);
+
+    &.leading {
+      background: rgba($ui-highlight-color, 0.2);
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss
index 9fd0b95bb..296182ff5 100644
--- a/app/javascript/flavours/glitch/styles/tables.scss
+++ b/app/javascript/flavours/glitch/styles/tables.scss
@@ -82,6 +82,10 @@
       }
     }
   }
+
+  &--invites tbody td {
+    vertical-align: middle;
+  }
 }
 
 .table-wrapper {
diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss
index 1eaf30c5b..645192ea4 100644
--- a/app/javascript/flavours/glitch/styles/widgets.scss
+++ b/app/javascript/flavours/glitch/styles/widgets.scss
@@ -295,6 +295,11 @@
       cursor: default;
     }
 
+    &.disabled > div {
+      opacity: 0.5;
+      cursor: default;
+    }
+
     h4 {
       flex: 1 1 auto;
       font-size: 18px;
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 4c145febc..61fef19e9 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -92,7 +92,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
 
 const excludeTypesFromFilter = filter => {
-  const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
+  const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']);
   return allTypes.filterNot(item => item === filter).toJS();
 };
 
diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js
index a1a4bd024..54f8eb310 100644
--- a/app/javascript/mastodon/containers/timeline_container.js
+++ b/app/javascript/mastodon/containers/timeline_container.js
@@ -7,7 +7,6 @@ import { hydrateStore } from '../actions/store';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import PublicTimeline from '../features/standalone/public_timeline';
-import CommunityTimeline from '../features/standalone/community_timeline';
 import HashtagTimeline from '../features/standalone/hashtag_timeline';
 import ModalContainer from '../features/ui/containers/modal_container';
 import initialState from '../initial_state';
@@ -26,24 +25,22 @@ export default class TimelineContainer extends React.PureComponent {
   static propTypes = {
     locale: PropTypes.string.isRequired,
     hashtag: PropTypes.string,
-    showPublicTimeline: PropTypes.bool.isRequired,
+    local: PropTypes.bool,
   };
 
   static defaultProps = {
-    showPublicTimeline: initialState.settings.known_fediverse,
+    local: !initialState.settings.known_fediverse,
   };
 
   render () {
-    const { locale, hashtag, showPublicTimeline } = this.props;
+    const { locale, hashtag, local } = this.props;
 
     let timeline;
 
     if (hashtag) {
       timeline = <HashtagTimeline hashtag={hashtag} />;
-    } else if (showPublicTimeline) {
-      timeline = <PublicTimeline />;
     } else {
-      timeline = <CommunityTimeline />;
+      timeline = <PublicTimeline local={local} />;
     }
 
     return (
@@ -51,6 +48,7 @@ export default class TimelineContainer extends React.PureComponent {
         <Provider store={store}>
           <Fragment>
             {timeline}
+
             {ReactDOM.createPortal(
               <ModalContainer />,
               document.getElementById('modal-container'),
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index a334fd63c..60a86312a 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -89,6 +89,17 @@ export default class ColumnSettings extends React.PureComponent {
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
           </div>
         </div>
+
+        <div role='group' aria-labelledby='notifications-poll'>
+          <span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
+          </div>
+        </div>
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.js b/app/javascript/mastodon/features/notifications/components/filter_bar.js
index 6ae8b7491..3f3e6ab7d 100644
--- a/app/javascript/mastodon/features/notifications/components/filter_bar.js
+++ b/app/javascript/mastodon/features/notifications/components/filter_bar.js
@@ -7,6 +7,7 @@ const tooltips = defineMessages({
   mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
   favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
   boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
+  polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
   follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
 });
 
@@ -80,6 +81,13 @@ class FilterBar extends React.PureComponent {
           <Icon id='retweet' fixedWidth />
         </button>
         <button
+          className={selectedFilter === 'poll' ? 'active' : ''}
+          onClick={this.onClick('poll')}
+          title={intl.formatMessage(tooltips.polls)}
+        >
+          <Icon id='tasks' fixedWidth />
+        </button>
+        <button
           className={selectedFilter === 'follow' ? 'active' : ''}
           onClick={this.onClick('follow')}
           title={intl.formatMessage(tooltips.follows)}
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index 9669b6e7d..4bdf09166 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -205,6 +205,38 @@ class Notification extends ImmutablePureComponent {
     );
   }
 
+  renderPoll (notification) {
+    const { intl } = this.props;
+
+    return (
+      <HotKeys handlers={this.getHandlers()}>
+        <div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }), notification.get('created_at'))}>
+          <div className='notification__message'>
+            <div className='notification__favourite-icon-wrapper'>
+              <Icon id='tasks' fixedWidth />
+            </div>
+
+            <span title={notification.get('created_at')}>
+              <FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
+            </span>
+          </div>
+
+          <StatusContainer
+            id={notification.get('status')}
+            account={notification.get('account')}
+            muted
+            withDismiss
+            hidden={this.props.hidden}
+            getScrollPosition={this.props.getScrollPosition}
+            updateScrollBottom={this.props.updateScrollBottom}
+            cachedMediaWidth={this.props.cachedMediaWidth}
+            cacheMediaWidth={this.props.cacheMediaWidth}
+          />
+        </div>
+      </HotKeys>
+    );
+  }
+
   render () {
     const { notification } = this.props;
     const account          = notification.get('account');
@@ -220,6 +252,8 @@ class Notification extends ImmutablePureComponent {
       return this.renderFavourite(notification, link);
     case 'reblog':
       return this.renderReblog(notification, link);
+    case 'poll':
+      return this.renderPoll(notification);
     }
 
     return null;
diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js
deleted file mode 100644
index f917f41c9..000000000
--- a/app/javascript/mastodon/features/standalone/community_timeline/index.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../../ui/containers/status_list_container';
-import { expandCommunityTimeline } from '../../../actions/timelines';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connectCommunityStream } from '../../../actions/streaming';
-
-const messages = defineMessages({
-  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
-});
-
-export default @connect()
-@injectIntl
-class CommunityTimeline extends React.PureComponent {
-
-  static propTypes = {
-    dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  handleHeaderClick = () => {
-    this.column.scrollTop();
-  }
-
-  setRef = c => {
-    this.column = c;
-  }
-
-  componentDidMount () {
-    const { dispatch } = this.props;
-
-    dispatch(expandCommunityTimeline());
-    this.disconnect = dispatch(connectCommunityStream());
-  }
-
-  componentWillUnmount () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
-  }
-
-  handleLoadMore = maxId => {
-    this.props.dispatch(expandCommunityTimeline({ maxId }));
-  }
-
-  render () {
-    const { intl } = this.props;
-
-    return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
-        <ColumnHeader
-          icon='users'
-          title={intl.formatMessage(messages.title)}
-          onClick={this.handleHeaderClick}
-        />
-
-        <StatusListContainer
-          timelineId='community'
-          onLoadMore={this.handleLoadMore}
-          scrollKey='standalone_public_timeline'
-          trackScroll={false}
-        />
-      </Column>
-    );
-  }
-
-}
diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
index 333726f94..0880d98c8 100644
--- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
@@ -2,13 +2,13 @@ import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { expandHashtagTimeline } from '../../../actions/timelines';
-import { connectHashtagStream } from '../../../actions/streaming';
+import { expandHashtagTimeline } from 'mastodon/actions/timelines';
+import { connectHashtagStream } from 'mastodon/actions/streaming';
 import Masonry from 'react-masonry-infinite';
 import { List as ImmutableList } from 'immutable';
-import DetailedStatusContainer from '../../status/containers/detailed_status_container';
+import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
 import { debounce } from 'lodash';
-import LoadingIndicator from '../../../components/loading_indicator';
+import LoadingIndicator from 'mastodon/components/loading_indicator';
 
 const mapStateToProps = (state, { hashtag }) => ({
   statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js
index 618696eb1..10129e606 100644
--- a/app/javascript/mastodon/features/standalone/public_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js
@@ -1,70 +1,112 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
-import StatusListContainer from '../../ui/containers/status_list_container';
-import { expandPublicTimeline } from '../../../actions/timelines';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connectPublicStream } from '../../../actions/streaming';
-
-const messages = defineMessages({
-  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
-});
-
-export default @connect()
-@injectIntl
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
+import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
+import Masonry from 'react-masonry-infinite';
+import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
+import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
+import { debounce } from 'lodash';
+import LoadingIndicator from 'mastodon/components/loading_indicator';
+
+const mapStateToProps = (state, { local }) => {
+  const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
+
+  return {
+    statusIds: timeline.get('items', ImmutableList()),
+    isLoading: timeline.get('isLoading', false),
+    hasMore: timeline.get('hasMore', false),
+  };
+};
+
+export default @connect(mapStateToProps)
 class PublicTimeline extends React.PureComponent {
 
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    isLoading: PropTypes.bool.isRequired,
+    hasMore: PropTypes.bool.isRequired,
+    local: PropTypes.bool,
   };
 
-  handleHeaderClick = () => {
-    this.column.scrollTop();
+  componentDidMount () {
+    this._connect();
   }
 
-  setRef = c => {
-    this.column = c;
+  componentDidUpdate (prevProps) {
+    if (prevProps.local !== this.props.local) {
+      this._disconnect();
+      this._connect();
+    }
   }
 
-  componentDidMount () {
-    const { dispatch } = this.props;
+  componentWillUnmount () {
+    this._disconnect();
+  }
+
+  _connect () {
+    const { dispatch, local } = this.props;
 
-    dispatch(expandPublicTimeline());
-    this.disconnect = dispatch(connectPublicStream());
+    dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
+    this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream());
   }
 
-  componentWillUnmount () {
+  _disconnect () {
     if (this.disconnect) {
       this.disconnect();
       this.disconnect = null;
     }
   }
 
-  handleLoadMore = maxId => {
-    this.props.dispatch(expandPublicTimeline({ maxId }));
+  handleLoadMore = () => {
+    const { dispatch, statusIds, local } = this.props;
+    const maxId = statusIds.last();
+
+    if (maxId) {
+      dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
+    }
+  }
+
+  setRef = c => {
+    this.masonry = c;
   }
 
+  handleHeightChange = debounce(() => {
+    if (!this.masonry) {
+      return;
+    }
+
+    this.masonry.forcePack();
+  }, 50)
+
   render () {
-    const { intl } = this.props;
+    const { statusIds, hasMore, isLoading } = this.props;
+
+    const sizes = [
+      { columns: 1, gutter: 0 },
+      { mq: '415px', columns: 1, gutter: 10 },
+      { mq: '640px', columns: 2, gutter: 10 },
+      { mq: '960px', columns: 3, gutter: 10 },
+      { mq: '1255px', columns: 3, gutter: 10 },
+    ];
+
+    const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
-        <ColumnHeader
-          icon='globe'
-          title={intl.formatMessage(messages.title)}
-          onClick={this.handleHeaderClick}
-        />
-
-        <StatusListContainer
-          timelineId='public'
-          onLoadMore={this.handleLoadMore}
-          scrollKey='standalone_public_timeline'
-          trackScroll={false}
-        />
-      </Column>
+      <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
+        {statusIds.map(statusId => (
+          <div className='statuses-grid__item' key={statusId}>
+            <DetailedStatusContainer
+              id={statusId}
+              compact
+              measureHeight
+              onHeightChange={this.handleHeightChange}
+            />
+          </div>
+        )).toArray()}
+      </Masonry>
     );
   }
 
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 5cd50f055..5c79f9f19 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -23,7 +23,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
   };
 
   static propTypes = {
-    status: ImmutablePropTypes.map.isRequired,
+    status: ImmutablePropTypes.map,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
     onToggleHidden: PropTypes.func.isRequired,
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 868e54751..78a5648af 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -17,6 +17,10 @@
       {
         "defaultMessage": "File upload limit exceeded.",
         "id": "upload_error.limit"
+      },
+      {
+        "defaultMessage": "File upload not allowed with polls.",
+        "id": "upload_error.poll"
       }
     ],
     "path": "app/javascript/mastodon/actions/compose.json"
@@ -910,6 +914,52 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Add a poll",
+        "id": "poll_button.add_poll"
+      },
+      {
+        "defaultMessage": "Remove poll",
+        "id": "poll_button.remove_poll"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/compose/components/poll_button.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Choice {number}",
+        "id": "compose_form.poll.option_placeholder"
+      },
+      {
+        "defaultMessage": "Add a choice",
+        "id": "compose_form.poll.add_option"
+      },
+      {
+        "defaultMessage": "Remove this choice",
+        "id": "compose_form.poll.remove_option"
+      },
+      {
+        "defaultMessage": "Poll duration",
+        "id": "compose_form.poll.duration"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# minute} other {# minutes}}",
+        "id": "intervals.full.minutes"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# hour} other {# hours}}",
+        "id": "intervals.full.hours"
+      },
+      {
+        "defaultMessage": "{number, plural, one {# day} other {# days}}",
+        "id": "intervals.full.days"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/compose/components/poll_form.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Public",
         "id": "privacy.public.short"
       },
@@ -1853,6 +1903,10 @@
       {
         "defaultMessage": "{name} boosted your status",
         "id": "notification.reblog"
+      },
+      {
+        "defaultMessage": "Your poll has ended",
+        "id": "notification.poll"
       }
     ],
     "path": "app/javascript/mastodon/features/notifications/components/notification.json"
@@ -1917,24 +1971,6 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "A look inside...",
-        "id": "standalone.public_title"
-      }
-    ],
-    "path": "app/javascript/mastodon/features/standalone/community_timeline/index.json"
-  },
-  {
-    "descriptors": [
-      {
-        "defaultMessage": "A look inside...",
-        "id": "standalone.public_title"
-      }
-    ],
-    "path": "app/javascript/mastodon/features/standalone/public_timeline/index.json"
-  },
-  {
-    "descriptors": [
-      {
         "defaultMessage": "Delete",
         "id": "status.delete"
       },
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index ae37322e8..eb82cd9a9 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -77,6 +77,10 @@
   "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
   "compose_form.lock_disclaimer.lock": "locked",
   "compose_form.placeholder": "What's on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "Media is marked as sensitive",
@@ -155,6 +159,9 @@
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
@@ -245,6 +252,7 @@
   "notification.favourite": "{name} favourited your status",
   "notification.follow": "{name} followed you",
   "notification.mention": "{name} mentioned you",
+  "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} boosted your status",
   "notifications.clear": "Clear notifications",
   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
@@ -269,6 +277,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -303,7 +313,6 @@
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "standalone.public_title": "A look inside...",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
   "status.block": "Block @{name}",
@@ -361,6 +370,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
   "upload_form.focus": "Change preview",
   "upload_form.undo": "Delete",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index e2d3a98f3..6388c7e9c 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -77,6 +77,10 @@
   "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
   "compose_form.lock_disclaimer.lock": "承認制",
   "compose_form.placeholder": "今なにしてる?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
   "compose_form.publish": "トゥート",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.marked": "メディアに閲覧注意が設定されています",
@@ -155,6 +159,9 @@
   "home.column_settings.basic": "基本設定",
   "home.column_settings.show_reblogs": "ブースト表示",
   "home.column_settings.show_replies": "返信表示",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
   "introduction.federation.action": "次へ",
   "introduction.federation.federated.headline": "連合タイムライン",
   "introduction.federation.federated.text": "Fediverseの他のサーバーからの公開投稿は連合タイムラインに表示されます。",
@@ -245,6 +252,7 @@
   "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
   "notification.follow": "{name}さんにフォローされました",
   "notification.mention": "{name}さんがあなたに返信しました",
+  "notification.poll": "Your poll has ended",
   "notification.reblog": "{name}さんがあなたのトゥートをブーストしました",
   "notifications.clear": "通知を消去",
   "notifications.clear_confirmation": "本当に通知を消去しますか?",
@@ -269,6 +277,8 @@
   "poll.refresh": "Refresh",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
   "privacy.change": "公開範囲を変更",
   "privacy.direct.long": "メンションしたユーザーだけに公開",
   "privacy.direct.short": "ダイレクト",
@@ -303,7 +313,6 @@
   "search_results.hashtags": "ハッシュタグ",
   "search_results.statuses": "トゥート",
   "search_results.total": "{count, number}件の結果",
-  "standalone.public_title": "今こんな話をしています...",
   "status.admin_account": "@{name} のモデレーション画面を開く",
   "status.admin_status": "このトゥートをモデレーション画面で開く",
   "status.block": "@{name}さんをブロック",
@@ -361,6 +370,7 @@
   "upload_area.title": "ドラッグ&ドロップでアップロード",
   "upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "アップロードできる上限を超えています。",
+  "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "視覚障害者のための説明",
   "upload_form.focus": "焦点",
   "upload_form.undo": "削除",
diff --git a/app/javascript/mastodon/reducers/push_notifications.js b/app/javascript/mastodon/reducers/push_notifications.js
index 85628c6b1..317352b79 100644
--- a/app/javascript/mastodon/reducers/push_notifications.js
+++ b/app/javascript/mastodon/reducers/push_notifications.js
@@ -9,6 +9,7 @@ const initialState = Immutable.Map({
     favourite: false,
     reblog: false,
     mention: false,
+    poll: false,
   }),
   isSubscribed: false,
   browserSupport: false,
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 2e1878cf7..a0eea137f 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -31,6 +31,7 @@ const initialState = ImmutableMap({
       favourite: true,
       reblog: true,
       mention: true,
+      poll: true,
     }),
 
     quickFilter: ImmutableMap({
@@ -44,6 +45,7 @@ const initialState = ImmutableMap({
       favourite: true,
       reblog: true,
       mention: true,
+      poll: true,
     }),
 
     sounds: ImmutableMap({
@@ -51,6 +53,7 @@ const initialState = ImmutableMap({
       favourite: true,
       reblog: true,
       mention: true,
+      poll: true,
     }),
   }),
 
diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js
index ce96ae297..5ce8c7b50 100644
--- a/app/javascript/mastodon/service_worker/web_push_locales.js
+++ b/app/javascript/mastodon/service_worker/web_push_locales.js
@@ -18,6 +18,7 @@ filenames.forEach(filename => {
     'notification.follow': full['notification.follow'] || '',
     'notification.mention': full['notification.mention'] || '',
     'notification.reblog': full['notification.reblog'] || '',
+    'notification.poll': full['notification.poll'] || '',
 
     'status.show_more': full['status.show_more'] || '',
     'status.reblog': full['status.reblog'] || '',
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index b078d4d24..465ef2c11 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -193,6 +193,7 @@ $small-breakpoint: 960px;
     }
 
     strong {
+      font-family: $font-display, sans-serif;
       font-weight: 500;
       font-size: 32px;
       line-height: 48px;
@@ -280,168 +281,6 @@ $small-breakpoint: 960px;
 }
 
 .landing-page {
-  .grid {
-    display: grid;
-    grid-gap: 10px;
-    grid-template-columns: 1fr 2fr;
-    grid-auto-columns: 25%;
-    grid-auto-rows: max-content;
-
-    .column-0 {
-      display: none;
-    }
-
-    .column-1 {
-      grid-column: 1;
-      grid-row: 1;
-    }
-
-    .column-2 {
-      grid-column: 2;
-      grid-row: 1;
-    }
-
-    .column-3 {
-      grid-column: 3;
-      grid-row: 1 / 3;
-    }
-
-    .column-4 {
-      grid-column: 1 / 3;
-      grid-row: 2;
-    }
-  }
-
-  @media screen and (max-width: $small-breakpoint) {
-    .grid {
-      grid-template-columns: 40% 60%;
-
-      .column-0 {
-        display: none;
-      }
-
-      .column-1 {
-        grid-column: 1;
-        grid-row: 1;
-
-        &.non-preview .landing-page__forms {
-          height: 100%;
-        }
-      }
-
-      .column-2 {
-        grid-column: 2;
-        grid-row: 1 / 3;
-
-        &.non-preview {
-          grid-column: 2;
-          grid-row: 1;
-        }
-      }
-
-      .column-3 {
-        grid-column: 1;
-        grid-row: 2 / 4;
-      }
-
-      .column-4 {
-        grid-column: 2;
-        grid-row: 3;
-
-        &.non-preview {
-          grid-column: 1 / 3;
-          grid-row: 2;
-        }
-      }
-    }
-  }
-
-  @media screen and (max-width: $column-breakpoint) {
-    .grid {
-      grid-template-columns: 100%;
-
-      .column-0 {
-        display: block;
-        grid-column: 1;
-        grid-row: 1;
-      }
-
-      .column-1 {
-        grid-column: 1;
-        grid-row: 3;
-
-        .brand {
-          display: none;
-        }
-      }
-
-      .column-2 {
-        grid-column: 1;
-        grid-row: 2;
-
-        .landing-page__logo,
-        .landing-page__call-to-action {
-          display: none;
-        }
-
-        &.non-preview {
-          grid-column: 1;
-          grid-row: 2;
-        }
-      }
-
-      .column-3 {
-        grid-column: 1;
-        grid-row: 5;
-      }
-
-      .column-4 {
-        grid-column: 1;
-        grid-row: 4;
-
-        &.non-preview {
-          grid-column: 1;
-          grid-row: 4;
-        }
-      }
-    }
-  }
-
-  .column-flex {
-    display: flex;
-    flex-direction: column;
-  }
-
-  .separator-or {
-    position: relative;
-    margin: 40px 0;
-    text-align: center;
-
-    &::before {
-      content: "";
-      display: block;
-      width: 100%;
-      height: 0;
-      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
-      position: absolute;
-      top: 50%;
-      left: 0;
-    }
-
-    span {
-      display: inline-block;
-      background: $ui-base-color;
-      font-size: 12px;
-      font-weight: 500;
-      color: $darker-text-color;
-      text-transform: uppercase;
-      position: relative;
-      z-index: 1;
-      padding: 0 8px;
-      cursor: default;
-    }
-  }
-
   p,
   li {
     font-family: $font-sans-serif, sans-serif;
@@ -458,28 +297,6 @@ $small-breakpoint: 960px;
     }
   }
 
-  .closed-registrations-message {
-    margin-top: 20px;
-
-    &,
-    p {
-      text-align: center;
-      font-size: 12px;
-      line-height: 18px;
-      color: $darker-text-color;
-      margin-bottom: 0;
-
-      a {
-        color: $highlight-text-color;
-        text-decoration: underline;
-      }
-    }
-
-    p:last-child {
-      margin-bottom: 0;
-    }
-  }
-
   em {
     display: inline;
     margin: 0;
@@ -593,187 +410,6 @@ $small-breakpoint: 960px;
     }
   }
 
-  .container-alt {
-    width: 100%;
-    box-sizing: border-box;
-    max-width: 800px;
-    margin: 0 auto;
-    word-wrap: break-word;
-  }
-
-  .header-wrapper {
-    padding-top: 15px;
-    background: $ui-base-color;
-    background: linear-gradient(150deg, lighten($ui-base-color, 8%), $ui-base-color);
-    position: relative;
-
-    &.compact {
-      background: $ui-base-color;
-      padding-bottom: 15px;
-
-      .hero .heading {
-        padding-bottom: 20px;
-        font-family: $font-sans-serif, sans-serif;
-        font-size: 16px;
-        font-weight: 400;
-        font-size: 16px;
-        line-height: 30px;
-        color: $darker-text-color;
-
-        a {
-          color: $highlight-text-color;
-          text-decoration: underline;
-        }
-      }
-    }
-  }
-
-  .brand {
-    a {
-      padding-left: 0;
-      padding-right: 0;
-      color: $white;
-    }
-
-    img {
-      height: 32px;
-      position: relative;
-      top: 4px;
-      left: -10px;
-    }
-  }
-
-  .header {
-    line-height: 30px;
-    overflow: hidden;
-
-    .container-alt {
-      display: flex;
-      justify-content: space-between;
-    }
-
-    .links {
-      position: relative;
-      z-index: 4;
-
-      a {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        color: $darker-text-color;
-        text-decoration: none;
-        padding: 12px 16px;
-        line-height: 32px;
-        font-family: $font-display, sans-serif;
-        font-weight: 500;
-        font-size: 14px;
-
-        &:hover {
-          color: $secondary-text-color;
-        }
-      }
-
-      ul {
-        list-style: none;
-        margin: 0;
-
-        li {
-          display: inline-block;
-          vertical-align: bottom;
-          margin: 0;
-
-          &:first-child a {
-            padding-left: 0;
-          }
-
-          &:last-child a {
-            padding-right: 0;
-          }
-        }
-      }
-    }
-
-    .hero {
-      margin-top: 50px;
-      align-items: center;
-      position: relative;
-
-      .heading {
-        position: relative;
-        z-index: 4;
-        padding-bottom: 150px;
-      }
-
-      .simple_form,
-      .closed-registrations-message {
-        background: darken($ui-base-color, 4%);
-        width: 280px;
-        padding: 15px 20px;
-        border-radius: 4px 4px 0 0;
-        line-height: initial;
-        position: relative;
-        z-index: 4;
-
-        .actions {
-          margin-bottom: 0;
-
-          button,
-          .button,
-          .block-button {
-            margin-bottom: 0;
-          }
-        }
-      }
-
-      .closed-registrations-message {
-        min-height: 330px;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-      }
-    }
-  }
-
-  .about-short {
-    background: darken($ui-base-color, 4%);
-    padding: 50px 0 30px;
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 16px;
-    font-weight: 400;
-    font-size: 16px;
-    line-height: 30px;
-    color: $darker-text-color;
-
-    a {
-      color: $highlight-text-color;
-      text-decoration: underline;
-    }
-  }
-
-  &.alternative {
-    padding: 10px 0;
-
-    .brand {
-      text-align: center;
-      padding: 30px 0;
-      margin-bottom: 10px;
-
-      img {
-        position: static;
-        padding: 10px 0;
-      }
-
-      @media screen and (max-width: $small-breakpoint) {
-        padding: 15px 0;
-      }
-
-      @media screen and (max-width: $column-breakpoint) {
-        padding: 0;
-        margin-bottom: -10px;
-      }
-    }
-  }
-
   &__information,
   &__forms {
     padding: 20px;
@@ -967,353 +603,253 @@ $small-breakpoint: 960px;
     }
   }
 
-  &__forms {
-    height: 100%;
-
-    @media screen and (max-width: $small-breakpoint) {
-      height: auto;
-    }
-
-    @media screen and (max-width: $column-breakpoint) {
-      background: transparent;
-      box-shadow: none;
-      padding: 0 20px;
-      margin-top: 30px;
-      margin-bottom: 40px;
-
-      .separator-or {
-        span {
-          background: darken($ui-base-color, 8%);
-        }
+  @media screen and (max-width: 840px) {
+    .information-board {
+      .container-alt {
+        padding-right: 20px;
       }
-    }
-
-    hr {
-      margin: 40px 0;
-    }
 
-    .button {
-      display: block;
-    }
-
-    .subtle-hint a {
-      text-decoration: none;
+      .panel {
+        position: static;
+        margin-top: 20px;
+        width: 100%;
+        border-radius: 4px;
 
-      &:hover,
-      &:focus,
-      &:active {
-        text-decoration: underline;
+        .panel-header {
+          text-align: center;
+        }
       }
     }
   }
 
-  #mastodon-timeline {
-    display: flex;
-    -webkit-overflow-scrolling: touch;
-    -ms-overflow-style: -ms-autohiding-scrollbar;
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 13px;
-    line-height: 18px;
-    font-weight: 400;
-    color: $primary-text-color;
-    width: 100%;
-    flex: 1 1 auto;
-    overflow: hidden;
-    height: 100%;
-
-    .column-header {
-      color: inherit;
-      font-family: inherit;
-      font-size: 16px;
-      line-height: inherit;
-      font-weight: inherit;
-      margin: 0;
-      padding: 0;
-    }
-
-    .column {
-      padding: 0;
-      border-radius: 4px;
-      overflow: hidden;
-      width: 100%;
-    }
-
-    .scrollable {
-      height: 400px;
-    }
-
-    p {
-      font-size: inherit;
-      line-height: inherit;
-      font-weight: inherit;
-      color: $primary-text-color;
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
+  @media screen and (max-width: 675px) {
+    .header-wrapper {
+      padding-top: 0;
 
-      a {
-        color: $secondary-text-color;
-        text-decoration: none;
+      &.compact {
+        padding-bottom: 0;
       }
-    }
-
-    .attachment-list__list {
-      margin-left: 0;
-      list-style: none;
-
-      li {
-        font-size: inherit;
-        line-height: inherit;
-        font-weight: inherit;
-        margin-bottom: 0;
-
-        a {
-          color: $dark-text-color;
-          text-decoration: none;
 
-          &:hover {
-            text-decoration: underline;
-          }
-        }
+      &.compact .hero .heading {
+        text-align: initial;
       }
     }
 
-    @media screen and (max-width: $column-breakpoint) {
-      display: none;
+    .header .container-alt,
+    .features .container-alt {
+      display: block;
     }
   }
 
-  &__features {
-    & > p {
-      padding-right: 60px;
-    }
-
-    .features-list {
-      margin: 40px 0;
-      margin-top: 30px;
-    }
-
-    &__action {
-      text-align: center;
-    }
+  .cta {
+    margin: 20px;
   }
+}
 
-  .features-list {
-    .features-list__row {
-      display: flex;
-      padding: 10px 0;
-      justify-content: space-between;
-
-      .visual {
-        flex: 0 0 auto;
-        display: flex;
-        align-items: center;
-        margin-left: 15px;
+.landing {
+  margin-bottom: 100px;
 
-        .fa {
-          display: block;
-          color: $darker-text-color;
-          font-size: 48px;
-        }
-      }
+  @media screen and (max-width: 738px) {
+    margin-bottom: 0;
+  }
 
-      .text {
-        font-size: 16px;
-        line-height: 30px;
-        color: $darker-text-color;
+  &__brand {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 100px;
 
-        h6 {
-          font-size: inherit;
-          line-height: inherit;
-          margin-bottom: 0;
-        }
-      }
+    img {
+      height: 52px;
     }
 
-    @media screen and (min-width: $small-breakpoint) {
-      display: grid;
-      grid-gap: 30px;
-      grid-template-columns: 1fr 1fr;
-      grid-auto-columns: 50%;
-      grid-auto-rows: max-content;
+    @media screen and (max-width: $no-gap-breakpoint) {
+      padding: 0;
+      margin-bottom: 30px;
     }
   }
 
-  .footer-links {
-    padding-bottom: 50px;
-    text-align: right;
-    color: $dark-text-color;
+  .directory {
+    margin-top: 30px;
+    background: transparent;
+    box-shadow: none;
+    border-radius: 0;
+  }
 
-    p {
-      font-size: 14px;
-    }
+  .hero-widget {
+    margin-top: 30px;
+    margin-bottom: 0;
 
-    a {
-      color: inherit;
-      text-decoration: underline;
+    h4 {
+      padding: 10px;
+      text-transform: uppercase;
+      font-weight: 700;
+      font-size: 13px;
+      color: $darker-text-color;
     }
-  }
 
-  &__footer {
-    margin-top: 10px;
-    text-align: center;
-    color: $dark-text-color;
+    &__text {
+      border-radius: 0;
+      padding-bottom: 0;
+    }
 
-    p {
-      font-size: 14px;
+    &__footer {
+      background: $ui-base-color;
+      padding: 10px;
+      border-radius: 0 0 4px 4px;
+      display: flex;
 
-      a {
-        color: inherit;
-        text-decoration: underline;
+      &__column {
+        flex: 1 1 50%;
       }
     }
-  }
 
-  @media screen and (max-width: 840px) {
-    .container-alt {
-      padding: 0 20px;
-    }
+    .account {
+      padding: 10px 0;
+      border-bottom: 0;
 
-    .information-board {
-      .container-alt {
-        padding-right: 20px;
+      .account__display-name {
+        display: flex;
+        align-items: center;
       }
 
-      .panel {
-        position: static;
-        margin-top: 20px;
-        width: 100%;
-        border-radius: 4px;
-
-        .panel-header {
-          text-align: center;
-        }
+      .account__avatar {
+        width: 44px;
+        height: 44px;
+        background-size: 44px 44px;
       }
     }
-  }
 
-  @media screen and (max-width: 675px) {
-    .header-wrapper {
-      padding-top: 0;
+    &__counter {
+      padding: 10px;
 
-      &.compact {
-        padding-bottom: 0;
+      strong {
+        font-family: $font-display, sans-serif;
+        font-size: 15px;
+        font-weight: 700;
+        display: block;
       }
 
-      &.compact .hero .heading {
-        text-align: initial;
+      span {
+        font-size: 14px;
+        color: $darker-text-color;
       }
     }
+  }
 
-    .header .container-alt,
-    .features .container-alt {
-      display: block;
-    }
-
-    .header {
-      .links {
-        padding-top: 15px;
-        background: darken($ui-base-color, 4%);
+  .simple_form .user_agreement .label_input > label {
+    font-weight: 400;
+    color: $darker-text-color;
+  }
 
-        a {
-          padding: 12px 8px;
-        }
+  .simple_form p.lead {
+    color: $darker-text-color;
+    font-size: 15px;
+    line-height: 20px;
+    font-weight: 400;
+    margin-bottom: 25px;
+  }
 
-        .nav {
-          display: flex;
-          flex-flow: row wrap;
-          justify-content: space-around;
-        }
+  &__grid {
+    max-width: 960px;
+    margin: 0 auto;
+    display: grid;
+    grid-template-columns: minmax(0, 50%) minmax(0, 50%);
+    grid-gap: 30px;
 
-        .brand img {
-          left: 0;
-          top: 0;
-        }
-      }
+    @media screen and (max-width: 738px) {
+      grid-template-columns: minmax(0, 100%);
+      grid-gap: 10px;
 
-      .hero {
-        margin-top: 30px;
-        padding: 0;
+      &__column-login {
+        grid-row: 1;
+        display: flex;
+        flex-direction: column;
 
-        .heading {
-          padding: 30px 20px;
-          text-align: center;
+        .box-widget {
+          order: 2;
+          flex: 0 0 auto;
         }
 
-        .simple_form,
-        .closed-registrations-message {
-          background: darken($ui-base-color, 8%);
-          width: 100%;
-          border-radius: 0;
-          box-sizing: border-box;
+        .hero-widget {
+          margin-top: 0;
+          margin-bottom: 10px;
+          order: 1;
+          flex: 0 0 auto;
         }
       }
-    }
-  }
-
-  .cta {
-    margin: 20px;
-  }
 
-  &.tag-page {
-    @media screen and (max-width: $column-breakpoint) {
-      padding: 0;
-
-      .container {
-        padding: 0;
+      &__column-registration {
+        grid-row: 2;
       }
 
-      #mastodon-timeline {
-        display: flex;
-        height: 100vh;
-        border-radius: 0;
+      .directory {
+        margin-top: 10px;
       }
     }
 
-    .grid {
-      @media screen and (min-width: $small-breakpoint) {
-        grid-template-columns: 33% 67%;
-      }
-
-      .column-2 {
-        grid-column: 2;
-        grid-row: 1;
-      }
-    }
+    @media screen and (max-width: $no-gap-breakpoint) {
+      grid-gap: 0;
 
-    .brand {
-      text-align: unset;
-      padding: 0;
+      .hero-widget {
+        display: block;
+        margin-bottom: 0;
+        box-shadow: none;
 
-      img {
-        height: 48px;
-        width: auto;
+        &__img,
+        &__img img,
+        &__footer {
+          border-radius: 0;
+        }
       }
-    }
 
-    .cta {
-      margin: 0;
-
-      .button {
-        margin-right: 4px;
+      .hero-widget,
+      .box-widget,
+      .directory__tag {
+        border-bottom: 1px solid lighten($ui-base-color, 8%);
       }
-    }
 
-    @media screen and (max-width: $column-breakpoint) {
-      .grid {
-        grid-gap: 0;
+      .directory {
+        margin-top: 0;
 
-        .column-1 {
-          grid-column: 1;
-          grid-row: 1;
-        }
+        &__tag {
+          margin-bottom: 0;
 
-        .column-2 {
-          display: none;
+          & > a,
+          & > div {
+            border-radius: 0;
+            box-shadow: none;
+          }
+
+          &:last-child {
+            border-bottom: 0;
+          }
         }
       }
     }
   }
 }
+
+.brand {
+  position: relative;
+  text-decoration: none;
+}
+
+.brand__tagline {
+  display: block;
+  position: absolute;
+  bottom: -10px;
+  left: 50px;
+  width: 300px;
+  color: $ui-primary-color;
+  text-decoration: none;
+  font-size: 14px;
+
+  @media screen and (max-width: $no-gap-breakpoint) {
+    position: static;
+    width: auto;
+    margin-top: 20px;
+    color: $dark-text-color;
+  }
+}
+
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 4dbbaa1e8..42f53f525 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -705,3 +705,11 @@ a.name-tag,
   overflow: hidden;
   text-overflow: ellipsis;
 }
+
+.ellipsized-ip {
+  display: inline-block;
+  max-width: 120px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  vertical-align: middle;
+}
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index bab982706..6051c1d00 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -68,6 +68,17 @@ code {
         top: 2px;
         left: 0;
       }
+
+      label a {
+        color: $highlight-text-color;
+        text-decoration: underline;
+
+        &:hover,
+        &:active,
+        &:focus {
+          text-decoration: none;
+        }
+      }
     }
   }
 
@@ -305,7 +316,7 @@ code {
       box-shadow: none;
     }
 
-    &:focus:invalid {
+    &:focus:invalid:not(:placeholder-shown) {
       border-color: lighten($error-red, 12%);
     }
 
@@ -346,6 +357,10 @@ code {
     }
   }
 
+  .input.disabled {
+    opacity: 0.5;
+  }
+
   .actions {
     margin-top: 30px;
     display: flex;
@@ -392,6 +407,10 @@ code {
       background-color: darken($ui-highlight-color, 5%);
     }
 
+    &:disabled:hover {
+      background-color: $ui-primary-color;
+    }
+
     &.negative {
       background: $error-value-color;
 
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index 4f8c94d83..d8bc5473a 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -190,3 +190,15 @@
     color: darken($simple-background-color, 14%);
   }
 }
+
+.muted .poll {
+  color: $dark-text-color;
+
+  &__chart {
+    background: rgba(darken($ui-primary-color, 14%), 0.2);
+
+    &.leading {
+      background: rgba($ui-highlight-color, 0.2);
+    }
+  }
+}
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index adb75afe5..9e8785679 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -82,6 +82,10 @@
       }
     }
   }
+
+  &--invites tbody td {
+    vertical-align: middle;
+  }
 }
 
 .table-wrapper {
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 1eaf30c5b..645192ea4 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -295,6 +295,11 @@
       cursor: default;
     }
 
+    &.disabled > div {
+      opacity: 0.5;
+      cursor: default;
+    }
+
     h4 {
       flex: 1 1 auto;
       font-size: 18px;
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 7e4e57ead..8fe7b9138 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -241,8 +241,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
   def poll_vote?
     return false if replied_to_status.nil? || replied_to_status.poll.nil? || !replied_to_status.local? || !replied_to_status.poll.options.include?(@object['name'])
-    return true if replied_to_status.poll.expired?
-    replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name']), uri: @object['id'])
+
+    unless replied_to_status.poll.expired?
+      replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name']), uri: @object['id'])
+      ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.poll.hide_totals?
+    end
+
+    true
   end
 
   def resolve_thread(status)
@@ -369,15 +374,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
   end
 
-  def invalid_origin?(url)
-    return true if unsupported_uri_scheme?(url)
-
-    needle   = Addressable::URI.parse(url).host
-    haystack = Addressable::URI.parse(@account.uri).host
-
-    !haystack.casecmp(needle).zero?
-  end
-
   def reply_to_local?
     !replied_to_status.nil? && replied_to_status.account.local?
   end
diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb
index dc76dd3e2..4236af071 100644
--- a/app/lib/activitypub/activity/delete.rb
+++ b/app/lib/activitypub/activity/delete.rb
@@ -75,13 +75,4 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
   def lock_options
     { redis: Redis.current, key: "create:#{object_uri}" }
   end
-
-  def invalid_origin?(url)
-    return true if unsupported_uri_scheme?(url)
-
-    needle   = Addressable::URI.parse(url).host
-    haystack = Addressable::URI.parse(@account.uri).host
-
-    !haystack.casecmp(needle).zero?
-  end
 end
diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb
index 67dae3f81..bc9a63f98 100644
--- a/app/lib/activitypub/activity/update.rb
+++ b/app/lib/activitypub/activity/update.rb
@@ -4,7 +4,11 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
   SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
 
   def perform
-    update_account if equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
+    if equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
+      update_account
+    elsif equals_or_includes_any?(@object['type'], %w(Question))
+      update_poll
+    end
   end
 
   private
@@ -14,4 +18,13 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
 
     ActivityPub::ProcessAccountService.new.call(@account.username, @account.domain, @object, signed_with_known_key: true)
   end
+
+  def update_poll
+    return reject_payload! if invalid_origin?(@object['id'])
+
+    status = Status.find_by(uri: object_uri, account_id: @account.id)
+    return if status.nil? || status.poll.nil?
+
+    ActivityPub::ProcessPollService.new.call(status.poll, @object)
+  end
 end
diff --git a/app/lib/request.rb b/app/lib/request.rb
index ef4aeaf29..e555ae6a1 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -171,7 +171,7 @@ class Request
         outer_e = nil
 
         Resolv::DNS.open do |dns|
-          dns.timeouts = 1
+          dns.timeouts = 5
 
           addresses = dns.getaddresses(host).take(2)
           time_slot = 10.0 / addresses.size
diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb
index a30468eb8..db154cad5 100644
--- a/app/mailers/admin_mailer.rb
+++ b/app/mailers/admin_mailer.rb
@@ -14,4 +14,14 @@ class AdminMailer < ApplicationMailer
       mail to: @me.user_email, subject: I18n.t('admin_mailer.new_report.subject', instance: @instance, id: @report.id)
     end
   end
+
+  def new_pending_account(recipient, user)
+    @account  = user.account
+    @me       = recipient
+    @instance = Rails.configuration.x.local_domain
+
+    locale_for_account(@me) do
+      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_pending_account.subject', instance: @instance, username: @account.username)
+    end
+  end
 end
diff --git a/app/models/account.rb b/app/models/account.rb
index 79eecc306..6b539f004 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -108,6 +108,8 @@ class Account < ApplicationRecord
            :current_sign_in_ip,
            :current_sign_in_at,
            :confirmed?,
+           :approved?,
+           :pending?,
            :admin?,
            :moderator?,
            :staff?,
@@ -474,6 +476,7 @@ class Account < ApplicationRecord
 
   before_create :generate_keys
   before_validation :prepare_contents, if: :local?
+  before_validation :prepare_username, on: :create
   before_destroy :clean_feed_manager
 
   private
@@ -483,6 +486,10 @@ class Account < ApplicationRecord
     note&.strip!
   end
 
+  def prepare_username
+    username&.squish!
+  end
+
   def generate_keys
     return unless local? && !Rails.env.test?
 
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index b10f50db7..d2503100c 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -22,7 +22,7 @@ class AccountFilter
 
   def set_defaults!
     params['local']  = '1' if params['remote'].blank?
-    params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank?
+    params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank? && params['pending'].blank?
   end
 
   def scope_for(key, value)
@@ -35,6 +35,8 @@ class AccountFilter
       Account.where(domain: value)
     when 'active'
       Account.without_suspended
+    when 'pending'
+      accounts_with_users.merge User.pending
     when 'silenced'
       Account.silenced
     when 'suspended'
diff --git a/app/models/concerns/expireable.rb b/app/models/concerns/expireable.rb
index 2c0631476..f7d2bab49 100644
--- a/app/models/concerns/expireable.rb
+++ b/app/models/concerns/expireable.rb
@@ -18,7 +18,11 @@ module Expireable
     end
 
     def expired?
-      !expires_at.nil? && expires_at < Time.now.utc
+      expires? && expires_at < Time.now.utc
+    end
+
+    def expires?
+      !expires_at.nil?
     end
   end
 end
diff --git a/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb
new file mode 100644
index 000000000..e1b5e3832
--- /dev/null
+++ b/app/models/concerns/ldap_authenticable.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module LdapAuthenticable
+  extend ActiveSupport::Concern
+
+  def ldap_setup(_attributes)
+    self.confirmed_at = Time.now.utc
+    self.admin        = false
+
+    save!
+  end
+
+  class_methods do
+    def ldap_get_user(attributes = {})
+      resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
+
+      if resource.blank?
+        resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
+        resource.ldap_setup(attributes)
+      end
+
+      resource
+    end
+  end
+end
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index 4dd2e9383..1b28b8162 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -7,6 +7,8 @@ module Omniauthable
   TEMP_EMAIL_REGEX = /\Achange@me/
 
   included do
+    devise :omniauthable
+
     def omniauth_providers
       Devise.omniauth_configs.keys
     end
diff --git a/app/models/concerns/pam_authenticable.rb b/app/models/concerns/pam_authenticable.rb
new file mode 100644
index 000000000..2f651c1a3
--- /dev/null
+++ b/app/models/concerns/pam_authenticable.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module PamAuthenticable
+  extend ActiveSupport::Concern
+
+  included do
+    devise :pam_authenticatable if ENV['PAM_ENABLED'] == 'true'
+
+    def pam_conflict(_attributes)
+      # Block pam login tries on traditional account
+    end
+
+    def pam_conflict?
+      if Devise.pam_authentication
+        encrypted_password.present? && pam_managed_user?
+      else
+        false
+      end
+    end
+
+    def pam_get_name
+      if account.present?
+        account.username
+      else
+        super
+      end
+    end
+
+    def pam_setup(_attributes)
+      account = Account.new(username: pam_get_name)
+      account.save!(validate: false)
+
+      self.email        = "#{account.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix
+      self.confirmed_at = Time.now.utc
+      self.admin        = false
+      self.account      = account
+
+      account.destroy! unless save
+    end
+
+    def self.pam_get_user(attributes = {})
+      return nil unless attributes[:email]
+
+      resource = begin
+        if Devise.check_at_sign && !attributes[:email].index('@')
+          joins(:account).find_by(accounts: { username: attributes[:email] })
+        else
+          find_by(email: attributes[:email])
+        end
+      end
+
+      if resource.nil?
+        resource = new(email: attributes[:email], agreement: true)
+
+        if Devise.check_at_sign && !resource[:email].index('@')
+          resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false)
+          resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email]
+        end
+      end
+
+      resource
+    end
+
+    def self.authenticate_with_pam(attributes = {})
+      super if Devise.pam_authentication
+    end
+  end
+end
diff --git a/app/models/concerns/user_roles.rb b/app/models/concerns/user_roles.rb
new file mode 100644
index 000000000..58dffdc46
--- /dev/null
+++ b/app/models/concerns/user_roles.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module UserRoles
+  extend ActiveSupport::Concern
+
+  included do
+    scope :admins, -> { where(admin: true) }
+    scope :moderators, -> { where(moderator: true) }
+    scope :staff, -> { admins.or(moderators) }
+  end
+
+  def staff?
+    admin? || moderator?
+  end
+
+  def role
+    if admin?
+      'admin'
+    elsif moderator?
+      'moderator'
+    else
+      'user'
+    end
+  end
+
+  def role?(role)
+    case role
+    when 'user'
+      true
+    when 'moderator'
+      staff?
+    when 'admin'
+      admin?
+    else
+      false
+    end
+  end
+
+  def promote!
+    if moderator?
+      update!(moderator: false, admin: true)
+    elsif !admin?
+      update!(moderator: true)
+    end
+  end
+
+  def demote!
+    if admin?
+      update!(admin: false, moderator: true)
+    elsif moderator?
+      update!(moderator: false)
+    end
+  end
+end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index d568200ed..929c65793 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -18,8 +18,8 @@ class Form::AdminSettings
     :site_extended_description=,
     :site_terms,
     :site_terms=,
-    :open_registrations,
-    :open_registrations=,
+    :registrations_mode,
+    :registrations_mode=,
     :closed_registrations_message,
     :closed_registrations_message=,
     :open_deletion,
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 2f0a9b78c..982136c05 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -22,6 +22,7 @@ class Notification < ApplicationRecord
     follow:         'Follow',
     follow_request: 'FollowRequest',
     favourite:      'Favourite',
+    poll:           'Poll',
   }.freeze
 
   STATUS_INCLUDES = [:account, :application, :media_attachments, :tags, active_mentions: :account, reblog: [:account, :application, :media_attachments, :tags, active_mentions: :account]].freeze
@@ -35,6 +36,7 @@ class Notification < ApplicationRecord
   belongs_to :follow,         foreign_type: 'Follow',        foreign_key: 'activity_id', optional: true
   belongs_to :follow_request, foreign_type: 'FollowRequest', foreign_key: 'activity_id', optional: true
   belongs_to :favourite,      foreign_type: 'Favourite',     foreign_key: 'activity_id', optional: true
+  belongs_to :poll,           foreign_type: 'Poll',          foreign_key: 'activity_id', optional: true
 
   validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
   validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values }
@@ -44,7 +46,7 @@ class Notification < ApplicationRecord
     where(activity_type: types)
   }
 
-  cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account
+  cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, poll: [status: STATUS_INCLUDES]
 
   def type
     @type ||= TYPE_CLASS_MAP.invert[activity_type].to_sym
@@ -58,6 +60,8 @@ class Notification < ApplicationRecord
       favourite&.status
     when :mention
       mention&.status
+    when :poll
+      poll&.status
     end
   end
 
@@ -97,7 +101,7 @@ class Notification < ApplicationRecord
     return unless new_record?
 
     case activity_type
-    when 'Status', 'Follow', 'Favourite', 'FollowRequest'
+    when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll'
       self.from_account_id = activity&.account_id
     when 'Mention'
       self.from_account_id = activity&.status&.account_id
diff --git a/app/models/poll.rb b/app/models/poll.rb
index 09f0b65ec..6df230337 100644
--- a/app/models/poll.rb
+++ b/app/models/poll.rb
@@ -26,6 +26,8 @@ class Poll < ApplicationRecord
 
   has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :destroy
 
+  has_many :notifications, as: :activity, dependent: :destroy
+
   validates :options, presence: true
   validates :expires_at, presence: true, if: :local?
   validates_with PollValidator, on: :create, if: :local?
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 788a678bd..7db76d157 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -72,6 +72,14 @@ class Tag < ApplicationRecord
          .limit(limit)
          .offset(offset)
     end
+
+    def find_normalized(name)
+      find_by(name: name.mb_chars.downcase.to_s)
+    end
+
+    def find_normalized!(name)
+      find_normalized(name) || raise(ActiveRecord::RecordNotFound)
+    end
   end
 
   private
diff --git a/app/models/user.rb b/app/models/user.rb
index f91ed7015..47657a670 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -37,11 +37,12 @@
 #  remember_token            :string
 #  chosen_languages          :string           is an Array
 #  created_by_application_id :bigint(8)
+#  approved                  :boolean          default(TRUE), not null
 #
 
 class User < ApplicationRecord
   include Settings::Extend
-  include Omniauthable
+  include UserRoles
 
   # The home and list feeds will be stored in Redis for this amount
   # of time, and status fan-out to followers will include only people
@@ -61,9 +62,9 @@ class User < ApplicationRecord
   devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
          :confirmable
 
-  devise :pam_authenticatable if ENV['PAM_ENABLED'] == 'true'
-
-  devise :omniauthable
+  include Omniauthable
+  include PamAuthenticable
+  include LdapAuthenticable
 
   belongs_to :account, inverse_of: :user
   belongs_to :invite, counter_cache: :uses, optional: true
@@ -79,9 +80,8 @@ class User < ApplicationRecord
   validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
 
   scope :recent, -> { order(id: :desc) }
-  scope :admins, -> { where(admin: true) }
-  scope :moderators, -> { where(moderator: true) }
-  scope :staff, -> { admins.or(moderators) }
+  scope :pending, -> { where(approved: false) }
+  scope :approved, -> { where(approved: true) }
   scope :confirmed, -> { where.not(confirmed_at: nil) }
   scope :enabled, -> { where(disabled: false) }
   scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
@@ -90,6 +90,7 @@ class User < ApplicationRecord
   scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
 
   before_validation :sanitize_languages
+  before_create :set_approved
 
   # This avoids a deprecation warning from Rails 5.1
   # It seems possible that a future release of devise-two-factor will
@@ -104,39 +105,6 @@ class User < ApplicationRecord
 
   attr_reader :invite_code
 
-  def pam_conflict(_)
-    # block pam login tries on traditional account
-    nil
-  end
-
-  def pam_conflict?
-    return false unless Devise.pam_authentication
-    encrypted_password.present? && pam_managed_user?
-  end
-
-  def pam_get_name
-    return account.username if account.present?
-    super
-  end
-
-  def pam_setup(_attributes)
-    acc = Account.new(username: pam_get_name)
-    acc.save!(validate: false)
-
-    self.email = "#{acc.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix
-    self.confirmed_at = Time.now.utc
-    self.admin = false
-    self.account = acc
-
-    acc.destroy! unless save
-  end
-
-  def ldap_setup(_attributes)
-    self.confirmed_at = Time.now.utc
-    self.admin = false
-    save!
-  end
-
   def confirmed?
     confirmed_at.present?
   end
@@ -145,33 +113,6 @@ class User < ApplicationRecord
     invite_id.present?
   end
 
-  def staff?
-    admin? || moderator?
-  end
-
-  def role
-    if admin?
-      'admin'
-    elsif moderator?
-      'moderator'
-    else
-      'user'
-    end
-  end
-
-  def role?(role)
-    case role
-    when 'user'
-      true
-    when 'moderator'
-      staff?
-    when 'admin'
-      admin?
-    else
-      false
-    end
-  end
-
   def disable!
     update!(disabled: true,
             last_sign_in_at: current_sign_in_at,
@@ -186,7 +127,12 @@ class User < ApplicationRecord
     new_user = !confirmed?
 
     super
-    prepare_new_user! if new_user
+
+    if new_user && approved?
+      prepare_new_user!
+    elsif new_user
+      notify_staff_about_pending_account!
+    end
   end
 
   def confirm!
@@ -194,28 +140,32 @@ class User < ApplicationRecord
 
     skip_confirmation!
     save!
-    prepare_new_user! if new_user
+
+    prepare_new_user! if new_user && approved?
   end
 
-  def update_tracked_fields!(request)
-    super
-    prepare_returning_user!
+  def pending?
+    !approved?
   end
 
-  def promote!
-    if moderator?
-      update!(moderator: false, admin: true)
-    elsif !admin?
-      update!(moderator: true)
-    end
+  def active_for_authentication?
+    super && approved?
   end
 
-  def demote!
-    if admin?
-      update!(admin: false, moderator: true)
-    elsif moderator?
-      update!(moderator: false)
-    end
+  def inactive_message
+    !approved? ? :pending : super
+  end
+
+  def approve!
+    return if approved?
+
+    update!(approved: true)
+    prepare_new_user!
+  end
+
+  def update_tracked_fields!(request)
+    super
+    prepare_returning_user!
   end
 
   def disable_two_factor!
@@ -297,43 +247,6 @@ class User < ApplicationRecord
     super
   end
 
-  def self.pam_get_user(attributes = {})
-    return nil unless attributes[:email]
-
-    resource =
-      if Devise.check_at_sign && !attributes[:email].index('@')
-        joins(:account).find_by(accounts: { username: attributes[:email] })
-      else
-        find_by(email: attributes[:email])
-      end
-
-    if resource.blank?
-      resource = new(email: attributes[:email], agreement: true)
-
-      if Devise.check_at_sign && !resource[:email].index('@')
-        resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false)
-        resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email]
-      end
-    end
-    resource
-  end
-
-  def self.ldap_get_user(attributes = {})
-    resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
-
-    if resource.blank?
-      resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
-      resource.ldap_setup(attributes)
-    end
-
-    resource
-  end
-
-  def self.authenticate_with_pam(attributes = {})
-    return nil unless Devise.pam_authentication
-    super
-  end
-
   def show_all_media?
     setting_display_media == 'show_all'
   end
@@ -350,6 +263,10 @@ class User < ApplicationRecord
 
   private
 
+  def set_approved
+    self.approved = Setting.registrations_mode == 'open' || invited?
+  end
+
   def sanitize_languages
     return if chosen_languages.nil?
     chosen_languages.reject!(&:blank?)
@@ -367,6 +284,13 @@ class User < ApplicationRecord
     regenerate_feed! if needs_feed_update?
   end
 
+  def notify_staff_about_pending_account!
+    User.staff.includes(:account).each do |u|
+      next unless u.allows_report_emails?
+      AdminMailer.new_pending_account(u.account, self).deliver_later
+    end
+  end
+
   def regenerate_feed!
     return unless Redis.current.setnx("account:#{account_id}:regeneration", true)
     Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds)
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 57af5c61c..d832bff75 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -21,6 +21,14 @@ class UserPolicy < ApplicationPolicy
     staff?
   end
 
+  def approve?
+    staff? && !record.approved?
+  end
+
+  def reject?
+    staff? && !record.approved?
+  end
+
   def disable?
     staff? && !record.admin?
   end
@@ -36,7 +44,7 @@ class UserPolicy < ApplicationPolicy
   private
 
   def promoteable?
-    !record.staff? || !record.admin?
+    record.approved? && (!record.staff? || !record.admin?)
   end
 
   def demoteable?
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index d8670f124..94a2c1692 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -2,9 +2,7 @@
 
 class InstancePresenter
   delegate(
-    :closed_registrations_message,
     :site_contact_email,
-    :open_registrations,
     :site_title,
     :site_short_description,
     :site_description,
@@ -21,6 +19,10 @@ class InstancePresenter
     Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count }
   end
 
+  def active_user_count
+    Rails.cache.fetch('active_user_count') { Redis.current.pfcount(*(0..3).map { |i| "activity:logins:#{i.weeks.ago.utc.to_date.cweek}" }) }
+  end
+
   def status_count
     Rails.cache.fetch('local_status_count') { Account.local.joins(:account_stat).sum('account_stats.statuses_count') }.to_i
   end
@@ -29,6 +31,10 @@ class InstancePresenter
     Rails.cache.fetch('distinct_domain_count') { Account.distinct.count(:domain) }
   end
 
+  def sample_accounts
+    Rails.cache.fetch('sample_accounts', expires_in: 12.hours) { Account.discoverable.popular.limit(3) }
+  end
+
   def version_number
     Mastodon::Version
   end
diff --git a/app/serializers/activitypub/update_poll_serializer.rb b/app/serializers/activitypub/update_poll_serializer.rb
new file mode 100644
index 000000000..f7933346f
--- /dev/null
+++ b/app/serializers/activitypub/update_poll_serializer.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ActivityPub::UpdatePollSerializer < ActiveModel::Serializer
+  attributes :id, :type, :actor, :to
+
+  has_one :object, serializer: ActivityPub::NoteSerializer
+
+  def id
+    [ActivityPub::TagManager.instance.uri_for(object), '#updates/', object.poll.updated_at.to_i].join
+  end
+
+  def type
+    'Update'
+  end
+
+  def actor
+    ActivityPub::TagManager.instance.uri_for(object)
+  end
+
+  def to
+    ActivityPub::TagManager.instance.to(object)
+  end
+
+  def cc
+    ActivityPub::TagManager.instance.cc(object)
+  end
+end
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 30e8dcbc1..97fed63d1 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -65,7 +65,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
   end
 
   def registrations
-    Setting.open_registrations && !Rails.configuration.x.single_user_mode
+    Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
   end
 
   private
diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb
index 541a6b8b5..80812ad0d 100644
--- a/app/serializers/rest/notification_serializer.rb
+++ b/app/serializers/rest/notification_serializer.rb
@@ -11,6 +11,6 @@ class REST::NotificationSerializer < ActiveModel::Serializer
   end
 
   def status_type?
-    [:favourite, :reblog, :mention].include?(object.type)
+    [:favourite, :reblog, :mention, :poll].include?(object.type)
   end
 end
diff --git a/app/services/activitypub/fetch_remote_poll_service.rb b/app/services/activitypub/fetch_remote_poll_service.rb
index 4f9814fcd..44a23712c 100644
--- a/app/services/activitypub/fetch_remote_poll_service.rb
+++ b/app/services/activitypub/fetch_remote_poll_service.rb
@@ -4,54 +4,7 @@ class ActivityPub::FetchRemotePollService < BaseService
   include JsonLdHelper
 
   def call(poll, on_behalf_of = nil)
-    @json = fetch_resource(poll.status.uri, true, on_behalf_of)
-
-    return unless supported_context? && expected_type?
-
-    expires_at = begin
-      if @json['closed'].is_a?(String)
-        @json['closed']
-      elsif !@json['closed'].nil? && !@json['closed'].is_a?(FalseClass)
-        Time.now.utc
-      else
-        @json['endTime']
-      end
-    end
-
-    items = begin
-      if @json['anyOf'].is_a?(Array)
-        @json['anyOf']
-      else
-        @json['oneOf']
-      end
-    end
-
-    latest_options = items.map { |item| item['name'].presence || item['content'] }
-
-    # If for some reasons the options were changed, it invalidates all previous
-    # votes, so we need to remove them
-    poll.votes.delete_all if latest_options != poll.options
-
-    begin
-      poll.update!(
-        last_fetched_at: Time.now.utc,
-        expires_at: expires_at,
-        options: latest_options,
-        cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
-      )
-    rescue ActiveRecord::StaleObjectError
-      poll.reload
-      retry
-    end
-  end
-
-  private
-
-  def supported_context?
-    super(@json)
-  end
-
-  def expected_type?
-    equals_or_includes_any?(@json['type'], %w(Question))
+    json = fetch_resource(poll.status.uri, true, on_behalf_of)
+    ActivityPub::ProcessPollService.new.call(poll, json)
   end
 end
diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb
index 569d0d7c1..8cb309e52 100644
--- a/app/services/activitypub/fetch_replies_service.rb
+++ b/app/services/activitypub/fetch_replies_service.rb
@@ -46,13 +46,4 @@ class ActivityPub::FetchRepliesService < BaseService
     # Also limit to 5 fetched replies to limit potential for DoS.
     @items.map { |item| value_or_id(item) }.reject { |uri| invalid_origin?(uri) }.take(5)
   end
-
-  def invalid_origin?(url)
-    return true if unsupported_uri_scheme?(url)
-
-    needle   = Addressable::URI.parse(url).host
-    haystack = Addressable::URI.parse(@account.uri).host
-
-    !haystack.casecmp(needle).zero?
-  end
 end
diff --git a/app/services/activitypub/process_poll_service.rb b/app/services/activitypub/process_poll_service.rb
new file mode 100644
index 000000000..ee248169d
--- /dev/null
+++ b/app/services/activitypub/process_poll_service.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+class ActivityPub::ProcessPollService < BaseService
+  include JsonLdHelper
+
+  def call(poll, json)
+    @json = json
+    return unless supported_context? && expected_type?
+
+    previous_expires_at = poll.expires_at
+
+    expires_at = begin
+      if @json['closed'].is_a?(String)
+        @json['closed']
+      elsif !@json['closed'].nil? && !@json['closed'].is_a?(FalseClass)
+        Time.now.utc
+      else
+        @json['endTime']
+      end
+    end
+
+    items = begin
+      if @json['anyOf'].is_a?(Array)
+        @json['anyOf']
+      else
+        @json['oneOf']
+      end
+    end
+
+    latest_options = items.map { |item| item['name'].presence || item['content'] }
+
+    # If for some reasons the options were changed, it invalidates all previous
+    # votes, so we need to remove them
+    poll.votes.delete_all if latest_options != poll.options
+
+    begin
+      poll.update!(
+        last_fetched_at: Time.now.utc,
+        expires_at: expires_at,
+        options: latest_options,
+        cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
+      )
+    rescue ActiveRecord::StaleObjectError
+      poll.reload
+      retry
+    end
+
+    # If the poll had no expiration date set but now has, and people have voted,
+    # schedule a notification.
+    if previous_expires_at.nil? && poll.expires_at.present? && poll.votes.exists?
+      PollExpirationNotifyWorker.perform_at(poll.expires_at + 5.minutes, poll.id)
+    end
+  end
+
+  private
+
+  def supported_context?
+    super(@json)
+  end
+
+  def expected_type?
+    equals_or_includes_any?(@json['type'], %w(Question))
+  end
+end
diff --git a/app/services/app_sign_up_service.rb b/app/services/app_sign_up_service.rb
index d621cc462..6dee9cd81 100644
--- a/app/services/app_sign_up_service.rb
+++ b/app/services/app_sign_up_service.rb
@@ -18,6 +18,6 @@ class AppSignUpService < BaseService
   private
 
   def allowed_registrations?
-    Setting.open_registrations && !Rails.configuration.x.single_user_mode
+    Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
   end
 end
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index b80ceef03..b5c721589 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -38,6 +38,10 @@ class NotifyService < BaseService
     false
   end
 
+  def blocked_poll?
+    false
+  end
+
   def following_sender?
     return @following_sender if defined?(@following_sender)
     @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
@@ -88,7 +92,7 @@ class NotifyService < BaseService
 
   def blocked?
     blocked   = @recipient.suspended?                            # Skip if the recipient account is suspended anyway
-    blocked ||= from_self?                                       # Skip for interactions with self
+    blocked ||= from_self? && @notification.type != :poll        # Skip for interactions with self
 
     return blocked if message? && from_staff?
 
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 8a9d26c56..b9952369d 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -91,10 +91,13 @@ class PostStatusService < BaseService
   def postprocess_status!
     LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
     DistributionWorker.perform_async(@status.id)
+
     unless @status.local_only?
       Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id)
       ActivityPub::DistributionWorker.perform_async(@status.id)
     end
+
+    PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
   end
 
   def validate_media!
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 99c8e6cbb..7eec11ddf 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -10,7 +10,7 @@ class RemoveStatusService < BaseService
     @account      = status.account
     @tags         = status.tags.pluck(:name).to_a
     @mentions     = status.active_mentions.includes(:account).to_a
-    @reblogs      = status.reblogs.to_a
+    @reblogs      = status.reblogs.includes(:account).to_a
     @stream_entry = status.stream_entry
     @options      = options
 
@@ -78,8 +78,8 @@ class RemoveStatusService < BaseService
     end
 
     # ActivityPub
-    ActivityPub::DeliveryWorker.push_bulk(target_accounts.select(&:activitypub?).uniq(&:inbox_url)) do |target_account|
-      [signed_activity_json, @account.id, target_account.inbox_url]
+    ActivityPub::DeliveryWorker.push_bulk(target_accounts.select(&:activitypub?).uniq(&:preferred_inbox_url)) do |target_account|
+      [signed_activity_json, @account.id, target_account.preferred_inbox_url]
     end
   end
 
diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb
index 5b80da03a..0cace6c00 100644
--- a/app/services/vote_service.rb
+++ b/app/services/vote_service.rb
@@ -19,8 +19,27 @@ class VoteService < BaseService
       end
     end
 
-    return if @poll.account.local?
+    if @poll.account.local?
+      distribute_poll!
+    else
+      deliver_votes!
+      queue_final_poll_check!
+    end
+  end
+
+  private
+
+  def distribute_poll!
+    return if @poll.hide_totals?
+    ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, @poll.status.id)
+  end
+
+  def queue_final_poll_check!
+    return unless @poll.expires?
+    PollExpirationNotifyWorker.perform_at(@poll.expires_at + 5.minutes, @poll.id)
+  end
 
+  def deliver_votes!
     @votes.each do |vote|
       ActivityPub::DeliveryWorker.perform_async(
         build_json(vote),
@@ -30,8 +49,6 @@ class VoteService < BaseService
     end
   end
 
-  private
-
   def build_json(vote)
     ActiveModelSerializers::SerializableResource.new(
       vote,
diff --git a/app/views/about/_features.html.haml b/app/views/about/_features.html.haml
deleted file mode 100644
index 8fbc6b760..000000000
--- a/app/views/about/_features.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-.features-list
-  .features-list__row
-    .text
-      %h6= t 'about.features.real_conversation_title'
-      = t 'about.features.real_conversation_body'
-    .visual
-      = fa_icon 'fw comments'
-  .features-list__row
-    .text
-      %h6= t 'about.features.not_a_product_title'
-      = t 'about.features.not_a_product_body'
-    .visual
-      = fa_icon 'fw users'
-  .features-list__row
-    .text
-      %h6= t 'about.features.within_reach_title'
-      = t 'about.features.within_reach_body'
-    .visual
-      = fa_icon 'fw mobile'
-  .features-list__row
-    .text
-      %h6= t 'about.features.humane_approach_title'
-      = t 'about.features.humane_approach_body'
-    .visual
-      = fa_icon 'fw leaf'
diff --git a/app/views/about/_forms.html.haml b/app/views/about/_forms.html.haml
deleted file mode 100644
index 78a422690..000000000
--- a/app/views/about/_forms.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-- if @instance_presenter.open_registrations
-  = render 'registration'
-- else
-  = link_to t('auth.register_elsewhere'), 'https://joinmastodon.org/#getting-started', class: 'button button-primary'
-
-  .closed-registrations-message
-    - if @instance_presenter.closed_registrations_message.blank?
-      %p= t('about.closed_registrations')
-    - else
-      = @instance_presenter.closed_registrations_message.html_safe
-
-.separator-or
-  %span= t('auth.or')
-
-= link_to t('auth.login'), new_user_session_path, class: 'button button-alternative-2 webapp-btn'
diff --git a/app/views/about/_links.html.haml b/app/views/about/_links.html.haml
deleted file mode 100644
index 381f301f9..000000000
--- a/app/views/about/_links.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.container-alt.links
-  .brand
-    = link_to root_url do
-      = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
-
-  %ul.nav
-    %li
-      - if user_signed_in?
-        = link_to t('settings.back'), root_url, class: 'webapp-btn'
-      - else
-        = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
-    %li= link_to t('about.about_this'), about_more_path
-    %li
-      = link_to 'https://joinmastodon.org/#getting-started' do
-        = "#{t('about.other_instances')}"
-        %i.fa.fa-external-link{ style: 'padding-left: 5px;' }
diff --git a/app/views/about/_login.html.haml b/app/views/about/_login.html.haml
new file mode 100644
index 000000000..d286f0d3c
--- /dev/null
+++ b/app/views/about/_login.html.haml
@@ -0,0 +1,13 @@
+= simple_form_for(new_user, url: user_session_path) do |f|
+  .fields-group
+    - if use_seamless_external_login?
+      = f.input :email, placeholder: t('simple_form.labels.defaults.username_or_email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }, hint: false
+    - else
+      = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
+
+    = f.input :password, placeholder: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }, hint: false
+
+  .actions
+    = f.button :button, t('auth.login'), type: :submit, class: 'button button-primary'
+
+  %p.hint.subtle-hint= link_to t('auth.trouble_logging_in'), new_user_password_path
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index ee4f8fe2e..9cb4eb2bc 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -1,12 +1,16 @@
 = simple_form_for(new_user, url: user_registration_path) do |f|
-  = f.simple_fields_for :account do |account_fields|
-    = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false
+  %p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
 
-  = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false
-  = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false
-  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false
+  .fields-group
+    = f.simple_fields_for :account do |account_fields|
+      = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
 
-  .actions
-    = f.button :button, t('auth.register'), type: :submit, class: 'button button-primary'
+    = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+    = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+    = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+
+  .fields-group
+    = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations?
 
-  %p.hint.subtle-hint=t('auth.agreement_html', rules_path: about_more_path, terms_path: terms_path)
+  .actions
+    = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations?
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 6c28f83ce..15d0af64e 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -3,143 +3,76 @@
 
 - content_for :header_tags do
   %link{ rel: 'canonical', href: about_url }/
-  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = render partial: 'shared/og'
 
-.landing-page.alternative
-  .container
-    .grid
-      .column-0
-        .brand
-          = link_to root_url do
-            = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
-
-      - if Setting.timeline_preview
-        .column-1
-          .landing-page__forms
-            .brand
-              = link_to root_url do
-                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
-
-            = render 'forms'
-
-      - else
-        .column-1.non-preview
-          .landing-page__forms
-            .brand
-              = link_to root_url do
-                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
-
-            = render 'forms'
-
-      - if Setting.timeline_preview
-        .column-2
-          .landing-page__hero
-            = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
-
-          .landing-page__information
-            .landing-page__short-description
-              .row
-                .landing-page__logo
-                  = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
-
-                %h1
-                  = @instance_presenter.site_title
-                  %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
-
-              %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
-
-          .landing-page__call-to-action{ dir: 'ltr' }
-            .row
-              .row__information-board
-                .information-board__section
-                  %span= t 'about.user_count_before'
-                  %strong= number_with_delimiter @instance_presenter.user_count
-                  %span= t 'about.user_count_after', count: @instance_presenter.user_count
-                .information-board__section
-                  %span= t 'about.status_count_before'
-                  %strong= number_with_delimiter @instance_presenter.status_count
-                  %span= t 'about.status_count_after', count: @instance_presenter.status_count
-              .row__mascot
-                .landing-page__mascot
-                  = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('elephant_ui_plane.svg'), alt: ''
-
-      - else
-        .column-2.non-preview
-          .landing-page__hero
-            = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
-
-          .landing-page__information
-            .landing-page__short-description
-              .row
-                .landing-page__logo
-                  = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
-
-                %h1
-                  = @instance_presenter.site_title
-                  %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
-
-              %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
-
-          .landing-page__call-to-action
-            .row
-              .row__information-board
-                .information-board__section
-                  %span= t 'about.user_count_before'
-                  %strong= number_with_delimiter @instance_presenter.user_count
-                  %span= t 'about.user_count_after', count: @instance_presenter.user_count
-                .information-board__section
-                  %span= t 'about.status_count_before'
-                  %strong= number_with_delimiter @instance_presenter.status_count
-                  %span= t 'about.status_count_after', count: @instance_presenter.status_count
-              .row__mascot
-                .landing-page__mascot
-                  = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('elephant_ui_plane.svg'), alt: ''
-
-      - if Setting.timeline_preview
-        .column-3
-          #mastodon-timeline{ data: { props: Oj.dump(default_props) } }
-
-      - if Setting.timeline_preview
-        .column-4.landing-page__information
-          .landing-page__features
-            .features-list
-              %div
-                %h3= t 'about.what_is_mastodon'
-                %p= t 'about.about_mastodon_html'
-              %div.contact
-                %h3= t 'about.administered_by'
-                = account_link_to(@instance_presenter.contact_account, link_to(t('about.learn_more'), about_more_path, class: 'button button-alternative'))
-
-            = render 'features'
-
-            .landing-page__features__action
-              = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-alternative'
-
-          .landing-page__footer
+.landing
+  .landing__brand
+    = link_to root_url, class: 'brand' do
+      = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+      %span.brand__tagline=t 'about.tagline'
+
+  .landing__grid
+    .landing__grid__column.landing__grid__column-registration
+      .box-widget
+        = render 'registration'
+
+      .directory
+        .directory__tag{ class: Setting.profile_directory ? nil : 'disabled' }
+          = optional_link_to Setting.profile_directory, explore_path do
+            %h4
+              = fa_icon 'address-book fw'
+              = t('about.discover_users')
+              %small= t('about.browse_directory')
+
+            .avatar-stack
+              - @instance_presenter.sample_accounts.each do |account|
+                = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar'
+
+        .directory__tag{ class: Setting.timeline_preview ? nil : 'disabled' }
+          = optional_link_to Setting.timeline_preview, public_timeline_path do
+            %h4
+              = fa_icon 'globe fw'
+              = t('about.see_whats_happening')
+              %small= t('about.browse_public_posts')
+
+        .directory__tag
+          = link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener' do
+            %h4
+              = fa_icon 'tablet fw'
+              = t('about.get_apps')
+              %small= t('about.apps_platforms')
+
+    .landing__grid__column.landing__grid__column-login
+      .box-widget
+        = render 'login'
+
+      .hero-widget
+        .hero-widget__img
+          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+
+        - if @instance_presenter.site_short_description.present?
+          .hero-widget__text
             %p
-              = link_to t('about.source_code'), @instance_presenter.source_url
-              = " (#{@instance_presenter.version_number})"
-
-      - else
-        .column-4.non-preview.landing-page__information
-          .landing-page__features
-            .features-list
-              %div
-                %h3= t 'about.what_is_mastodon'
-                %p= t 'about.about_mastodon_html'
-              %div.contact
-                %h3= t 'about.administered_by'
-                = account_link_to(@instance_presenter.contact_account, link_to(t('about.learn_more'), about_more_path, class: 'button button-alternative'))
-
-            = render 'features'
-
-            .landing-page__features__action
-              = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-alternative'
-
-          .landing-page__footer
-            %p
-              = link_to t('about.source_code'), @instance_presenter.source_url
-              = " (#{@instance_presenter.version_number})"
-
-#modal-container
+              = @instance_presenter.site_short_description.html_safe.presence
+              = link_to about_more_path do
+                = t('about.learn_more')
+                = fa_icon 'angle-double-right'
+
+        .hero-widget__footer
+          .hero-widget__footer__column
+            %h4= t 'about.administered_by'
+
+            = account_link_to @instance_presenter.contact_account
+
+          .hero-widget__footer__column
+            %h4= t 'about.server_stats'
+
+            %div{ style: 'display: flex' }
+              .hero-widget__counter{ style: 'width: 50%' }
+                %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true
+                %span= t 'about.user_count_after', count: @instance_presenter.user_count
+              .hero-widget__counter{ style: 'width: 50%' }
+                %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true
+                %span
+                  = t 'about.active_count_after'
+                  %abbr{ title: t('about.active_footnote') } *
diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml
index 1e1bb1812..eba3ad804 100644
--- a/app/views/admin/accounts/_account.html.haml
+++ b/app/views/admin/accounts/_account.html.haml
@@ -5,7 +5,7 @@
     %div{ style: 'margin: -2px 0' }= account_badge(account, all: true)
   %td
     - if account.user_current_sign_in_ip
-      %samp= account.user_current_sign_in_ip
+      %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip
     - else
       \-
   %td
@@ -14,5 +14,9 @@
     - else
       \-
   %td
-    = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
-    = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account)
+    - if account.local? && account.user_pending?
+      = table_link_to 'check', t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:approve, account.user)
+      = table_link_to 'times', t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:reject, account.user)
+    - else
+      = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
+      = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account)
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 345f74f90..66808add7 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -10,9 +10,10 @@
   .filter-subset
     %strong= t('admin.accounts.moderation.title')
     %ul
-      %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil
-      %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil
-      %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil
+      %li= filter_link_to t('admin.accounts.moderation.pending'), pending: '1', silenced: nil, suspended: nil
+      %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil, pending: nil
+      %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil, pending: nil
+      %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil, pending: nil
   .filter-subset
     %strong= t('admin.accounts.role')
     %ul
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 7ac73bd07..7494c9fa2 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -37,6 +37,8 @@
           %span.red= t('admin.accounts.disabled')
         - elsif @account.local? && !@account.user&.confirmed?
           %span.neutral= t('admin.accounts.confirming')
+        - elsif @account.local? && !@account.user_approved?
+          %span.neutral= t('admin.accounts.pending')
         - else
           %span.neutral= t('admin.accounts.no_limits_imposed')
       .dashboard__counters__label= t 'admin.accounts.login_status'
@@ -95,7 +97,7 @@
             %td
               - if @account.user&.disabled?
                 = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
-              - else
+              - elsif @account.user_approved?
                 = table_link_to 'lock', t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable') if can?(:disable, @account.user)
 
           %tr
@@ -144,26 +146,30 @@
         = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
         - if @account.user&.otp_required_for_login?
           = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
-        - unless @account.memorial?
+        - if !@account.memorial? && @account.user_approved?
           = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
       - else
         = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
 
     %div{ style: 'float: left' }
-      - if @account.local?
+      - if @account.local? && @account.user_approved?
         = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
       - if @account.silenced?
         = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
-      - else
+      - elsif !@account.local? || @account.user_approved?
         = link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
 
       - if @account.local?
+        - if @account.user_pending?
+          = link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
+          = link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
+
         - unless @account.user_confirmed?
           = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
 
       - if @account.suspended?
         = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
-      - else
+      - elsif !@account.local? || @account.user_approved?
         = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
 
       - unless @account.local?
diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml
index d7b697286..ee0eacaf5 100644
--- a/app/views/admin/invites/_invite.html.haml
+++ b/app/views/admin/invites/_invite.html.haml
@@ -1,21 +1,29 @@
 %tr
   %td
+    .input-copy
+      .input-copy__wrapper
+        %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) }
+      %button{ type: :button }= t('generic.copy')
+
+  %td
     .name-tag
       = image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar'
       %span.username= invite.user.account.username
-  %td
-    = invite.uses
-    = " / #{invite.max_uses}" unless invite.max_uses.nil?
-  %td
-    - if invite.expired?
+
+  - if invite.expired?
+    %td{ colspan: 2 }
       = t('invites.expired')
-    - else
+  - else
+    %td
+      = fa_icon 'user fw'
+      = invite.uses
+      = " / #{invite.max_uses}" unless invite.max_uses.nil?
+    %td
       - if invite.expires_at.nil?

       - else
         %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) }
           = l invite.expires_at
-  %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code)
   %td
     - if !invite.expired? && policy(invite).destroy?
       = table_link_to 'times', t('invites.delete'), admin_invite_path(invite), method: :delete
diff --git a/app/views/admin/invites/index.html.haml b/app/views/admin/invites/index.html.haml
index 42159e9f3..ee6ba0f57 100644
--- a/app/views/admin/invites/index.html.haml
+++ b/app/views/admin/invites/index.html.haml
@@ -18,15 +18,15 @@
 
   %hr.spacer/
 
-.table-wrapper
-  %table.table
+.table-wrapper.simple_form
+  %table.table.table--invites
     %thead
       %tr
         %th
+        %th
         %th= t('invites.table.uses')
         %th= t('invites.table.expires_at')
         %th
-        %th
     %tbody
       = render @invites
 
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index e3ceb4344..9995e0b2a 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -6,8 +6,11 @@
   .fields-group
     = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')
 
-  .fields-group
-    = f.input :flavour_and_skin, collection: Themes.instance.flavours_and_skins, group_label_method: lambda { |(flavour, _)| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, include_blank: false, as: :grouped_select, label_method: :last, value_method: lambda { |value| value.join('/') }, group_method: :last
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :flavour_and_skin, collection: Themes.instance.flavours_and_skins, group_label_method: lambda { |(flavour, _)| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, include_blank: false, as: :grouped_select, label_method: :last, value_method: lambda { |value| value.join('/') }, group_method: :last
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, label: t('admin.settings.registrations_mode.title'), include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
 
   .fields-row
     .fields-row__column.fields-row__column-6.fields-group
@@ -48,9 +51,6 @@
     = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html')
 
   .fields-group
-    = f.input :open_registrations, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.open.title'), hint: t('admin.settings.registrations.open.desc_html')
-
-  .fields-group
     = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
 
   .fields-group
diff --git a/app/views/admin_mailer/new_pending_account.text.erb b/app/views/admin_mailer/new_pending_account.text.erb
new file mode 100644
index 000000000..ed31ae2eb
--- /dev/null
+++ b/app/views/admin_mailer/new_pending_account.text.erb
@@ -0,0 +1,8 @@
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
+
+<%= raw t('admin_mailer.new_pending_account.body') %>
+
+<%= raw t('admin.accounts.email') %>: <%= @account.user_email %>
+<%= raw t('admin.accounts.most_recent_ip') %>: <%= @account.user_current_sign_in_ip %>
+
+<%= raw t('application_mailer.view')%> <%= admin_account_url(@account.id) %>
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index 72ce8e531..1caf2b401 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -29,6 +29,6 @@
   %p.hint= t('auth.agreement_html', rules_path: about_more_path, terms_path: terms_path)
 
   .actions
-    = f.button :button, t('auth.register'), type: :submit
+    = f.button :button, sign_up_message, type: :submit
 
 .form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml
index 516c625a6..3c68ccd22 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'), open_registrations? ? new_registration_path(resource_name) : 'https://joinmastodon.org/#getting-started'
+    %li= link_to t('auth.register'), available_sign_up_path
 
   - 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/invites/_invite.html.haml b/app/views/invites/_invite.html.haml
index 1c7ec311d..4240aa3e7 100644
--- a/app/views/invites/_invite.html.haml
+++ b/app/views/invites/_invite.html.haml
@@ -1,17 +1,25 @@
 %tr
   %td
-    = invite.uses
-    = " / #{invite.max_uses}" unless invite.max_uses.nil?
-  %td
-    - if invite.expired?
+    .input-copy
+      .input-copy__wrapper
+        %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) }
+      %button{ type: :button }= t('generic.copy')
+
+  - if invite.expired?
+    %td{ colspan: 2 }
       = t('invites.expired')
-    - else
+  - else
+    %td
+      = fa_icon 'user fw'
+      = invite.uses
+      = " / #{invite.max_uses}" unless invite.max_uses.nil?
+    %td
       - if invite.expires_at.nil?

       - else
         %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) }
           = l invite.expires_at
-  %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code)
+
   %td
     - if !invite.expired? && policy(invite).destroy?
       = table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete
diff --git a/app/views/invites/index.html.haml b/app/views/invites/index.html.haml
index fb827f6e6..61420ab1e 100644
--- a/app/views/invites/index.html.haml
+++ b/app/views/invites/index.html.haml
@@ -8,12 +8,13 @@
 
   %hr.spacer/
 
-%table.table
-  %thead
-    %tr
-      %th= t('invites.table.uses')
-      %th= t('invites.table.expires_at')
-      %th
-      %th
-  %tbody
-    = render @invites
+.simple_form
+  %table.table.table--invites
+    %thead
+      %tr
+        %th
+        %th= t('invites.table.uses')
+        %th= t('invites.table.expires_at')
+        %th
+    %tbody
+      = render @invites
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index c1c0f4b87..1d3519b8a 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -1,22 +1,22 @@
 - content_for :content do
   .public-layout
-    .container
-      %nav.header
-        .nav-left
-          = link_to root_url, class: 'brand' do
-            = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+    - unless @hide_navbar
+      .container
+        %nav.header
+          .nav-left
+            = link_to root_url, class: 'brand' do
+              = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
 
-          - if Setting.profile_directory
-            = link_to t('directories.directory'), explore_path, class: 'nav-link optional'
-          = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
-          = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
-        .nav-center
-        .nav-right
-          - if user_signed_in?
-            = 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'), open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started', class: 'webapp-btn nav-link nav-button'
+            = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
+            = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
+            = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
+          .nav-center
+          .nav-right
+            - if user_signed_in?
+              = 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'), available_sign_up_path, class: 'webapp-btn nav-link nav-button'
 
     .container= yield
 
diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml
new file mode 100644
index 000000000..f80157c67
--- /dev/null
+++ b/app/views/public_timelines/show.html.haml
@@ -0,0 +1,13 @@
+- content_for :page_title do
+  = t('about.see_whats_happening')
+
+- content_for :header_tags do
+  %meta{ name: 'robots', content: 'noindex' }/
+  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
+
+.page-header
+  %h1= t('about.see_whats_happening')
+  %p= t('about.browse_public_posts')
+
+#mastodon-timeline{ data: { props: Oj.dump(default_props) }}
+#modal-container
diff --git a/app/views/remote_follow/new.html.haml b/app/views/remote_follow/new.html.haml
index 5cf6977ba..4e9601f6a 100644
--- a/app/views/remote_follow/new.html.haml
+++ b/app/views/remote_follow/new.html.haml
@@ -1,6 +1,5 @@
 - content_for :header_tags do
-  - if @account.user&.setting_noindex
-    %meta{ name: 'robots', content: 'noindex' }/
+  %meta{ name: 'robots', content: 'noindex' }/
 
 .form-container
   .follow-prompt
@@ -18,4 +17,4 @@
 
     %p.hint.subtle-hint
       = t('remote_follow.reason_html', instance: site_hostname)
-      = t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started')
+      = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path)
diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml
index a0b106814..c8c08991f 100644
--- a/app/views/remote_interaction/new.html.haml
+++ b/app/views/remote_interaction/new.html.haml
@@ -1,3 +1,6 @@
+- content_for :header_tags do
+  %meta{ name: 'robots', content: 'noindex' }/
+
 .form-container
   .follow-prompt
     %h2= t("remote_interaction.#{@interaction_type}.prompt")
@@ -18,4 +21,4 @@
 
     %p.hint.subtle-hint
       = t('remote_follow.reason_html', instance: site_hostname)
-      = t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started')
+      = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path)
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 850160ac1..1a9c58983 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -2,6 +2,7 @@
   = "##{@tag.name}"
 
 - content_for :header_tags do
+  %meta{ name: 'robots', content: 'noindex' }/
   %link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/
 
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml
index f75f7529a..70d0f5a24 100644
--- a/app/views/user_mailer/confirmation_instructions.html.haml
+++ b/app/views/user_mailer/confirmation_instructions.html.haml
@@ -36,7 +36,7 @@
                         %tbody
                           %tr
                             %td.column-cell.text-center
-                              %p= t 'devise.mailer.confirmation_instructions.explanation', host: site_hostname
+                              %p= t @resource.approved? ? 'devise.mailer.confirmation_instructions.explanation' : 'devise.mailer.confirmation_instructions.explanation_when_pending', host: site_hostname
 
 %table.email-table{ cellspacing: 0, cellpadding: 0 }
   %tbody
diff --git a/app/views/user_mailer/confirmation_instructions.text.erb b/app/views/user_mailer/confirmation_instructions.text.erb
index 65b4626c6..aad91cd9d 100644
--- a/app/views/user_mailer/confirmation_instructions.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.text.erb
@@ -2,7 +2,7 @@
 
 ===
 
-<%= t 'devise.mailer.confirmation_instructions.explanation', host: site_hostname %>
+<%= t @resource.approved? ? 'devise.mailer.confirmation_instructions.explanation' : 'devise.mailer.confirmation_instructions.explanation_when_pending', host: site_hostname %>
 
 => <%= confirmation_url(@resource, confirmation_token: @token, redirect_to_app: @resource.created_by_application ? 'true' : nil) %>
 
diff --git a/app/workers/activitypub/distribute_poll_update_worker.rb b/app/workers/activitypub/distribute_poll_update_worker.rb
new file mode 100644
index 000000000..5536bd744
--- /dev/null
+++ b/app/workers/activitypub/distribute_poll_update_worker.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+class ActivityPub::DistributePollUpdateWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: 'push', unique: :until_executed, retry: 0
+
+  def perform(status_id)
+    @status  = Status.find(status_id)
+    @account = @status.account
+
+    return if @status.poll.nil? || @status.local_only?
+
+    ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
+      [payload, @account.id, inbox_url]
+    end
+
+    relay! if relayable?
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+
+  private
+
+  def relayable?
+    @status.public_visibility?
+  end
+
+  def inboxes
+    return @inboxes if defined?(@inboxes)
+
+    @inboxes = [@status.mentions, @status.reblogs, @status.poll.votes].flat_map do |relation|
+      relation.includes(:account).map do |record|
+        record.account.preferred_inbox_url if !record.account.local? && record.account.activitypub?
+      end
+    end
+
+    @inboxes.concat(@account.followers.inboxes) unless @status.direct_visibility?
+    @inboxes.uniq!
+    @inboxes.compact!
+    @inboxes
+  end
+
+  def signed_payload
+    Oj.dump(ActivityPub::LinkedDataSignature.new(unsigned_payload).sign!(@account))
+  end
+
+  def unsigned_payload
+    ActiveModelSerializers::SerializableResource.new(
+      @status,
+      serializer: ActivityPub::UpdatePollSerializer,
+      adapter: ActivityPub::Adapter
+    ).as_json
+  end
+
+  def payload
+    @payload ||= @status.distributable? ? signed_payload : Oj.dump(unsigned_payload)
+  end
+
+  def relay!
+    ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
+      [payload, @account.id, inbox_url]
+    end
+  end
+end
diff --git a/app/workers/poll_expiration_notify_worker.rb b/app/workers/poll_expiration_notify_worker.rb
new file mode 100644
index 000000000..e08f0c249
--- /dev/null
+++ b/app/workers/poll_expiration_notify_worker.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class PollExpirationNotifyWorker
+  include Sidekiq::Worker
+
+  sidekiq_options unique: :until_executed
+
+  def perform(poll_id)
+    poll = Poll.find(poll_id)
+
+    # Notify poll owner and remote voters
+    if poll.local?
+      ActivityPub::DistributePollUpdateWorker.perform_async(poll.status.id)
+      NotifyService.new.call(poll.account, poll)
+    end
+
+    # Notify local voters
+    poll.votes.includes(:account).map(&:account).select(&:local?).each do |account|
+      NotifyService.new.call(account, poll)
+    end
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index 67fa97c59..b0b8d8b40 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -7,7 +7,6 @@ ar:
     administered_by: 'يُديره :'
     api: واجهة برمجة التطبيقات
     apps: تطبيقات الأجهزة المحمولة
-    closed_registrations: التسجيلات في مثيل الخادوم هذا مُغلقة حاليًا. غير أنه بامكانك العثور على خادم آخر لإنشاء حسابك و مِن ثم النفاذ إلى نفس الشبكة مِن هناك.
     contact: للتواصل معنا
     contact_missing: لم يتم تعيينه
     contact_unavailable: غير متوفر
@@ -15,19 +14,9 @@ ar:
     extended_description_html: |
       <h3>مكان جيد للقواعد</h3>
       <p>لم يتم بعد إدخال الوصف الطويل.</p>
-    features:
-      humane_approach_body: تعلُّمًا مِن فشل الشبكات الأخرى، غاية ماستدون هي بلوغ الخيارات الأخلاقية في التصميم لمُحارَبة إسائة إستعمال شبكات التواصل الإجتماعية.
-      humane_approach_title: أسلوب يُعيد الإعتبار للفَرد
-      not_a_product_body: ماستدون ليس شبكة تجارية. لا يحتوي على إعلانات و لا يقوم باستغلال البيانات و لا هو بِبُستان مُسيَّج. لا تحكم فيه وليس له أية هيئةٍ مركزيةٍ.
-      not_a_product_title: إنك فرد و لست سلعة
-      real_conversation_body: يُمكنكم التعبير عن آرائكم بكل حرية بفضل 500 حرف و انتقاء دقيق للمحتوى و الوسائط بفضل أدوات التحذير التي هي بين أيديكم.
-      real_conversation_title: مبني لتحقيق تواصل حقيقي
-      within_reach_body: إبقوا على اتصال دائم بأصدقائكم حيثما كانوا عبر عدة تطبيقات لنظام آي أواس و أندرويد و عدة منصات أخرى بفضل واجهة برمجية للتطبيقات و بيئة صديقة للتطوير.
-      within_reach_title: في مُتناوَل يدك دائمًا
     generic_description: "%{domain} هو سيرفر من بين سيرفرات الشبكة"
     hosted_on: ماستدون مُستضاف على %{domain}
     learn_more: تعلم المزيد
-    other_instances: خوادم أخرى
     privacy_policy: سياسة الخصوصية
     source_code: الشفرة المصدرية
     status_count_after:
@@ -427,9 +416,6 @@ ar:
         min_invite_role:
           disabled: لا أحد
           title: المستخدِمون المصرح لهم لإرسال الدعوات
-        open:
-          desc_html: السماح للجميع بإنشاء حساب
-          title: فتح التسجيل
       show_known_fediverse_at_about_page:
         desc_html: عند التثبت ، سوف تظهر toots من جميع fediverse المعروفة على عرض مسبق. وإلا فإنه سيعرض فقط toots المحلية.
         title: إظهار الفيديفرس الموحَّد في خيط المُعايَنة
@@ -524,13 +510,11 @@ ar:
     logout: خروج
     migrate_account: الإنتقال إلى حساب آخر
     migrate_account_html: إن كنت ترغب في تحويل هذا الحساب نحو حساب آخَر، يُمكِنُك <a href="%{path}">إعداده هنا</a>.
-    or: أو
     or_log_in_with: أو قم بتسجيل الدخول بواسطة
     providers:
       cas: CAS
       saml: SAML
     register: إنشاء حساب
-    register_elsewhere: التسجيل على خادوم آخَر
     resend_confirmation: إعادة إرسال تعليمات التأكيد
     reset_password: إعادة تعيين كلمة المرور
     security: الأمان
diff --git a/config/locales/ast.yml b/config/locales/ast.yml
index 78ad796a0..ebf6a3799 100644
--- a/config/locales/ast.yml
+++ b/config/locales/ast.yml
@@ -12,12 +12,6 @@ ast:
     extended_description_html: |
       <h3>Un llugar bonu pa les regles</h3>
       <p>Entá nun se configuró la descripción estendida.</p>
-    features:
-      humane_approach_title: Una visión más humana
-      not_a_product_body: Mastodon nun ye una rede comercial, nun hai anuncios, nun recueye datos o nun pon muries a xardinos. Nin siquier tien una autoridá central.
-      not_a_product_title: Yes una persona, non un productu
-      real_conversation_title: Fechu pa conversaciones de verdá
-      within_reach_title: Siempres al algame
     hosted_on: Mastodon ta agospiáu en %{domain}
     learn_more: Deprendi más
     source_code: Códigu fonte
@@ -141,7 +135,6 @@ ast:
       cas: CAS
       saml: SAML
     register: Rexistrase
-    register_elsewhere: Rexistrase n'otru sirvidor
     security: Seguranza
   authorize_follow:
     already_following: Yá tas siguiendo a esta cuenta
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index 4de5b1e22..2424d9399 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -3,9 +3,7 @@ bg:
   about:
     about_mastodon_html: Mastodon е <em>безплатен</em> сървър с <em>отворен код</em> за социални мрежи. Като <em>децентрализирана</em> алтернатива на комерсиалните платформи, той позволява избягването на риска от монополизация на твоята комуникация от единични компании. Изберете си сървър, на който се доверявате, и ще можете да контактувате с всички останали. Всеки може да пусне Mastodon и лесно да вземе участие в <em>социалната мрежа</em>.
     about_this: За тази инстанция
-    closed_registrations: В момента регистрациите за тази инстанция са затворени.
     contact: За контакти
-    other_instances: Други инстанции
     source_code: Програмен код
     status_count_after: публикации
     status_count_before: Написали
diff --git a/config/locales/bn.yml b/config/locales/bn.yml
index 560e74398..e76c7ba21 100644
--- a/config/locales/bn.yml
+++ b/config/locales/bn.yml
@@ -7,7 +7,6 @@ bn:
     administered_by: 'পরিচালনা করছেন:'
     api: সফটওয়্যার তৈরীর নিয়ম (API)
     apps: মোবাইল অ্যাপ
-    closed_registrations: এই সার্ভারে এখন নিবন্ধন বন্ধ। কিন্তু ! অন্য একটি সার্ভার খুঁজে নিবন্ধন করলেও একই নেটওয়ার্কে ঢুকতে পারবেন।
     contact: যোগাযোগ
     contact_missing: নেই
     contact_unavailable: প্রযোজ্য নয়
@@ -15,8 +14,3 @@ bn:
     extended_description_html: |
       <h3>নিয়মের জন্য উপযুক্ত জায়গা</h3>
       <p>বিস্তারিত বিবরণ এখনো যুক্ত করা হয়নি</p>
-    features:
-      humane_approach_body: অনন্যা নেটওয়ার্কের ব্যর্থতা থেকে শিখে, মাস্টাডনের লক্ষ্য  নৈতিক পরিকল্পনার দ্বারা সামাজিক মাধ্যমের অপব্যবহারের বিরোধিতা করা।
-      humane_approach_title: একটি মনুষ্যত্বপূর্ণ চেষ্টা
-      not_a_product_body: মাস্টাডন কোনো ব্যবসায়িক নেটওয়ার্ক না। কোনো বিজ্ঞাপন নেই, কোনো তথ্য খনি নেই, কোনো বাধার দেয়াল নেই। এর কোনো কেন্দ্রীয় কর্তৃপক্ষ নেই।
-      not_a_product_title: আপনি একজন মানুষ, পণ্য নন
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index c650dda1f..524dedbb4 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -7,7 +7,6 @@ ca:
     administered_by: 'Administrat per:'
     api: API
     apps: Apps mòbil
-    closed_registrations: Actualment, el registre està tancat en aquesta instància. Malgrat això! Pots trobar una altra instància per fer-te un compte i obtenir accés a la mateixa xarxa des d'allà.
     contact: Contacte
     contact_missing: No configurat
     contact_unavailable: N/D
@@ -15,19 +14,9 @@ ca:
     extended_description_html: |
       <h3>Un bon lloc per les regles</h3>
       <p>Encara no s'ha configurat la descripció ampliada.</p>
-    features:
-      humane_approach_body: Aprenent dels errors d'altres xarxes, Mastodon té com a objectiu fer eleccions ètiques de disseny per a combatre el mal ús de les xarxes socials.
-      humane_approach_title: Un enfocament més humà
-      not_a_product_body: Mastodon no és una xarxa comercial. Sense publicitat, sense mineria de dades, sense jardins emmurallats. No hi ha cap autoritat central.
-      not_a_product_title: Ets una persona, no un producte
-      real_conversation_body: Amb 500 caràcters a la teva disposició i suport per a continguts granulars i avisos multimèdia, pots expressar-te de la manera que vulguis.
-      real_conversation_title: Construït per a converses reals
-      within_reach_body: Diverses aplicacions per a iOS, Android i altres plataformes gràcies a un ecosistema API amable amb el desenvolupador, et permet mantenir-te al dia amb els amics en qualsevol lloc..
-      within_reach_title: Sempre a l'abast
     generic_description: "%{domain} és un servidor a la xarxa"
     hosted_on: Mastodon allotjat a %{domain}
     learn_more: Més informació
-    other_instances: Altres instàncies
     privacy_policy: Política de privacitat
     source_code: Codi font
     status_count_after:
@@ -410,9 +399,6 @@ ca:
         min_invite_role:
           disabled: Ningú
           title: Permet les invitacions de
-        open:
-          desc_html: Permet que qualsevol pugui crear un compte
-          title: Registre obert
       show_known_fediverse_at_about_page:
         desc_html: Quan s'activa, mostrarà tots els toots de tot el fedivers conegut en vista prèvia. En cas contrari, només es mostraran toots locals.
         title: Mostra el fedivers conegut en vista prèvia de la línia de temps
@@ -507,13 +493,11 @@ ca:
     logout: Tanca sessió
     migrate_account: Mou a un compte diferent
     migrate_account_html: Si vols redirigir aquest compte a un altre diferent, el pots  <a href="%{path}">configurar aquí</a>.
-    or: o
     or_log_in_with: O inicia sessió amb
     providers:
       cas: CAS
       saml: SAML
     register: Registre
-    register_elsewhere: Registra't en un altre servidor
     resend_confirmation: Torna a enviar el correu de confirmació
     reset_password: Restableix la contrasenya
     security: Seguretat
diff --git a/config/locales/co.yml b/config/locales/co.yml
index 8fcb27598..77c3efeda 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -7,7 +7,6 @@ co:
     administered_by: 'Amministratu da:'
     api: API
     apps: Applicazione per u telefuninu
-    closed_registrations: Pè avà, l’arregistramenti sò chjosi nant’à stu servore. Mà pudete truvà un’altru per fà un contu è avè accessu à listessa reta da quallà.
     contact: Cuntattu
     contact_missing: Mancante
     contact_unavailable: Micca dispunibule
@@ -15,19 +14,9 @@ co:
     extended_description_html: |
       <h3>Una bona piazza per e regule</h3>
       <p>A descrizzione stesa ùn hè micca stata riempiuta.</p>
-    features:
-      humane_approach_body: Mastodon hà amparatu da i sbagli di l’altre rete suciale, è prova à fà scelte di cuncezzione più etiche per luttà contr’à l’abusu di i media suciali.
-      humane_approach_title: Una mentalità più umana
-      not_a_product_body: Mastodon ùn hè micca una rete cummerciale. Micca pubblicità, micca pruspizzione di dati, micca ambienti chjosi, è micca auturità centrale.
-      not_a_product_title: Site una parsona, micca un pruduttu
-      real_conversation_body: Cù 500 caratteri dispunibuli, diffusione persunalizata di u cuntinutu è avertimenti per media sensibili, pudete cumunicà cum’è voi vulete.
-      real_conversation_title: Fattu per una vera cunversazione
-      within_reach_body: Parechje app per iOS, Android è altre piattaforme, create cù un sistemu d’API accessibile à i prugrammatori, vi permettenu d’avè accessu à i vostri amichi senza prublemi.
-      within_reach_title: Sempre accessibile
     generic_description: "%{domain} hè un servore di a rete"
     hosted_on: Mastodon allughjatu nant’à %{domain}
     learn_more: Amparà di più
-    other_instances: Lista di i servori
     privacy_policy: Pulitica di vita privata
     source_code: Codice di fonte
     status_count_after:
@@ -411,9 +400,6 @@ co:
         min_invite_role:
           disabled: Nisunu
           title: Auturizà l’invitazione da
-        open:
-          desc_html: Auturizà tuttu u mondu à creà un contu quì
-          title: Apre l’arregistramenti
       show_known_fediverse_at_about_page:
         desc_html: Quandu ghjè selezziunatu, statuti di tuttu l’istanze cunnisciute saranu affissati indè a vista di e linee. Altrimente soli i statuti lucali saranu mustrati.
         title: Vedde tuttu u fediverse cunnisciutu nant’a vista di e linee
@@ -508,13 +494,11 @@ co:
     logout: Scunnettassi
     migrate_account: Cambià di contu
     migrate_account_html: S’è voi vulete riindirizà stu contu versu un’altru, <a href="%{path}">ghjè pussibule quì</a>.
-    or: o
     or_log_in_with: O cunnettatevi cù
     providers:
       cas: CAS
       saml: SAML
     register: Arregistrassi
-    register_elsewhere: Arregistrassi altrò
     resend_confirmation: Rimandà l’istruzzioni di cunfirmazione
     reset_password: Cambià a chjave d’accessu
     security: Sicurità
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index fe83bd57a..6f5553194 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -7,7 +7,6 @@ cs:
     administered_by: 'Server spravuje:'
     api: API
     apps: Mobilní aplikace
-    closed_registrations: Registrace na tomto serveru jsou momentálně uzavřené. Ale pozor! Můžete si najít jiný server, vytvořit si na něm účet a získat z něj přístup do naprosto stejné sítě.
     contact: Kontakt
     contact_missing: Nenastaveno
     contact_unavailable: Neuvedeno
@@ -15,19 +14,9 @@ cs:
     extended_description_html: |
       <h3>Dobré místo pro pravidla</h3>
       <p>Rozšířený popis ještě nebyl nastaven.</p>
-    features:
-      humane_approach_body: Mastodon se učí z chyb jiných sociálních sítí a volením etických rozhodnutí při designu se snaží bojovat s jejich zneužíváním.
-      humane_approach_title: Lidštější přístup
-      not_a_product_body: Mastodon není komerční síť. Žádné reklamy, žádné dolování dat, žádné hranice. Žádná centrální autorita.
-      not_a_product_title: Jste osoba, ne produkt
-      real_conversation_body: S 500 znaky k vaší dispozici a podporou pro varování o obsahu a médiích se můžete vyjadřovat tak, jak chcete.
-      real_conversation_title: Vytvořen pro opravdovou konverzaci
-      within_reach_body: Několik aplikací pro iOS, Android a jiné platformy vám díky jednoduchému API ekosystému dovolují držet krok s vašimi přáteli, ať už jste kdekoliv.
-      within_reach_title: Vždy v dosahu
     generic_description: "%{domain} je jedním ze serverů v síti"
     hosted_on: Server Mastodon na adrese %{domain}
     learn_more: Zjistit více
-    other_instances: Seznam serverů
     privacy_policy: Zásady soukromí
     source_code: Zdrojový kód
     status_count_after:
@@ -417,9 +406,6 @@ cs:
         min_invite_role:
           disabled: Nikdo
           title: Povolit pozvánky od
-        open:
-          desc_html: Dovolit každému vytvořit si účet
-          title: Zpřístupnit registraci
       show_known_fediverse_at_about_page:
         desc_html: Je-li toto zapnuto, zobrazí se v náhledu tooty ze všech známých serverů na fediverse. Jinak budou zobrazeny pouze místní tooty.
         title: Zobrazit celou známou fediverse na náhledu časové osy
@@ -514,13 +500,11 @@ cs:
     logout: Odhlásit
     migrate_account: Přesunout se na jiný účet
     migrate_account_html: Chcete-li přesměrovat tento účet na jiný, můžete to <a href="%{path}">nastavit zde</a>.
-    or: nebo
     or_log_in_with: Nebo se přihlaste pomocí
     providers:
       cas: CAS
       saml: SAML
     register: Registrovat
-    register_elsewhere: Registrovat na jiném serveru
     resend_confirmation: Znovu odeslat pokyny pro potvrzení
     reset_password: Obnovit heslo
     security: Zabezpečení
diff --git a/config/locales/cy.yml b/config/locales/cy.yml
index f225cc086..b6f94606d 100644
--- a/config/locales/cy.yml
+++ b/config/locales/cy.yml
@@ -7,7 +7,6 @@ cy:
     administered_by: 'Gweinyddir gan:'
     api: API
     apps: Apiau symudol
-    closed_registrations: Mae cofrestru wedi cau ar yr achos hwn ar hyn o bryd. Fodd bynnag, mae modd ffeindio achos arall er mwyn creu cyfrif arno a chael mynediad at union yr un rhwydwaith o'r man hwnnw.
     contact: Cyswllt
     contact_missing: Heb ei osod
     contact_unavailable: Ddim yn berthnasol
@@ -15,19 +14,9 @@ cy:
     extended_description_html: |
       <h3>Lle da ar gyfer rheolau</h3>
       <p>Nid yw'r disgrifiad estynedig wedi ei osod eto.</p>
-    features:
-      humane_approach_body: Gan ddysgu o fethiannau rhwydweithiau eraill, mae Mastodon yn anelu i wneud penderfyniadau dylunio moesol i ymladd camddefnydd o gyfryngau cymdeithasol.
-      humane_approach_title: Agwedd fwy dynol
-      not_a_product_body: Nid yw Mastodon yn rwydwaith masnachol. Nid oes hysbysebion, cloddio data na gerddi caeedig. Nid oes awdurdod canolog.
-      not_a_product_title: Rwyt yn berson, nid yn beth
-      real_conversation_body: Gyda'r modd i ddefnyddio hyd at 500 o nodau a chefnogaeth ar gyfer cynnwys gronynnol a rhybuddion cyfryngau, mae modd i chi fynegi'ch hun yn y ffordd yr hoffech chi.
-      real_conversation_title: Wedi ei adeiladu ar gyfer sgyrsiau go iawn
-      within_reach_body: Nifer o apiau ar gyfer iOS, Android, a nifer blatfformau eraill diolch i amgylchedd API hygyrch i ddatblygwyr sy'n caniatau i chi gadw mewn cysylltiad a'ch ffrindiau o unrhywle.
-      within_reach_title: Bob tro o fewn gafael
     generic_description: Mae %{domain} yn un gweinydd yn y rhwydwaith
     hosted_on: Mastodon wedi ei weinyddu ar %{domain}
     learn_more: Dysu mwy
-    other_instances: Rhestr achosion
     privacy_policy: Polisi preifatrwydd
     source_code: Cod ffynhonnell
     status_count_after:
@@ -434,9 +423,6 @@ cy:
         min_invite_role:
           disabled: Neb
           title: Caniatau gwahoddiadau gan
-        open:
-          desc_html: Caniatau i unrhywun greu cyfrif
-          title: Agor cofrestru
       show_known_fediverse_at_about_page:
         desc_html: Wedi'i ddewis, bydd yn dangos rhagolwg o dŵtiau o'r holl ffedysawd. Fel arall bydd ond yn dangos tŵtiau lleol.
         title: Dangos ffedysawd hysbys ar ragolwg y ffrwd
@@ -529,13 +515,11 @@ cy:
     logout: Allgofnodi
     migrate_account: Symud i gyfrif gwahanol
     migrate_account_html: Os hoffech chi ailgyfeirio'r cyfrif hwn at un gwahanol, mae modd <a href="%{path}">ei ffurfweddu yma</a>.
-    or: neu
     or_log_in_with: Neu logiwch mewn a
     providers:
       cas: CAS
       saml: SAML
     register: Cofrestru
-    register_elsewhere: Cofrestru ar weinydd gwahanol
     resend_confirmation: Ailanfon cyfarwyddiadau cadarnhau
     reset_password: Ailosod cyfrinair
     security: Diogelwch
diff --git a/config/locales/da.yml b/config/locales/da.yml
index ca4ff32da..a44a345d7 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -7,7 +7,6 @@ da:
     administered_by: 'Administreret af:'
     api: API
     apps: Apps til mobilen
-    closed_registrations: Registreringer er på nuværrende tidspunkt lukkede for denne instans. Du kan dog finde andre instanser du kan oprette dig på og få adgang til det samme netværk derfra.
     contact: Kontakt
     contact_missing: Ikke sat
     contact_unavailable: Ikke tilgængeligt
@@ -15,19 +14,9 @@ da:
     extended_description_html: |
       <h3>Et godt sted for regler</h3>
       <p>Den udvidede beskrivelse er endnu ikke blevet opsat.</p>
-    features:
-      humane_approach_body: Ved at lære fra fejl fra andre netværk, sigter Mastodon for at tage etisk designmæssig valg for at bekæmpe misbrug af sociale medier.
-      humane_approach_title: En mere human tilgang
-      not_a_product_body: Mastodon er ikke et kommercielt netværk. Ingen reklamer, ingen datamining, ingen indhegnet haver. Der er ingen central regering.
-      not_a_product_title: Du er en person, ikke et produkt
-      real_conversation_body: Med 500 tegn til din rådighed og understøttelse af granulært indhold og medie advarsler, kan du udtrykke dig på en hvilken som helst måde du ønsker.
-      real_conversation_title: Bygget til rigtige samtaler
-      within_reach_body: Adskillige apps for iOS, Android og andre platforme takket være et udviklervenligt API økosystem tillader dig at holde kontakten med dine venner hvor som helst.
-      within_reach_title: Altid indenfor rækkevidde
     generic_description: "%{domain} er en server i netværket"
     hosted_on: Mostodon hostet på %{domain}
     learn_more: Lær mere
-    other_instances: Liste over instanser
     privacy_policy: Privatlivspolitik
     source_code: Kildekode
     status_count_after:
@@ -371,9 +360,6 @@ da:
         min_invite_role:
           disabled: Ingen
           title: Tillad invitationer af
-        open:
-          desc_html: Tillad alle at oprette en konto
-          title: Åben registrering
       show_known_fediverse_at_about_page:
         desc_html: Når slået til, vil det vise trut fra hele det kendte fedivers på forhåndsvisning. Ellers vil det kun vise lokale trut.
         title: Vis kendte fedivers på tidslinje forhåndsvisning
@@ -457,13 +443,11 @@ da:
     logout: Log ud
     migrate_account: Flyt til en anden konto
     migrate_account_html: Hvis du ønsker at omdirigere denne konto til en anden, kan du <a href="%{path}">gøre det her</a>.
-    or: eller
     or_log_in_with: Eller log in med
     providers:
       cas: CAS
       saml: SAML
     register: Opret dig
-    register_elsewhere: Opret dig på en anden server
     resend_confirmation: Gensend bekræftelses instrukser
     reset_password: Nulstil kodeord
     security: Sikkerhed
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 1dff7156e..ae2948fb5 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -7,7 +7,6 @@ de:
     administered_by: 'Administriert von:'
     api: API
     apps: Mobile Apps
-    closed_registrations: Die Registrierung auf diesem Server ist momentan geschlossen. Aber du kannst dein Konto auch auf einem anderen Server erstellen! Von dort hast du genauso Zugriff auf das Mastodon-Netzwerk.
     contact: Kontakt
     contact_missing: Nicht angegeben
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ de:
     extended_description_html: |
       <h3>Ein guter Platz für Regeln</h3>
       <p>Die erweiterte Beschreibung wurde noch nicht aufgesetzt.</p>
-    features:
-      humane_approach_body: Aus den Fehlern anderer Netzwerke lernend, zielt Mastodon darauf ab, mit ethischen Design-Entscheidungen den Missbrauch sozialer Medien zu verhindern.
-      humane_approach_title: Ein menschlicherer Ansatz
-      not_a_product_body: Mastodon ist kein kommerzielles Netzwerk. Keine Werbung, kein Abgraben deiner Daten, keine geschlossene Plattform. Es gibt keine Zentrale.
-      not_a_product_title: Du bist ein Mensch und keine Ware
-      real_conversation_body: Mit 500 Zeichen pro Beitrag und Features wie Inhalts- und Bilderwarnungen kannst du dich so ausdrücken, wie du es möchtest.
-      real_conversation_title: Geschaffen für echte Gespräche
-      within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben es dir, dank unseres blühenden API-Ökosystems, dich von überall auf dem Laufenden zu halten.
-      within_reach_title: Immer für dich da
     generic_description: "%{domain} ist ein Server im Netzwerk"
     hosted_on: Mastodon, beherbergt auf %{domain}
     learn_more: Mehr erfahren
-    other_instances: Andere Server
     privacy_policy: Datenschutzerklärung
     source_code: Quellcode
     status_count_after:
@@ -410,9 +399,6 @@ de:
         min_invite_role:
           disabled: Niemand
           title: Einladungen erlauben von
-        open:
-          desc_html: Allen erlauben, ein Konto zu erstellen
-          title: Registrierung öffnen
       show_known_fediverse_at_about_page:
         desc_html: Wenn aktiviert, wird es alle Beiträge aus dem bereits bekannten Teil des Fediversums auf der Startseite anzeigen. Andernfalls werden lokale Beitrage der Instanz angezeigt.
         title: Verwende öffentliche Zeitleiste für die Vorschau
@@ -507,13 +493,11 @@ de:
     logout: Abmelden
     migrate_account: Ziehe zu einem anderen Konto um
     migrate_account_html: Wenn du wünschst, dieses Konto zu einem anderen umzuziehen, kannst du <a href="%{path}">dies hier einstellen</a>.
-    or: oder
     or_log_in_with: Oder anmelden mit
     providers:
       cas: CAS
       saml: SAML
     register: Registrieren
-    register_elsewhere: Registrieren auf einem anderen Server
     resend_confirmation: Bestätigungs-Mail erneut versenden
     reset_password: Passwort zurücksetzen
     security: Sicherheit
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 726c0504e..2930733c0 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -12,6 +12,7 @@ en:
       last_attempt: You have one more attempt before your account is locked.
       locked: Your account is locked.
       not_found_in_database: Invalid %{authentication_keys} or password.
+      pending: Your account is still under review.
       timeout: Your session expired. Please sign in again to continue.
       unauthenticated: You need to sign in or sign up before continuing.
       unconfirmed: You have to confirm your email address before continuing.
@@ -20,6 +21,7 @@ en:
         action: Verify email address
         action_with_app: Confirm and return to %{app}
         explanation: You have created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email.
+        explanation_when_pending: You applied for an invite to %{host} with this email address. Once you confirm your e-mail address, we will review your application. You can't login until then. If your application is rejected, your data will be removed, so no further action will be required from you. If this wasn't you, please ignore this email.
         extra_html: Please also check out <a href="%{terms_path}">the rules of the server</a> and <a href="%{policy_path}">our terms of service</a>.
         subject: 'Mastodon: Confirmation instructions for %{instance}'
         title: Verify email address
@@ -60,6 +62,7 @@ en:
       signed_up: Welcome! You have signed up successfully.
       signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated.
       signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked.
+      signed_up_but_pending: A message with a confirmation link has been sent to your email address. After you click the link, we will review your application. You will be notified if it is approved.
       signed_up_but_unconfirmed: A message with a confirmation link has been sent to your email address. Please follow the link to activate your account. Please check your spam folder if you didn't receive this email.
       update_needs_confirmation: You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address. Please check your spam folder if you didn't receive this email.
       updated: Your account has been updated successfully.
diff --git a/config/locales/el.yml b/config/locales/el.yml
index 35da50af7..f5a2c5d4b 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -7,7 +7,6 @@ el:
     administered_by: 'Διαχειριστής:'
     api: API
     apps: Εφαρμογές κινητών
-    closed_registrations: Αυτή τη στιγμή οι εγγραφές σε αυτό τον κόμβο είναι κλειστές. Αλλά! Μπορείς να βρεις έναν άλλο κόμβο για να ανοίξεις λογαριασμό και να έχεις πρόσβαση από εκεί στο ίδιο ακριβώς δίκτυο.
     contact: Επικοινωνία
     contact_missing: Δεν έχει οριστεί
     contact_unavailable: Μ/Δ
@@ -15,19 +14,9 @@ el:
     extended_description_html: |
       <h3>Ένα καλό σημείο για κανόνες</h3>
       <p>Η αναλυτική περιγραφή δεν έχει ακόμα οριστεί</p>
-    features:
-      humane_approach_body: Μαθαίνοντας από τις αποτυχίες άλλων δικτύων, το Mastodon στοχεύει να κάνει σχεδιαστικά ηθικές επιλογές για να καταπολεμήσει την κακόβουλη χρήση των κοινωνικών δικτύων.
-      humane_approach_title: Μια πιο ανθρώπινη προσέγγιση
-      not_a_product_body: Το Mastodon δεν είναι ένα εμπορικό δίκτυο. Δεν έχει διαφημίσεις, δεν έχει εξόρυξη δεδομένων, δεν έχει περιφραγμένους κήπους. Δεν υπάρχει κεντρικό σημείο ελέγχου.
-      not_a_product_title: Είσαι άνθρωπος, όχι προϊόν
-      real_conversation_body: Με 500 χαρακτήρες στη διάθεσή σου και υποστήριξη για λεπτομερή έλεγχο και προειδοποιήσεις πολυμέσων, μπορείς να εκφραστείς με τον τρόπο που θέλεις.
-      real_conversation_title: Φτιαγμένο για αληθινή συζήτηση
-      within_reach_body: Οι πολλαπλές εφαρμογές για το iOS, το Android και τις υπόλοιπες πλατφόρμες, χάρη σε ένα φιλικό προς τους προγραμματιστές οικοσύστημα API, σου επιτρέπουν να κρατάς επαφή με τους φίλους και τις φίλες σου οπουδήποτε.
-      within_reach_title: Πάντα προσβάσιμο
     generic_description: "%{domain} είναι ένας εξυπηρετητής στο δίκτυο"
     hosted_on: Το Mastodon φιλοξενείται στο %{domain}
     learn_more: Μάθε περισσότερα
-    other_instances: Λίστα κόμβων
     privacy_policy: Πολιτική απορρήτου
     source_code: Πηγαίος κώδικας
     status_count_after:
@@ -411,9 +400,6 @@ el:
         min_invite_role:
           disabled: Κανείς
           title: Επέτρεψε προσκλήσεις από
-        open:
-          desc_html: Επέτρεψε σε οποιονδήποτε να δημιουργήσει λογαριασμό
-          title: Άνοιξε τις εγγραφές
       show_known_fediverse_at_about_page:
         desc_html: Όταν αντιστραφεί, θα δείχνει τα τουτ από όλο το γνωστό fediverse στην προεπισκόπηση. Διαφορετικά θα δείχνει μόνο τοπικά τουτ.
         title: Εμφάνιση του γνωστού fediverse στην προεπισκόπηση ροής
@@ -508,13 +494,11 @@ el:
     logout: Αποσύνδεση
     migrate_account: Μετακόμισε σε διαφορετικό λογαριασμό
     migrate_account_html: Αν θέλεις να ανακατευθύνεις αυτό τον λογαριασμό σε έναν διαφορετικό, μπορείς να το <a href="%{path}">διαμορφώσεις εδώ</a>.
-    or: ή
     or_log_in_with: Ή συνδέσου με
     providers:
       cas: Υπηρεσία Κεντρικής Πιστοποίησης (CAS)
       saml: SAML
     register: Εγγραφή
-    register_elsewhere: Εγγραφή σε διαφορετικό εξυπηρετητή
     resend_confirmation: Στείλε ξανά τις οδηγίες επιβεβαίωσης
     reset_password: Επαναφορά συνθηματικού
     security: Ασφάλεια
diff --git a/config/locales/en.yml b/config/locales/en.yml
index b77387890..5dd9bb28b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -4,36 +4,36 @@ en:
     about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
     about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
     about_this: About
+    active_count_after: active
+    active_footnote: Monthly Active Users (MAU)
     administered_by: 'Administered by:'
     api: API
     apps: Mobile apps
-    closed_registrations: Registrations are currently closed on this server. However! You can find a different server to make an account on and get access to the very same network from there.
+    apps_platforms: Use Mastodon from iOS, Android and other platforms
+    browse_directory: Browse a profile directory and filter by interests
+    browse_public_posts: Browse a live stream of public posts on Mastodon
     contact: Contact
     contact_missing: Not set
     contact_unavailable: N/A
+    discover_users: Discover users
     documentation: Documentation
     extended_description_html: |
       <h3>A good place for rules</h3>
       <p>The extended description has not been set up yet.</p>
-    features:
-      humane_approach_body: Learning from failures of other networks, Mastodon aims to make ethical design choices to combat the misuse of social media.
-      humane_approach_title: A more humane approach
-      not_a_product_body: Mastodon is not a commercial network. No advertising, no data mining, no walled gardens. There is no central authority.
-      not_a_product_title: You’re a person, not a product
-      real_conversation_body: With 500 characters at your disposal and support for granular content and media warnings, you can express yourself the way you want to.
-      real_conversation_title: Built for real conversation
-      within_reach_body: Multiple apps for iOS, Android, and other platforms thanks to a developer-friendly API ecosystem allow you to keep up with your friends anywhere.
-      within_reach_title: Always within reach
+    federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
     generic_description: "%{domain} is one server in the network"
+    get_apps: Try a mobile app
     hosted_on: Mastodon hosted on %{domain}
     learn_more: Learn more
-    other_instances: Server list
     privacy_policy: Privacy policy
+    see_whats_happening: See what's happening
+    server_stats: 'Server stats:'
     source_code: Source code
     status_count_after:
       one: status
       other: statuses
     status_count_before: Who authored
+    tagline: Follow friends and discover new ones
     terms: Terms of service
     user_count_after:
       one: user
@@ -79,6 +79,7 @@ en:
       delete: Delete
       destroyed_msg: Moderation note successfully destroyed!
     accounts:
+      approve: Approve
       are_you_sure: Are you sure?
       avatar: Avatar
       by_domain: Domain
@@ -124,6 +125,7 @@ en:
       moderation:
         active: Active
         all: All
+        pending: Pending
         silenced: Silenced
         suspended: Suspended
         title: Moderation
@@ -133,6 +135,7 @@ en:
       no_limits_imposed: No limits imposed
       not_subscribed: Not subscribed
       outbox_url: Outbox URL
+      pending: Pending review
       perform_full_suspension: Suspend
       profile_url: Profile URL
       promote: Promote
@@ -140,6 +143,7 @@ en:
       public: Public
       push_subscription_expires: PuSH subscription expires
       redownload: Refresh profile
+      reject: Reject
       remove_avatar: Remove avatar
       remove_header: Remove header
       resend_confirmation:
@@ -414,9 +418,12 @@ en:
         min_invite_role:
           disabled: No one
           title: Allow invitations by
-        open:
-          desc_html: Allow anyone to create an account
-          title: Open registration
+      registrations_mode:
+        modes:
+          approved: Approval required for sign up
+          none: Nobody can sign up
+          open: Anyone can sign up
+        title: Registrations mode
       show_known_fediverse_at_about_page:
         desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
         title: Show known fediverse on timeline preview
@@ -479,6 +486,9 @@ en:
       edit_preset: Edit warning preset
       title: Manage warning presets
   admin_mailer:
+    new_pending_account:
+      body: The details of the new account are below. You can approve or reject this application.
+      subject: New account up for review on %{instance} (%{username})
     new_report:
       body: "%{reporter} has reported %{target}"
       body_remote: Someone from %{domain} has reported %{target}
@@ -500,7 +510,9 @@ en:
     your_token: Your access token
   auth:
     agreement_html: By clicking "Sign up" below you agree to follow <a href="%{rules_path}">the rules of the server</a> and <a href="%{terms_path}">our terms of service</a>.
+    apply_for_account: Request an invite
     change_password: Password
+    checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a>
     confirm_email: Confirm email
     delete_account: Delete account
     delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
@@ -511,17 +523,17 @@ en:
     logout: Logout
     migrate_account: Move to a different account
     migrate_account_html: If you wish to redirect this account to a different one, you can <a href="%{path}">configure it here</a>.
-    or: or
     or_log_in_with: Or log in with
     providers:
       cas: CAS
       saml: SAML
     register: Sign up
-    register_elsewhere: Sign up on another server
+    registration_closed: "%{instance} is not accepting new members"
     resend_confirmation: Resend confirmation instructions
     reset_password: Reset password
     security: Security
     set_new_password: Set new password
+    trouble_logging_in: Trouble logging in?
   authorize_follow:
     already_following: You are already following this account
     error: Unfortunately, there was an error looking up the remote account
diff --git a/config/locales/eo.yml b/config/locales/eo.yml
index 4e404d9eb..967396326 100644
--- a/config/locales/eo.yml
+++ b/config/locales/eo.yml
@@ -7,7 +7,6 @@ eo:
     administered_by: 'Administrata de:'
     api: API
     apps: Poŝtelefonaj aplikaĵoj
-    closed_registrations: Registriĝoj estas nuntempe fermitaj en ĉi tiu servilo. Tamen, vi povas trovi alian servilon por fari konton kaj aliri al la sama reto de tie.
     contact: Kontakti
     contact_missing: Ne elektita
     contact_unavailable: Ne disponebla
@@ -15,19 +14,9 @@ eo:
     extended_description_html: |
       <h3>Bona loko por reguloj</h3>
       <p>La detala priskribo ne estis elektita.</p>
-    features:
-      humane_approach_body: Lernante de eraroj de aliaj retoj, Mastodon celas fari etikajn fasonajn elektojn por batali kontraŭ misuzado de sociaj retoj.
-      humane_approach_title: Aliro pli humana
-      not_a_product_body: Mastodon ne estas komerca reto. Neniu reklamo, neniu kolektado de datumoj, neniu privilegio. Ne estas centra aŭtoritato.
-      not_a_product_title: Vi estas homo, ne produkto
-      real_conversation_body: Per 500 disponeblaj signoj, per elektebloj pri videbleco, kaj per avertoj pri enhavo, vi povas esprimi vin tiel, kiel vi volas.
-      real_conversation_title: Konstruita por veraj konversacioj
-      within_reach_body: Pluraj aplikaĵoj por iOS, Android, kaj aliaj platformoj danke al API-medio bonveniga por programistoj permesas resti en kontakto kun viaj amikoj ĉie.
-      within_reach_title: Ĉiam kontaktebla
     generic_description: "%{domain} estas unu servilo en la reto"
     hosted_on: "%{domain} estas nodo de Mastodon"
     learn_more: Lerni pli
-    other_instances: Listo de serviloj
     privacy_policy: Privateca politiko
     source_code: Fontkodo
     status_count_after:
@@ -411,9 +400,6 @@ eo:
         min_invite_role:
           disabled: Neniu
           title: Permesi invitojn de
-        open:
-          desc_html: Permesi iun ajn krei konton
-          title: Malfermi registriĝojn
       show_known_fediverse_at_about_page:
         desc_html: Kiam ŝaltita, ĝi montros mesaĝojn de la tuta konata fediverse antaŭvide. Aliokaze, ĝi montros nur lokajn mesaĝojn.
         title: Montri konatan fediverse en tempolinia antaŭvido
@@ -508,13 +494,11 @@ eo:
     logout: Elsaluti
     migrate_account: Movi al alia konto
     migrate_account_html: Se vi deziras alidirekti ĉi tiun konton al alia, vi povas <a href="%{path}">agordi ĝin ĉi tie</a>.
-    or: aŭ
     or_log_in_with: Aŭ ensaluti per
     providers:
       cas: CAS
       saml: SAML
     register: Registriĝi
-    register_elsewhere: Registriĝi en alia servilo
     resend_confirmation: Resendi la instrukciojn por konfirmi
     reset_password: Ŝanĝi pasvorton
     security: Sekureco
diff --git a/config/locales/es.yml b/config/locales/es.yml
index b7be2b365..c203517b2 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -7,7 +7,6 @@ es:
     administered_by: 'Administrado por:'
     api: API
     apps: Aplicaciones móviles
-    closed_registrations: Los registros están actualmente cerrados en este servidor. Aun así, puedes encontrar un servidor diferente para registrarte y tener acceso a la misma comunidad
     contact: Contacto
     contact_missing: No especificado
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ es:
     extended_description_html: |
       <h3>Un buen lugar para las reglas</h3>
       <p>La descripción extendida no se ha colocado aún.</p>
-    features:
-      humane_approach_body: Aprendiendo de los errores de otras redes, Mastodon apunta a las decisiones de diseño ético para combatir el desuso de las redes sociales.
-      humane_approach_title: Una misión más humana
-      not_a_product_body: Mastodon no es una red comercial. Nada de publicidad, nada de minado de datos, nada de jardines murados. No hay ninguna autoridad central.
-      not_a_product_title: Eres una persona, no un producto
-      real_conversation_body: Con 500 caracteres a tu disposición y soporte para contenido granular y advertencias de contenido, puedes expresarte como quieras.
-      real_conversation_title: Hecho para verdaderas conversaciones
-      within_reach_body: Aplicaciones múltiples para iOS, Android, y otras plataformas gracias a un ecosistema de APIs amigable al desarrollador para permitirte estar con tus amigos donde sea.
-      within_reach_title: Siempre al alcance
     generic_description: "%{domain} es un servidor en la red"
     hosted_on: Mastodon hosteado en %{domain}
     learn_more: Aprende más
-    other_instances: Otras instancias
     privacy_policy: Política de privacidad
     source_code: Código fuente
     status_count_after:
@@ -377,9 +366,6 @@ es:
         min_invite_role:
           disabled: Nadie
           title: Permitir invitaciones de
-        open:
-          desc_html: Permite a cualquiera a registrar una cuenta
-          title: Registro abierto
       show_known_fediverse_at_about_page:
         desc_html: Cuando esté activado, se mostrarán toots de todo el fediverso conocido en la vista previa. En otro caso, se mostrarán solamente toots locales.
         title: Mostrar fediverso conocido en la vista previa de la historia
@@ -460,13 +446,11 @@ es:
     logout: Cerrar sesión
     migrate_account: Mudarse a otra cuenta
     migrate_account_html: Si deseas redireccionar esta cuenta a otra distinta, puedes <a href="%{path}">configurarlo aquí</a>.
-    or: o
     or_log_in_with: O inicia sesión con
     providers:
       cas: CAS
       saml: SAML
     register: Registrarse
-    register_elsewhere: Registrarse en otro servidor
     resend_confirmation: Volver a enviar el correo de confirmación
     reset_password: Restablecer contraseña
     security: Cambiar contraseña
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index f8eb18279..59cba6287 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -7,7 +7,6 @@ eu:
     administered_by: 'Administratzailea(k):'
     api: APIa
     apps: Aplikazio mugikorrak
-    closed_registrations: Harpidetza itxita dago orain zerbitzari honetan. Hala ere, beste zerbitzari bat aurkitu dezakezu kontua egiteko eta hona ere sarbidea izan.
     contact: Kontaktua
     contact_missing: Ezarri gabe
     contact_unavailable: E/E
@@ -15,19 +14,9 @@ eu:
     extended_description_html: |
       <h3>Arauentzako toki egoki bat</h3>
       <p>Azalpen luzea ez da ezarri oraindik.</p>
-    features:
-      humane_approach_body: Beste sareen akatsetatik ikasiz, Mastodon diseinu erabaki etikoak hartzen saiatzen da gizarte sareen erabilera okerrak borrokatzeko.
-      humane_approach_title: Ikuspuntu humanoago bat
-      not_a_product_body: Mastodon ez da sare komertzial bat. Ez du iragarkirik, eta ditu datuak mehatzen, ez da hormaz babestutako lorategi bat. Ez dago autoritate zentralik.
-      not_a_product_title: Pertsona bat zara, ez produktu bat
-      real_conversation_body: 500 karaktere dituzu eskura, edukia xehetasunez kudeatu daiteke eta multimediari abisuak jarri, adierazi zure burua zure erara.
-      real_conversation_title: Egiazko elkarrizketarako eraikia
-      within_reach_body: iOS, Android eta beste plataformetarako aplikazio ugari, eta garatzaileentzako erabilterraza den API ekosistema bati esker beste plataforma batzuetako lagunekin aritzeko aukera.
-      within_reach_title: Beti eskura
     generic_description: "%{domain} sareko zerbitzari bat da"
     hosted_on: Mastodon %{domain} domeinuan ostatatua
     learn_more: Ikasi gehiago
-    other_instances: Zerbitzarien zerrenda
     privacy_policy: Pribatutasun politika
     source_code: Iturburu kodea
     status_count_after:
@@ -410,9 +399,6 @@ eu:
         min_invite_role:
           disabled: Inor ez
           title: Baimendu hauen gobidapenak
-        open:
-          desc_html: Baimendu edonori kontu bat sortzea
-          title: Ireki izen ematea
       show_known_fediverse_at_about_page:
         desc_html: Txandakatzean, fedibertsu ezagun osoko toot-ak bistaratuko ditu aurrebistan. Bestela, toot lokalak besterik ez ditu erakutsiko.
         title: Erakutsi fedibertsu ezagun osoko denbora-lerroa aurrebistan
@@ -507,13 +493,11 @@ eu:
     logout: Amaitu saioa
     migrate_account: Lekualdatu beste kontu batera
     migrate_account_html: Kontu hau beste batera birbideratu nahi baduzu, <a href="%{path}">hemen konfiguratu</a> dezakezu.
-    or: edo
     or_log_in_with: Edo hasi saioa honekin
     providers:
       cas: CAS
       saml: SAML
     register: Eman izena
-    register_elsewhere: Eman izena beste zerbitzari batean
     resend_confirmation: Birbidali berresteko argibideak
     reset_password: Berrezarri pasahitza
     security: Segurtasuna
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index 4214e793c..a1c891bc7 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -7,7 +7,6 @@ fa:
     administered_by: 'با مدیریت:'
     api: رابط برنامه‌نویسی کاربردی
     apps: اپ‌های موبایل
-    closed_registrations: ثبت‌نام روی این سرور هم‌اینک فعال نیست. اما شما می‌توانید سرور دیگری بیابید و با حسابی که آن‌جا می‌سازید دقیقاً به همین شبکه دسترسی داشته باشید.
     contact: تماس
     contact_missing: تعیین نشده
     contact_unavailable: موجود نیست
@@ -15,19 +14,9 @@ fa:
     extended_description_html: |
       <h3>جای خوبی برای قانون‌ها</h3>
       <p>توضیحات تکمیلی نوشته نشده است.</p>
-    features:
-      humane_approach_body: با آموختن از کاستی‌های شبکه‌های دیگر، ماستدون می‌خواهد به کمک انتخاب‌های اخلاقی‌تر در طراحی خودش با آسیب‌های شبکه‌های اجتماعی مبارزه کند.
-      humane_approach_title: رویکردی انسانی‌تر
-      not_a_product_body: ماستدون یک شبکهٔ تجاری نیست. بدون تبلیغات، بدون داده‌کاوی، بدون حصارکشی. هیچ قدرت مرکزی‌ای وجود ندارد.
-      not_a_product_title: شما یک انسان هستید، نه یک محصول
-      real_conversation_body: با ۵۰۰ نویسه برای هر نوشته و با پشتیبانی از هشدارهای موردی برای نوشته‌ها و تصاویر، می‌توانید خود را همان گونه که می‌خواهید ابراز کنید.
-      real_conversation_title: برای گفتگوهای واقعی
-      within_reach_body: اپ‌های متنوع برای iOS، اندروید، و سیستم‌های دیگر به خاطر وجود یک اکوسیستم API دوستانه برای برنامه‌نویسان. از همه جا با دوستان خود ارتباط داشته باشید.
-      within_reach_title: همیشه در دسترس
     generic_description: "%{domain} یک سرور روی شبکه است"
     hosted_on: ماستدون، میزبانی‌شده روی %{domain}
     learn_more: بیشتر بدانید
-    other_instances: فهرست سرورها
     privacy_policy: سیاست رازداری
     source_code: کدهای منبع
     status_count_after:
@@ -411,9 +400,6 @@ fa:
         min_invite_role:
           disabled: هیچ کس
           title: اجازهٔ دعوت به
-        open:
-          desc_html: همه بتوانند حساب باز کنند
-          title: امکان ثبت نام
       show_known_fediverse_at_about_page:
         desc_html: اگر انتخاب شود، بوق‌های همهٔ سرورهای دیگر نیز در پیش‌نمایش این سرور نمایش می‌یابد. وگرنه فقط بوق‌های محلی نشان داده می‌شوند.
         title: نمایش سرورهای دیگر در پیش‌نمایش این سرور
@@ -508,13 +494,11 @@ fa:
     logout: خروج
     migrate_account: نقل مکان به یک حساب دیگر
     migrate_account_html: اگر می‌خواهید این حساب را به حساب دیگری منتقل کنید، <a href="%{path}">این‌جا را کلیک کنید</a>.
-    or: یا
     or_log_in_with: یا ورود به وسیلهٔ
     providers:
       cas: CAS
       saml: SAML
     register: عضو شوید
-    register_elsewhere: ثبت نام روی یک سرور دیگر
     resend_confirmation: راهنمایی برای تأیید را دوباره بفرست
     reset_password: بازنشانی رمز
     security: امنیت
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index c3c48cbe3..deacd351a 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -7,7 +7,6 @@ fi:
     administered_by: 'Ylläpitäjä:'
     api: API
     apps: Mobiili sovellukset
-    closed_registrations: Tähän instanssiin ei voi tällä hetkellä rekisteröityä. Voit kuitenkin luoda tilin johonkin toiseen instanssiin ja käyttää samaa verkostoa sitä kautta.
     contact: Ota yhteyttä
     contact_missing: Ei asetettu
     contact_unavailable: Ei saatavilla
@@ -15,19 +14,9 @@ fi:
     extended_description_html: |
       <h3>Hyvä paikka säännöille</h3>
       <p>Pidempää kuvausta ei ole vielä laadittu.</p>
-    features:
-      humane_approach_body: Mastodonissa otetaan oppia muiden verkostojen virheistä, ja sen suunnittelussa pyritään toimimaan eettisesti ja ehkäisemään sosiaalisen median väärinkäyttöä.
-      humane_approach_title: Ihmisläheisempi ote
-      not_a_product_body: Mastodon ei ole kaupallinen verkosto. Ei mainoksia, ei tiedonlouhintaa, ei suljettuja protokollia. Mastodonissa ei ole keskusjohtoa.
-      not_a_product_title: Olet henkilö, et tuote
-      real_conversation_body: 'Voit ilmaista itseäsi niin kuin itse haluat: tilaa on 500 merkkiä, ja sisältövaroituksia voi tehdä monin tavoin.'
-      real_conversation_title: Tehty oikeaa keskustelua varten
-      within_reach_body: Rajapintoja on tarjolla moniin eri kehitysympäristöihin, minkä ansiosta iOS:lle, Androidille ja muille alustoille on saatavana useita eri sovelluksia. Näin voit pitää yhteyttä ystäviisi missä vain.
-      within_reach_title: Aina lähellä
     generic_description: "%{domain} on yksi verkostoon kuuluvista palvelimista"
     hosted_on: Mastodon palvelimella %{domain}
     learn_more: Lisätietoja
-    other_instances: Muut palvelimet
     privacy_policy: Tietosuojaseloste
     source_code: Lähdekoodi
     status_count_after:
@@ -317,9 +306,6 @@ fi:
         min_invite_role:
           disabled: Ei kukaan
           title: Salli kutsut käyttäjältä
-        open:
-          desc_html: Salli kenen tahansa luoda tili
-          title: Avoin rekisteröinti
       show_known_fediverse_at_about_page:
         desc_html: Kun tämä on valittu, esikatselussa näytetään tuuttaukset kaikkialta tunnetusta fediversumista. Muutoin näytetään vain paikalliset tuuttaukset.
         title: Näytä aikajanan esikatselussa koko tunnettu fediversumi
@@ -396,13 +382,11 @@ fi:
     logout: Kirjaudu ulos
     migrate_account: Muuta toiseen tiliin
     migrate_account_html: Jos haluat ohjata tämän tilin toiseen tiliin, voit <a href="%{path}">asettaa toisen tilin tästä</a>.
-    or: tai
     or_log_in_with: Tai käytä kirjautumiseen
     providers:
       cas: CAS
       saml: SAML
     register: Rekisteröidy
-    register_elsewhere: Rekisteröidy toiselle palvelimelle
     resend_confirmation: Lähetä vahvistusohjeet uudestaan
     reset_password: Palauta salasana
     security: Tunnukset
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index b9d813e9e..1694fda82 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -7,7 +7,6 @@ fr:
     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.
     contact: Contact
     contact_missing: Manquant
     contact_unavailable: Non disponible
@@ -15,19 +14,9 @@ fr:
     extended_description_html: |
       <h3>Un bon endroit pour les règles</h3>
       <p>La description étendue n’a pas été remplie.</p>
-    features:
-      humane_approach_body: Ayant appris des échecs d’autres réseaux, Mastodon à l’ambition de combattre l’abus des médias sociaux en effectuant des choix de conception éthiques.
-      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è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
     generic_description: "%{domain} est seulement un serveur du réseau"
     hosted_on: Instance Mastodon hébergée par %{domain}
     learn_more: En savoir plus
-    other_instances: Liste des instances
     privacy_policy: Politique de vie privée
     source_code: Code source
     status_count_after:
@@ -411,9 +400,6 @@ fr:
         min_invite_role:
           disabled: Personne
           title: Autoriser les invitations par
-        open:
-          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. Sinon, seuls les pouets locaux sont affichés.
         title: Afficher le fediverse connu dans la prévisualisation du fil
@@ -508,13 +494,11 @@ fr:
     logout: Se déconnecter
     migrate_account: Déplacer vers un compte différent
     migrate_account_html: Si vous voulez rediriger ce compte vers un autre, vous pouvez le <a href="%{path}">configurer ici</a>.
-    or: ou
     or_log_in_with: Ou authentifiez-vous avec
     providers:
       cas: CAS
       saml: SAML
     register: S’inscrire
-    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é
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 2435137f9..249128426 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -7,7 +7,6 @@ gl:
     administered_by: 'Administrada por:'
     api: API
     apps: Apps móbiles
-    closed_registrations: O rexistro en este servidor está pechado neste momento. Porén! Pode atopar un servidor diferente para obter unha conta e ter acceso exactamente a misma rede desde alí.
     contact: Contacto
     contact_missing: Non establecido
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ gl:
     extended_description_html: |
       <h3>Un bo lugar para regras</h3>
       <p>A descrición extendida aínda non se proporcionou.</p>
-    features:
-      humane_approach_body: Aprendendo dos erros de outras redes, Mastodon intenta tomar decisións éticas de deseño para loitar contra os usos incorrectos da rede.
-      humane_approach_title: Unha aproximación máis humana
-      not_a_product_body: Mastodon non é unha rede comercial. Sen anuncios, sen minería de datos, sen xardíns privados. Non hai autoridade centralizada.
-      not_a_product_title: Vostede é unha persoa, non un producto
-      real_conversation_body: Con 500 caracteres a súa disposición, soporte para contido polo miúdo e avisos sobre o contido, pode expresarse vostede con libertade.
-      real_conversation_title: Construído para conversacións reais
-      within_reach_body: Existen múltiples aplicacións para iOS, Android e outras plataformas grazas a un entorno API amigable para o desenvolvedor que lle permite estar ao tanto cos seus amigos en calquer lugar.
-      within_reach_title: Sempre en contacto
     generic_description: "%{domain} é un servidor na rede"
     hosted_on: Mastodon aloxado en %{domain}
     learn_more: Coñeza máis
-    other_instances: Lista de servidores
     privacy_policy: Política de intimidade
     source_code: Código fonte
     status_count_after:
@@ -411,9 +400,6 @@ gl:
         min_invite_role:
           disabled: Ninguén
           title: Permitir convites por
-        open:
-          desc_html: Permitir que calquera poida crear unha conta
-          title: Abrir rexistro
       show_known_fediverse_at_about_page:
         desc_html: Si activado, mostraralle os toots de todo o fediverso coñecido nunha vista previa. Si non só mostrará os toots locais.
         title: Mostrar vista previa do fediverso na liña temporal
@@ -508,13 +494,11 @@ gl:
     logout: Desconectar
     migrate_account: Mover a unha conta diferente
     migrate_account_html: Se desexa redirixir esta conta hacia outra diferente, pode <a href="%{path}">configuralo aquí</a>.
-    or: ou
     or_log_in_with: ou conectar con
     providers:
       cas: CAS
       saml: SAML
     register: Rexistro
-    register_elsewhere: Rexístrese en outro servidor
     resend_confirmation: Voltar a enviar intruccións de confirmación
     reset_password: Restablecer contrasinal
     security: Seguridade
diff --git a/config/locales/he.yml b/config/locales/he.yml
index bc92ed908..1ddb1361d 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -6,7 +6,6 @@ he:
     about_this: אודות שרת זה
     api: API
     apps: יישומונים לנייד
-    closed_registrations: הרשמות סגורות לשרת זה לעת עתה.
     contact: יצירת קשר
     contact_missing: ללא הגדרה
     contact_unavailable: לא רלוונטי/חסר
@@ -14,19 +13,9 @@ he:
     extended_description_html: |
       <h3>מקום טוב לכללים</h3>
       <p>התיאור המורחב טרם הוגדר.</p>
-    features:
-      humane_approach_body: מתוך למידה מכשלים של רשתות אחרות, מסטודון מכוונת להחלטות תכנוניות אתיות שנאבקות בשימוש לרעה של מדיה חברתית.
-      humane_approach_title: גישה הומאנית יותר
-      not_a_product_body: מסטודון איננה רשת מסחרית. אין פרסום, אין כריית מידע, אין גנים סגורים. אין סמכות מרכזית.
-      not_a_product_title: את(ה) אדם, לא מוצר
-      real_conversation_body: עם 500 תווים לרשותך, ואפשרויות פרטניות לאזהרות תוכן והסתרת מדיה, יש לך את החופש להתבטא כרצונך.
-      real_conversation_title: בנוי לשיחות אמתיות
-      within_reach_body: שלל אפליקציות עבור iOS, אנדרואיד ופלטפורמות אחרות שיאפשרו לך לשמור על קשר עם חברים בכל מקום, תודות למערכת מנשקי תוכנה ידידותיים למפתחים.
-      within_reach_title: תמיד במרחק נגיעה
     generic_description: "%{domain} הוא שרת אחד בתוך הרשת"
     hosted_on: מסטודון שיושב בכתובת %{domain}
     learn_more: מידע נוסף
-    other_instances: שרתים אחרים
     source_code: קוד מקור
     status_count_after: הודעות
     status_count_before: שכתבו
@@ -188,8 +177,6 @@ he:
         closed_message:
           desc_html: מוצג על הדף הראשי כאשר ההרשמות סגורות<br>ניתן להשתמש בתגיות HTML
           title: מסר סגירת הרשמות
-        open:
-          title: הרשמה פתוחה
       site_description:
         desc_html: מוצג כפסקה על הדף הראשי ומשמש כתגית מטא. ניתן להשתמש בתגיות HTML, ובמיוחד ב־<code> &lt; a&gt; </code> ו־<code> &lt; em&gt; </code> .
         title: תיאור האתר
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index 38971833c..f53515d7a 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -3,9 +3,7 @@ hr:
   about:
     about_mastodon_html: Mastodon je <em>besplatna, open-source</em> socijalna mreža. <em>Decentralizirana</em> alternativa komercijalnim platformama, izbjegava rizik toga da jedna tvrtka monopolizira vašu komunikaciju. Izaberite server kojem ćete vjerovati &mdash; koji god odabrali, moći ćete komunicirati sa svima ostalima. Bilo tko može imati svoju vlastitu Mastodon instancu i sudjelovati u <em>socijalnoj mreži</em> bez problema.
     about_this: O ovoj instanci
-    closed_registrations: Registracije na ovoj instanci su trenutno zatvorene.
     contact: Kontakt
-    other_instances: Druge instance
     source_code: Izvorni kod
     status_count_after: statusi
     status_count_before: Tko je autor
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 79363b9ee..44399778c 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -4,26 +4,15 @@ hu:
     about_hashtag_html: Ezek a <strong>#%{hashtag}</strong> címkével ellátott publikus tülkök. Reagálhatsz rájuk, ha már van felhasználói fiókod valahol a föderációban.
     about_mastodon_html: Mastodon egy <em>szabad, nyílt forráskódú</em> szociális hálózati kiszolgálo. Egy <em>központosítatlan</em> alternatíva a kereskedelmi platformokra, elkerüli a kommunikációd monopolizációját veszélyét. Bárki futtathatja a Mastodon-t és részt vehet a <em>szociális hálózatban</em>.
     about_this: Rólunk
-    closed_registrations: A regisztráció jelenleg nem engedélyezett ezen az instancián. De ne csüggedj! Létrehozhatsz fiókot egy másik instancián és azon keresztül is hozzáférsz a teljes föderációhoz.
     contact: Kapcsolat
     contact_missing: Nincs megadva
     contact_unavailable: N/A
     extended_description_html: |
       <h3>Ez itt a szabályzat helye</h3>
       <p>Még nem állítottál be bővebb leírást.</p>
-    features:
-      humane_approach_body: Más alkalmazások hibáiból tanulva a Mastodon etikus alapokon nyugvó döntésekkel küzd a közösségi média ártalmai ellen.
-      humane_approach_title: Emberséges attitűd
-      not_a_product_body: A Mastodon nem a profitszerzésre épül, nem is privát játszótér. Nincsenek reklámok, nincs adatbányászat és központosított döntéshozatal sincsen.
-      not_a_product_title: Ember vagy, nem pedig árucikk
-      real_conversation_body: Az 500 karakteres limit, az érzékeny tartalomként jelölés és más kifinomult eszközök segítségével tényleg egyedi módon fejezheted ki önmagad.
-      real_conversation_title: Valódi beszélgetésekre tervezve
-      within_reach_body: A fejlesztőbarát API-nak köszönhetően számos iOS, Android és egyéb platformra írt alkalmazás teszi lehetővé, hogy bármikor, bárhonnan részt vehess a társalgásban.
-      within_reach_title: Mindig elérhetőnek lenni
     generic_description: "%{domain} csak egy a számtalan szerver közül a föderációban"
     hosted_on: "%{domain} Mastodon instancia"
     learn_more: Tudj meg többet
-    other_instances: Instanciák listája
     source_code: Forráskód
     status_count_after: tülköt küldött
     status_count_before: eddig
@@ -260,9 +249,6 @@ hu:
         min_invite_role:
           disabled: Senkinek
           title: Meghívások engedélyezése
-        open:
-          desc_html: Bárki létrehozhat felhasználói fiókot
-          title: Nyitott regisztráció
       show_staff_badge:
         desc_html: Stáb-jelvény megjelenítése a felhasználó oldalán
         title: Stáb-jelvény megjelenítése
diff --git a/config/locales/id.yml b/config/locales/id.yml
index d155ab0a7..a91f459a4 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -7,7 +7,6 @@ id:
     administered_by: 'Dikelola oleh:'
     api: API
     apps: Aplikasi hp
-    closed_registrations: Pendaftaran untuk server ini sedang ditutup. Tetapi, anda bisa mencari server lain untuk membuat akun dan mendapatkan akses dari jaringan yang sama di sana.
     contact: Kontak
     contact_missing: Belum diset
     contact_unavailable: Tidak Tersedia
@@ -15,19 +14,9 @@ id:
     extended_description_html: |
       <h3>Tempat yang baik untuk peraturan</h3>
       <p>Deskripsi lainnya belum diset.</p>
-    features:
-      humane_approach_body: Belajar dari kegagalan jaringan lain, Mastodon berupaya untuk membuat pilihan desain yang etis untuk melawan penyalahgunaan media sosial.
-      humane_approach_title: Pendekatan yang lebih manusiawi
-      not_a_product_body: Mastodon bukanlah jaringan komersil. Tidak ada iklan, tidak ada pengumpulan data, tidak ada batasan vendor. Tidak ada otoritas terpusat.
-      not_a_product_title: Anda adalah orang, bukanlah sebuah produk
-      real_conversation_body: Dengan 500 karakter dan dukungan konten granular dan peringatan media, anda dapat mengekspresikan diri anda sendiri sesuai yang anda mau.
-      real_conversation_title: Dibangun untuk percakapan yang sebenarnya
-      within_reach_body: Berbagai aplikasi untuk iOS, Android, dan platform lainnya berkat ekosistem API yang ramah pada pengembang untuk tetap terhubung dengan teman-teman anda dimanapun.
-      within_reach_title: Selalu dalam jangkauan
     generic_description: "%{domain} adalah satu server dalam jaringan"
     hosted_on: Mastodon dihosting di %{domain}
     learn_more: Pelajari selengkapnya
-    other_instances: Daftar Server
     privacy_policy: Kebijakan Privasi
     source_code: Kode sumber
     status_count_after:
@@ -215,8 +204,6 @@ id:
         closed_message:
           desc_html: Ditampilkan pada halaman depan saat pendaftaran ditutup<br>Anda bisa menggunakan tag HTML
           title: Pesan penutupan pendaftaran
-        open:
-          title: Pendaftaran terbuka
       site_description:
         desc_html: Ditampilkan sebagai sebuah paragraf di halaman depan dan digunakan sebagai tag meta.<br>Anda bisa menggunakan tag HTML, khususnya <code>&lt;a&gt;</code> dan <code>&lt;em&gt;</code>.
         title: Deskripsi situs
diff --git a/config/locales/io.yml b/config/locales/io.yml
index 73c981a98..b926fe641 100644
--- a/config/locales/io.yml
+++ b/config/locales/io.yml
@@ -3,9 +3,7 @@ io:
   about:
     about_mastodon_html: Mastodon esas <em>gratuita, apertitkodexa</em> sociala reto. Ol esas <em>sencentra</em> altra alternativo a komercala servadi. Ol evitigas, ke sola firmo guvernez tua tota komunikadol. Selektez servero, quan tu fidas. Irge qua esas tua selekto, tu povas komunikar kun omna altra uzeri. Irgu povas krear sua propra instaluro di Mastodon en sua servero, e partoprenar en la <em>sociala reto</em> tote glate.
     about_this: Pri ta instaluro
-    closed_registrations: Membresko ne nun esas posible en ta instaluro.
     contact: Kontaktar
-    other_instances: Altra instaluri
     source_code: Fontkodexo
     status_count_after: mesaji
     status_count_before: Qua publikigis
@@ -106,8 +104,6 @@ io:
         closed_message:
           desc_html: Displayed on frontpage when registrations are closed<br>You can use HTML tags
           title: Closed registration message
-        open:
-          title: Open registration
       site_description:
         desc_html: Displayed as a paragraph on the frontpage and used as a meta tag.<br>You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>.
         title: Site description
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 76fcb2b91..1af8bc08c 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -7,7 +7,6 @@ it:
     administered_by: 'Amministrato da:'
     api: API
     apps: Applicazioni Mobile
-    closed_registrations: Al momento le iscrizioni a questo server sono chiuse. Tuttavia! Puoi provare a cercare un server diverso su cui creare un account ed avere accesso alla stessa identica rete.
     contact: Contatti
     contact_missing: Non impostato
     contact_unavailable: N/D
@@ -15,19 +14,9 @@ it:
     extended_description_html: |
       <h3>Un buon posto per le regole</h3>
       <p>La descrizione estesa non è ancora stata preparata.</p>
-    features:
-      humane_approach_body: Imparando dai fallimenti degli altri networks, Mastodon mira a fare scelte etiche di design per combattere l'abuso dei social media.
-      humane_approach_title: Un approccio più umano
-      not_a_product_body: Mastodon non è una rete commerciale. Niente pubblicità, niente data mining, nessun recinto dorato. Non c'è nessuna autorità centrale.
-      not_a_product_title: Tu sei una persona, non un prodotto
-      real_conversation_body: Con 500 caratteri a disposizione, un supporto per i contenuti granulari ed avvisi sui media potrai esprimerti nel modo desiderato.
-      real_conversation_title: Creato per conversazioni reali
-      within_reach_body: Apps per iOS, Android ed altre piattaforme, realizzate grazie ad un ecosistema di API adatto agli sviluppatori, ti consentono di poter stare in contatto con i tuoi amici ovunque ti trovi.
-      within_reach_title: Sempre a portata di mano
     generic_description: "%{domain} è un server nella rete"
     hosted_on: Mastodon ospitato su %{domain}
     learn_more: Scopri altro
-    other_instances: Elenco server
     privacy_policy: Politica della privacy
     source_code: Codice sorgente
     status_count_after:
@@ -400,9 +389,6 @@ it:
         min_invite_role:
           disabled: Nessuno
           title: Permetti inviti da
-        open:
-          desc_html: Consenti a chiunque di creare un account
-          title: Apri registrazioni
       show_known_fediverse_at_about_page:
         desc_html: Quando attivato, mostra nell'anteprima i toot da tutte le istanze conosciute. Altrimenti mostra solo i toot locali.
         title: Mostra la fediverse conosciuta nell'anteprima della timeline
@@ -484,10 +470,8 @@ it:
     logout: Esci da Mastodon
     migrate_account: Sposta ad un account differente
     migrate_account_html: Se vuoi che questo account sia reindirizzato a uno diverso, puoi <a href="%{path}">configurarlo qui</a>.
-    or: o
     or_log_in_with: Oppure accedi con
     register: Iscriviti
-    register_elsewhere: Iscriviti su un altro server
     resend_confirmation: Invia di nuovo le istruzioni di conferma
     reset_password: Resetta la password
     security: Credenziali
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 5e5b3ae36..ace17a987 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -4,36 +4,36 @@ ja:
     about_hashtag_html: ハッシュタグ <strong>#%{hashtag}</strong> の付いた公開トゥートです。どこでもいいので、連合に参加しているSNS上にアカウントを作れば会話に参加することができます。
     about_mastodon_html: Mastodon は、オープンなウェブプロトコルを採用した、自由でオープンソースなソーシャルネットワークです。電子メールのような分散型の仕組みを採っています。
     about_this: 詳細情報
+    active_count_after: 人アクティブ
+    active_footnote: 月間アクティブユーザー数 (MAU)
     administered_by: '管理者:'
     api: API
     apps: アプリ
-    closed_registrations: 現在このサーバーでの新規登録は受け付けていません。しかし、他のサーバーにアカウントを作成しても全く同じネットワークに参加することができます。
+    apps_platforms: iOSやAndroid、その他プラットフォームから使用する
+    browse_directory: ディレクトリで関心を軸に見つける
+    browse_public_posts: Mastodonの公開ライブストリームを見てみる
     contact: 連絡先
     contact_missing: 未設定
     contact_unavailable: N/A
+    discover_users: ユーザーを見つける
     documentation: ドキュメント
     extended_description_html: |
       <h3>ルールを書くのに適した場所</h3>
       <p>詳細説明が設定されていません。</p>
-    features:
-      humane_approach_body: 他の SNS の失敗から学び、Mastodon はソーシャルメディアが誤った使い方をされることの無いように倫理的な設計を目指しています。
-      humane_approach_title: より思いやりのある設計
-      not_a_product_body: Mastodon は営利的な SNS ではありません。広告や、データの収集・解析によるターゲティングは無く、またユーザーの囲い込みもありません。ここには中央権力はありません。
-      not_a_product_title: あなたは人間であり、商品ではありません
-      real_conversation_body: 好きなように書ける500文字までの投稿や、文章やメディアの内容に警告をつけられる機能で、思い通りに自分自身を表現することができます。
-      real_conversation_title: 本当のコミュニケーションのために
-      within_reach_body: デベロッパーフレンドリーな API により実現された、iOS や Android、その他様々なプラットフォームのためのアプリでどこでも友人とやりとりできます。
-      within_reach_title: いつでも身近に
+    federation_hint_html: "%{instance} にアカウントを作ればどこのMastodonや互換性のあるサーバーのユーザーでもフォローできます。"
     generic_description: "%{domain} は、Mastodon サーバーの一つです"
+    get_apps: モバイルアプリを試す
     hosted_on: Mastodon hosted on %{domain}
     learn_more: もっと詳しく
-    other_instances: 他のサーバー
     privacy_policy: プライバシーポリシー
+    see_whats_happening: 何が起きているのか見てみる
+    server_stats: 'サーバー統計:'
     source_code: ソースコード
     status_count_after:
       one: トゥート
       other: トゥート
     status_count_before: トゥート数
+    tagline: Follow friends and discover new ones
     terms: 利用規約
     user_count_after:
       one: 人
@@ -411,9 +411,6 @@ ja:
         min_invite_role:
           disabled: 誰も許可しない
           title: 招待の作成を許可
-        open:
-          desc_html: 誰でも自由にアカウントを作成できるようにします
-          title: 新規登録を受け付ける
       show_known_fediverse_at_about_page:
         desc_html: チェックを入れるとプレビュー欄に既知の連合先全てのトゥートを表示します。外すとローカルのトゥートだけ表示します。
         title: タイムラインプレビューに連合タイムラインを表示する
@@ -498,6 +495,7 @@ ja:
   auth:
     agreement_html: 登録するをクリックすると <a href="%{rules_path}">サーバーのルール</a> と <a href="%{terms_path}">プライバシーポリシー</a> に従うことに同意したことになります。
     change_password: パスワード
+    checkbox_agreement_html: <a href="%{rules_path}" target="_blank">サーバーのルール</a> と <a href="%{terms_path}" target="_blank">プライバシーポリシー</a> に同意します
     confirm_email: メールアドレスの確認
     delete_account: アカウントの削除
     delete_account_html: アカウントを削除したい場合、<a href="%{path}">こちら</a> から手続きが行えます。削除する前に、確認画面があります。
@@ -508,17 +506,17 @@ ja:
     logout: ログアウト
     migrate_account: 別のアカウントに引っ越す
     migrate_account_html: 引っ越し先を明記したい場合は<a href="%{path}">こちら</a>で設定できます。
-    or: または
     or_log_in_with: または次のサービスでログイン
     providers:
       cas: CAS
       saml: SAML
     register: 登録する
-    register_elsewhere: 他のサーバーで新規登録
+    registration_closed: "%{instance} は現在新しいメンバーを受け入れていません"
     resend_confirmation: 確認メールを再送する
     reset_password: パスワードを再発行
     security: セキュリティ
     set_new_password: 新しいパスワード
+    trouble_logging_in: ログインできませんか?
   authorize_follow:
     already_following: あなたは既にこのアカウントをフォローしています
     error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました
@@ -734,6 +732,16 @@ ja:
     older: 以前のトゥート
     prev: 前
     truncate: "&hellip;"
+  polls:
+    errors:
+      already_voted: You have already voted on this poll
+      duplicate_options: contain duplicate items
+      duration_too_long: is too far into the future
+      duration_too_short: is too soon
+      expired: The poll has already ended
+      over_character_limit: cannot be longer than %{max} characters each
+      too_few_options: must have more than one item
+      too_many_options: can't contain more than %{max} items
   preferences:
     languages: 言語
     other: その他
@@ -844,6 +852,11 @@ ja:
       ownership: 他人のトゥートを固定することはできません
       private: 非公開のトゥートを固定することはできません
       reblog: ブーストされたトゥートを固定することはできません
+    poll:
+      total_votes:
+        one: "%{count} vote"
+        other: "%{count} votes"
+      vote: Vote
     show_more: もっと見る
     sign_in_to_participate: ログインして会話に参加
     title: '%{name}: "%{quote}"'
diff --git a/config/locales/ka.yml b/config/locales/ka.yml
index 056942ecd..5d0bba510 100644
--- a/config/locales/ka.yml
+++ b/config/locales/ka.yml
@@ -7,7 +7,6 @@ ka:
     administered_by: 'ადმინისტრატორი:'
     api: აპი
     apps: მობილური აპლიკაციები
-    closed_registrations: რეგისტრაციები ამჟამად ინსტანციაზე დახურულია. თუმცა! ანგარიშის შესაქმნელად შეგიძლიათ იპოვოთ სხვა ინსტანცია და იმავე ქსელზე იქონიოთ წვდომა იქიდან.
     contact: კონტაქტი
     contact_missing: არაა დაყენებული
     contact_unavailable: მიუწ.
@@ -15,19 +14,9 @@ ka:
     extended_description_html: |
       <h3>კარგი ადგილი წესებისთვის</h3>
       <p>განვრცობილი აღწერილობა ჯერ არ შექმნილა.</p>
-    features:
-      humane_approach_body: სხვა ქსელების შეცდომების გათვალისწინებით, მასტოდონი მიზნად ისახავს ეტიკური დიზაინის არჩევნების გაკეთებას, დაუპირისპირდეს სოციალური მედიის არასწორ მოხმარებას.
-      humane_approach_title: უფრო ადამიანური მიდგომა
-      not_a_product_body: მასტოდონი არ არის კომერციული ქსელი. არაა რეკლამა, არაა მაინინგი, არაა შემოღობილი ბაღები. არაა ცენტრალური ავტორიტეტი.
-      not_a_product_title: შენ ხარ პერსონა და არა პროდუქტი
-      real_conversation_body: 500 ნიშნის განკარგულებით, მარცვლოვანი კონტენტის და მედია გაფრთხილებების მხარდაჭერით, შეგიძლიათ გამოხატოთ ისე როგორც გსურთ.
-      real_conversation_title: შექმნილია ნამდვილი საუბრისთვის
-      within_reach_body: დეველოპერისთვის-მეგობრული აპი ექოსისტემის წყალობით, მრავალი აპლიკაცია აი-ოსისთვის, ანდროიდისთვის და სხვა პლატფორმებისთვის, საშალებას მოგცემთ ნებისმიერი ადგილიდან იქონიოთ კავშირი თქვენს მეგობრებთან.
-      within_reach_title: მუდამ წვდომის ქვეშ
     generic_description: "%{domain} ერთი სერვერია ქსელში"
     hosted_on: მასტოდონს მასპინძლობს %{domain}
     learn_more: გაიგე მეტი
-    other_instances: ინსტანციების სია
     privacy_policy: კონფიდენციალურობის პოლიტიკა
     source_code: კოდი
     status_count_after: სტატუსები
@@ -344,9 +333,6 @@ ka:
         min_invite_role:
           disabled: არავინ
           title: ნება დაერთოს მოწვეევებს
-        open:
-          desc_html: უფლება მიეცით ყველას, გახსნან ანგარიში
-          title: ღია რეგისტრაცია
       show_known_fediverse_at_about_page:
         desc_html: ჩართვისას, ეს გამოაჩენს ტუტებს ყველა ცნობილი ფედივერსისგან პრევიუზე. სხვა შემთხვევაში, გამოაჩენს მხოლოდ ლოკალურ ტუტებს.
         title: გამოჩნდეს ცნობილი ვედივერსი თაიმლაინ პრევიუში
@@ -427,13 +413,11 @@ ka:
     logout: გასვლა
     migrate_account: სხვა ანგარიშზე გადასვლა
     migrate_account_html: თუ გსურთ ამ ანგარიშის რედირექტის ხვაზე, შეგიძლიათ <a href="%{path}">გაუწიოთ კონფიგურაცია აქ</a>.
-    or: ან
     or_log_in_with: ან გამოიყენეთ
     providers:
       cas: ქეს
       saml: სამლ
     register: რეგისტრაცია
-    register_elsewhere: რეგისტრაცია სხვა სერვერზე
     resend_confirmation: დამოწმების ინსტრუქციების ხელახალი გამოგზავნა
     reset_password: პაროლის გადატვირთვა
     security: უსაფრთხოება
diff --git a/config/locales/kk.yml b/config/locales/kk.yml
index 1c40adeb7..4897bc095 100644
--- a/config/locales/kk.yml
+++ b/config/locales/kk.yml
@@ -7,7 +7,6 @@ kk:
     administered_by: 'Админ:'
     api: API
     apps: Мобиль қосымшалар
-    closed_registrations: Бұл серверде тіркелу уақытша тоқтатылған. Дегенмен, сіз басқа сервер арқылы тіркеліп, сол аккаунтыңызбен қолдана берсеңіз болады.
     contact: Байланыс
     contact_missing: Бапталмаған
     contact_unavailable: Белгісіз
@@ -15,19 +14,9 @@ kk:
     extended_description_html: |
       <h3>Ережелерге арналған жақсы орын</h3>
       <p>Әлі ештеңе жазылмапты</p>
-    features:
-      humane_approach_body: Басқа желілердің сәтсіздіктерінен сабақ алып, Mastodon әлеуметтік медианы дұрыс пайдаланбаумен күресу үшін этикалық дизайнды таңдауға бағытталған.
-      humane_approach_title: Гуманистік көзқарас басым
-      not_a_product_body: Mastodon коммерциялық желі емес. Жарнама жоқ, деректерді өңдеу, қоршаулы бақтар да жоқ. Орталықтан басқару да жоқ.
-      not_a_product_title: Сіз тұлғасыз, тауар емес
-      real_conversation_body: 500 таңба арқылы мазмұнды пікір және қызық медиа қолданып, өз ойыңызды жеткізе аласыз.
-      real_conversation_title: Нақты әңгімелерге арналған
-      within_reach_body: Ыңғайлы API экожүйесі арқасында iOS, Android және басқа платформаларға арналған бірнеше қосымшалар арқылы достарыңызбен кез-келген жерде әңгіме құруға мүмкіндік береді.
-      within_reach_title: Әрқашан қол жетімді
     generic_description: "%{domain} желідегі серверлердің бірі"
     hosted_on: Mastodon орнатылған %{domain} доменінде
     learn_more: Көбірек білу
-    other_instances: Серверлер тізімі
     privacy_policy: Құпиялылық саясаты
     source_code: Ашық коды
     status_count_after:
@@ -411,9 +400,6 @@ kk:
         min_invite_role:
           disabled: Ешкім
           title: Allow шақырулар by
-        open:
-          desc_html: Allow anyone to create an аккаунт
-          title: Ашық тіркелу
       show_known_fediverse_at_about_page:
         desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show жергілікті toots.
         title: Show known fediverse on timeline превью
@@ -508,13 +494,11 @@ kk:
     logout: Шығу
     migrate_account: Басқа аккаунтқа көшіру
     migrate_account_html: Егер аккаунтыңызды басқасына байлағыңыз келсе, <a href="%{path}">мына жерге келіңіз</a>.
-    or: немесе
     or_log_in_with: Немесе былай кіріңіз
     providers:
       cas: САS
       saml: SАML
     register: Тіркелу
-    register_elsewhere: Басқа серверге тіркелу
     resend_confirmation: Растау нұсқаулықтарын жіберу
     reset_password: Құпиясөзді қалпына келтіру
     security: Қауіпсіздік
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index fe347a703..9d480e7bc 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -7,7 +7,6 @@ ko:
     administered_by: '관리자:'
     api: API
     apps: 모바일 앱
-    closed_registrations: 현재 이 서버에서는 신규 등록을 받고 있지 않습니다. 하지만! 다른 서버에 계정을 만들어 똑같은 네트워크에 접속 할 수 있습니다.
     contact: 연락처
     contact_missing: 미설정
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ ko:
     extended_description_html: |
       <h3>룰을 작성하는 장소</h3>
       <p>아직 설명이 작성되지 않았습니다.</p>
-    features:
-      humane_approach_body: 다른 SNS의 실패를 교훈삼아, 마스토돈은 소셜미디어가 잘못 사용되는 것을 막기 위하여 윤리적인 설계를 추구합니다.
-      humane_approach_title: 보다 배려를 의식한 설계를 추구
-      not_a_product_body: 마스토돈은 이익을 추구하는 SNS가 아닙니다. 그러므로 광고와 데이터의 수집 및 분석이 존재하지 않고, 유저를 구속하지도 않습니다.
-      not_a_product_title: 여러분은 사람이며, 상품이 아닙니다
-      real_conversation_body: 자유롭게 사용할 수 있는 500문자의 메세지와 미디어 경고 내용을 바탕으로, 자기자신을 자유롭게 표현할 수 있습니다.
-      real_conversation_title: 진정한 커뮤니케이션을 위하여
-      within_reach_body: 개발자 친화적인 API에 의해서 실현된 iOS나 Android, 그 외의 여러 Platform들 덕분에 어디서든 친구들과 자유롭게 메세지를 주고 받을 수 있습니다.
-      within_reach_title: 언제나 유저의 곁에서
     generic_description: "%{domain} 은 네트워크에 있는 한 서버입니다"
     hosted_on: "%{domain}에서 호스팅 되는 마스토돈"
     learn_more: 자세히
-    other_instances: 서버 목록
     privacy_policy: 개인정보 정책
     source_code: 소스 코드
     status_count_after:
@@ -413,9 +402,6 @@ ko:
         min_invite_role:
           disabled: 아무도 못 하게
           title: 초대링크를 만들 수 있는 권한
-        open:
-          desc_html: 계정을 생성할 수 있도록 허용합니다
-          title: 신규 계정 등록을 받음
       show_known_fediverse_at_about_page:
         desc_html: 활성화 되면 프리뷰 페이지에서 페디버스의 모든 툿을 표시합니다. 비활성화시 로컬에 있는 툿만 표시 됩니다.
         title: 타임라인 프리뷰에 알려진 페디버스 표시하기
@@ -510,13 +496,11 @@ ko:
     logout: 로그아웃
     migrate_account: 계정 옮기기
     migrate_account_html: 이 계정을 다른 계정으로 리디렉션 하길 원하는 경우 <a href="%{path}">여기</a>에서 설정할 수 있습니다.
-    or: 또는
     or_log_in_with: 다른 방법으로 로그인 하려면
     providers:
       cas: CAS
       saml: SAML
     register: 등록하기
-    register_elsewhere: 다른 인스턴스에서 가입
     resend_confirmation: 확인 메일을 다시 보내기
     reset_password: 비밀번호 재설정
     security: 보안
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index fa3469b11..4f8fd5825 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -7,7 +7,6 @@ lt:
     administered_by: 'Administruoja:'
     api: API
     apps: Mobilioji Aplikacija
-    closed_registrations: Registracija šiuo metu uždaryta prie šito tinklo.  Jūs galite rasti kitą būdą susikurti paskyrą ir gauti prieigą prie to paties tinklo.
     contact: Kontaktai
     contact_missing: Nenustatyta
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ lt:
     extended_description_html: |
       <h3>Taisyklės</h3>
       <p>Ilgas aprašymas dar nėra sudartyas</p>
-    features:
-      humane_approach_body: Mokantis iš kitų socialinių tinklų, bei jų daromu klaidų, Mastodon siekia sukurti etiška dizainą, kuris kovotu su netinkamu socialinių tinklų naudojimu.
-      humane_approach_title: Humaniškesnis metodas
-      not_a_product_body: Mastodon nėra komercinis tinklas. Jokių reklamų, privačios informacijos rinkimo. Čia nėra vieno žmogaus, kuris už viską atsako.
-      not_a_product_title: Tu esi žmogus, o ne produktas
-      real_conversation_body: Su 500 simbolių limitu, ir galimybe pažymėti savo įkeliama informacija su įspėjamaisiais ženklais, galite išsireikšti kaip tik norite.
-      real_conversation_title: Sukurtas tikram bendravimui
-      within_reach_body: Mobiliosios aplikacijos skirtos iOS, Android, ir kitoms platformoms. Draugiškos API ekosistemos dėka, Jūs galite palaikyti pokalbi su draugais bet kur.
-      within_reach_title: Visada pasiekama
     generic_description: "%{domain} yra vienas serveris tinkle"
     hosted_on: Mastodon palaikomas naudojantis %{domain} talpinimu
     learn_more: Daugiau
-    other_instances: Serverių sąrašas
     privacy_policy: Privatumo Politika
     source_code: Šaltinio kodas
     status_count_after:
@@ -419,9 +408,6 @@ lt:
         min_invite_role:
           disabled: Nei vienas
           title: Leisti pakvietimus
-        open:
-          desc_html: Leisti bet kam susikurti paskyrą
-          title: Atidaryta registracija
       show_known_fediverse_at_about_page:
         desc_html: Kai įjungta, rodys įrašus iš visos žinomos fedi-visatos. Kitokiu atvėju, rodys tik lokalius įrašus.
         title: Rodyti žinoma fedi-visatos laiko juosta peržiūroje
@@ -516,13 +502,11 @@ lt:
     logout: Atsijungti
     migrate_account: Prisijungti prie kitos paskyros
     migrate_account_html: Jeigu norite nukreipti šią paskyrą į kita, galite tai <a href="%{path}">konfiguruoti čia</a>.
-    or: arba
     or_log_in_with: Arba prisijungti su
     providers:
       cas: CAS
       saml: SAML
     register: Užsiregistruoti
-    register_elsewhere: Užsiregistruoti kitame serveryje
     resend_confirmation: Išsiųsti dar kartą patvirtinimo instrukcijas
     reset_password: Atstatyti slaptažodį
     security: Apsauga
diff --git a/config/locales/ms.yml b/config/locales/ms.yml
index e3c901eff..0b1269fb2 100644
--- a/config/locales/ms.yml
+++ b/config/locales/ms.yml
@@ -7,7 +7,6 @@ ms:
     administered_by: 'Ditadbir oleh:'
     api: API
     apps: Aplikasi mudah alih
-    closed_registrations: Pendaftaran ditutup di tika ini. Tetapi! Anda boleh mencari tika lain untuk mencipta akaun dan capai ke rangkaian yang sama daripada sana.
     contact: Hubungi kami
     contact_missing: Tidak ditetapkan
     contact_unavailable: Tidak tersedia
@@ -15,19 +14,9 @@ ms:
     extended_description_html: |
       <h3>Tempat sesuai untuk peraturan</h3>
       <p>Kenyataan penuh masih belum ditetapkan.</p>
-    features:
-      humane_approach_body: Belajar daripada kegagalan rangkaian lain, Mastodon berazam untuk membuat pilihan reka cipta beretika untuk mengatasi penyalahgunaan media sosial.
-      humane_approach_title: Pendekatan yang lebih berperikemanusiaan
-      not_a_product_body: Mastodon bukannya rangkaian komersial. Tiada iklan, tiada perlombongan data, tiada kurungan atau tapisan. Tiada pihak berkuasa pusat.
-      not_a_product_title: Anda seorang manusia, bukannya sebuah produk
-      real_conversation_body: Dengan had 500 aksara dan sokongan kandungan berbutir serta pemberi amaran media, anda boleh meluahkan diri anda dengan cara yang anda inginkan.
-      real_conversation_title: Dibina untuk perbualan sebenar
-      within_reach_body: Pelbagai aplikasi untuk iOS, Android, dan platform lain telah dibangunkan dengan ekosistem API mesra-pembangun membolehkan anda terus berhubung dengan rakan anda di mana-mana sahaja.
-      within_reach_title: Sentiasa dalam jangkauan
     generic_description: "%{domain} ialah salah sebuah pelayan dalam rangkaian Mastodon"
     hosted_on: Mastodon dihoskan di %{domain}
     learn_more: Ketahui lebih lanjut
-    other_instances: Senarai tika
     privacy_policy: Polisi privasi
     source_code: Kod sumber
     status_count_after:
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 70094f764..f92ae3bf1 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -7,7 +7,6 @@ nl:
     administered_by: 'Beheerd door:'
     api: API
     apps: Mobiele apps
-    closed_registrations: Registreren op deze server is momenteel niet mogelijk. Je kunt echter een andere server vinden om zo toegang te krijgen tot het netwerk.
     contact: Contact
     contact_missing: Niet ingesteld
     contact_unavailable: n.v.t
@@ -15,19 +14,9 @@ nl:
     extended_description_html: |
       <h3>Een goede plek voor richtlijnen</h3>
       <p>De uitgebreide omschrijving is nog niet ingevuld.</p>
-    features:
-      humane_approach_body: Mastodon heeft van de fouten van andere sociale netwerken geleerd en probeert aan de hand van ethische ontwerpkeuzes misbruik van sociale media te voorkomen.
-      humane_approach_title: Een meer menselijke aanpak
-      not_a_product_body: Mastodon is geen commercieel netwerk. Dus geen advertenties, geen datamining en geen besloten systemen. Er is geen centrale organisatie die alles bepaalt.
-      not_a_product_title: Jij bent een persoon, geen product
-      real_conversation_body: Met 500 tekens tot jouw beschikking en ondersteuning voor tekst- en media-waarschuwingen, kan je jezelf uiten zoals jij dat wil.
-      real_conversation_title: Voor echte gesprekken gemaakt
-      within_reach_body: Meerdere apps voor iOS, Android en andere platformen, met dank aan het ontwikkelaarsvriendelijke API-systeem, zorgen ervoor dat je overal op de hoogte blijft.
-      within_reach_title: Altijd binnen bereik
     generic_description: "%{domain} is een server in het Mastodonnetwerk"
     hosted_on: Mastodon op %{domain}
     learn_more: Meer leren
-    other_instances: Andere servers
     privacy_policy: Privacybeleid
     source_code: Broncode
     status_count_after:
@@ -411,9 +400,6 @@ nl:
         min_invite_role:
           disabled: Niemand
           title: Uitnodigingen toestaan door
-        open:
-          desc_html: Toestaan dat iedereen een account kan registereren
-          title: Open registratie
       show_known_fediverse_at_about_page:
         desc_html: Wanneer ingeschakeld wordt de globale tijdlijn op de voorpagina getoond en wanneer uitgeschakeld de lokale tijdljn.
         title: De globale tijdlijn op de voorpagina tonen
@@ -508,13 +494,11 @@ nl:
     logout: Uitloggen
     migrate_account: Naar een ander account verhuizen
     migrate_account_html: Wanneer je dit account naar een ander account wilt doorverwijzen, kun je <a href="%{path}">dit hier instellen</a>.
-    or: of
     or_log_in_with: Of inloggen met
     providers:
       cas: CAS
       saml: SAML
     register: Registreren
-    register_elsewhere: Op een andere server registreren
     resend_confirmation: Verstuur de bevestigingsinstructies nogmaals
     reset_password: Wachtwoord opnieuw instellen
     security: Beveiliging
diff --git a/config/locales/no.yml b/config/locales/no.yml
index cf8f77b4c..6ee42a7ca 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -4,26 +4,15 @@
     about_hashtag_html: Dette er offentlige toots merket med <strong>#%{hashtag}</strong>. Du kan interagere med dem om du har en konto et sted i fediverset.
     about_mastodon_html: Mastodon er et sosialt nettverk laget med <em>fri programvare</em>. Et <em>desentralisert</em> alternativ til kommersielle plattformer. Slik kan det unngå risikoene ved å ha et enkelt selskap som monopoliserer din kommunikasjon. Velg en tjener du stoler på &mdash; uansett hvilken du velger så kan du kommunisere med alle andre. Alle kan kjøre sin egen Mastodon og delta sømløst i det sosiale nettverket.
     about_this: Om denne instansen
-    closed_registrations: Registreringer er for øyeblikket lukket på denne instansen.
     contact: Kontakt
     contact_missing: Ikke innstilt
     contact_unavailable: Ikke tilgjengelig
     extended_description_html: |
       <h3>En god plassering for regler</h3>
       <p>En utvidet beskrivelse er ikke satt opp ennå.</p>
-    features:
-      humane_approach_body: Mastodon har tatt lærdom fra andre nettverk og har til mål å gjøre etiske designvalg for å bekjempe misbruk av sosiale medier.
-      humane_approach_title: En mer menneskelig tilnærming
-      not_a_product_body: Mastodon er ikke et kommerst nettverk. Ingen reklame, ingen datainnsamling, ingen innhegnede hager. Det finnes ingen sentral myndighet.
-      not_a_product_title: Du er en person, ikke et produkt
-      real_conversation_body: Med 500 tegn til din disposisjon og støtte for granulært innhold og media-advarsler kan du uttrykke deg på den måten du selv vil.
-      real_conversation_title: Laget for ekte samtaler
-      within_reach_body: Takket være et utviklingsvennlig API-økosystem vil flere apper for iOS, Android og andre plattformer la deg holde kontakten med dine venner hvor som helst.
-      within_reach_title: Alltid innen rekkevidde
     generic_description: "%{domain} er en tjener i nettverket"
     hosted_on: Mastodon driftet på %{domain}
     learn_more: Lær mer
-    other_instances: Andre instanser
     source_code: Kildekode
     status_count_after: statuser
     status_count_before: Som skrev
@@ -260,9 +249,6 @@
         min_invite_role:
           disabled: Ingen
           title: Tillat invitasjoner fra
-        open:
-          desc_html: Tillatt alle å lage seg en konto
-          title: Åpen registrering
       show_staff_badge:
         desc_html: Vis personalemerke på brukersiden
         title: Vis personalemerke
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index a0c077b89..1e3a6d429 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -7,7 +7,6 @@ oc:
     administered_by: 'Gerida per :'
     api: API
     apps: Aplicacions per mobil
-    closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància.
     contact: Contacte
     contact_missing: Pas parametrat
     contact_unavailable: Pas disponible
@@ -15,19 +14,9 @@ oc:
     extended_description_html: |
       <h3>Una bona plaça per las règlas</h3>
       <p>La descripcion longa es pas estada causida pel moment.</p>
-    features:
-      humane_approach_body: Amb l’experiéncia dels fracasses d’autres malhums, Mastodon ten per objectiu de lutar contra los abuses dels malhums socials en far de causidas eticas.
-      humane_approach_title: Un biais mai uman
-      not_a_product_body: Mastodon es pas un malhum comercial. Pas cap de reclama, d’utilizacion de vòstras donadas o d’òrt daurat clavat. I a pas cap d’autoritat centrala.
-      not_a_product_title: Sètz una persona, non pas un produit
-      real_conversation_body: Amb 500 caractèrs a vòstra disposicion e un nivèl de confidencialitat per cada publicacion, podètz vos exprimir coma volètz.
-      real_conversation_title: Fach per de conversacions vertadièras
-      within_reach_body: Multiplas aplicacion per iOS, Android, e autras plataformas mercés a un entorn API de bon utilizar, vos permet de gardar lo contacte pertot.
-      within_reach_title: Totjorn al costat
     generic_description: "%{domain} es un dels servidors del malhum"
     hosted_on: Mastodon albergat sus %{domain}
     learn_more: Ne saber mai
-    other_instances: Lista d’instàncias
     privacy_policy: Politica de confidencialitat
     source_code: Còdi font
     status_count_after:
@@ -411,9 +400,6 @@ oc:
         min_invite_role:
           disabled: Degun
           title: Autorizat amb invitacions
-        open:
-          desc_html: Autorizar lo monde a se marcar
-          title: Inscripcions
       show_known_fediverse_at_about_page:
         desc_html: Un còp activat mostrarà los tuts de totes los fediverse dins l’apercebut. Autrament mostrarà pas que los tuts locals.
         title: Mostrar los fediverse coneguts dins l’apercebut del flux
@@ -508,13 +494,11 @@ oc:
     logout: Se desconnectar
     migrate_account: Mudar endacòm mai
     migrate_account_html: Se volètz mandar los visitors d’aqueste compte a un autre, podètz<a href="%{path}"> o configurar aquí</a>.
-    or: o
     or_log_in_with: O autentificatz-vos amb
     providers:
       cas: CAS
       saml: SAML
     register: Se marcar
-    register_elsewhere: Se marcar endacòm mai
     resend_confirmation: Tornar mandar las instruccions de confirmacion
     reset_password: Reïnicializar lo senhal
     security: Seguretat
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 1567ac626..b3ba6539d 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -7,7 +7,6 @@ pl:
     administered_by: 'Administrowana przez:'
     api: API
     apps: Aplikacje
-    closed_registrations: Rejestracja na tej instancji jest obecnie zamknięta. Możesz jednak zarejestrować się na innej instancji, uzyskując dostęp do tej samej sieci.
     contact: Kontakt
     contact_missing: Nie ustawiono
     contact_unavailable: Nie dotyczy
@@ -15,19 +14,9 @@ pl:
     extended_description_html: |
       <h3>Dobre miejsce na zasady użytkowania</h3>
       <p>Nie ustawiono jeszcze szczegółowego opisu</p>
-    features:
-      humane_approach_body: Nauczeni na błędach innych sieci społecznościowych, zaprojektowaliśmy Mastodona tak, aby uniknąć częstych nadużyć.
-      humane_approach_title: Bardziej ludzkie podejście
-      not_a_product_body: Mastodon nie jest komercyjną siecią. Nie doświadczysz tu reklam, zbierania danych, ani centralnego ośrodka, tak jak w przypadku wielu rozwiązań.
-      not_a_product_title: Jesteś człowiekiem, nie produktem
-      real_conversation_body: Mając do dyspozycji 500 znaków na wpis, rozdrobnienie zawartości i ostrzeżenia o multimediach, możesz wyrażać siebie na wszystkie możliwe sposoby.
-      real_conversation_title: Zaprojektowany do prawdziwych rozmów
-      within_reach_body: Wiele aplikacji dla Androida, iOS i innych platform dzięki przyjaznemu programistom API sprawia, że możesz utrzymywać kontakt ze znajomymi praktycznie wszędzie.
-      within_reach_title: Zawsze w Twoim zasięgu
     generic_description: "%{domain} jest jednym z serwerów sieci"
     hosted_on: Mastodon uruchomiony na %{domain}
     learn_more: Dowiedz się więcej
-    other_instances: Lista instancji
     privacy_policy: Polityka prywatności
     source_code: Kod źródłowy
     status_count_after:
@@ -422,9 +411,6 @@ pl:
         min_invite_role:
           disabled: Nikt
           title: Kto może zapraszać użytkowników
-        open:
-          desc_html: Pozwól każdemu na założenie konta
-          title: Otwarta rejestracja
       show_known_fediverse_at_about_page:
         desc_html: Jeśli włączone, podgląd instancji będzie wyświetlał wpisy z całego Fediwersum. W innym przypadku, będą wyświetlane tylko lokalne wpisy.
         title: Pokazuj wszystkie znane wpisy na podglądzie instancji
@@ -519,13 +505,11 @@ pl:
     logout: Wyloguj się
     migrate_account: Przenieś konto
     migrate_account_html: Jeżeli chcesz skonfigurować przekierowanie z obecnego konta na inne, możesz <a href="%{path}">zrobić to tutaj</a>.
-    or: lub
     or_log_in_with: Lub zaloguj się z użyciem
     providers:
       cas: CAS
       saml: SAML
     register: Rejestracja
-    register_elsewhere: Zarejestruj się na innym serwerze
     resend_confirmation: Ponownie prześlij instrukcje weryfikacji
     reset_password: Zresetuj hasło
     security: Bezpieczeństwo
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 1ea2e6ac9..b9b5eeee3 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -7,7 +7,6 @@ pt-BR:
     administered_by: 'Administrado por:'
     api: API
     apps: Apps
-    closed_registrations: Os cadastros estão atualmente fechados nesta instância. No entanto, você pode procurar uma instância diferente na qual possa criar uma conta e acessar a mesma rede por lá.
     contact: Contato
     contact_missing: Não definido
     contact_unavailable: Não disponível
@@ -15,19 +14,9 @@ pt-BR:
     extended_description_html: |
       <h3>Um bom lugar para regras</h3>
       <p>A descrição da instância ainda não foi feita.</p>
-    features:
-      humane_approach_body: Aprendendo com erros de outras redes, Mastodon tem como objetivo fazer decisões éticas de design para combater o desuso de redes sociais.
-      humane_approach_title: Uma abordagem mais humana
-      not_a_product_body: Mastodon não é uma rede comercial. Sem propagandas, coleta de dados, jardins fechados. Não há uma autoridade central.
-      not_a_product_title: Você é uma pessoa e não um produto
-      real_conversation_body: Com 500 caracteres à sua disposição e suporte para conteúdo granular e avisos de conteúdo, você pode se expressar da maneira que desejar.
-      real_conversation_title: Feito para conversas reais
-      within_reach_body: Vários apps para iOS, Android e outras plataformas graças a um ecossistema de API amigável para desenvolvedores permitem que você possa se manter atualizado sobre seus amigos de qualquer lugar.
-      within_reach_title: Sempre ao seu alcance
     generic_description: "%{domain} é um servidor na rede"
     hosted_on: Mastodon hospedado em %{domain}
     learn_more: Saiba mais
-    other_instances: Lista de instâncias
     privacy_policy: Política de Privacidade
     source_code: Código-fonte
     status_count_after:
@@ -410,9 +399,6 @@ pt-BR:
         min_invite_role:
           disabled: Ninguém
           title: Permitir convites de
-        open:
-          desc_html: Permitir que qualquer um crie uma conta
-          title: Cadastro aberto
       show_known_fediverse_at_about_page:
         desc_html: Quando ligado, vai mostrar toots de todo o fediverso conhecido na prévia da timeline. Senão, mostra somente toots locais.
         title: Mostrar fediverso conhecido na prévia da timeline
@@ -507,13 +493,11 @@ pt-BR:
     logout: Sair
     migrate_account: Mudar para uma conta diferente
     migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
-    or: ou
     or_log_in_with: Ou faça login com
     providers:
       cas: CAS
       saml: SAML
     register: Cadastrar-se
-    register_elsewhere: Cadastrar-se em um outro servidor
     resend_confirmation: Reenviar instruções de confirmação
     reset_password: Redefinir senha
     security: Segurança
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index 037582f34..c2a7c36f0 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -4,26 +4,15 @@ pt:
     about_hashtag_html: Estes são toots públicos marcados com <strong>#%{hashtag}</strong>. Podes interagir com eles se tiveres uma conta Mastodon.
     about_mastodon_html: Mastodon é uma rede social baseada em protocolos abertos da web e software livre e gratuito. É descentralizado como e-mail.
     about_this: Sobre esta instância
-    closed_registrations: Novos registos estão fechados nesta instância. No entanto! Podes procurar uma instância diferente na qual criar uma conta e obter acesso à mesma rede desde lá.
     contact: Contacto
     contact_missing: Não configurado
     contact_unavailable: n.d.
     extended_description_html: |
       <h3>Um bom lugar para regras</h3>
       <p>A descrição estendida ainda não foi configurada.</p>
-    features:
-      humane_approach_body: Aprendendo com erros de outras redes sociais, Mastodon tem como objetivo fazer decisões éticas de design para combater o utilização errada de redes sociais.
-      humane_approach_title: Uma abordagem mais humana
-      not_a_product_body: Mastodon não é uma rede comercial. Sem publicidade, sem recolha de dados ou portas fechadas. Não existe uma autoridade central.
-      not_a_product_title: Tu és uma pessoa, não um produto
-      real_conversation_body: Com 500 caracteres à sua disposição e suporte para conteúdo granular e avisos de conteúdo, podes te expressar da forma que desejares.
-      real_conversation_title: Feito para conversas reais
-      within_reach_body: Várias aplicações para iOS, Android e outras plataformas graças a um ecossistema de API amigável para desenvolvedores, permitem-te que te mantenhas em contacto com os teus amigos em qualquer lugar.
-      within_reach_title: Sempre ao teu alcance
     generic_description: "%{domain} é um servidor na rede"
     hosted_on: Mastodon em %{domain}
     learn_more: Saber mais
-    other_instances: Outras instâncias
     source_code: Código fonte
     status_count_after: publicações
     status_count_before: Que fizeram
@@ -260,9 +249,6 @@ pt:
         min_invite_role:
           disabled: Ninguém
           title: Permitir convites de
-        open:
-          desc_html: Permitir que qualquer um crie uma conta
-          title: Aceitar novos registos
       show_staff_badge:
         desc_html: Mostrar um crachá da equipa na página de utilizador
         title: Mostrar crachá da equipa
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index aa4d3c967..82872e651 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -1,8 +1,6 @@
 ---
 ro:
   about:
-    features:
-      not_a_product_title: Ești o persoană, nu un produs
     hosted_on: Mastodon găzduit de %{domain}
   accounts:
     posts:
@@ -22,13 +20,11 @@ ro:
     logout: Deconectare
     migrate_account: Transfer către un alt cont
     migrate_account_html: Dacă dorești să redirecționezi acest cont către un altul, poți <a href="%{path}">configura asta aici</a>.
-    or: sau
     or_log_in_with: Sau conectează-te cu
     providers:
       cas: CAS
       saml: SAML
     register: Înregistrare
-    register_elsewhere: Înregistrează-te pe un alt server
     resend_confirmation: Retrimite instrucțiunile de confirmare
     reset_password: Resetare parolă
     security: Securitate
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 3e37391a8..72513e58c 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -7,26 +7,15 @@ ru:
     administered_by: 'Администратор узла:'
     api: API
     apps: Приложения
-    closed_registrations: В данный момент регистрация на этом узле закрыта. Но вы можете найти другой узел, создать на нём учётную запись и получить доступ к той же сети оттуда.
     contact: Связаться
     contact_missing: Не установлено
     contact_unavailable: Недоступен
     extended_description_html: |
       <h3>Хорошее место для правил</h3>
       <p>Расширенное описание еще не настроено.</p>
-    features:
-      humane_approach_body: Наученный ошибками других проектов, Mastodon направлен на выбор этичных решений в борьбе со злоупотреблениями возможностями социальных сетей.
-      humane_approach_title: Человечный подход
-      not_a_product_body: Mastodon -  не коммерческая сеть. Здесь нет рекламы, сбора данных, отгороженных мест. Здесь нет централизованного управления.
-      not_a_product_title: Вы - человек, а не продукт
-      real_conversation_body: С 500 символами в Вашем распоряжении и поддержкой предупреждений о содержании статусов Вы сможете выражать свои мысли так, как Вы этого хотите.
-      real_conversation_title: Создан для настоящего общения
-      within_reach_body: Различные приложения для iOS, Android и других платформ, написанные благодаря дружественной к разработчикам экосистеме API, позволят Вам держать связь с Вашими друзьями где угодно.
-      within_reach_title: Всегда под рукой
     generic_description: "%{domain} - один из серверов сети"
     hosted_on: Mastodon размещен на %{domain}
     learn_more: Узнать больше
-    other_instances: Другие узлы
     privacy_policy: Политика конфиденциальности
     source_code: Исходный код
     status_count_after:
@@ -367,9 +356,6 @@ ru:
         min_invite_role:
           disabled: Никого
           title: Разрешать приглашения от
-        open:
-          desc_html: Позволяет любому создавать аккаунт
-          title: Открыть регистрацию
       show_known_fediverse_at_about_page:
         desc_html: Если включено, показывает посты со всех известных узлов в предпросмотре ленты. В противном случае отображаются только локальные посты.
         title: Показывать известные узлы в предпросмотре ленты
@@ -450,13 +436,11 @@ ru:
     logout: Выйти
     migrate_account: Перенести аккаунт
     migrate_account_html: Если Вы хотите перенести этот аккаунт на другой, вы можете <a href="%{path}">сделать это здесь</a>.
-    or: или
     or_log_in_with: Или войти с помощью
     providers:
       cas: CAS
       saml: SAML
     register: Зарегистрироваться
-    register_elsewhere: Зарегистрироваться на другом узле
     resend_confirmation: Повторить отправку инструкции для подтверждения
     reset_password: Сбросить пароль
     security: Безопасность
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 0800b8f8c..565b2e8a8 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -7,7 +7,6 @@ sk:
     administered_by: 'Správcom je:'
     api: API
     apps: Aplikácie
-    closed_registrations: Registrácie na tomto serveri sú momentálne uzatvorené. Avšak, môžeš nájsť nejaký iný server kde si založíš účet a získaš tak prístup do presne tej istej siete odtiaľ.
     contact: Kontakt
     contact_missing: Nezadaný
     contact_unavailable: Neuvedený
@@ -15,19 +14,9 @@ sk:
     extended_description_html: |
       <h3>Pravidlá</h3>
       <p>Žiadne zatiaľ uvedené nie sú</p>
-    features:
-      humane_approach_body: Poučený z chýb iných sociálnych sietí, Mastodon sa snaží bojovať so zneužívaním siete voľbou etických návrhov.
-      humane_approach_title: Ľudskejší prístup
-      not_a_product_body: Mastodon nie je komerčná sieť. Žiadne reklamy, žiadne dolovanie dát, žiadne múry. Nieje tu žiadna centrálna autorita.
-      not_a_product_title: Si človekom, nie produktom
-      real_conversation_body: K dispozícii s 500 znakmi a podporou pre varovania o obsahu a médiách sa môžeš vyjadriť tak ako budeš chcieť.
-      real_conversation_title: Vytvorený pre naozajstnú konverzáciu
-      within_reach_body: Viacero aplikácií pre iOS, Android a iné platformy, ktoré ti vďaka jednoduchému API ekosystému dovoľujú byť online so svojimi priateľmi kdekoľvek.
-      within_reach_title: Stále v dosahu
     generic_description: "%{domain} je jeden server v sieti"
     hosted_on: Mastodon hostovaný na %{domain}
     learn_more: Zisti viac
-    other_instances: Zoznam serverov
     privacy_policy: Ustanovenia o súkromí
     source_code: Zdrojový kód
     status_count_after:
@@ -417,9 +406,6 @@ sk:
         min_invite_role:
           disabled: Nikto
           title: Povoliť pozvánky od
-        open:
-          desc_html: Povoliť každému aby si mohli vytvoriť účet
-          title: Verejná registrácia
       show_known_fediverse_at_about_page:
         desc_html: Pokiaľ je zapnuté, bude v ukážke osi možné nahliadnúť príspevky z celého známeho fediversa. Inak budú ukázané iba príspevky z miestnej osi.
         title: Ukáž celé známe fediverse na náhľade osi
@@ -514,13 +500,11 @@ sk:
     logout: Odhlás sa
     migrate_account: Presúvam sa na iný účet
     migrate_account_html: Pokiaľ si želáš presmerovať tento účet na nejaký iný, môžeš si to <a href="%{path}">nastaviť tu</a>.
-    or: alebo
     or_log_in_with: Alebo prihlásiť z
     providers:
       cas: CAS
       saml: SAML
     register: Zaregistruj sa
-    register_elsewhere: Zaregistruj sa na inom serveri
     resend_confirmation: Poslať potvrdzujúce pokyny znovu
     reset_password: Resetovať heslo
     security: Zabezpečenie
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index 35cba927b..5a4ffb6cd 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -7,7 +7,6 @@ sl:
     administered_by: 'Upravlja:'
     api: API
     apps: Mobilne aplikacije
-    closed_registrations: Registracije so trenutno zaprte na tem vozlišču. Vendar! Tukaj lahko najdete druga vozlišča, na katerih se prijavite in dostopate do istega omrežja od tam.
     contact: Kontakt
     contact_missing: Ni nastavljeno
     contact_unavailable: Ni na voljo
@@ -15,19 +14,9 @@ sl:
     extended_description_html: |
       <h3>Dober prostor za pravila</h3>
       <p>Razširjen opis še ni bil nastavljen.</p>
-    features:
-      humane_approach_body: Na podlagi učenja od neuspehov drugih omrežij, želi Mastodon oblikovati etične načrte za boj proti zlorabi socialnih medijev.
-      humane_approach_title: Bolj human pristop
-      not_a_product_body: Mastodon ni komercialno omrežje. Brez oglaševanja, brez podatkovnega rudarjenja, brez obzidanih vrtov. Ni osrednjega organa.
-      not_a_product_title: Ti si oseba, ne izdelek
-      real_conversation_body: S 500 znaki, ki so vam na voljo, in podporo za zrnate vsebine ter opozorila pred mediji, se lahko izrazite tako, kot želite.
-      real_conversation_title: Zgrajen za pravi pogovor
-      within_reach_body: Zahvaljujoč razvijalcem prijaznemu API ekosistemu, obstaja več aplikacija za iOS, Arduino in druge platforme, ki vam omogočajo, da sledite svojim prijateljem kjerkoli.
-      within_reach_title: Vedno na dosegu roke
     generic_description: "%{domain} je en strežnik v omrežju"
     hosted_on: Mastodon gostuje na %{domain}
     learn_more: Spoznaj več
-    other_instances: Seznam vozlišč
     privacy_policy: Politika zasebnosti
     source_code: Izvorna koda
     status_count_after:
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index b542ffc97..b29564e74 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -7,7 +7,6 @@ sq:
     administered_by: 'Administruar nga:'
     api: API
     apps: Aplikacione për celular
-    closed_registrations: Hëpërhë regjistrimet në këtë shërbyes janë të mbyllura. Por! Mund të gjeni një shërbyes tjetër për të krijuar një llogari dhe të mund të përdorni andej pikërisht të njëjtin rrjet të këtushëm.
     contact: Kontakt
     contact_missing: I parregulluar
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ sq:
     extended_description_html: |
       <h3>Një vend i mirë për rregulla</h3>
       <p>Përshkrimi i zgjeruar s’është sajuar ende.</p>
-    features:
-      humane_approach_body: Duke nxjerrë mësime nga dështimet e rrjeteve të tjera, Mastodon-i synon të bëjë zgjedhje konceptuale etike, për të luftuar keqpërdorimin e mediave shoqërore.
-      humane_approach_title: Një trajtim më njerëzor
-      not_a_product_body: Mastodon-i s’është rrjet komercial. Pa reklama, pa monetarizim të dhënash, pa gardhe. S’ka autoritet qendror.
-      not_a_product_title: Jeni një person, jo një produkt
-      real_conversation_body: Me 500 shenja në dorën tuaj për t’i përdorur dhe mbulim për sinjalizime të imta lidhur me lëndën dhe median, mund të shpreheni ashtu si dëshironi.
-      real_conversation_title: Ndërtuar për bashkëbisedim të njëmendtë
-      within_reach_body: Aplikacione të shumtë, për iOS, Android, dhe të tjera platforma, falë një ekosistemi API miqësor ndaj zhvilluesve, ju lejojnë të mbani lidhje me miqtë tuaj kudo.
-      within_reach_title: Përherë i kapshëm
     generic_description: "%{domain} është një shërbyes te rrjeti"
     hosted_on: Mastodon i strehuar në %{domain}
     learn_more: Mësoni më tepër
-    other_instances: Listë shërbyesish
     privacy_policy: Rregulla privatësie
     source_code: Kod burim
     status_count_after:
@@ -408,9 +397,6 @@ sq:
         min_invite_role:
           disabled: Asnjë
           title: Lejo vetëm me ftesa
-        open:
-          desc_html: Lejo cilindo të krijojë llogari
-          title: Hapni regjistrimin
       show_known_fediverse_at_about_page:
         desc_html: Kur përdoret, do të shfaqë mesazhe prej krejt fediversit të njohur, si paraparje. Përndryshe do të shfaqë vetëm mesazhe vendore.
         title: Shfaq te paraparja e rrjedhës kohore fedivers të njohur
@@ -505,13 +491,11 @@ sq:
     logout: Dalje
     migrate_account: Kaloni në një tjetër llogari
     migrate_account_html: Nëse doni ta ridrejtoni këtë llogari te një tjetër, këtë mund <a href="%{path}">ta formësoni këtu</a>.
-    or: ose
     or_log_in_with: Ose bëni hyrjen me
     providers:
       cas: CAS
       saml: SAML
     register: Regjistrohuni
-    register_elsewhere: Regjistrohuni në një tjetër shërbyes
     resend_confirmation: Ridërgo udhëzime ripohimi
     reset_password: Ricaktoni fjalëkalimin
     security: Siguri
diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml
index 82739c9bb..a43c639c0 100644
--- a/config/locales/sr-Latn.yml
+++ b/config/locales/sr-Latn.yml
@@ -4,26 +4,15 @@ sr-Latn:
     about_hashtag_html: Ovo su javni statusi tagovani sa <strong>#%{hashtag}</strong>. Možete odgovarati na njih ako imate nalog bilo gde u fediversu.
     about_mastodon_html: Mastodont je društvena mreža bazirana na otvorenim protokolima i slobodnom softveru otvorenog koda. Decentralizovana je kao što je decentralizovana e-pošta.
     about_this: O instanci
-    closed_registrations: Registracije su trenutno zatvorene na ovoj instanci. Ipak! Možete naći drugu instancu na kojoj ćete napraviti nalog i odatle dobiti pristup istoj ovoj mreži.
     contact: Kontakt
     contact_missing: Nije postavljeno
     contact_unavailable: N/A
     extended_description_html: |
       <h3>Dobro mesto za pravila</h3>
       <p>Prošireni opis koji još nije postavljen.</p>
-    features:
-      humane_approach_body: Učeći od grešaka sa ostalih mreža, a da bi se borio protiv zloupotreba na društvenim mrežama, Mastodont pokušava da pravi što etičkije odluke prilikom razvoja.
-      humane_approach_title: Humaniji pristup
-      not_a_product_body: Mastodont nije komercijalna mreža. Nema reklama, nema skupljanja privatnih podataka, nema zaštićenih delova. Nema centralnog autoriteta.
-      not_a_product_title: Vi ste osoba, ne proizvod
-      real_conversation_body: Sa 500 karaktera na raspolaganju i podrškom za granularniji sadržaj i upozorenja na osetljiviji sadržaj, možete se izraziti kako god želite.
-      real_conversation_title: Pravljen za pravi razgovor
-      within_reach_body: Više aplikacija za iOS, Android, kao i druge platforme zahvaljujući ekosistemu dobrih API-ja će Vam omogućiti da ostanete u kontaktu sa prijateljima svuda.
-      within_reach_title: Uvek u kontaktu
     generic_description: "%{domain} je server na mreži"
     hosted_on: Mastodont hostovan na %{domain}
     learn_more: Saznajte više
-    other_instances: Lista instanci
     source_code: Izvorni kod
     status_count_after: statusa
     status_count_before: Koji su napisali
@@ -256,9 +245,6 @@ sr-Latn:
         min_invite_role:
           disabled: Niko
           title: Samo preko pozivnice
-        open:
-          desc_html: Dozvoli svakome da kreira nalog
-          title: Otvorena registracija
       show_staff_badge:
         desc_html: Prikaži bedž osoblja na korisničkoj strani
         title: Prikaži bedž osoblja
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 331ec3f80..5f7533ee1 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -7,7 +7,6 @@ sr:
     administered_by: 'Администрирано од стране:'
     api: API
     apps: Мобилне апликације
-    closed_registrations: Регистрације су тренутно затворене на овој инстанци. Међутим! Можете наћи другу инстанцу на којој ћете направити налог и одатле добити приступ на истој овој мрежи.
     contact: Контакт
     contact_missing: Није постављено
     contact_unavailable: N/A
@@ -15,19 +14,9 @@ sr:
     extended_description_html: |
       <h3>Добро место за правила</h3>
       <p>Проширени опис који још није постављен.</p>
-    features:
-      humane_approach_body: Учећи од грешака са осталих мрежа, а да би се борио против злоупотреба на друштвеним мрежама, Мастодонт покушава да прави што етичкије одлуке приликом развоја.
-      humane_approach_title: Хуманији приступ
-      not_a_product_body: Мастодонт није комерцијална мрежа. Нема реклама, нема скупљања приватних података, нема заштићених делова. Нема централног ауторитета.
-      not_a_product_title: Ви сте особа, не производ
-      real_conversation_body: Са 500 карактера на располагању и подршком за грануларнији садржај и упозорења на осетљивији садржај, можете се изразити како год желите.
-      real_conversation_title: Прављен за прави разговор
-      within_reach_body: Више апликација за iOS, Андроид, као и друге платформе захваљујући екосистему добрих API-ја ће Вам омогућити да останете у контакту са пријатељима свуда.
-      within_reach_title: Увек у контакту
     generic_description: "%{domain} је сервер на мрежи"
     hosted_on: Мастодонт хостован на %{domain}
     learn_more: Сазнајте више
-    other_instances: Листа инстанци
     privacy_policy: Полиса приватности
     source_code: Изворни код
     status_count_after:
@@ -421,9 +410,6 @@ sr:
         min_invite_role:
           disabled: Нико
           title: Само преко позивнице
-        open:
-          desc_html: Дозволи свакоме да креира налог
-          title: Отворена регистрација
       show_known_fediverse_at_about_page:
         desc_html: Када се упали, показаће трубе из свих знаних федиверса на преглед. У супротном ће само показати локалне трубе.
         title: Покажи познате здружене инстанце у прегледнику временске линије
@@ -518,13 +504,11 @@ sr:
     logout: Одјава
     migrate_account: Помери у други налог
     migrate_account_html: Ако желите да преусмерите овај налог на неки други, можете то <a href="%{path}">подесити овде</a>.
-    or: или
     or_log_in_with: Или се пријавите са
     providers:
       cas: CAS-ом
       saml: SAML-ом
     register: Региструј се
-    register_elsewhere: Региструјте се на другом серверу
     resend_confirmation: Пошаљи поруку са упутствима о потврди налога поново
     reset_password: Ресетуј лозинку
     security: Безбедност
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index aa5b3420d..7478bef6c 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -5,26 +5,15 @@ sv:
     about_mastodon_html: Mastodon är ett socialt nätverk baserat på öppna webbprotokoll och gratis, öppen källkodsprogramvara. Det är decentraliserat som e-post.
     about_this: Om
     administered_by: 'Administreras av:'
-    closed_registrations: Registreringar är för närvarande stängda i denna instans. Dock så kan du hitta en annan instans för att skapa ett konto och få tillgång till samma nätverk från det.
     contact: Kontakt
     contact_missing: Inte inställd
     contact_unavailable: N/A
     extended_description_html: |
       <h3>En bra plats för regler</h3>
       <p>Den utökade beskrivningen har inte konfigurerats ännu.</p>
-    features:
-      humane_approach_body: Mastodon, har lärt sig från tidigare misslyckanden i andra nätverk och syftar till att göra etiska designval i Mastodon för att bekämpa missbruk av sociala medier.
-      humane_approach_title: En mer human inställning
-      not_a_product_body: Mastodon är inte ett kommersiellt nätverk. Ingen reklam, ingen datautvinning, inga muromgärdade trädgårdar. Det finns ingen central myndighet.
-      not_a_product_title: Du är en person, inte en produkt
-      real_conversation_body: Med 500 tecken till ditt förfogande och stöd för granulärt innehåll och mediavarningar så kan du uttrycka dig själv, som du vill.
-      real_conversation_title: Byggd för riktiga konversationer
-      within_reach_body: Flera appar för iOS, Android och andra plattformar tack vare ett utvecklingsvänligt API-ekosystem gör att du kan hålla kontakten med dina vänner var som helst.
-      within_reach_title: Alltid inom räckhåll
     generic_description: "%{domain} är en server i nätverket"
     hosted_on: Mastodon värd på %{domain}
     learn_more: Lär dig mer
-    other_instances: Instanslista
     source_code: Källkod
     status_count_after: statusar
     status_count_before: Som skapat
@@ -301,9 +290,6 @@ sv:
         min_invite_role:
           disabled: Ingen
           title: Tillåt inbjudningar av
-        open:
-          desc_html: Tillåt alla att skapa ett konto
-          title: Öppen registrering
       show_known_fediverse_at_about_page:
         desc_html: När den växlas, kommer toots från hela fediverse visas på förhandsvisning. Annars visas bara lokala toots.
         title: Visa det kända fediverse på tidslinjens förhandsgranskning
@@ -380,13 +366,11 @@ sv:
     logout: Logga ut
     migrate_account: Flytta till ett annat konto
     migrate_account_html: Om du vill omdirigera detta konto till ett annat, kan du <a href="%{path}">konfigurera det här</a>.
-    or: eller
     or_log_in_with: Eller logga in med
     providers:
       cas: CAS
       saml: SAML
     register: Registrera
-    register_elsewhere: Registrera dig på en annan server
     resend_confirmation: Skicka instruktionerna om bekräftelse igen
     reset_password: Återställ lösenord
     security: Säkerhet
diff --git a/config/locales/te.yml b/config/locales/te.yml
index f0f6942ab..1dfc87060 100644
--- a/config/locales/te.yml
+++ b/config/locales/te.yml
@@ -7,7 +7,6 @@ te:
     administered_by: 'నిర్వహణలో:'
     api: API
     apps: మొబైల్ యాప్స్
-    closed_registrations: ప్రస్తుతం ఈ ఇన్స్టెన్స్ లో రిజిస్టేషన్లు మూసివేయబడ్డాయి. అయితే, వేరే ఇన్స్టెన్స్ లో ఖాతా తెరచికూడా ఈ ఇన్స్టెన్స్ ను అక్కడినుండే యాక్సెస్ చేయవచ్చు.
     contact: సంప్రదించండి
     contact_missing: ఇంకా సెట్ చేయలేదు
     contact_unavailable: వర్తించదు
@@ -15,19 +14,9 @@ te:
     extended_description_html: |
       <h3>నియమాలకు ఒక మంచి ప్రదేశం</h3>
       <p>మరింత విశదీకరణ ఇంకా సెట్ చేయబడలేదు.</p>
-    features:
-      humane_approach_body: వేరే సామాజిక మాధ్యమాల వైఫల్యాల నుండి నేర్చుకుని, నైతిక రూపకల్పనలతో సామాజిక మాధ్యమాల దుర్వినియోగంపై  మాస్టొడాన్ పోరాటం చేసే లక్ష్యంతో పనిచేస్తుంది.
-      humane_approach_title: మరింత మానవత్వంతో కూడిన విధానం
-      not_a_product_body: మాస్టొడాన్ వ్యాపార సంబంధిత మాధ్యమం కాదు. ఎటువంటి ప్రకటనలు, డేటా మైనింగ్, కంచెలు లేనిది. ఏ కేంద్ర అధికరమూ లేదు.
-      not_a_product_title: మీరొక వ్యక్తి, వస్తువు కాదు
-      real_conversation_body: With 500 characters at your disposal and support for granular content and media warnings, you can express yourself the way you want to.
-      real_conversation_title: నిజమైన సంభాషణలకోసం నిర్మించబడింది
-      within_reach_body: ఆండ్రాయిడ్, iOS మరియు ఇతర ప్లాట్ఫాంలకు వివిధరకాల యాప్స్ వున్నాయి. డెవలపర్ సహిత API వ్యవస్థే ఇందుకు మూలకారణం. ఇవి మీ స్ణేహితులతో అన్నివేళలా అందుబాటులో వుండడానికి సహాయపడతాయి.
-      within_reach_title: ఎల్లప్పుడూ అందుబాటులో
     generic_description: "%{domain} అనేది నెట్వర్కులోని ఒక సర్వరు"
     hosted_on: మాస్టొడాన్ %{domain} లో హోస్టు చేయబడింది
     learn_more: మరింత తెలుసుకోండి
-    other_instances: ఇన్స్టాన్స్ ల జాబితా
     privacy_policy: గోప్యత విధానము
     source_code: సోర్సు కోడ్
     status_count_after:
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 5be8e02c0..5e9be4da7 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -3,9 +3,7 @@ th:
   about:
     about_mastodon_html: แมสโทดอน เป็น  <em>ดีเซ็นทรัลไลซ์</em><em>ฟรีโอเพ่นซอร์ส</em> โซเชี่ยวเน็ตเวริ์ค.  เป็นทางเลือกทดแทนโซเชี่ยวเน็ตเวิร์คที่ทำเป็นธุรกิจการค้า, ป้องกันการผูกขาดช่องทางการสื่อสารของคุณ. เลือกเซร์ฟเวอร์ที่คุณไว้ใจ &mdash; ที่คุณเลือกได้เอง, สื่อสารกับคนที่คุณต้องการได้เสมอ. ใครๆก็รันแมสโทดอนอินซะแตนซ์ได้ และ เชื่อมต่อกับ<em>โซเชี่ยวเน็ตเวิร์ค</em> โดยไม่มีอะไรมาขวางกั้น.
     about_this: เกี่ยวกับอินซะแตนซ์นี้
-    closed_registrations: อินซะแตนซ์นี้ปิดรับลงทะเบียนแล้ว.
     contact: ติดต่อ
-    other_instances: อินซะแตนซ์อื่นๆ
     source_code: ซอร์สโค๊ด
     status_count_after: สถานะ
     status_count_before: Who authored
@@ -115,8 +113,6 @@ th:
         closed_message:
           desc_html: Displayed on frontpage when registrations are closed<br> ใช้ HTML tags ได้
           title: ปิดข้อความลงทะเบียน
-        open:
-          title: เปิดรับลงทะเบียน
       site_description:
         desc_html: Displayed as a paragraph on the frontpage and used as a meta tag.<br> ใช้ HTML tags ได้, in particular <code>&lt;a&gt;</code> และ <code>&lt;em&gt;</code>.
         title: คำอธิบายไซต์
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index fefbb6667..d5f48ee45 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -7,7 +7,6 @@ tr:
     administered_by: 'Tarafından yönetildi:'
     api: API
     apps: Mobil uygulamalar
-    closed_registrations: Bu sunucudaki kayıtlar şu anda kapalı. Ancak! Bir hesap oluşturmak ve oradan aynı ağa erişmek için farklı bir sunucu bulabilirsiniz.
     contact: İletişim
     contact_missing: Ayarlanmadı
     contact_unavailable: Yok
@@ -15,19 +14,9 @@ tr:
     extended_description_html: |
       <h3>Kural için iyi bir yer</h3>
       <p>Genişletilmiş açıklama henüz ayarlanmamış.</p>
-    features:
-      humane_approach_body: Diğer ağların başarısızlıklarından öğrenen Mastodon, sosyal medyanın kötüye kullanımı ile mücadele etmek için etik tasarım seçimleri yapmayı amaçlamaktadır.
-      humane_approach_title: Daha insancıl bir yaklaşım
-      not_a_product_body: Mastodon ticari bir ağ değildir. Reklam yok, Veri madenciliği yok, duvarlı bahçeler yok. Merkezi bir otorite yok.
-      not_a_product_title: Sen bir insansın, bir ürün değil
-      real_conversation_body: Emrindeki 500 karakter ve granüler içerik ve medya uyarıları için destek ile kendinizi istediğiniz şekilde ifade edebilirsiniz.
-      real_conversation_title: Gerçek sohbet için üretildi
-      within_reach_body: Geliştirici dostu bir API ekosistemi sayesinde iOS, Android ve diğer platformlar için birden fazla uygulama, arkadaşlarınıza her yerden ulaşmanızı sağlar.
-      within_reach_title: Her zaman ulaşılabilir
     generic_description: "%{domain} ağdaki bir sunucudur"
     hosted_on: Mastodon %{domain} üzerinde barındırılıyor
     learn_more: Daha fazla bilgi edinin
-    other_instances: Sunucu listesi
     privacy_policy: Gizlilik politikası
     source_code: Kaynak kodu
     status_count_after:
@@ -218,8 +207,6 @@ tr:
         closed_message:
           desc_html: Kayıt alımları kapatıldığında ana sayfada görüntülenecek mesajdır. <br> HTML etiketleri kullanabilirsiniz
           title: Kayıt alımları kapatılma mesajı
-        open:
-          title: Kayıt alımları
       site_description:
         desc_html: Ana sayfada paragraf olarak görüntülenecek bilgidir.<br>Özellikle <code>&lt;a&gt;</code> ve <code>&lt;em&gt;</code> olmak suretiyle HTML etiketlerini kullanabilirsiniz.
         title: Site açıklaması
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 9a63854b5..d8e2aa066 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -6,7 +6,6 @@ uk:
     about_this: Про цю інстанцію
     administered_by: 'Адміністратор:'
     api: API
-    closed_registrations: На даний момент реєстрація на цій інстанції закрита.
     contact: Зв'язатися
     contact_missing: Не зазначено
     contact_unavailable: Недоступно
@@ -14,19 +13,9 @@ uk:
     extended_description_html: |
       <h3>Гарне місце для правил</h3>
       <p>Детальний опис ще не налаштований.</p>
-    features:
-      humane_approach_body: Навчаючись з помилок інших соціальних мереж, Mastodon націлений на етичні рішення для боротьби з направильним використанням соціальних медіа.
-      humane_approach_title: Більш людський підхід
-      not_a_product_body: Mastodon - це некомерційна мережа. Ніякої реклами, збору даних і залізних стін. Тут немає центральної влади.
-      not_a_product_title: Ви - особистість, а не продукт
-      real_conversation_body: Висловлюйте свої думки себе у будь-який спосіб маючи в розпорядженні 500 символів з підтримкою гранульованого контенту та попереджень медіа.
-      real_conversation_title: Побудований для справжньої розмови
-      within_reach_body: Велика кількість застосунків для iOS, Android та інших платформ, завдяки дружній до розробника екосистемі дозволяє бути на звязку з друзями звідусіль.
-      within_reach_title: Завжди на звязку
     generic_description: "%{domain} є одним сервером у мережі"
     hosted_on: Mastodon розміщено на %{domain}
     learn_more: Дізнатися більше
-    other_instances: Інші інстанції
     privacy_policy: Політика приватності
     source_code: Вихідний код
     status_count_after: статусів
@@ -331,9 +320,6 @@ uk:
         min_invite_role:
           disabled: Ніхто
           title: Дозволити запрошення від
-        open:
-          desc_html: Дозволити будь-ком створювати аккаунт
-          title: Відкрити реєстрацію
       show_known_fediverse_at_about_page:
         desc_html: Коли увімкнено, будуть показані пости з усього відомого федисвіту у передпоказі. Інакше будуть показані локальні пости.
         title: Показувати доступний федисвіт у передпоказі фіду
@@ -412,13 +398,11 @@ uk:
     logout: Вийти
     migrate_account: Переїхати до іншого аккаунту
     migrate_account_html: Якщо ви бажаєте, щоб відвідувачі цього акканту були перенаправлені до іншого, ви можете <a href="%{path}">налаштувати це тут</a>.
-    or: або
     or_log_in_with: Або увійдіть з
     providers:
       cas: CAS
       saml: SAML
     register: Зареєструватися
-    register_elsewhere: Зареєструватися на іншому сервері
     resend_confirmation: Повторно відправити інструкції з підтвердження
     reset_password: Скинути пароль
     security: Зміна паролю
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index e482e9c41..f91cef4a4 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -7,7 +7,6 @@ zh-CN:
     administered_by: 本实例的管理员:
     api: API
     apps: 移动应用
-    closed_registrations: 这个实例目前没有开放注册。不过,你可以前往其他实例注册一个帐户,同样可以加入到这个网络中哦!
     contact: 联系方式
     contact_missing: 未设定
     contact_unavailable: 未公开
@@ -15,19 +14,9 @@ zh-CN:
     extended_description_html: |
       <h3>这里可以写一些规定</h3>
       <p>本站尚未设置详细介绍。</p>
-    features:
-      humane_approach_body: Mastodon 从其他网络的失败经验中汲取了教训,致力于在与错误的社交媒体使用方式的斗争中做出符合伦理化设计的选择。
-      humane_approach_title: 更加以人为本
-      not_a_product_body: Mastodon 绝非一个商业网络。这里既没有广告,也没有数据挖掘,更没有围墙花园。中心机构在这里不复存在。
-      not_a_product_title: 作为用户,你并非一件商品
-      real_conversation_body: Mastodon 有着高达 500 字的字数限制,以及对内容的细化控制和媒体警告提示的支持,只为让你能够畅所欲言。
-      real_conversation_title: 为真正的交流而生
-      within_reach_body: 通过一个面向开发者友好的 API 生态系统,Mastodon 让你可以随时随地通过众多 iOS、Android 以及其他平台的应用与朋友们保持联系。
-      within_reach_title: 始终触手可及
     generic_description: "%{domain} 是这个庞大网络中的一台服务器"
     hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例
     learn_more: 了解详情
-    other_instances: 其他实例
     privacy_policy: 隐私政策
     source_code: 源代码
     status_count_after: 条嘟文
@@ -351,9 +340,6 @@ zh-CN:
         min_invite_role:
           disabled: 没有人
           title: 允许发送邀请的用户组
-        open:
-          desc_html: 允许所有人建立帐户
-          title: 开放注册
       show_known_fediverse_at_about_page:
         desc_html: 启用此选项将会在预览中显示来自已知实例的嘟文,否则只会显示本站时间轴的内容.
         title: 在时间轴预览中显示已知实例
@@ -430,13 +416,11 @@ zh-CN:
     logout: 登出
     migrate_account: 迁移到另一个帐户
     migrate_account_html: 如果你希望引导他人关注另一个帐户,请<a href="%{path}">点击这里进行设置</a>。
-    or: 或者
     or_log_in_with: 或通过其他方式登录
     providers:
       cas: CAS
       saml: SAML
     register: 注册
-    register_elsewhere: 前往其他实例注册
     resend_confirmation: 重新发送确认邮件
     reset_password: 重置密码
     security: 帐户安全
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index 737ca000c..a2cfe56a9 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -5,26 +5,15 @@ zh-HK:
     about_mastodon_html: Mastodon(萬象)是<em>自由、開源</em>的社交網絡。服務站<em>各自獨立而互連</em>,避免單一商業機構壟斷。找你所信任的服務站,建立帳號,你即可與任何服務站上的用戶溝通,享受無縫的<em>網絡交流</em>。
     about_this: 關於本服務站
     administered_by: 管理者:
-    closed_registrations: 本服務站暫時停止接受登記。
     contact: 聯絡
     contact_missing: 未設定
     contact_unavailable: 未公開
     extended_description_html: |
       <h3>這裡可以寫一些網站規則</h3>
       <p>本站未有詳細介紹</p>
-    features:
-      humane_approach_body: Mastodon 從其他網絡的失敗經驗中汲取教訓,以合乎道德的設計對抗社交媒體的濫用問題。
-      humane_approach_title: 以人為本
-      not_a_product_body: Mastodon 不是商業網絡。沒有廣告,沒有數據挖掘,也沒有中央機構操控。平台完全開放。
-      not_a_product_title: 你是用戶,不是商品
-      real_conversation_body: Mastodon 的字數限制高達 500 字,並支援仔細的媒體警告選項,令你暢所欲言。
-      real_conversation_title: 為真正的交流而生
-      within_reach_body: 簡易的 API 系統,令用戶可以透過不同的 iOS、Android 及其他平台的應用軟件,與朋友保持聯繫。
-      within_reach_title: 無處不在
     generic_description: "%{domain} 是 Mastodon 網絡中其中一個服務站"
     hosted_on: 在 %{domain} 運作的 Mastodon 服務站
     learn_more: 了解更多
-    other_instances: 其他服務站
     source_code: 源代碼
     status_count_after: 篇文章
     status_count_before: 他們共發佈了
@@ -299,9 +288,6 @@ zh-HK:
         min_invite_role:
           disabled: 沒有人
           title: 允許發送邀請的身份
-        open:
-          desc_html: 允許所有人建立帳戶
-          title: 開放註冊
       show_known_fediverse_at_about_page:
         desc_html: 如果開啟,就會在時間軸預覽顯示跨站文章,否則就只會顯示本站文章。
         title: 在時間軸預覽顯示跨站文章
@@ -378,13 +364,11 @@ zh-HK:
     logout: 登出
     migrate_account: 轉移到另一個帳號
     migrate_account_html: 想要將這個帳號指向另一個帳號可<a href="%{path}">到這裡設定</a>。
-    or: 或
     or_log_in_with: 或登入於
     providers:
       cas: CAS
       saml: SAML
     register: 登記
-    register_elsewhere: 在其他服務站登記
     resend_confirmation: 重發確認指示電郵
     reset_password: 重設密碼
     security: 登入資訊
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index f4bda0f34..4498eff95 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -7,7 +7,6 @@ zh-TW:
     administered_by: 管理者:
     api: API
     apps: Mobile apps
-    closed_registrations: 本站暫時停止接受註冊。
     contact: 聯絡我們
     contact_missing: 未設定
     contact_unavailable: 未公開
@@ -15,19 +14,9 @@ zh-TW:
     extended_description_html: |
       <h3>這裡可以寫一些網站規則</h3>
       <p>本站點未有詳細介紹</p>
-    features:
-      humane_approach_body: Mastodon 從其他網路的失敗經驗中汲取教訓,以合乎道德的設計對抗社交媒體的濫用問題。
-      humane_approach_title: 以人為本
-      not_a_product_body: Mastodon 不是商業網站。沒有廣告,沒有數據挖掘,也沒有中央機構操控。平台完全開放。
-      not_a_product_title: 你是用戶,不是商品
-      real_conversation_body: Mastodon 的字數限制高達 500 字,並支援仔細的媒體警告選項,令你暢所欲言。
-      real_conversation_title: 為真正的交流而生
-      within_reach_body: 簡易的 API 系統,令用戶可以透過不同的 iOS、Android 及其他平台的應用軟體,與朋友保持聯繫。
-      within_reach_title: 始終觸手可及
     generic_description: "%{domain} 是 Mastodon 網路中其中一個站點"
     hosted_on: 在 %{domain} 運作的 Mastodon 站點
     learn_more: 了解詳細
-    other_instances: 其他站點
     source_code: 原始碼
     status_count_after: 狀態
     status_count_before: 他們共嘟出了
@@ -304,9 +293,6 @@ zh-TW:
         min_invite_role:
           disabled: 沒有人
           title: 允許發送邀請的身份
-        open:
-          desc_html: 允許所有人建立帳戶
-          title: 開放註冊
       show_known_fediverse_at_about_page:
         desc_html: 如果開啟,就會在時間軸預覽顯示其他站點嘟文,否則就只會顯示本站點嘟文。
         title: 在時間軸預覽顯示其他站點嘟文
@@ -383,13 +369,11 @@ zh-TW:
     logout: 登出
     migrate_account: 轉移到另一個帳號
     migrate_account_html: 如果你希望引導他人關注另一個帳戶,請<a href="%{path}">到這裡設定</a>。
-    or: 或
     or_log_in_with: 或透過其他方式登入
     providers:
       cas: CAS
       saml: SAML
     register: 註冊
-    register_elsewhere: 在其他站點註冊
     resend_confirmation: 重新寄送E-mail
     reset_password: 重設密碼
     security: 登入資訊
diff --git a/config/routes.rb b/config/routes.rb
index 09bcf8b12..7a7d0bc01 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -133,6 +133,7 @@ Rails.application.routes.draw do
   resources :invites, only: [:index, :create, :destroy]
   resources :filters, except: [:show]
 
+  get '/public', to: 'public_timelines#show', as: :public_timeline
   get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy
 
   # Remote follow
@@ -189,6 +190,8 @@ Rails.application.routes.draw do
         post :remove_avatar
         post :remove_header
         post :memorialize
+        post :approve
+        post :reject
       end
 
       resource :change_email, only: [:show, :update]
diff --git a/config/settings.yml b/config/settings.yml
index af596b738..c4359f573 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -9,7 +9,7 @@ defaults: &defaults
   site_terms: ''
   site_contact_username: ''
   site_contact_email: ''
-  open_registrations: true
+  registrations_mode: 'open'
   profile_directory: true
   closed_registrations_message: ''
   open_deletion: true
diff --git a/db/migrate/20190307234537_add_approved_to_users.rb b/db/migrate/20190307234537_add_approved_to_users.rb
new file mode 100644
index 000000000..c57a66dbc
--- /dev/null
+++ b/db/migrate/20190307234537_add_approved_to_users.rb
@@ -0,0 +1,23 @@
+require Rails.root.join('lib', 'mastodon', 'migration_helpers')
+
+class AddApprovedToUsers < ActiveRecord::Migration[5.2]
+  include Mastodon::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def up
+    safety_assured do
+      add_column_with_default(
+        :users,
+        :approved,
+        :bool,
+        allow_null: false,
+        default: true
+      )
+    end
+  end
+
+  def down
+    remove_column :users, :approved
+  end
+end
diff --git a/db/migrate/20190314181829_migrate_open_registrations_setting.rb b/db/migrate/20190314181829_migrate_open_registrations_setting.rb
new file mode 100644
index 000000000..e5fe95009
--- /dev/null
+++ b/db/migrate/20190314181829_migrate_open_registrations_setting.rb
@@ -0,0 +1,15 @@
+class MigrateOpenRegistrationsSetting < ActiveRecord::Migration[5.2]
+  def up
+    open_registrations = Setting.find_by(var: 'open_registrations')
+    return if open_registrations.nil? || open_registrations.value
+    setting = Setting.where(var: 'registrations_mode').first_or_initialize(var: 'registrations_mode')
+    setting.update(value: 'none')
+  end
+
+  def down
+    registrations_mode = Setting.find_by(var: 'registrations_mode')
+    return if registrations_mode.nil?
+    setting = Setting.where(var: 'open_registrations').first_or_initialize(var: 'open_registrations')
+    setting.update(value: registrations_mode.value == 'open')
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 858cd1e9b..ad8b56d2e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2019_03_06_145741) do
+ActiveRecord::Schema.define(version: 2019_03_14_181829) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -711,6 +711,7 @@ ActiveRecord::Schema.define(version: 2019_03_06_145741) do
     t.string "remember_token"
     t.string "chosen_languages", array: true
     t.bigint "created_by_application_id"
+    t.boolean "approved", default: true, null: false
     t.index ["account_id"], name: "index_users_on_account_id"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id"
diff --git a/db/seeds.rb b/db/seeds.rb
index cf62ebf39..9a6e9dd78 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -4,5 +4,5 @@ if Rails.env.development?
   domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
   admin  = Account.where(username: 'admin').first_or_initialize(username: 'admin')
   admin.save(validate: false)
-  User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true).save!
+  User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true).save!
 end
diff --git a/lib/cli.rb b/lib/cli.rb
index 6036adfbe..59f0f3076 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -6,6 +6,7 @@ require_relative 'mastodon/emoji_cli'
 require_relative 'mastodon/accounts_cli'
 require_relative 'mastodon/feeds_cli'
 require_relative 'mastodon/settings_cli'
+require_relative 'mastodon/statuses_cli'
 require_relative 'mastodon/domains_cli'
 require_relative 'mastodon/version'
 
@@ -30,6 +31,9 @@ module Mastodon
     desc 'settings SUBCOMMAND ...ARGS', 'Manage dynamic settings'
     subcommand 'settings', Mastodon::SettingsCLI
 
+    desc 'statuses SUBCOMMAND ...ARGS', 'Manage statuses'
+    subcommand 'statuses', Mastodon::StatusesCLI
+
     desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
     subcommand 'domains', Mastodon::DomainsCLI
 
diff --git a/lib/mastodon/settings_cli.rb b/lib/mastodon/settings_cli.rb
index c81cfbe52..061650a80 100644
--- a/lib/mastodon/settings_cli.rb
+++ b/lib/mastodon/settings_cli.rb
@@ -12,13 +12,13 @@ module Mastodon
 
     desc 'open', 'Open registrations'
     def open
-      Setting.open_registrations = true
+      Setting.registrations_mode = 'open'
       say('OK', :green)
     end
 
     desc 'close', 'Close registrations'
     def close
-      Setting.open_registrations = false
+      Setting.registrations_mode = 'none'
       say('OK', :green)
     end
   end
diff --git a/lib/mastodon/statuses_cli.rb b/lib/mastodon/statuses_cli.rb
new file mode 100644
index 000000000..5881ba260
--- /dev/null
+++ b/lib/mastodon/statuses_cli.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+  class StatusesCLI < Thor
+    include ActionView::Helpers::NumberHelper
+
+    def self.exit_on_failure?
+      true
+    end
+
+    option :days, type: :numeric, default: 90
+    desc 'remove', 'Remove statuses'
+    def remove
+      say('Creating temporary database indices...')
+
+      ActiveRecord::Base.connection.add_index(:accounts, :id, name: :index_accounts_local, where: 'domain is null', algorithm: :concurrently) unless ActiveRecord::Base.connection.index_name_exists?(:accounts, :index_accounts_local)
+      ActiveRecord::Base.connection.add_index(:status_pins, :status_id, name: :index_status_pins_status_id, algorithm: :concurrently) unless ActiveRecord::Base.connection.index_name_exists?(:status_pins, :index_status_pins_status_id)
+      ActiveRecord::Base.connection.add_index(:media_attachments, :remote_url, name: :index_media_attachments_remote_url, where: 'remote_url is not null', algorithm: :concurrently) unless ActiveRecord::Base.connection.index_name_exists?(:media_attachments, :index_media_attachments_remote_url)
+
+      max_id   = Mastodon::Snowflake.id_at(options[:days].days.ago)
+      start_at = Time.now.to_f
+
+      say('Beginning removal... This might take a while...')
+
+      Status.remote
+            .where('id < ?', max_id)
+            .where(reblog_of_id: nil)                                                                                                                                                                                              # Skip reblogs
+            .where(in_reply_to_id: nil)                                                                                                                                                                                            # Skip replies
+            .where('id NOT IN (SELECT status_pins.status_id FROM status_pins WHERE statuses.id = status_id)')                                                                                                                      # Skip statuses that are pinned on profiles
+            .where('id NOT IN (SELECT mentions.status_id FROM mentions WHERE statuses.id = mentions.status_id AND mentions.account_id IN (SELECT accounts.id FROM accounts WHERE domain IS NULL))')                                # Skip statuses that mention local accounts
+            .where('id NOT IN (SELECT statuses1.in_reply_to_id FROM statuses AS statuses1 WHERE statuses.id = statuses1.in_reply_to_id)')                                                                                          # Skip statuses favourited by local accounts
+            .where('id NOT IN (SELECT statuses1.reblog_of_id FROM statuses AS statuses1 WHERE statuses.id = statuses1.reblog_of_id AND statuses1.account_id IN (SELECT accounts.id FROM accounts WHERE accounts.domain IS NULL))') # Skip statuses reblogged by local accounts
+            .where('account_id NOT IN (SELECT follows.target_account_id FROM follows WHERE statuses.account_id = follows.target_account_id)')                                                                                      # Skip accounts followed by local accounts
+            .in_batches
+            .delete_all
+
+      say('Beginning removal of now-orphaned media attachments to free up disk space...')
+
+      Scheduler::MediaCleanupScheduler.new.perform
+
+      say("Done after #{Time.now.to_f - start_at}s", :green)
+    ensure
+      say('Removing temporary database indices to restore write performance...')
+
+      ActiveRecord::Base.connection.remove_index(:accounts, name: :index_accounts_local) if ActiveRecord::Base.connection.index_name_exists?(:accounts, :index_accounts_local)
+      ActiveRecord::Base.connection.remove_index(:status_pins, name: :index_status_pins_status_id) if ActiveRecord::Base.connection.index_name_exists?(:status_pins, :index_status_pins_status_id)
+      ActiveRecord::Base.connection.remove_index(:media_attachments, name: :index_media_attachments_remote_url) if ActiveRecord::Base.connection.index_name_exists?(:media_attachments, :index_media_attachments_remote_url)
+    end
+  end
+end
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 3ba5d8aec..b728d719f 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe AccountsController, type: :controller do
   render_views
 
-  let(:alice) { Fabricate(:account, username: 'alice') }
+  let(:alice) { Fabricate(:account, username: 'alice', user: Fabricate(:user)) }
   let(:eve) { Fabricate(:user) }
 
   describe 'GET #show' do
diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb
index eaf99679a..34f6bbdae 100644
--- a/spec/controllers/admin/settings_controller_spec.rb
+++ b/spec/controllers/admin/settings_controller_spec.rb
@@ -62,22 +62,6 @@ RSpec.describe Admin::SettingsController, type: :controller do
           expect(Setting.site_title).to eq 'New title'
         end
       end
-
-      context do
-        around do |example|
-          open_registrations = Setting.open_registrations
-          example.run
-          Setting.open_registrations = open_registrations
-        end
-
-        it 'typecasts open_registrations to boolean' do
-          Setting.open_registrations = false
-          patch :update, params: { form_admin_settings: { open_registrations: '1' } }
-
-          expect(response).to redirect_to(edit_admin_settings_path)
-          expect(Setting.open_registrations).to eq true
-        end
-      end
     end
   end
 end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index eeb01d5ad..1095df034 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -5,14 +5,14 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
 
   shared_examples 'checks for enabled registrations' do |path|
     around do |example|
-      open_registrations = Setting.open_registrations
+      registrations_mode = Setting.registrations_mode
       example.run
-      Setting.open_registrations = open_registrations
+      Setting.registrations_mode = registrations_mode
     end
 
     it 'redirects if it is in single user mode while it is open for registration' do
       Fabricate(:account)
-      Setting.open_registrations = true
+      Setting.registrations_mode = 'open'
       expect(Rails.configuration.x).to receive(:single_user_mode).and_return(true)
 
       get path
@@ -21,7 +21,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
     end
 
     it 'redirects if it is not open for registration while it is not in single user mode' do
-      Setting.open_registrations = false
+      Setting.registrations_mode = 'none'
       expect(Rails.configuration.x).to receive(:single_user_mode).and_return(false)
 
       get path
@@ -55,13 +55,13 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
 
     context do
       around do |example|
-        open_registrations = Setting.open_registrations
+        registrations_mode = Setting.registrations_mode
         example.run
-        Setting.open_registrations = open_registrations
+        Setting.registrations_mode = registrations_mode
       end
 
       it 'returns http success' do
-        Setting.open_registrations = true
+        Setting.registrations_mode = 'open'
         get :new
         expect(response).to have_http_status(200)
       end
@@ -83,13 +83,13 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
 
     context do
       around do |example|
-        open_registrations = Setting.open_registrations
+        registrations_mode = Setting.registrations_mode
         example.run
-        Setting.open_registrations = open_registrations
+        Setting.registrations_mode = registrations_mode
       end
 
       subject do
-        Setting.open_registrations = true
+        Setting.registrations_mode = 'open'
         request.headers["Accept-Language"] = accept_language
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
       end
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index 93685103f..ea2b4a2a1 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -17,7 +17,15 @@ describe ApplicationController, type: :controller do
 
   context 'when account is suspended' do
     it 'returns http gone' do
-      account = Fabricate(:account, suspended: true)
+      account = Fabricate(:account, suspended: true, user: Fabricate(:user))
+      get 'success', params: { account_username: account.username }
+      expect(response).to have_http_status(410)
+    end
+  end
+
+  context 'when account is deleted by owner' do
+    it 'returns http gone' do
+      account = Fabricate(:account, suspended: true, user: nil)
       get 'success', params: { account_username: account.username }
       expect(response).to have_http_status(410)
     end
@@ -25,19 +33,19 @@ describe ApplicationController, type: :controller do
 
   context 'when account is not suspended' do
     it 'assigns @account' do
-      account = Fabricate(:account)
+      account = Fabricate(:account, user: Fabricate(:user))
       get 'success', params: { account_username: account.username }
       expect(assigns(:account)).to eq account
     end
 
     it 'sets link headers' do
-      account = Fabricate(:account, username: 'username')
+      account = Fabricate(:account, username: 'username', user: Fabricate(:user))
       get 'success', params: { account_username: 'username' }
       expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/xrd+xml", <http://test.host/users/username.atom>; rel="alternate"; type="application/atom+xml", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
     end
 
     it 'returns http success' do
-      account = Fabricate(:account)
+      account = Fabricate(:account, user: Fabricate(:user))
       get 'success', params: { account_username: account.username }
       expect(response).to have_http_status(200)
     end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 61780b46b..f09e32ecc 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -69,7 +69,7 @@ describe ApplicationHelper do
   describe 'open_registrations?' do
     it 'returns true when open for registrations' do
       without_partial_double_verification do
-        expect(Setting).to receive(:open_registrations).and_return(true)
+        expect(Setting).to receive(:registrations_mode).and_return('open')
       end
 
       expect(helper.open_registrations?).to eq true
@@ -77,7 +77,7 @@ describe ApplicationHelper do
 
     it 'returns false when closed for registrations' do
       without_partial_double_verification do
-        expect(Setting).to receive(:open_registrations).and_return(false)
+        expect(Setting).to receive(:registrations_mode).and_return('none')
       end
 
       expect(helper.open_registrations?).to eq false
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index a43421b76..379872316 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -558,6 +558,11 @@ RSpec.describe Account, type: :model do
       expect(account).to model_have_error_on_field(:username)
     end
 
+    it 'squishes the username before validation' do
+      account = Fabricate(:account, domain: nil, username: " \u3000bob \t \u00a0 \n ")
+      expect(account.username).to eq 'bob'
+    end
+
     context 'when is local' do
       it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
         account_1 = Fabricate(:account, username: 'the_doctor')
diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb
index ccc558f71..81d8d0e98 100644
--- a/spec/presenters/instance_presenter_spec.rb
+++ b/spec/presenters/instance_presenter_spec.rb
@@ -33,34 +33,6 @@ describe InstancePresenter do
 
   context do
     around do |example|
-      open_registrations = Setting.open_registrations
-      example.run
-      Setting.open_registrations = open_registrations
-    end
-
-    it "delegates open_registrations to Setting" do
-      Setting.open_registrations = false
-
-      expect(instance_presenter.open_registrations).to eq false
-    end
-  end
-
-  context do
-    around do |example|
-      closed_registrations_message = Setting.closed_registrations_message
-      example.run
-      Setting.closed_registrations_message = closed_registrations_message
-    end
-
-    it "delegates closed_registrations_message to Setting" do
-      Setting.closed_registrations_message = "Closed message"
-
-      expect(instance_presenter.closed_registrations_message).to eq "Closed message"
-    end
-  end
-
-  context do
-    around do |example|
       site_contact_email = Setting.site_contact_email
       example.run
       Setting.site_contact_email = site_contact_email
diff --git a/spec/requests/localization_spec.rb b/spec/requests/localization_spec.rb
index f625a93a4..496a885e8 100644
--- a/spec/requests/localization_spec.rb
+++ b/spec/requests/localization_spec.rb
@@ -11,8 +11,9 @@ describe 'Localization' do
     headers = { 'Accept-Language' => 'zh-HK' }
 
     get "/about", headers: headers
+
     expect(response.body).to include(
-      I18n.t('about.about_mastodon_html', locale: 'zh-HK')
+      I18n.t('about.tagline', locale: 'zh-HK')
     )
   end
 
@@ -20,16 +21,18 @@ describe 'Localization' do
     headers = { 'Accept-Language' => 'es-FAKE' }
 
     get "/about", headers: headers
+
     expect(response.body).to include(
-      I18n.t('about.about_mastodon_html', locale: 'es')
+      I18n.t('about.tagline', locale: 'es')
     )
   end
   it 'falls back to english when locale is missing' do
     headers = { 'Accept-Language' => '12-FAKE' }
 
     get "/about", headers: headers
+
     expect(response.body).to include(
-      I18n.t('about.about_mastodon_html', locale: 'en')
+      I18n.t('about.tagline', locale: 'en')
     )
   end
 end
diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb
index d480df348..7948bb53b 100644
--- a/spec/services/app_sign_up_service_spec.rb
+++ b/spec/services/app_sign_up_service_spec.rb
@@ -8,8 +8,10 @@ RSpec.describe AppSignUpService, type: :service do
 
   describe '#call' do
     it 'returns nil when registrations are closed' do
-      Setting.open_registrations = false
+      tmp = Setting.registrations_mode
+      Setting.registrations_mode = 'none'
       expect(subject.call(app, good_params)).to be_nil
+      Setting.registrations_mode = tmp
     end
 
     it 'raises an error when params are missing' do
diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb
index 65124fcf2..26b131977 100644
--- a/spec/views/about/show.html.haml_spec.rb
+++ b/spec/views/about/show.html.haml_spec.rb
@@ -8,24 +8,30 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
   before do
     allow(view).to receive(:site_hostname).and_return('example.com')
     allow(view).to receive(:site_title).and_return('example site')
+    allow(view).to receive(:new_user).and_return(User.new)
+    allow(view).to receive(:use_seamless_external_login?).and_return(false)
   end
 
   it 'has valid open graph tags' do
-    instance_presenter = double(:instance_presenter,
-                                site_title: 'something',
-                                site_short_description: 'something',
-                                site_description: 'something',
-                                version_number: '1.0',
-                                source_url: 'https://github.com/tootsuite/mastodon',
-                                open_registrations: false,
-                                thumbnail: nil,
-                                hero: nil,
-                                mascot: nil,
-                                user_count: 0,
-                                status_count: 0,
-                                commit_hash: commit_hash,
-                                contact_account: nil,
-                                closed_registrations_message: 'yes')
+    instance_presenter = double(
+      :instance_presenter,
+      site_title: 'something',
+      site_short_description: 'something',
+      site_description: 'something',
+      version_number: '1.0',
+      source_url: 'https://github.com/tootsuite/mastodon',
+      open_registrations: false,
+      thumbnail: nil,
+      hero: nil,
+      mascot: nil,
+      user_count: 420,
+      status_count: 69,
+      active_user_count: 420,
+      commit_hash: commit_hash,
+      contact_account: nil,
+      sample_accounts: []
+    )
+
     assign(:instance_presenter, instance_presenter)
     render
 
diff --git a/streaming/index.js b/streaming/index.js
index bf7218e8a..d4fb8cad3 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -105,7 +105,8 @@ const startWorker = (workerId) => {
     pgConfigs.development.ssl = true;
     pgConfigs.production.ssl  = true;
   }
-  const app    = express();
+
+  const app = express();
   app.set('trusted proxy', process.env.TRUSTED_PROXY_IP || 'loopback,uniquelocal');
 
   const pgPool = new pg.Pool(Object.assign(pgConfigs[env], dbUrlToConfig(process.env.DATABASE_URL)));