about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.env.production.sample10
-rw-r--r--.nvmrc2
-rw-r--r--app/controllers/admin/announcements_controller.rb69
-rw-r--r--app/controllers/admin/followers_controller.rb18
-rw-r--r--app/controllers/admin/relationships_controller.rb25
-rw-r--r--app/controllers/api/base_controller.rb2
-rw-r--r--app/controllers/api/oembed_controller.rb14
-rw-r--r--app/controllers/api/v1/announcements/reactions_controller.rb29
-rw-r--r--app/controllers/api/v1/announcements_controller.rb33
-rw-r--r--app/controllers/auth/passwords_controller.rb6
-rw-r--r--app/controllers/auth/registrations_controller.rb7
-rw-r--r--app/controllers/relationships_controller.rb46
-rw-r--r--app/controllers/statuses_controller.rb4
-rw-r--r--app/helpers/admin/action_logs_helper.rb8
-rw-r--r--app/helpers/admin/announcements_helper.rb11
-rw-r--r--app/helpers/admin/filter_helper.rb1
-rw-r--r--app/javascript/flavours/glitch/actions/announcements.js133
-rw-r--r--app/javascript/flavours/glitch/actions/importer/normalizer.js10
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js3
-rw-r--r--app/javascript/flavours/glitch/actions/streaming.js11
-rw-r--r--app/javascript/flavours/glitch/actions/timelines.js2
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/directory/components/account_card.js1
-rw-r--r--app/javascript/flavours/glitch/features/emoji_picker/index.js7
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/announcements.js395
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js21
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/media_modal.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/announcements.js72
-rw-r--r--app/javascript/flavours/glitch/reducers/index.js2
-rw-r--r--app/javascript/flavours/glitch/selectors/index.js1
-rw-r--r--app/javascript/flavours/glitch/styles/components/announcements.scss212
-rw-r--r--app/javascript/flavours/glitch/styles/components/composer.scss22
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss1
-rw-r--r--app/javascript/flavours/glitch/styles/forms.scss6
-rw-r--r--app/javascript/flavours/glitch/util/stream.js54
-rw-r--r--app/javascript/images/elephant_ui_plane.svg2
-rw-r--r--app/javascript/mastodon/actions/announcements.js133
-rw-r--r--app/javascript/mastodon/actions/importer/normalizer.js10
-rw-r--r--app/javascript/mastodon/actions/notifications.js3
-rw-r--r--app/javascript/mastodon/actions/streaming.js11
-rw-r--r--app/javascript/mastodon/actions/timelines.js2
-rw-r--r--app/javascript/mastodon/components/error_boundary.js2
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js1
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js7
-rw-r--r--app/javascript/mastodon/features/directory/components/account_card.js1
-rw-r--r--app/javascript/mastodon/features/getting_started/components/announcements.js395
-rw-r--r--app/javascript/mastodon/features/getting_started/containers/announcements_container.js21
-rw-r--r--app/javascript/mastodon/features/getting_started/containers/trends_container.js2
-rw-r--r--app/javascript/mastodon/features/home_timeline/index.js3
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js1
-rw-r--r--app/javascript/mastodon/locales/ar.json7
-rw-r--r--app/javascript/mastodon/locales/ast.json3
-rw-r--r--app/javascript/mastodon/locales/bg.json1
-rw-r--r--app/javascript/mastodon/locales/bn.json1
-rw-r--r--app/javascript/mastodon/locales/br.json1
-rw-r--r--app/javascript/mastodon/locales/ca.json5
-rw-r--r--app/javascript/mastodon/locales/co.json1
-rw-r--r--app/javascript/mastodon/locales/cs.json5
-rw-r--r--app/javascript/mastodon/locales/cy.json7
-rw-r--r--app/javascript/mastodon/locales/da.json1
-rw-r--r--app/javascript/mastodon/locales/de.json7
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json8
-rw-r--r--app/javascript/mastodon/locales/el.json9
-rw-r--r--app/javascript/mastodon/locales/en.json1
-rw-r--r--app/javascript/mastodon/locales/eo.json1
-rw-r--r--app/javascript/mastodon/locales/es-AR.json1
-rw-r--r--app/javascript/mastodon/locales/es.json13
-rw-r--r--app/javascript/mastodon/locales/et.json1
-rw-r--r--app/javascript/mastodon/locales/eu.json7
-rw-r--r--app/javascript/mastodon/locales/fa.json7
-rw-r--r--app/javascript/mastodon/locales/fi.json1
-rw-r--r--app/javascript/mastodon/locales/fr.json1
-rw-r--r--app/javascript/mastodon/locales/ga.json1
-rw-r--r--app/javascript/mastodon/locales/gl.json81
-rw-r--r--app/javascript/mastodon/locales/he.json1
-rw-r--r--app/javascript/mastodon/locales/hi.json1
-rw-r--r--app/javascript/mastodon/locales/hr.json1
-rw-r--r--app/javascript/mastodon/locales/hu.json7
-rw-r--r--app/javascript/mastodon/locales/hy.json19
-rw-r--r--app/javascript/mastodon/locales/id.json1
-rw-r--r--app/javascript/mastodon/locales/io.json1
-rw-r--r--app/javascript/mastodon/locales/is.json9
-rw-r--r--app/javascript/mastodon/locales/it.json3
-rw-r--r--app/javascript/mastodon/locales/ja.json1
-rw-r--r--app/javascript/mastodon/locales/ka.json1
-rw-r--r--app/javascript/mastodon/locales/kab.json193
-rw-r--r--app/javascript/mastodon/locales/kk.json1
-rw-r--r--app/javascript/mastodon/locales/kn.json1
-rw-r--r--app/javascript/mastodon/locales/ko.json1
-rw-r--r--app/javascript/mastodon/locales/lt.json1
-rw-r--r--app/javascript/mastodon/locales/lv.json1
-rw-r--r--app/javascript/mastodon/locales/mk.json1
-rw-r--r--app/javascript/mastodon/locales/ml.json1
-rw-r--r--app/javascript/mastodon/locales/mr.json1
-rw-r--r--app/javascript/mastodon/locales/ms.json1
-rw-r--r--app/javascript/mastodon/locales/nl.json1
-rw-r--r--app/javascript/mastodon/locales/nn.json5
-rw-r--r--app/javascript/mastodon/locales/no.json1
-rw-r--r--app/javascript/mastodon/locales/oc.json1
-rw-r--r--app/javascript/mastodon/locales/pl.json1
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json1
-rw-r--r--app/javascript/mastodon/locales/pt-PT.json1
-rw-r--r--app/javascript/mastodon/locales/ro.json1
-rw-r--r--app/javascript/mastodon/locales/ru.json9
-rw-r--r--app/javascript/mastodon/locales/sk.json7
-rw-r--r--app/javascript/mastodon/locales/sl.json1
-rw-r--r--app/javascript/mastodon/locales/sq.json1
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json1
-rw-r--r--app/javascript/mastodon/locales/sr.json1
-rw-r--r--app/javascript/mastodon/locales/sv.json7
-rw-r--r--app/javascript/mastodon/locales/ta.json3
-rw-r--r--app/javascript/mastodon/locales/te.json1
-rw-r--r--app/javascript/mastodon/locales/th.json5
-rw-r--r--app/javascript/mastodon/locales/tr.json1
-rw-r--r--app/javascript/mastodon/locales/uk.json1
-rw-r--r--app/javascript/mastodon/locales/ur.json1
-rw-r--r--app/javascript/mastodon/locales/vi.json1
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json1
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json1
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json1
-rw-r--r--app/javascript/mastodon/reducers/announcements.js72
-rw-r--r--app/javascript/mastodon/reducers/index.js2
-rw-r--r--app/javascript/mastodon/selectors/index.js1
-rw-r--r--app/javascript/mastodon/stream.js54
-rw-r--r--app/javascript/styles/mastodon/components.scss213
-rw-r--r--app/javascript/styles/mastodon/forms.scss6
-rw-r--r--app/lib/entity_cache.rb2
-rw-r--r--app/lib/formatter.rb4
-rw-r--r--app/lib/inline_renderer.rb4
-rw-r--r--app/lib/sanitize_config.rb2
-rw-r--r--app/middleware/handle_bad_encoding_middleware.rb18
-rw-r--r--app/models/account.rb6
-rw-r--r--app/models/announcement.rb85
-rw-r--r--app/models/announcement_filter.rb39
-rw-r--r--app/models/announcement_mute.rb19
-rw-r--r--app/models/announcement_reaction.rb37
-rw-r--r--app/models/backup.rb2
-rw-r--r--app/models/bookmark.rb6
-rw-r--r--app/models/concerns/account_interactions.rb1
-rw-r--r--app/models/custom_emoji.rb2
-rw-r--r--app/models/custom_filter.rb1
-rw-r--r--app/models/media_attachment.rb1
-rw-r--r--app/models/relationship_filter.rb109
-rw-r--r--app/models/user.rb2
-rw-r--r--app/policies/announcement_policy.rb19
-rw-r--r--app/serializers/rest/announcement_serializer.rb34
-rw-r--r--app/serializers/rest/reaction_serializer.rb31
-rw-r--r--app/validators/reaction_validator.rb17
-rw-r--r--app/views/accounts/show.html.haml2
-rw-r--r--app/views/admin/accounts/show.html.haml2
-rw-r--r--app/views/admin/announcements/_announcement.html.haml14
-rw-r--r--app/views/admin/announcements/edit.html.haml22
-rw-r--r--app/views/admin/announcements/index.html.haml30
-rw-r--r--app/views/admin/announcements/new.html.haml21
-rw-r--r--app/views/admin/followers/index.html.haml28
-rw-r--r--app/views/admin/relationships/index.html.haml39
-rw-r--r--app/workers/publish_announcement_reaction_worker.rb22
-rw-r--r--app/workers/publish_scheduled_announcement_worker.rb18
-rw-r--r--app/workers/scheduler/scheduled_statuses_scheduler.rb28
-rw-r--r--config/application.rb2
-rw-r--r--config/initializers/simple_form.rb2
-rw-r--r--config/initializers/twitter_regex.rb35
-rw-r--r--config/locales/ar.yml10
-rw-r--r--config/locales/ast.yml1
-rw-r--r--config/locales/ca.yml131
-rw-r--r--config/locales/co.yml3
-rw-r--r--config/locales/cs.yml3
-rw-r--r--config/locales/cy.yml6
-rw-r--r--config/locales/da.yml3
-rw-r--r--config/locales/de.yml30
-rw-r--r--config/locales/doorkeeper.ar.yml1
-rw-r--r--config/locales/doorkeeper.ast.yml1
-rw-r--r--config/locales/doorkeeper.ca.yml1
-rw-r--r--config/locales/doorkeeper.co.yml1
-rw-r--r--config/locales/doorkeeper.cs.yml1
-rw-r--r--config/locales/doorkeeper.de.yml1
-rw-r--r--config/locales/doorkeeper.el.yml1
-rw-r--r--config/locales/doorkeeper.eo.yml1
-rw-r--r--config/locales/doorkeeper.es-AR.yml1
-rw-r--r--config/locales/doorkeeper.es.yml3
-rw-r--r--config/locales/doorkeeper.et.yml1
-rw-r--r--config/locales/doorkeeper.eu.yml1
-rw-r--r--config/locales/doorkeeper.fa.yml1
-rw-r--r--config/locales/doorkeeper.fr.yml1
-rw-r--r--config/locales/doorkeeper.gl.yml41
-rw-r--r--config/locales/doorkeeper.hu.yml1
-rw-r--r--config/locales/doorkeeper.id.yml1
-rw-r--r--config/locales/doorkeeper.is.yml11
-rw-r--r--config/locales/doorkeeper.it.yml1
-rw-r--r--config/locales/doorkeeper.ja.yml3
-rw-r--r--config/locales/doorkeeper.kk.yml1
-rw-r--r--config/locales/doorkeeper.ko.yml1
-rw-r--r--config/locales/doorkeeper.pt-BR.yml1
-rw-r--r--config/locales/doorkeeper.pt-PT.yml1
-rw-r--r--config/locales/doorkeeper.ru.yml1
-rw-r--r--config/locales/doorkeeper.sv.yml1
-rw-r--r--config/locales/doorkeeper.th.yml1
-rw-r--r--config/locales/doorkeeper.tr.yml1
-rw-r--r--config/locales/el.yml5
-rw-r--r--config/locales/en.yml31
-rw-r--r--config/locales/eo.yml5
-rw-r--r--config/locales/es-AR.yml7
-rw-r--r--config/locales/es.yml31
-rw-r--r--config/locales/et.yml7
-rw-r--r--config/locales/eu.yml4
-rw-r--r--config/locales/fa.yml3
-rw-r--r--config/locales/fr.yml29
-rw-r--r--config/locales/gl.yml201
-rw-r--r--config/locales/hu.yml4
-rw-r--r--config/locales/id.yml7
-rw-r--r--config/locales/is.yml56
-rw-r--r--config/locales/it.yml4
-rw-r--r--config/locales/ja.yml8
-rw-r--r--config/locales/kab.yml8
-rw-r--r--config/locales/kk.yml3
-rw-r--r--config/locales/ko.yml4
-rw-r--r--config/locales/lt.yml3
-rw-r--r--config/locales/nl.yml3
-rw-r--r--config/locales/nn.yml3
-rw-r--r--config/locales/no.yml3
-rw-r--r--config/locales/oc.yml3
-rw-r--r--config/locales/pl.yml3
-rw-r--r--config/locales/pt-BR.yml7
-rw-r--r--config/locales/pt-PT.yml7
-rw-r--r--config/locales/ru.yml15
-rw-r--r--config/locales/simple_form.ar.yml4
-rw-r--r--config/locales/simple_form.ca.yml2
-rw-r--r--config/locales/simple_form.de.yml12
-rw-r--r--config/locales/simple_form.en.yml12
-rw-r--r--config/locales/simple_form.es.yml30
-rw-r--r--config/locales/simple_form.fr.yml12
-rw-r--r--config/locales/simple_form.gl.yml12
-rw-r--r--config/locales/simple_form.is.yml41
-rw-r--r--config/locales/simple_form.ja.yml4
-rw-r--r--config/locales/sk.yml4
-rw-r--r--config/locales/sl.yml3
-rw-r--r--config/locales/sq.yml3
-rw-r--r--config/locales/sr.yml3
-rw-r--r--config/locales/sv.yml3
-rw-r--r--config/locales/th.yml6
-rw-r--r--config/locales/tr.yml15
-rw-r--r--config/locales/uk.yml3
-rw-r--r--config/locales/vi.yml3
-rw-r--r--config/locales/zh-CN.yml3
-rw-r--r--config/locales/zh-HK.yml3
-rw-r--r--config/locales/zh-TW.yml3
-rw-r--r--config/navigation.rb1
-rw-r--r--config/routes.rb15
-rw-r--r--db/migrate/20191218153258_create_announcements.rb16
-rw-r--r--db/migrate/20200113125135_create_announcement_mutes.rb12
-rw-r--r--db/migrate/20200114113335_create_announcement_reactions.rb15
-rw-r--r--db/migrate/20200119112504_add_public_index_to_statuses.rb11
-rw-r--r--db/schema.rb44
-rw-r--r--lib/cli.rb2
-rw-r--r--lib/mastodon/version.rb6
-rw-r--r--lib/tasks/auto_annotate_models.rake1
-rw-r--r--spec/controllers/api/v1/announcements/reactions_controller_spec.rb65
-rw-r--r--spec/controllers/api/v1/announcements_controller_spec.rb59
-rw-r--r--spec/controllers/api/v1/trends_controller_spec.rb18
-rw-r--r--spec/fabricators/announcement_fabricator.rb6
-rw-r--r--spec/fabricators/announcement_mute_fabricator.rb4
-rw-r--r--spec/fabricators/announcement_reaction_fabricator.rb5
-rw-r--r--spec/fabricators/media_attachment_fabricator.rb18
-rw-r--r--spec/lib/formatter_spec.rb8
-rw-r--r--spec/middleware/handle_bad_encoding_middleware_spec.rb21
-rw-r--r--spec/models/announcement_mute_spec.rb4
-rw-r--r--spec/models/announcement_reaction_spec.rb4
-rw-r--r--spec/models/announcement_spec.rb4
-rw-r--r--spec/models/media_attachment_spec.rb13
-rw-r--r--spec/services/post_status_service_spec.rb8
272 files changed, 3841 insertions, 799 deletions
diff --git a/.env.production.sample b/.env.production.sample
index 6036095e4..f573a37de 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -275,3 +275,13 @@ STREAMING_CLUSTER_NUM=1
 # http_proxy=http://gateway.local:8118
 # Access control for hidden service.
 # ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
+
+# Authorized fetch mode (optional)
+# Require remote servers to authentify when fetching toots, see
+# https://docs.joinmastodon.org/admin/config/#authorized_fetch
+# AUTHORIZED_FETCH=true
+
+# Whitelist mode (optional)
+# Only allow federation with whitelisted domains, see
+# https://docs.joinmastodon.org/admin/config/#whitelist_mode
+# WHITELIST_MODE=true
diff --git a/.nvmrc b/.nvmrc
index 45a4fb75d..48082f72f 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-8
+12
diff --git a/app/controllers/admin/announcements_controller.rb b/app/controllers/admin/announcements_controller.rb
new file mode 100644
index 000000000..02198f0b5
--- /dev/null
+++ b/app/controllers/admin/announcements_controller.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+class Admin::AnnouncementsController < Admin::BaseController
+  before_action :set_announcements, only: :index
+  before_action :set_announcement, except: [:index, :new, :create]
+
+  def index
+    authorize :announcement, :index?
+  end
+
+  def new
+    authorize :announcement, :create?
+
+    @announcement = Announcement.new
+  end
+
+  def create
+    authorize :announcement, :create?
+
+    @announcement = Announcement.new(resource_params)
+
+    if @announcement.save
+      log_action :create, @announcement
+      redirect_to admin_announcements_path
+    else
+      render :new
+    end
+  end
+
+  def edit
+    authorize :announcement, :update?
+  end
+
+  def update
+    authorize :announcement, :update?
+
+    if @announcement.update(resource_params)
+      log_action :update, @announcement
+      redirect_to admin_announcements_path
+    else
+      render :edit
+    end
+  end
+
+  def destroy
+    authorize :announcement, :destroy?
+    @announcement.destroy!
+    log_action :destroy, @announcement
+    redirect_to admin_announcements_path
+  end
+
+  private
+
+  def set_announcements
+    @announcements = AnnouncementFilter.new(filter_params).results.page(params[:page])
+  end
+
+  def set_announcement
+    @announcement = Announcement.find(params[:id])
+  end
+
+  def filter_params
+    params.slice(*AnnouncementFilter::KEYS).permit(*AnnouncementFilter::KEYS)
+  end
+
+  def resource_params
+    params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
+  end
+end
diff --git a/app/controllers/admin/followers_controller.rb b/app/controllers/admin/followers_controller.rb
deleted file mode 100644
index d826f47c5..000000000
--- a/app/controllers/admin/followers_controller.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
-  class FollowersController < BaseController
-    before_action :set_account
-
-    PER_PAGE = 40
-
-    def index
-      authorize :account, :index?
-      @followers = @account.followers.local.recent.page(params[:page]).per(PER_PAGE)
-    end
-
-    def set_account
-      @account = Account.find(params[:account_id])
-    end
-  end
-end
diff --git a/app/controllers/admin/relationships_controller.rb b/app/controllers/admin/relationships_controller.rb
new file mode 100644
index 000000000..f8a95cfc8
--- /dev/null
+++ b/app/controllers/admin/relationships_controller.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Admin
+  class RelationshipsController < BaseController
+    before_action :set_account
+
+    PER_PAGE = 40
+
+    def index
+      authorize :account, :index?
+
+      @accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
+    end
+
+    private
+
+    def set_account
+      @account = Account.find(params[:account_id])
+    end
+
+    def filter_params
+      params.slice(*RelationshipFilter::KEYS).permit(*RelationshipFilter::KEYS)
+    end
+  end
+end
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 144fdd6ac..68bf425f4 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -85,7 +85,7 @@ class Api::BaseController < ApplicationController
   end
 
   def require_authenticated_user!
-    render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
+    render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
   end
 
   def require_user!
diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb
index c8c60b1cf..66da65bed 100644
--- a/app/controllers/api/oembed_controller.rb
+++ b/app/controllers/api/oembed_controller.rb
@@ -1,17 +1,25 @@
 # frozen_string_literal: true
 
 class Api::OEmbedController < Api::BaseController
-  respond_to :json
-
   skip_before_action :require_authenticated_user!
 
+  before_action :set_status
+  before_action :require_public_status!
+
   def show
-    @status = status_finder.status
     render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
   end
 
   private
 
+  def set_status
+    @status = status_finder.status
+  end
+
+  def require_public_status!
+    not_found if @status.hidden?
+  end
+
   def status_finder
     StatusFinder.new(params[:url])
   end
diff --git a/app/controllers/api/v1/announcements/reactions_controller.rb b/app/controllers/api/v1/announcements/reactions_controller.rb
new file mode 100644
index 000000000..e4a72e595
--- /dev/null
+++ b/app/controllers/api/v1/announcements/reactions_controller.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class Api::V1::Announcements::ReactionsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
+  before_action :require_user!
+
+  before_action :set_announcement
+  before_action :set_reaction, except: :update
+
+  def update
+    @announcement.announcement_reactions.create!(account: current_account, name: params[:id])
+    render_empty
+  end
+
+  def destroy
+    @reaction.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_reaction
+    @reaction = @announcement.announcement_reactions.where(account: current_account).find_by!(name: params[:id])
+  end
+
+  def set_announcement
+    @announcement = Announcement.published.find(params[:announcement_id])
+  end
+end
diff --git a/app/controllers/api/v1/announcements_controller.rb b/app/controllers/api/v1/announcements_controller.rb
new file mode 100644
index 000000000..6724fac2e
--- /dev/null
+++ b/app/controllers/api/v1/announcements_controller.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class Api::V1::AnnouncementsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: :dismiss
+  before_action :require_user!
+  before_action :set_announcements, only: :index
+  before_action :set_announcement, except: :index
+
+  def index
+    render json: @announcements, each_serializer: REST::AnnouncementSerializer
+  end
+
+  def dismiss
+    AnnouncementMute.create!(account: current_account, announcement: @announcement)
+    render_empty
+  end
+
+  private
+
+  def set_announcements
+    @announcements = begin
+      scope = Announcement.published
+
+      scope.merge!(Announcement.without_muted(current_account)) unless truthy_param?(:with_dismissed)
+
+      scope.chronological
+    end
+  end
+
+  def set_announcement
+    @announcement = Announcement.published.find(params[:id])
+  end
+end
diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb
index a59806f0d..c224e1a03 100644
--- a/app/controllers/auth/passwords_controller.rb
+++ b/app/controllers/auth/passwords_controller.rb
@@ -7,6 +7,12 @@ class Auth::PasswordsController < Devise::PasswordsController
 
   layout 'auth'
 
+  def update
+    super do |resource|
+      resource.session_activations.destroy_all if resource.errors.empty?
+    end
+  end
+
   private
 
   def check_validity_of_reset_password_token
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index a9d075a45..531df7751 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -23,10 +23,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController
     not_found
   end
 
+  def update
+    super do |resource|
+      resource.clear_other_sessions(current_session.session_id) if resource.saved_change_to_encrypted_password?
+    end
+  end
+
   protected
 
   def update_resource(resource, params)
     params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
+
     super
   end
 
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
index 25dd0d2ad..f1ab980c8 100644
--- a/app/controllers/relationships_controller.rb
+++ b/app/controllers/relationships_controller.rb
@@ -20,53 +20,13 @@ class RelationshipsController < ApplicationController
   rescue ActionController::ParameterMissing
     # Do nothing
   ensure
-    redirect_to relationships_path(current_params)
+    redirect_to relationships_path(filter_params)
   end
 
   private
 
   def set_accounts
-    @accounts = relationships_scope.page(params[:page]).per(40)
-  end
-
-  def relationships_scope
-    scope = begin
-      if following_relationship?
-        current_account.following.eager_load(:account_stat).reorder(nil)
-      else
-        current_account.followers.eager_load(:account_stat).reorder(nil)
-      end
-    end
-
-    scope.merge!(Follow.recent)             if params[:order].blank? || params[:order] == 'recent'
-    scope.merge!(Account.by_recent_status)  if params[:order] == 'active'
-    scope.merge!(mutual_relationship_scope) if mutual_relationship?
-    scope.merge!(moved_account_scope)       if params[:status] == 'moved'
-    scope.merge!(primary_account_scope)     if params[:status] == 'primary'
-    scope.merge!(by_domain_scope)           if params[:by_domain].present?
-    scope.merge!(dormant_account_scope)     if params[:activity] == 'dormant'
-
-    scope
-  end
-
-  def mutual_relationship_scope
-    Account.where(id: current_account.following)
-  end
-
-  def moved_account_scope
-    Account.where.not(moved_to_account_id: nil)
-  end
-
-  def primary_account_scope
-    Account.where(moved_to_account_id: nil)
-  end
-
-  def dormant_account_scope
-    AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
-  end
-
-  def by_domain_scope
-    Account.where(domain: params[:by_domain])
+    @accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
   end
 
   def form_account_batch_params
@@ -85,7 +45,7 @@ class RelationshipsController < ApplicationController
     params[:relationship] == 'followed_by'
   end
 
-  def current_params
+  def filter_params
     params.slice(:page, *RelationshipFilter::KEYS).permit(:page, *RelationshipFilter::KEYS)
   end
 
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 1b00d38c9..588063d01 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -49,7 +49,7 @@ class StatusesController < ApplicationController
 
   def embed
     use_pack 'embed'
-    raise ActiveRecord::RecordNotFound if @status.hidden?
+    return not_found if @status.hidden?
 
     expires_in 180, public: true
     response.headers['X-Frame-Options'] = 'ALLOWALL'
@@ -71,7 +71,7 @@ class StatusesController < ApplicationController
     @status = @account.statuses.find(params[:id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def set_instance_presenter
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 608a99dd5..6bc75aa56 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -22,6 +22,8 @@ module Admin::ActionLogsHelper
       log.recorded_changes.slice('severity', 'reject_media')
     elsif log.target_type == 'Status' && log.action == :update
       log.recorded_changes.slice('sensitive')
+    elsif log.target_type == 'Announcement' && log.action == :update
+      log.recorded_changes.slice('text', 'starts_at', 'ends_at', 'all_day')
     end
   end
 
@@ -52,6 +54,8 @@ module Admin::ActionLogsHelper
       'pencil'
     when 'AccountWarning'
       'warning'
+    when 'Announcement'
+      'bullhorn'
     end
   end
 
@@ -94,6 +98,8 @@ module Admin::ActionLogsHelper
       link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
     when 'AccountWarning'
       link_to record.target_account.acct, admin_account_path(record.target_account_id)
+    when 'Announcement'
+      link_to "##{record.id}", edit_admin_announcement_path(record.id)
     end
   end
 
@@ -111,6 +117,8 @@ module Admin::ActionLogsHelper
       else
         I18n.t('admin.action_logs.deleted_status')
       end
+    when 'Announcement'
+      "##{attributes['id']}"
     end
   end
 end
diff --git a/app/helpers/admin/announcements_helper.rb b/app/helpers/admin/announcements_helper.rb
new file mode 100644
index 000000000..0c053ddec
--- /dev/null
+++ b/app/helpers/admin/announcements_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Admin::AnnouncementsHelper
+  def time_range(announcement)
+    if announcement.all_day?
+      safe_join([l(announcement.starts_at.to_date), ' - ', l(announcement.ends_at.to_date)])
+    else
+      safe_join([l(announcement.starts_at), ' - ', l(announcement.ends_at)])
+    end
+  end
+end
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 130686a02..6ab92939d 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -9,6 +9,7 @@ module Admin::FilterHelper
     InstanceFilter::KEYS,
     InviteFilter::KEYS,
     RelationshipFilter::KEYS,
+    AnnouncementFilter::KEYS,
   ].flatten.freeze
 
   def filter_link_to(text, link_to_params, link_class_params = link_to_params)
diff --git a/app/javascript/flavours/glitch/actions/announcements.js b/app/javascript/flavours/glitch/actions/announcements.js
new file mode 100644
index 000000000..d0e5ee176
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/announcements.js
@@ -0,0 +1,133 @@
+import api from 'flavours/glitch/util/api';
+import { normalizeAnnouncement } from './importer/normalizer';
+
+export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
+export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
+export const ANNOUNCEMENTS_FETCH_FAIL    = 'ANNOUNCEMENTS_FETCH_FAIL';
+export const ANNOUNCEMENTS_UPDATE        = 'ANNOUNCEMENTS_UPDATE';
+export const ANNOUNCEMENTS_DISMISS       = 'ANNOUNCEMENTS_DISMISS';
+
+export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
+export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
+export const ANNOUNCEMENTS_REACTION_ADD_FAIL    = 'ANNOUNCEMENTS_REACTION_ADD_FAIL';
+
+export const ANNOUNCEMENTS_REACTION_REMOVE_REQUEST = 'ANNOUNCEMENTS_REACTION_REMOVE_REQUEST';
+export const ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS = 'ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS';
+export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL    = 'ANNOUNCEMENTS_REACTION_REMOVE_FAIL';
+
+export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
+
+const noOp = () => {};
+
+export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
+  dispatch(fetchAnnouncementsRequest());
+
+  api(getState).get('/api/v1/announcements').then(response => {
+    dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
+  }).catch(error => {
+    dispatch(fetchAnnouncementsFail(error));
+  }).finally(() => {
+    done();
+  });
+};
+
+export const fetchAnnouncementsRequest = () => ({
+  type: ANNOUNCEMENTS_FETCH_REQUEST,
+  skipLoading: true,
+});
+
+export const fetchAnnouncementsSuccess = announcements => ({
+  type: ANNOUNCEMENTS_FETCH_SUCCESS,
+  announcements,
+  skipLoading: true,
+});
+
+export const fetchAnnouncementsFail= error => ({
+  type: ANNOUNCEMENTS_FETCH_FAIL,
+  error,
+  skipLoading: true,
+  skipAlert: true,
+});
+
+export const updateAnnouncements = announcement => ({
+  type: ANNOUNCEMENTS_UPDATE,
+  announcement: normalizeAnnouncement(announcement),
+});
+
+export const dismissAnnouncement = announcementId => (dispatch, getState) => {
+  dispatch({
+    type: ANNOUNCEMENTS_DISMISS,
+    id: announcementId,
+  });
+
+  api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
+};
+
+export const addReaction = (announcementId, name) => (dispatch, getState) => {
+  dispatch(addReactionRequest(announcementId, name));
+
+  api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
+    dispatch(addReactionSuccess(announcementId, name));
+  }).catch(err => {
+    dispatch(addReactionFail(announcementId, name, err));
+  });
+};
+
+export const addReactionRequest = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_ADD_REQUEST,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const addReactionSuccess = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_ADD_SUCCESS,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const addReactionFail = (announcementId, name, error) => ({
+  type: ANNOUNCEMENTS_REACTION_ADD_FAIL,
+  id: announcementId,
+  name,
+  error,
+  skipLoading: true,
+});
+
+export const removeReaction = (announcementId, name) => (dispatch, getState) => {
+  dispatch(removeReactionRequest(announcementId, name));
+
+  api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
+    dispatch(removeReactionSuccess(announcementId, name));
+  }).catch(err => {
+    dispatch(removeReactionFail(announcementId, name, err));
+  });
+};
+
+export const removeReactionRequest = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const removeReactionSuccess = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const removeReactionFail = (announcementId, name, error) => ({
+  type: ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+  id: announcementId,
+  name,
+  error,
+  skipLoading: true,
+});
+
+export const updateReaction = reaction => ({
+  type: ANNOUNCEMENTS_REACTION_UPDATE,
+  reaction,
+});
diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js
index 2bc603930..52ad17779 100644
--- a/app/javascript/flavours/glitch/actions/importer/normalizer.js
+++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js
@@ -74,7 +74,6 @@ export function normalizeStatus(status, normalOldStatus) {
 
 export function normalizePoll(poll) {
   const normalPoll = { ...poll };
-
   const emojiMap = makeEmojiMap(normalPoll);
 
   normalPoll.options = poll.options.map((option, index) => ({
@@ -85,3 +84,12 @@ export function normalizePoll(poll) {
 
   return normalPoll;
 }
+
+export function normalizeAnnouncement(announcement) {
+  const normalAnnouncement = { ...announcement };
+  const emojiMap = makeEmojiMap(normalAnnouncement);
+
+  normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
+
+  return normalAnnouncement;
+}
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 940f3c3d4..b3de7b5bf 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -168,9 +168,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 
       dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
       fetchRelatedRelationships(dispatch, response.data);
-      done();
     }).catch(error => {
       dispatch(expandNotificationsFail(error, isLoadingMore));
+    }).finally(() => {
       done();
     });
   };
@@ -199,6 +199,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
     type: NOTIFICATIONS_EXPAND_FAIL,
     error,
     skipLoading: !isLoadingMore,
+    skipAlert: !isLoadingMore,
   };
 };
 
diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js
index 21379f492..8294fbf36 100644
--- a/app/javascript/flavours/glitch/actions/streaming.js
+++ b/app/javascript/flavours/glitch/actions/streaming.js
@@ -8,6 +8,7 @@ import {
 } from './timelines';
 import { updateNotifications, expandNotifications } from './notifications';
 import { updateConversations } from './conversations';
+import { fetchAnnouncements, updateAnnouncements, updateReaction as updateAnnouncementsReaction } from './announcements';
 import { fetchFilters } from './filters';
 import { getLocale } from 'mastodon/locales';
 
@@ -44,6 +45,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
         case 'filters_changed':
           dispatch(fetchFilters());
           break;
+        case 'announcement':
+          dispatch(updateAnnouncements(JSON.parse(data.payload)));
+          break;
+        case 'announcement.reaction':
+          dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
+          break;
         }
       },
     };
@@ -51,7 +58,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
 }
 
 const refreshHomeTimelineAndNotification = (dispatch, done) => {
-  dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
+  dispatch(expandHomeTimeline({}, () =>
+    dispatch(expandNotifications({}, () =>
+      dispatch(fetchAnnouncements(done))))));
 };
 
 export const connectUserStream      = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index 097878c3b..2ef78025e 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -112,9 +112,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
 
       dispatch(importFetchedStatuses(response.data));
       dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
-      done();
     }).catch(error => {
       dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
+    }).finally(() => {
       done();
     });
   };
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index 2ef4ff602..f25c82a00 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -112,6 +112,7 @@ class AccountTimeline extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
           bindToDocument={!multiColumn}
+          timelineId='account'
         />
       </Column>
     );
diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js
index d1c406933..557120960 100644
--- a/app/javascript/flavours/glitch/features/directory/components/account_card.js
+++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js
@@ -22,6 +22,7 @@ const messages = defineMessages({
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
   unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+  unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
 });
 
 const makeMapStateToProps = () => {
diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js
index 6e5518b0c..3717fcd82 100644
--- a/app/javascript/flavours/glitch/features/emoji_picker/index.js
+++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js
@@ -372,6 +372,7 @@ class EmojiPickerDropdown extends React.PureComponent {
     onPickEmoji: PropTypes.func.isRequired,
     onSkinTone: PropTypes.func.isRequired,
     skinTone: PropTypes.number.isRequired,
+    button: PropTypes.node,
   };
 
   state = {
@@ -432,18 +433,18 @@ class EmojiPickerDropdown extends React.PureComponent {
   }
 
   render () {
-    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
+    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
     const title = intl.formatMessage(messages.emoji);
     const { active, loading, placement } = this.state;
 
     return (
       <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
         <div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
-          <img
+          {button || <img
             className={classNames('emojione', { 'pulse-loading': active && loading })}
             alt='🙂'
             src={`${assetHost}/emoji/1f602.svg`}
-          />
+          />}
         </div>
 
         <Overlay show={active} placement={placement} target={this.findTarget}>
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
new file mode 100644
index 000000000..010778727
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
@@ -0,0 +1,395 @@
+import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ReactSwipeableViews from 'react-swipeable-views';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import IconButton from 'flavours/glitch/components/icon_button';
+import Icon from 'flavours/glitch/components/icon';
+import { defineMessages, injectIntl, FormattedMessage, FormattedDate, FormattedNumber } from 'react-intl';
+import { autoPlayGif } from 'flavours/glitch/util/initial_state';
+import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
+import { mascot } from 'flavours/glitch/util/initial_state';
+import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
+import classNames from 'classnames';
+import EmojiPickerDropdown from 'flavours/glitch/features/emoji_picker';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+  previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
+  next: { id: 'lightbox.next', defaultMessage: 'Next' },
+});
+
+class Content extends ImmutablePureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  static propTypes = {
+    announcement: ImmutablePropTypes.map.isRequired,
+  };
+
+  setRef = c => {
+    this.node = c;
+  }
+
+  componentDidMount () {
+    this._updateLinks();
+    this._updateEmojis();
+  }
+
+  componentDidUpdate () {
+    this._updateLinks();
+    this._updateEmojis();
+  }
+
+  _updateEmojis () {
+    const node = this.node;
+
+    if (!node || autoPlayGif) {
+      return;
+    }
+
+    const emojis = node.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+
+      if (emoji.classList.contains('status-emoji')) {
+        continue;
+      }
+
+      emoji.classList.add('status-emoji');
+
+      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
+      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
+    }
+  }
+
+  _updateLinks () {
+    const node = this.node;
+
+    if (!node) {
+      return;
+    }
+
+    const links = node.querySelectorAll('a');
+
+    for (var i = 0; i < links.length; ++i) {
+      let link = links[i];
+
+      if (link.classList.contains('status-link')) {
+        continue;
+      }
+
+      link.classList.add('status-link');
+
+      let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url'));
+
+      if (mention) {
+        link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
+        link.setAttribute('title', mention.get('acct'));
+      } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
+        link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
+      } else {
+        link.setAttribute('title', link.href);
+        link.classList.add('unhandled-link');
+      }
+
+      link.setAttribute('target', '_blank');
+      link.setAttribute('rel', 'noopener noreferrer');
+    }
+  }
+
+  onMentionClick = (mention, e) => {
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.context.router.history.push(`/accounts/${mention.get('id')}`);
+    }
+  }
+
+  onHashtagClick = (hashtag, e) => {
+    hashtag = hashtag.replace(/^#/, '');
+
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.context.router.history.push(`/timelines/tag/${hashtag}`);
+    }
+  }
+
+  handleEmojiMouseEnter = ({ target }) => {
+    target.src = target.getAttribute('data-original');
+  }
+
+  handleEmojiMouseLeave = ({ target }) => {
+    target.src = target.getAttribute('data-static');
+  }
+
+  render () {
+    const { announcement } = this.props;
+
+    return (
+      <div
+        className='announcements__item__content'
+        ref={this.setRef}
+        dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
+      />
+    );
+  }
+
+}
+
+const assetHost = process.env.CDN_HOST || '';
+
+class Emoji extends React.PureComponent {
+
+  static propTypes = {
+    emoji: PropTypes.string.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+    hovered: PropTypes.bool.isRequired,
+  };
+
+  render () {
+    const { emoji, emojiMap, hovered } = this.props;
+
+    if (unicodeMapping[emoji]) {
+      const { filename, shortCode } = unicodeMapping[this.props.emoji];
+      const title = shortCode ? `:${shortCode}:` : '';
+
+      return (
+        <img
+          draggable='false'
+          className='emojione'
+          alt={emoji}
+          title={title}
+          src={`${assetHost}/emoji/${filename}.svg`}
+        />
+      );
+    } else if (emojiMap.get(emoji)) {
+      const filename  = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
+      const shortCode = `:${emoji}:`;
+
+      return (
+        <img
+          draggable='false'
+          className='emojione custom-emoji'
+          alt={shortCode}
+          title={shortCode}
+          src={filename}
+        />
+      );
+    } else {
+      return null;
+    }
+  }
+
+}
+
+class Reaction extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcementId: PropTypes.string.isRequired,
+    reaction: ImmutablePropTypes.map.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+  };
+
+  state = {
+    hovered: false,
+  };
+
+  handleClick = () => {
+    const { reaction, announcementId, addReaction, removeReaction } = this.props;
+
+    if (reaction.get('me')) {
+      removeReaction(announcementId, reaction.get('name'));
+    } else {
+      addReaction(announcementId, reaction.get('name'));
+    }
+  }
+
+  handleMouseEnter = () => this.setState({ hovered: true })
+
+  handleMouseLeave = () => this.setState({ hovered: false })
+
+  render () {
+    const { reaction } = this.props;
+
+    let shortCode = reaction.get('name');
+
+    if (unicodeMapping[shortCode]) {
+      shortCode = unicodeMapping[shortCode].shortCode;
+    }
+
+    return (
+      <button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`}>
+        <span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} /></span>
+        <span className='reactions-bar__item__count'><FormattedNumber value={reaction.get('count')} /></span>
+      </button>
+    );
+  }
+
+}
+
+class ReactionsBar extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcementId: PropTypes.string.isRequired,
+    reactions: ImmutablePropTypes.list.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+  };
+
+  handleEmojiPick = data => {
+    const { addReaction, announcementId } = this.props;
+    addReaction(announcementId, data.native.replace(/:/g, ''));
+  }
+
+  render () {
+    const { reactions } = this.props;
+    const visibleReactions = reactions.filter(x => x.get('count') > 0);
+
+    return (
+      <div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
+        {visibleReactions.map(reaction => (
+          <Reaction
+            key={reaction.get('name')}
+            reaction={reaction}
+            announcementId={this.props.announcementId}
+            addReaction={this.props.addReaction}
+            removeReaction={this.props.removeReaction}
+            emojiMap={this.props.emojiMap}
+          />
+        ))}
+
+        <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' />} />
+      </div>
+    );
+  }
+
+}
+
+class Announcement extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcement: ImmutablePropTypes.map.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+    dismissAnnouncement: PropTypes.func.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleDismissClick = () => {
+    const { dismissAnnouncement, announcement } = this.props;
+    dismissAnnouncement(announcement.get('id'));
+  }
+
+  render () {
+    const { announcement, intl } = this.props;
+    const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
+    const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
+    const now = new Date();
+    const hasTimeRange = startsAt && endsAt;
+    const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
+    const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
+    const skipTime = announcement.get('all_day');
+
+    return (
+      <div className='announcements__item'>
+        <strong className='announcements__item__range'>
+          <FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
+          {hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
+        </strong>
+
+        <Content announcement={announcement} />
+
+        <ReactionsBar
+          reactions={announcement.get('reactions')}
+          announcementId={announcement.get('id')}
+          addReaction={this.props.addReaction}
+          removeReaction={this.props.removeReaction}
+          emojiMap={this.props.emojiMap}
+        />
+
+        <IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
+      </div>
+    );
+  }
+
+}
+
+export default @injectIntl
+class Announcements extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcements: ImmutablePropTypes.list,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+    fetchAnnouncements: PropTypes.func.isRequired,
+    dismissAnnouncement: PropTypes.func.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    index: 0,
+  };
+
+  componentDidMount () {
+    const { fetchAnnouncements } = this.props;
+    fetchAnnouncements();
+  }
+
+  handleChangeIndex = index => {
+    this.setState({ index: index % this.props.announcements.size });
+  }
+
+  handleNextClick = () => {
+    this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
+  }
+
+  handlePrevClick = () => {
+    this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
+  }
+
+  render () {
+    const { announcements, intl } = this.props;
+    const { index } = this.state;
+
+    if (announcements.isEmpty()) {
+      return null;
+    }
+
+    return (
+      <div className='announcements'>
+        <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
+
+        <div className='announcements__container'>
+          <ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
+            {announcements.map(announcement => (
+              <Announcement
+                key={announcement.get('id')}
+                announcement={announcement}
+                emojiMap={this.props.emojiMap}
+                dismissAnnouncement={this.props.dismissAnnouncement}
+                addReaction={this.props.addReaction}
+                removeReaction={this.props.removeReaction}
+                intl={intl}
+              />
+            ))}
+          </ReactSwipeableViews>
+
+          <div className='announcements__pagination'>
+            <IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' onClick={this.handlePrevClick} size={13} />
+            <span>{index + 1} / {announcements.size}</span>
+            <IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' onClick={this.handleNextClick} size={13} />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js
new file mode 100644
index 000000000..b10d1d4ce
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux';
+import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
+import Announcements from '../components/announcements';
+import { createSelector } from 'reselect';
+import { Map as ImmutableMap } from 'immutable';
+
+const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
+
+const mapStateToProps = state => ({
+  announcements: state.getIn(['announcements', 'items']),
+  emojiMap: customEmojiMap(state),
+});
+
+const mapDispatchToProps = dispatch => ({
+  fetchAnnouncements: () => dispatch(fetchAnnouncements()),
+  dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
+  addReaction: (id, name) => dispatch(addReaction(id, name)),
+  removeReaction: (id, name) => dispatch(removeReaction(id, name)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Announcements);
diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js
index 1df3fb4fe..7a5268780 100644
--- a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js
+++ b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { fetchTrends } from '../../../actions/trends';
+import { fetchTrends } from 'mastodon/actions/trends';
 import Trends from '../components/trends';
 
 const mapStateToProps = state => ({
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js
index 9b71a4404..263371b06 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.js
@@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { Link } from 'react-router-dom';
+import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
 
 const messages = defineMessages({
   title: { id: 'column.home', defaultMessage: 'Home' },
@@ -112,6 +113,8 @@ class HomeTimeline extends React.PureComponent {
         </ColumnHeader>
 
         <StatusListContainer
+          prepend={<AnnouncementsContainer />}
+          alwaysPrepend
           trackScroll={!pinned}
           scrollKey={`home_timeline-${columnId}`}
           onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
index c7d6c374c..23e8dac7e 100644
--- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
@@ -191,7 +191,6 @@ class MediaModal extends ImmutablePureComponent {
             style={swipeableViewsStyle}
             containerStyle={containerStyle}
             onChangeIndex={this.handleSwipe}
-            onSwitching={this.handleSwitching}
             index={index}
           >
             {content}
diff --git a/app/javascript/flavours/glitch/reducers/announcements.js b/app/javascript/flavours/glitch/reducers/announcements.js
new file mode 100644
index 000000000..aa674e516
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/announcements.js
@@ -0,0 +1,72 @@
+import {
+  ANNOUNCEMENTS_FETCH_REQUEST,
+  ANNOUNCEMENTS_FETCH_SUCCESS,
+  ANNOUNCEMENTS_FETCH_FAIL,
+  ANNOUNCEMENTS_UPDATE,
+  ANNOUNCEMENTS_DISMISS,
+  ANNOUNCEMENTS_REACTION_UPDATE,
+  ANNOUNCEMENTS_REACTION_ADD_REQUEST,
+  ANNOUNCEMENTS_REACTION_ADD_FAIL,
+  ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
+  ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+} from '../actions/announcements';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+});
+
+const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
+  if (announcement.get('id') === id) {
+    return announcement.update('reactions', reactions => {
+      if (reactions.find(reaction => reaction.get('name') === name)) {
+        return reactions.map(reaction => {
+          if (reaction.get('name') === name) {
+            return updater(reaction);
+          }
+
+          return reaction;
+        });
+      }
+
+      return reactions.push(updater(fromJS({ name, count: 0 })));
+    });
+  }
+
+  return announcement;
+}));
+
+const updateReactionCount = (state, reaction) => updateReaction(state, reaction.announcement_id, reaction.name, x => x.set('count', reaction.count));
+
+const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', true).update('count', y => y + 1));
+
+const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));
+
+export default function announcementsReducer(state = initialState, action) {
+  switch(action.type) {
+  case ANNOUNCEMENTS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case ANNOUNCEMENTS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('items', fromJS(action.announcements));
+      map.set('isLoading', false);
+    });
+  case ANNOUNCEMENTS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case ANNOUNCEMENTS_UPDATE:
+    return state.update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
+  case ANNOUNCEMENTS_DISMISS:
+    return state.update('items', list => list.filterNot(announcement => announcement.get('id') === action.id));
+  case ANNOUNCEMENTS_REACTION_UPDATE:
+    return updateReactionCount(state, action.reaction);
+  case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
+  case ANNOUNCEMENTS_REACTION_REMOVE_FAIL:
+    return addReaction(state, action.id, action.name);
+  case ANNOUNCEMENTS_REACTION_REMOVE_REQUEST:
+  case ANNOUNCEMENTS_REACTION_ADD_FAIL:
+    return removeReaction(state, action.id, action.name);
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js
index 7dbca3a29..586b84749 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.js
@@ -35,8 +35,10 @@ import pinnedAccountsEditor from './pinned_accounts_editor';
 import polls from './polls';
 import identity_proofs from './identity_proofs';
 import trends from './trends';
+import announcements from './announcements';
 
 const reducers = {
+  announcements,
   dropdown_menu,
   timelines,
   meta,
diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js
index 8ceb71d03..ab7dac66a 100644
--- a/app/javascript/flavours/glitch/selectors/index.js
+++ b/app/javascript/flavours/glitch/selectors/index.js
@@ -27,6 +27,7 @@ export const toServerSideType = columnType => {
   case 'notifications':
   case 'public':
   case 'thread':
+  case 'account':
     return columnType;
   default:
     if (columnType.indexOf('list:') > -1) {
diff --git a/app/javascript/flavours/glitch/styles/components/announcements.scss b/app/javascript/flavours/glitch/styles/components/announcements.scss
new file mode 100644
index 000000000..0d1f1837b
--- /dev/null
+++ b/app/javascript/flavours/glitch/styles/components/announcements.scss
@@ -0,0 +1,212 @@
+.announcements__item__content {
+  word-wrap: break-word;
+
+  .emojione {
+    width: 20px;
+    height: 20px;
+    margin: -3px 0 0;
+  }
+
+  p {
+    margin-bottom: 10px;
+    white-space: pre-wrap;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  a {
+    color: $highlight-text-color;
+    text-decoration: none;
+
+    &:hover {
+      text-decoration: underline;
+    }
+
+    &.mention {
+      &:hover {
+        text-decoration: none;
+
+        span {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
+
+.announcements {
+  background: lighten($ui-base-color, 4%);
+  border-top: 1px solid $ui-base-color;
+  font-size: 13px;
+  display: flex;
+  align-items: flex-end;
+
+  &__mastodon {
+    width: 124px;
+    flex: 0 0 auto;
+
+    @media screen and (max-width: 124px + 300px) {
+      display: none;
+    }
+  }
+
+  &__container {
+    width: calc(100% - 124px);
+    flex: 0 0 auto;
+    position: relative;
+
+    @media screen and (max-width: 124px + 300px) {
+      width: 100%;
+    }
+  }
+
+  &__item {
+    box-sizing: border-box;
+    width: 100%;
+    padding: 15px;
+    padding-right: 15px + 18px;
+    position: relative;
+
+    &__range {
+      display: block;
+      font-weight: 500;
+      margin-bottom: 10px;
+    }
+
+    &__dismiss-icon {
+      position: absolute;
+      top: 12px;
+      right: 12px;
+    }
+  }
+
+  &__pagination {
+    padding: 15px;
+    color: $darker-text-color;
+    position: absolute;
+    bottom: 3px;
+    right: 0;
+  }
+}
+
+.layout-multiple-columns .announcements__mastodon {
+  display: none;
+}
+
+.layout-multiple-columns .announcements__container {
+  width: 100%;
+}
+
+.reactions-bar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  margin-top: 15px;
+  margin-left: -2px;
+  width: calc(100% - (90px - 33px));
+
+  &__item {
+    flex-shrink: 0;
+    background: lighten($ui-base-color, 12%);
+    border: 0;
+    border-radius: 3px;
+    margin: 2px;
+    cursor: pointer;
+    user-select: none;
+    padding: 0 6px;
+    display: flex;
+    align-items: center;
+    transition: all 100ms ease-in;
+    transition-property: background-color, color;
+
+    &__emoji {
+      display: block;
+      margin: 3px 0;
+      width: 16px;
+      height: 16px;
+
+      img {
+        display: block;
+        margin: 0;
+        width: 100%;
+        height: 100%;
+        min-width: auto;
+        min-height: auto;
+        vertical-align: bottom;
+        object-fit: contain;
+      }
+    }
+
+    &__count {
+      display: block;
+      min-width: 9px;
+      font-size: 13px;
+      font-weight: 500;
+      text-align: center;
+      margin-left: 6px;
+      color: $darker-text-color;
+    }
+
+    &:hover,
+    &:focus,
+    &:active {
+      background: lighten($ui-base-color, 16%);
+      transition: all 200ms ease-out;
+      transition-property: background-color, color;
+
+      &__count {
+        color: lighten($darker-text-color, 4%);
+      }
+    }
+
+    &.active {
+      transition: all 100ms ease-in;
+      transition-property: background-color, color;
+      background-color: mix(lighten($ui-base-color, 12%), $ui-highlight-color, 90%);
+
+      .reactions-bar__item__count {
+        color: $highlight-text-color;
+      }
+    }
+  }
+
+  .emoji-picker-dropdown {
+    margin: 2px;
+  }
+
+  &:hover .emoji-button {
+    opacity: 0.85;
+  }
+
+  .emoji-button {
+    color: $darker-text-color;
+    margin: 0;
+    font-size: 16px;
+    width: auto;
+    flex-shrink: 0;
+    padding: 0 6px;
+    height: 22px;
+    display: flex;
+    align-items: center;
+    opacity: 0.5;
+    transition: all 100ms ease-in;
+    transition-property: background-color, color;
+
+    &:hover,
+    &:active,
+    &:focus {
+      opacity: 1;
+      color: lighten($darker-text-color, 4%);
+      transition: all 200ms ease-out;
+      transition-property: background-color, color;
+    }
+  }
+
+  &--empty {
+    .emoji-button {
+      padding: 0;
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index 51287f62e..943776010 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -1,5 +1,16 @@
 .composer {
   padding: 10px;
+
+  .emoji-picker-dropdown {
+    position: absolute;
+    right: 5px;
+    top: 5px;
+
+    ::-webkit-scrollbar-track:hover,
+    ::-webkit-scrollbar-track:active {
+      background-color: rgba($base-overlay-background, 0.3);
+    }
+  }
 }
 
 .character-counter {
@@ -235,17 +246,6 @@
   }
 }
 
-.emoji-picker-dropdown {
-  position: absolute;
-  right: 5px;
-  top: 5px;
-
-  ::-webkit-scrollbar-track:hover,
-  ::-webkit-scrollbar-track:active {
-    background-color: rgba($base-overlay-background, 0.3);
-  }
-}
-
 .compose-form__autosuggest-wrapper,
 .autosuggest-input {
   position: relative;
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 8e576fd86..abe933860 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1649,3 +1649,4 @@ noscript {
 @import 'local_settings';
 @import 'error_boundary';
 @import 'single_column';
+@import 'announcements';
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index 1920c33ea..396e87c6c 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -213,6 +213,12 @@ code {
     }
   }
 
+  .input.datetime .label_input select {
+    display: inline-block;
+    width: auto;
+    flex: 0;
+  }
+
   .required abbr {
     text-decoration: none;
     color: lighten($error-value-color, 12%);
diff --git a/app/javascript/flavours/glitch/util/stream.js b/app/javascript/flavours/glitch/util/stream.js
index 50f90d44c..fe965bcb0 100644
--- a/app/javascript/flavours/glitch/util/stream.js
+++ b/app/javascript/flavours/glitch/util/stream.js
@@ -2,6 +2,14 @@ import WebSocketClient from '@gamestdio/websocket';
 
 const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
 
+const knownEventTypes = [
+  'update',
+  'delete',
+  'notification',
+  'conversation',
+  'filters_changed',
+];
+
 export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) {
   return (dispatch, getState) => {
     const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
@@ -69,14 +77,42 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 
 
 export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
-  const params = [ `stream=${stream}` ];
-
-  const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
-
-  ws.onopen      = connected;
-  ws.onmessage   = e => received(JSON.parse(e.data));
-  ws.onclose     = disconnected;
-  ws.onreconnect = reconnected;
+  const params = stream.split('&');
+  stream = params.shift();
+
+  if (streamingAPIBaseURL.startsWith('ws')) {
+    params.unshift(`stream=${stream}`);
+    const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
+
+    ws.onopen      = connected;
+    ws.onmessage   = e => received(JSON.parse(e.data));
+    ws.onclose     = disconnected;
+    ws.onreconnect = reconnected;
+
+    return ws;
+  }
+
+  params.push(`access_token=${accessToken}`);
+  const es = new EventSource(`${streamingAPIBaseURL}/api/v1/streaming/${stream}?${params.join('&')}`);
+
+  let firstConnect = true;
+  es.onopen = () => {
+    if (firstConnect) {
+      firstConnect = false;
+      connected();
+    } else {
+      reconnected();
+    }
+  };
+  for (let type of knownEventTypes) {
+    es.addEventListener(type, (e) => {
+      received({
+        event: e.type,
+        payload: e.data,
+      });
+    });
+  }
+  es.onerror = disconnected;
 
-  return ws;
+  return es;
 };
diff --git a/app/javascript/images/elephant_ui_plane.svg b/app/javascript/images/elephant_ui_plane.svg
index a2624d170..ca675c9eb 100644
--- a/app/javascript/images/elephant_ui_plane.svg
+++ b/app/javascript/images/elephant_ui_plane.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 292.85862 204.49997" width="292.85861" height="204.49997"><g transform="translate(-395.89999 -820.4)"><defs><path id="a" d="M395.89999 745.09998H690.5v279.79999H395.89999z"/></defs><clipPath id="b"><use xlink:href="#a" width="100%" height="100%" overflow="visible"/></clipPath><path class="st53" d="M339.3 1028.6c1.5-3.2 14.4-31.3 27.4-58.8-6-9.3-2-17 1.5-23.7 1.9-3.7 3.8-7.1 3.6-10.4-.8-22 8.1-38.3 22.8-41.6 2.8-.6 5.2-.9 7.5-.9 3 0 5.6.5 8.1 1.6 1.4-1.3 2.8-2.6 4.2-3.8-2.8-2.6-4.3-5.5-4.5-8.8-.3-4.5 2.2-9.5 6.8-13.7 5.3-4.8 16.5-12.9 31.7-12.9.9 0 1.7 0 2.6.1-.4-.9-1-2-2.1-2.9-2.1-1.6-1.9-3.2-1.6-4 .7-2.1 3.6-3.2 8.1-3.2 3.9 0 9.7 1.2 14 4.4.3-.7.7-1.3 1.3-1.7.5-.3 1.3-.5 2.2-.5 3.4 0 10.6 2.7 15.5 9.9 3.6 5.3 3.6 10.8.1 16 18.3 4.7 30.1 15.6 39.5 24.4 2.5 2.4 5 4.6 7.3 6.5 10.7 8.9 21.4 13.2 32.7 13.2.9 0 1.8 0 2.7-.1 2-13.5-4.1-25.5-10-35.7-6.2-10.7-6.4-12.1-4.9-13.9l.1-.1c.6-.7 1.3-1 2.1-1.1h.3c1.7 0 4.5 1 13 8.7 9.9 9 16.9 22.2 19.2 36.5 8.9-4.9 15.2-12.5 17.1-20.3 2-8.6.5-16.8-4.2-22.7l-13.1 6.1-7-16.9-15.6 3.2 7.2-19.5h.1l-.2-.5 4.6-11.1 65.7 11.9c3.1.6 3.9 2.7 3.6 4.5l-.2 1-.4-.1c-.3.4-.6.7-1 .9-.7.3-7.5 3.6-21 9.9 2.1 2.9 2.2 6.1.4 9.4-1.1 1.9-2.5 6.2.4 13.3 3.7 8.9 3.5 29.2-8.3 46.2-8.1 11.7-18.3 23-37.6 26.6-3 4.6-6.5 9-10.2 12.6-7.8 7.6-24 15.3-42.3 15.3-5.3 0-10.7-.6-15.9-1.9-7.6 12.9-10.5 26.2-10.6 32.1-.2 11 .9 16.1.9 16.2l.4 1.8-164.9.8.9-2.3z" clip-path="url(#b)"/><path class="st53" d="M339.8 1028.8c.1-.3 13.9-30.1 27.5-59.1-6.1-9.2-2.1-16.8 1.4-23.5 2-3.7 3.8-7.3 3.7-10.6-.8-21.7 8-37.9 22.4-41 2.7-.6 5.1-.9 7.4-.9 3 0 5.7.5 8.2 1.7 1.6-1.6 3.2-3 4.9-4.4-3-2.6-4.6-5.5-4.8-8.8-.3-4.4 2.1-9.2 6.6-13.3 5.3-4.8 16.4-12.8 31.4-12.8 1.1 0 2.2 0 3.3.1-.3-1.1-1-2.7-2.5-3.8-1.8-1.4-1.7-2.8-1.5-3.4.6-1.8 3.4-2.9 7.6-2.9 4.4 0 10.3 1.5 14.3 4.8.1-.9.6-1.7 1.3-2.1.4-.3 1.1-.4 1.9-.4 3.3 0 10.3 2.6 15.1 9.7 3.6 5.3 3.5 10.9-.3 16.1 18.6 4.6 30.5 15.6 40 24.4 2.6 2.4 5 4.6 7.3 6.5 10.8 8.9 21.6 13.3 33.1 13.3 1 0 2.1 0 3.1-.1 2.2-13.9-4-26.1-10-36.4-6.3-10.9-6.2-11.8-5-13.3l.1-.1c.2-.2.7-.9 1.8-.9h.2c1.5 0 4.2 1 12.7 8.6 10 9.1 17 22.5 19.2 36.9 9.3-5 16-12.8 17.9-20.9 2.1-8.9.4-17.4-4.5-23.4l-13 6.1-6.9-16.8-15.1 3.1 6.8-18.4h.6l-.5-1 4.4-10.6 65.3 11.8c3.5.7 3.3 3.2 3.2 3.9l-.1.5h-.2c-.2.4-.6.8-1 1-.7.3-7.9 3.7-21.6 10.2.1.1.2.2.3.4 2 2.7 2.2 5.8.4 8.9-1.1 2-2.6 6.4.4 13.7 3.6 8.8 3.4 28.8-8.2 45.7-8.1 11.7-18.2 22.9-37.5 26.4-3 4.7-6.5 9.1-10.3 12.7-7.8 7.5-23.7 15.1-42 15.1-5.4 0-10.9-.7-16.1-2-7.7 13.1-10.7 26.7-10.9 32.7-.2 11.1.9 16.2.9 16.3l.3 1.2-163.5.8.5-1.7z" clip-path="url(#b)"/><path d="M577.5 843.7l-1-2.1 3.9-9.4 64.5 11.6c2.4.5 2.5 2 2.4 2.8" clip-path="url(#b)" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path d="M584.2 856.7l6.9 16.6s53.7-25.1 55.5-26c.9-.5 1.5-2.2-1-2.3-2.5-.1-69.6-1.4-69.6-1.4l-5.9 16 14.1-2.9z" clip-path="url(#b)" fill="#fff" stroke="#000" stroke-width="1.70000005" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path d="M585.1 857l58.9-11.2 1.4-.5h.4c.8 0 .7.3.8.6.1.3-.1.6-.4.8L587 861.5l-1.9-4.5z" clip-path="url(#b)" fill="#d1d3d4"/><path class="st57" clip-path="url(#b)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M584.20001 856.70001l61.79999-11.5"/><path class="st58" d="M388.5 927.9c20-40 49.9-56.3 83.4-54.9 33.6 1.4 48.8 21.7 62.4 32.9 13.6 11.2 27.5 15.6 43.4 12.5 15.9-3.1 28.5-14.6 31.2-26.1 2.7-11.5-1-20.7-6.1-25.8-5.1-5.1-1.4-8.1 4.1-6.8 5.4 1.4 8.1 3.4 8.1 3.4s7.8-6.4 9.8-3.7c2 2.7 1.7 5.4.3 7.8-1.4 2.4-2.7 7.1.3 14.6 3.1 7.5 4.1 27.1-8.1 44.8-12.2 17.6-26.5 30.1-62.1 26.5-38.5-3.9-54.6 44.2-54.9 59-.2 11.5.9 16.5.9 16.5l-160.7.8c.2-.1 33.4-72.4 48-101.5z" clip-path="url(#b)" fill="#53606c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path class="st59" d="M399.4 989.3c-6.1 0-9.9-4.3-10-4.3l-.2-.2-.3.1c-3.1 1-6.1 1.5-9 1.5-6.1 0-11.4-2.2-15.6-6.5 1.4-3 2.9-6.1 4.3-9.1.1.1.1.2.2.3 6.1 8 12.7 9.7 17 9.7 3.2 0 5.6-.9 6.6-1.3.7 1.3 3 4.7 7.5 4.7 1.2 0 2.5-.3 3.9-.8 6.7-2.5 15.7-13.1 18.3-22.1 3.2 5.7-2.8 18.3-13.8 25.3-3 1.7-6 2.7-8.9 2.7zm24.5-78.1c-5.7-11.1-10.8-14.2-12.4-14.9 15.7-15.3 34.7-23 56.6-23 1.3 0 2.5 0 3.8.1 2.1.1 4.2.3 6.4.5 1.5 2.7-.4 5.6-.8 6.3-3 .8-11.2 6.5-18.5 11.6-1.9 1.3-3.7 2.6-5.3 3.7-5.3 3.7-13.6 4.2-18 4.2-1.8 0-2.9-.1-2.9-.1h-.3l-8.6 11.6zm95.3 49c4.2-7.1 12.6-15.2 28.3-15.2 3.5 0 7.2.4 11 1.2 3.4.7 7 1.1 10.6 1.1 15.9 0 29.8-7.9 34.8-12.5 6.5-6 10.5-9.8 12-12.4 1.4-2.4 3.2-2.8 4.7-2 .1.1-.7 1.1-.6 1.1-.9 1.7-1.9 3.3-3 4.8-11.5 16.6-23.9 26.9-50 26.9-3.6 0-7.5-.2-11.6-.6-1.5-.2-3-.2-4.4-.2-9.4 0-18.1 3.2-25.9 9.4l-5.9-1.6z" clip-path="url(#b)" fill="#38434f"/><path class="st58" d="M427.4 920.5c-9.1-24.2-19-27.9-32.4-25-13.4 3-22.4 18-21.6 40 .4 10.2-15.5 20.5-4.3 35s23.5 8 23.5 8 3 7.1 11 4.1 16.7-13.5 18.5-21.6" clip-path="url(#b)" fill="#53606c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path class="st59" d="M417.2 934.2c-7.3.5-8.6-4.8-7.3-7 1.2-2.1 4.6-3.2 4.6-3.2s-5.3-.2-6.3-4c-1-3.7 4-5.3 4-5.3s-2.9-8.6-13.3-6.1-14.4 11.7-14.2 19c.6 15.3-10.2 22.8 0 29.7 10.1 6.9 23.7-2.8 28.5-11.3 4.8-8.5 4-11.8 4-11.8z" clip-path="url(#b)" fill="#38434f"/><path class="st57" d="M392.6 978.6c6.3-3.9 9.1-7.5 9.1-7.5m15.5-36.9c-7.3.5-8.6-4.8-7.3-7 1.2-2.1 4.6-3.2 4.6-3.2s-5.3-.2-6.3-4c-1-3.7 4-5.3 4-5.3s-2.9-8.6-13.3-6.1-14.6 11.7-14.2 19" clip-path="url(#b)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path d="M417.5 891.4c-9.8-6.9-6.2-16 .3-21.8 6.5-5.8 19-14 35.2-12.2 0 0-.2-3.5-3.1-5.8s-.5-4.5 5.5-4.5 12.9 2.6 15.8 6.9c0 0-1.4-3.2.4-4.4 1.8-1.2 10.2 1 15.6 9s1.1 14.9-5.2 19.9c-6.3 5-36.2 11.6-48.8 10.9" clip-path="url(#b)" fill="#53606c"/><g class="st53" clip-path="url(#b)"><path class="st36" d="M459.5 878.7c-11.1.6-22.1 3.3-32.9 6.1-4.1 1.1-8.2 2.2-12.4 2-.4 0-.8-.1-1.3-.1 1 1.6 2.5 3.2 4.6 4.7l15.6-2.1c11.3.7 37-4.7 46.4-9.4-6.5-1.1-13.3-1.5-20-1.2z" fill="#38434f"/></g><path class="st59" d="M417.5 890.8c-3.5-2.5-5.3-5.5-5.5-8.8-.2-3.2 1.2-6.7 4-9.9-.4 1.6-.2 3.2.5 4.7 1.2 2.5 3.6 4 6.3 4 2.1 0 4.3-.9 6.3-2.6 7.2-6 19.4-10.4 29.1-10.4 1.9 0 3.7.2 5.3.5 7.7 1.6 12.3 3.7 13.8 6.7.8 1.6.8 3.3 0 5.4-10.1 4.3-31.2 8.6-42.1 8.6-.7 0-1.4 0-2.1-.1-1.4-.1-2.8-.1-4-.1-7.5-.1-10.6 1.4-11.6 2z" clip-path="url(#b)" fill="#38434f"/><path class="st57" d="M471.2 854c2.2 3.4 1.1 6.7 1.1 6.7" clip-path="url(#b)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path d="M489.6 967.9c34.6 23.5 68.7 8.7 80.2-2.4 11.4-11.1 20.8-28.5 20-45.9-.8-17.4-8.5-32.9-19.5-42.8-10.7-9.6-12.4-8.7-13.2-7.7-.8 1-1.4 1.1 5 12.2s13.4 24.6 9.5 40.2c-3.9 15.6-15.6 29.9-29.6 34-14 4-21.6-1.4-26.1-3.2 0 0 1 4-3.4 4.3-4.3.3-11.1-2.9-11.1-2.9s2.7 3 .1 5-5.9-.9-5.9-.9 2.5 2.2 1.5 3.3c-1 1.1-2.3.8-4.2-.6 0 0 3.2 3.6 1 5.5s-4.3 1.9-4.3 1.9z" clip-path="url(#b)" fill="#b3becd" stroke="#000" stroke-width="1.60000002" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path d="M528.6 979.9c-13.1 0-25.8-3.9-37.6-11.7.9-.2 2.1-.7 3.3-1.8 1.4-1.3 1-3 .3-4.3.4.1.8.2 1.1.2.7 0 1.3-.3 1.8-.8.3-.3.4-.6.4-1 0-.3-.1-.6-.2-.9.6.2 1.2.4 1.9.4.9 0 1.7-.3 2.4-.8.3-.2.5-.5.7-.8 12.6 6.2 22.6 9.1 31.5 9.1 7.8 0 14.7-2.3 21.1-6.9 16.8-12.3 21.3-21.3 24.6-27.9l.3-.6c2.3-4.5 4.2-6.5 6.4-6.5.9 0 1.7.3 2.7.9-1.4 13.6-8.7 28-19.6 38.6-7.9 7.4-23.4 14.8-41.1 14.8z" clip-path="url(#b)" fill="#92a1b5"/><path class="st57" d="M489.6 967.9c-3.9.1-6.7-.9-6.7-.9" clip-path="url(#b)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path d="M484 908.3c-2.2.9-2.1 3.2.1 6.7 2.1 3.5 4.5 7.9 5.9 10.3 1.4 2.4 2.6 3.5 4 2.8 1.4-.6 1.8-2 .4-4.9-1.4-2.9-5.8-11-7.2-12.7-1.6-1.7-2.4-2.5-3.2-2.2z" clip-path="url(#b)" fill="#38434f" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path class="st57" d="M525.9 907.8c2.5-3.7 4.8-5.2 4.8-5.2m3.8 11.9c1.5-3.1 3.7-5.6 3.7-5.6m11.9 13.3c1-3.9 2.7-5.6 2.7-5.6m10.7 9c.6-4.6.9-6.6.9-6.6" clip-path="url(#b)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><g class="st53" clip-path="url(#b)"><path class="st49" d="M504.9 862.2c.8-.4 1.5-.8 2.2-1.1" fill="none" stroke="#e3e5e5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M519.8 856.2c13.6-3.5 22.1.5 28.2 3.3 5.3 2.4 11.6 4.7 17.6 5.3" fill="none" stroke="#e3e5e5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4.5286,13.5857"/><path class="st49" d="M572.4 864.5c.8-.2 1.6-.4 2.4-.7" fill="none" stroke="#e3e5e5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></g><g class="st53" clip-path="url(#b)"><path d="M491.2 946.7c-4.1 1.1-6.1 1.8-10 3.5-2.1.9-4.6.1-5.6-1.9s0-4.6 2.3-5.6c4.3-1.9 6.6-2.7 11.2-3.9 2.4-.6 4.8.8 5.3 2.9.4 2.2-1 4.4-3.2 5z" fill="#505762"/></g><path class="st57" d="M515.8 952.3c-.2-2.3-1.7-3.3-1.7-3.3" clip-path="url(#b)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><g class="st53" clip-path="url(#b)"><path d="M454.5 887.5c-3.5.7-7.1 1.3-10.6 1.7-3.6.4-7.2.8-10.8.6-.3 0-.5-.2-.5-.5s.2-.5.5-.5c3.5.2 7.1-.1 10.6-.4 3.5-.4 7.1-.9 10.6-1.6.2 0 .4.1.4.3.1.2 0 .4-.2.4z"/></g><g class="st53" clip-path="url(#b)"><path d="M417.2 891.8c-2.2-1.6-4.3-3.6-5.4-6.2l-.4-1-.2-1-.1-.5v-.5c0-.4-.1-.7-.1-1.1 0-1.4.2-2.8.7-4.1.9-2.6 2.6-4.9 4.4-6.9 1.9-2 4-3.6 6.2-5.2s4.6-2.9 7-4.1c2.4-1.2 5-2.2 7.6-2.9 5.2-1.5 10.7-1.9 16.1-1.4l-.6.5c-.2-1.3-.7-2.7-1.4-3.8-.4-.6-.8-1.1-1.4-1.5-.5-.5-1.2-1-1.5-1.9-.1-.2-.1-.5-.1-.7.1-.2.1-.5.2-.7.2-.4.6-.7.9-.9.7-.5 1.4-.7 2.2-.9 1.5-.3 3-.4 4.4-.4 2.9.1 5.9.7 8.6 1.8 2.7 1.1 5.3 2.8 7.1 5.3l-.9.5c-.3-.7-.5-1.3-.5-2.1-.1-.7 0-1.5.4-2.3.3-.3.5-.7 1-.8.2-.1.4-.1.6-.2.2 0 .4-.1.6-.1.7 0 1.4.1 2.1.2 1.4.3 2.6.8 3.9 1.4 1.2.6 2.4 1.3 3.5 2.1 2.2 1.6 4.1 3.6 5.6 5.9.7 1.2 1.4 2.4 1.8 3.7.3.6.4 1.3.5 2 .1.3.1.7.1 1v1c-.1 2.8-1.3 5.5-3 7.6-1.7 2.2-3.6 4-5.9 5.6-2.4 1.4-4.9 2.3-7.4 3.2-2.5.8-5.1 1.5-7.8 1.9 1.2-.5 2.5-.9 3.7-1.4l3.7-1.3c2.5-.9 5-1.8 7.2-3.2 2.1-1.5 4-3.3 5.6-5.4 1.5-2.1 2.6-4.5 2.7-7 .1-2.5-.8-5-2.2-7.1-1.4-2.2-3.2-4.1-5.3-5.6-2.1-1.5-4.4-2.8-6.9-3.4-.6-.1-1.2-.2-1.8-.2-.6 0-1 .1-1.3.5-.5.8-.2 2.3.2 3.4.1.3 0 .5-.3.6-.2.1-.4 0-.6-.2-1.6-2.3-4-3.8-6.6-4.9-2.6-1.1-5.4-1.7-8.2-1.7-1.4 0-2.8.1-4.2.4-.7.2-1.3.4-1.8.7-.5.4-.8.8-.7 1.3.1.5.6.9 1.2 1.4.6.5 1.1 1.1 1.5 1.7.9 1.3 1.4 2.7 1.6 4.3 0 .3-.2.5-.4.5h-.1c-5.3-.5-10.7-.1-15.8 1.4-2.5.7-5 1.7-7.4 2.8-2.4 1.2-4.7 2.5-6.8 4.1-2.2 1.5-4.3 3.2-6.1 5.1-1.8 1.9-3.4 4.1-4.2 6.5-.4 1.2-.6 2.5-.7 3.8 0 .3.1.6.1 1v.5l.1.5.2.9.4.9c1 2.4 2.9 4.3 5 5.8.2.2.3.5.1.7-.2.2-.4.3-.7.1z"/></g><g class="st53" clip-path="url(#b)"><path class="st36" d="M615.1 863.2c2 1.2 2.3 1.8 2.3 1.8" fill="#38434f"/><path d="M615.3 862.9l1.2.9c.2.2.4.3.6.5.2.2.3.4.5.6 0 .1 0 .2-.1.2h-.2l-.5-.5c-.2-.1-.4-.3-.6-.4-.4-.3-.8-.5-1.3-.7-.2-.1-.2-.3-.1-.5s.2-.2.5-.1c-.1 0-.1 0 0 0z"/></g></g></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 252.85863 194.49997" width="252.85863" height="194.49997"><g transform="translate(-395.89999 -830.4)"><defs><path id="a" d="M395.89999 745.09998H690.5V1024.9H395.89999z"/></defs><clipPath id="b"><use xlink:href="#a" width="100%" height="100%" overflow="visible"/></clipPath><path class="st53" d="M339.3 1028.6c1.5-3.2 14.4-31.3 27.4-58.8-6-9.3-2-17 1.5-23.7 1.9-3.7 3.8-7.1 3.6-10.4-.8-22 8.1-38.3 22.8-41.6 2.8-.6 5.2-.9 7.5-.9 3 0 5.6.5 8.1 1.6 1.4-1.3 2.8-2.6 4.2-3.8-2.8-2.6-4.3-5.5-4.5-8.8-.3-4.5 2.2-9.5 6.8-13.7 5.3-4.8 16.5-12.9 31.7-12.9.9 0 1.7 0 2.6.1-.4-.9-1-2-2.1-2.9-2.1-1.6-1.9-3.2-1.6-4 .7-2.1 3.6-3.2 8.1-3.2 3.9 0 9.7 1.2 14 4.4.3-.7.7-1.3 1.3-1.7.5-.3 1.3-.5 2.2-.5 3.4 0 10.6 2.7 15.5 9.9 3.6 5.3 3.6 10.8.1 16 18.3 4.7 30.1 15.6 39.5 24.4 2.5 2.4 5 4.6 7.3 6.5 10.7 8.9 21.4 13.2 32.7 13.2.9 0 1.8 0 2.7-.1 2-13.5-4.1-25.5-10-35.7-6.2-10.7-6.4-12.1-4.9-13.9l.1-.1c.6-.7 1.3-1 2.1-1.1h.3c1.7 0 4.5 1 13 8.7 9.9 9 16.9 22.2 19.2 36.5 8.9-4.9 15.2-12.5 17.1-20.3 2-8.6.5-16.8-4.2-22.7l-13.1 6.1-7-16.9-15.6 3.2 7.2-19.5h.1l-.2-.5 4.6-11.1 65.7 11.9c3.1.6 3.9 2.7 3.6 4.5l-.2 1-.4-.1c-.3.4-.6.7-1 .9-.7.3-7.5 3.6-21 9.9 2.1 2.9 2.2 6.1.4 9.4-1.1 1.9-2.5 6.2.4 13.3 3.7 8.9 3.5 29.2-8.3 46.2-8.1 11.7-18.3 23-37.6 26.6-3 4.6-6.5 9-10.2 12.6-7.8 7.6-24 15.3-42.3 15.3-5.3 0-10.7-.6-15.9-1.9-7.6 12.9-10.5 26.2-10.6 32.1-.2 11 .9 16.1.9 16.2l.4 1.8-164.9.8.9-2.3z" clip-path="url(#b)"/><path class="st53" d="M339.8 1028.8c.1-.3 13.9-30.1 27.5-59.1-6.1-9.2-2.1-16.8 1.4-23.5 2-3.7 3.8-7.3 3.7-10.6-.8-21.7 8-37.9 22.4-41 2.7-.6 5.1-.9 7.4-.9 3 0 5.7.5 8.2 1.7 1.6-1.6 3.2-3 4.9-4.4-3-2.6-4.6-5.5-4.8-8.8-.3-4.4 2.1-9.2 6.6-13.3 5.3-4.8 16.4-12.8 31.4-12.8 1.1 0 2.2 0 3.3.1-.3-1.1-1-2.7-2.5-3.8-1.8-1.4-1.7-2.8-1.5-3.4.6-1.8 3.4-2.9 7.6-2.9 4.4 0 10.3 1.5 14.3 4.8.1-.9.6-1.7 1.3-2.1.4-.3 1.1-.4 1.9-.4 3.3 0 10.3 2.6 15.1 9.7 3.6 5.3 3.5 10.9-.3 16.1 18.6 4.6 30.5 15.6 40 24.4 2.6 2.4 5 4.6 7.3 6.5 10.8 8.9 21.6 13.3 33.1 13.3 1 0 2.1 0 3.1-.1 2.2-13.9-4-26.1-10-36.4-6.3-10.9-6.2-11.8-5-13.3l.1-.1c.2-.2.7-.9 1.8-.9h.2c1.5 0 4.2 1 12.7 8.6 10 9.1 17 22.5 19.2 36.9 9.3-5 16-12.8 17.9-20.9 2.1-8.9.4-17.4-4.5-23.4l-13 6.1-6.9-16.8-15.1 3.1 6.8-18.4h.6l-.5-1 4.4-10.6 65.3 11.8c3.5.7 3.3 3.2 3.2 3.9l-.1.5h-.2c-.2.4-.6.8-1 1-.7.3-7.9 3.7-21.6 10.2.1.1.2.2.3.4 2 2.7 2.2 5.8.4 8.9-1.1 2-2.6 6.4.4 13.7 3.6 8.8 3.4 28.8-8.2 45.7-8.1 11.7-18.2 22.9-37.5 26.4-3 4.7-6.5 9.1-10.3 12.7-7.8 7.5-23.7 15.1-42 15.1-5.4 0-10.9-.7-16.1-2-7.7 13.1-10.7 26.7-10.9 32.7-.2 11.1.9 16.2.9 16.3l.3 1.2-163.5.8.5-1.7z" clip-path="url(#b)"/><path d="M577.5 843.7l-1-2.1 3.9-9.4 64.5 11.6c2.4.5 2.5 2 2.4 2.8" clip-path="url(#b)" stroke-miterlimit="10" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path d="M584.2 856.7l6.9 16.6s53.7-25.1 55.5-26c.9-.5 1.5-2.2-1-2.3-2.5-.1-69.6-1.4-69.6-1.4l-5.9 16 14.1-2.9z" clip-path="url(#b)" stroke-miterlimit="10" fill="#fff" stroke="#000" stroke-width="1.70000005" stroke-linecap="round" stroke-linejoin="round"/><path d="M585.1 857l58.9-11.2 1.4-.5h.4c.8 0 .7.3.8.6.1.3-.1.6-.4.8L587 861.5l-1.9-4.5z" clip-path="url(#b)" fill="#d1d3d4"/><path class="st57" clip-path="url(#b)" stroke-miterlimit="10" d="M584.20001 856.70001l61.79999-11.5" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path class="st58" d="M388.5 927.9c20-40 49.9-56.3 83.4-54.9 33.6 1.4 48.8 21.7 62.4 32.9 13.6 11.2 27.5 15.6 43.4 12.5 15.9-3.1 28.5-14.6 31.2-26.1 2.7-11.5-1-20.7-6.1-25.8-5.1-5.1-1.4-8.1 4.1-6.8 5.4 1.4 8.1 3.4 8.1 3.4s7.8-6.4 9.8-3.7c2 2.7 1.7 5.4.3 7.8-1.4 2.4-2.7 7.1.3 14.6 3.1 7.5 4.1 27.1-8.1 44.8-12.2 17.6-26.5 30.1-62.1 26.5-38.5-3.9-54.6 44.2-54.9 59-.2 11.5.9 16.5.9 16.5l-160.7.8c.2-.1 33.4-72.4 48-101.5z" clip-path="url(#b)" stroke-miterlimit="10" fill="#53606c" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path class="st59" d="M399.4 989.3c-6.1 0-9.9-4.3-10-4.3l-.2-.2-.3.1c-3.1 1-6.1 1.5-9 1.5-6.1 0-11.4-2.2-15.6-6.5 1.4-3 2.9-6.1 4.3-9.1.1.1.1.2.2.3 6.1 8 12.7 9.7 17 9.7 3.2 0 5.6-.9 6.6-1.3.7 1.3 3 4.7 7.5 4.7 1.2 0 2.5-.3 3.9-.8 6.7-2.5 15.7-13.1 18.3-22.1 3.2 5.7-2.8 18.3-13.8 25.3-3 1.7-6 2.7-8.9 2.7zm24.5-78.1c-5.7-11.1-10.8-14.2-12.4-14.9 15.7-15.3 34.7-23 56.6-23 1.3 0 2.5 0 3.8.1 2.1.1 4.2.3 6.4.5 1.5 2.7-.4 5.6-.8 6.3-3 .8-11.2 6.5-18.5 11.6-1.9 1.3-3.7 2.6-5.3 3.7-5.3 3.7-13.6 4.2-18 4.2-1.8 0-2.9-.1-2.9-.1h-.3l-8.6 11.6zm95.3 49c4.2-7.1 12.6-15.2 28.3-15.2 3.5 0 7.2.4 11 1.2 3.4.7 7 1.1 10.6 1.1 15.9 0 29.8-7.9 34.8-12.5 6.5-6 10.5-9.8 12-12.4 1.4-2.4 3.2-2.8 4.7-2 .1.1-.7 1.1-.6 1.1-.9 1.7-1.9 3.3-3 4.8-11.5 16.6-23.9 26.9-50 26.9-3.6 0-7.5-.2-11.6-.6-1.5-.2-3-.2-4.4-.2-9.4 0-18.1 3.2-25.9 9.4l-5.9-1.6z" clip-path="url(#b)" fill="#38434f"/><path class="st58" d="M427.4 920.5c-9.1-24.2-19-27.9-32.4-25-13.4 3-22.4 18-21.6 40 .4 10.2-15.5 20.5-4.3 35s23.5 8 23.5 8 3 7.1 11 4.1 16.7-13.5 18.5-21.6" clip-path="url(#b)" stroke-miterlimit="10" fill="#53606c" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path class="st59" d="M417.2 934.2c-7.3.5-8.6-4.8-7.3-7 1.2-2.1 4.6-3.2 4.6-3.2s-5.3-.2-6.3-4c-1-3.7 4-5.3 4-5.3s-2.9-8.6-13.3-6.1-14.4 11.7-14.2 19c.6 15.3-10.2 22.8 0 29.7 10.1 6.9 23.7-2.8 28.5-11.3 4.8-8.5 4-11.8 4-11.8z" clip-path="url(#b)" fill="#38434f"/><path class="st57" d="M392.6 978.6c6.3-3.9 9.1-7.5 9.1-7.5m15.5-36.9c-7.3.5-8.6-4.8-7.3-7 1.2-2.1 4.6-3.2 4.6-3.2s-5.3-.2-6.3-4c-1-3.7 4-5.3 4-5.3s-2.9-8.6-13.3-6.1-14.6 11.7-14.2 19" clip-path="url(#b)" stroke-miterlimit="10" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path d="M417.5 891.4c-9.8-6.9-6.2-16 .3-21.8 6.5-5.8 19-14 35.2-12.2 0 0-.2-3.5-3.1-5.8s-.5-4.5 5.5-4.5 12.9 2.6 15.8 6.9c0 0-1.4-3.2.4-4.4 1.8-1.2 10.2 1 15.6 9s1.1 14.9-5.2 19.9c-6.3 5-36.2 11.6-48.8 10.9" clip-path="url(#b)" fill="#53606c"/><g class="st53" clip-path="url(#b)"><path class="st36" d="M459.5 878.7c-11.1.6-22.1 3.3-32.9 6.1-4.1 1.1-8.2 2.2-12.4 2-.4 0-.8-.1-1.3-.1 1 1.6 2.5 3.2 4.6 4.7l15.6-2.1c11.3.7 37-4.7 46.4-9.4-6.5-1.1-13.3-1.5-20-1.2z" fill="#38434f"/></g><path class="st59" d="M417.5 890.8c-3.5-2.5-5.3-5.5-5.5-8.8-.2-3.2 1.2-6.7 4-9.9-.4 1.6-.2 3.2.5 4.7 1.2 2.5 3.6 4 6.3 4 2.1 0 4.3-.9 6.3-2.6 7.2-6 19.4-10.4 29.1-10.4 1.9 0 3.7.2 5.3.5 7.7 1.6 12.3 3.7 13.8 6.7.8 1.6.8 3.3 0 5.4-10.1 4.3-31.2 8.6-42.1 8.6-.7 0-1.4 0-2.1-.1-1.4-.1-2.8-.1-4-.1-7.5-.1-10.6 1.4-11.6 2z" clip-path="url(#b)" fill="#38434f"/><path class="st57" d="M471.2 854c2.2 3.4 1.1 6.7 1.1 6.7" clip-path="url(#b)" stroke-miterlimit="10" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path d="M489.6 967.9c34.6 23.5 68.7 8.7 80.2-2.4 11.4-11.1 20.8-28.5 20-45.9-.8-17.4-8.5-32.9-19.5-42.8-10.7-9.6-12.4-8.7-13.2-7.7-.8 1-1.4 1.1 5 12.2s13.4 24.6 9.5 40.2c-3.9 15.6-15.6 29.9-29.6 34-14 4-21.6-1.4-26.1-3.2 0 0 1 4-3.4 4.3-4.3.3-11.1-2.9-11.1-2.9s2.7 3 .1 5-5.9-.9-5.9-.9 2.5 2.2 1.5 3.3c-1 1.1-2.3.8-4.2-.6 0 0 3.2 3.6 1 5.5s-4.3 1.9-4.3 1.9z" clip-path="url(#b)" stroke-miterlimit="10" fill="#b3becd" stroke="#000" stroke-width="1.60000002" stroke-linecap="round" stroke-linejoin="round"/><path d="M528.6 979.9c-13.1 0-25.8-3.9-37.6-11.7.9-.2 2.1-.7 3.3-1.8 1.4-1.3 1-3 .3-4.3.4.1.8.2 1.1.2.7 0 1.3-.3 1.8-.8.3-.3.4-.6.4-1 0-.3-.1-.6-.2-.9.6.2 1.2.4 1.9.4.9 0 1.7-.3 2.4-.8.3-.2.5-.5.7-.8 12.6 6.2 22.6 9.1 31.5 9.1 7.8 0 14.7-2.3 21.1-6.9 16.8-12.3 21.3-21.3 24.6-27.9l.3-.6c2.3-4.5 4.2-6.5 6.4-6.5.9 0 1.7.3 2.7.9-1.4 13.6-8.7 28-19.6 38.6-7.9 7.4-23.4 14.8-41.1 14.8z" clip-path="url(#b)" fill="#92a1b5"/><path class="st57" d="M489.6 967.9c-3.9.1-6.7-.9-6.7-.9" clip-path="url(#b)" stroke-miterlimit="10" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path d="M484 908.3c-2.2.9-2.1 3.2.1 6.7 2.1 3.5 4.5 7.9 5.9 10.3 1.4 2.4 2.6 3.5 4 2.8 1.4-.6 1.8-2 .4-4.9-1.4-2.9-5.8-11-7.2-12.7-1.6-1.7-2.4-2.5-3.2-2.2z" clip-path="url(#b)" stroke-miterlimit="10" fill="#38434f" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path class="st57" d="M525.9 907.8c2.5-3.7 4.8-5.2 4.8-5.2m3.8 11.9c1.5-3.1 3.7-5.6 3.7-5.6m11.9 13.3c1-3.9 2.7-5.6 2.7-5.6m10.7 9c.6-4.6.9-6.6.9-6.6" clip-path="url(#b)" stroke-miterlimit="10" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><g class="st53" clip-path="url(#b)"><path class="st49" d="M504.9 862.2c.8-.4 1.5-.8 2.2-1.1" fill="none" stroke="#e3e5e5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M519.8 856.2c13.6-3.5 22.1.5 28.2 3.3 5.3 2.4 11.6 4.7 17.6 5.3" fill="none" stroke="#e3e5e5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4.5286,13.5857"/><path class="st49" d="M572.4 864.5c.8-.2 1.6-.4 2.4-.7" fill="none" stroke="#e3e5e5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></g><g class="st53" clip-path="url(#b)"><path d="M491.2 946.7c-4.1 1.1-6.1 1.8-10 3.5-2.1.9-4.6.1-5.6-1.9s0-4.6 2.3-5.6c4.3-1.9 6.6-2.7 11.2-3.9 2.4-.6 4.8.8 5.3 2.9.4 2.2-1 4.4-3.2 5z" fill="#505762"/></g><path class="st57" d="M515.8 952.3c-.2-2.3-1.7-3.3-1.7-3.3" clip-path="url(#b)" stroke-miterlimit="10" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><g class="st53" clip-path="url(#b)"><path d="M454.5 887.5c-3.5.7-7.1 1.3-10.6 1.7-3.6.4-7.2.8-10.8.6-.3 0-.5-.2-.5-.5s.2-.5.5-.5c3.5.2 7.1-.1 10.6-.4 3.5-.4 7.1-.9 10.6-1.6.2 0 .4.1.4.3.1.2 0 .4-.2.4z"/></g><g class="st53" clip-path="url(#b)"><path d="M417.2 891.8c-2.2-1.6-4.3-3.6-5.4-6.2l-.4-1-.2-1-.1-.5v-.5c0-.4-.1-.7-.1-1.1 0-1.4.2-2.8.7-4.1.9-2.6 2.6-4.9 4.4-6.9 1.9-2 4-3.6 6.2-5.2s4.6-2.9 7-4.1c2.4-1.2 5-2.2 7.6-2.9 5.2-1.5 10.7-1.9 16.1-1.4l-.6.5c-.2-1.3-.7-2.7-1.4-3.8-.4-.6-.8-1.1-1.4-1.5-.5-.5-1.2-1-1.5-1.9-.1-.2-.1-.5-.1-.7.1-.2.1-.5.2-.7.2-.4.6-.7.9-.9.7-.5 1.4-.7 2.2-.9 1.5-.3 3-.4 4.4-.4 2.9.1 5.9.7 8.6 1.8 2.7 1.1 5.3 2.8 7.1 5.3l-.9.5c-.3-.7-.5-1.3-.5-2.1-.1-.7 0-1.5.4-2.3.3-.3.5-.7 1-.8.2-.1.4-.1.6-.2.2 0 .4-.1.6-.1.7 0 1.4.1 2.1.2 1.4.3 2.6.8 3.9 1.4 1.2.6 2.4 1.3 3.5 2.1 2.2 1.6 4.1 3.6 5.6 5.9.7 1.2 1.4 2.4 1.8 3.7.3.6.4 1.3.5 2 .1.3.1.7.1 1v1c-.1 2.8-1.3 5.5-3 7.6-1.7 2.2-3.6 4-5.9 5.6-2.4 1.4-4.9 2.3-7.4 3.2-2.5.8-5.1 1.5-7.8 1.9 1.2-.5 2.5-.9 3.7-1.4l3.7-1.3c2.5-.9 5-1.8 7.2-3.2 2.1-1.5 4-3.3 5.6-5.4 1.5-2.1 2.6-4.5 2.7-7 .1-2.5-.8-5-2.2-7.1-1.4-2.2-3.2-4.1-5.3-5.6-2.1-1.5-4.4-2.8-6.9-3.4-.6-.1-1.2-.2-1.8-.2-.6 0-1 .1-1.3.5-.5.8-.2 2.3.2 3.4.1.3 0 .5-.3.6-.2.1-.4 0-.6-.2-1.6-2.3-4-3.8-6.6-4.9-2.6-1.1-5.4-1.7-8.2-1.7-1.4 0-2.8.1-4.2.4-.7.2-1.3.4-1.8.7-.5.4-.8.8-.7 1.3.1.5.6.9 1.2 1.4.6.5 1.1 1.1 1.5 1.7.9 1.3 1.4 2.7 1.6 4.3 0 .3-.2.5-.4.5h-.1c-5.3-.5-10.7-.1-15.8 1.4-2.5.7-5 1.7-7.4 2.8-2.4 1.2-4.7 2.5-6.8 4.1-2.2 1.5-4.3 3.2-6.1 5.1-1.8 1.9-3.4 4.1-4.2 6.5-.4 1.2-.6 2.5-.7 3.8 0 .3.1.6.1 1v.5l.1.5.2.9.4.9c1 2.4 2.9 4.3 5 5.8.2.2.3.5.1.7-.2.2-.4.3-.7.1z"/></g><g class="st53" clip-path="url(#b)"><path class="st36" d="M615.1 863.2c2 1.2 2.3 1.8 2.3 1.8" fill="#38434f"/><path d="M615.3 862.9l1.2.9c.2.2.4.3.6.5.2.2.3.4.5.6 0 .1 0 .2-.1.2h-.2l-.5-.5c-.2-.1-.4-.3-.6-.4-.4-.3-.8-.5-1.3-.7-.2-.1-.2-.3-.1-.5s.2-.2.5-.1c-.1 0-.1 0 0 0z"/></g></g></svg>
diff --git a/app/javascript/mastodon/actions/announcements.js b/app/javascript/mastodon/actions/announcements.js
new file mode 100644
index 000000000..c65bc052e
--- /dev/null
+++ b/app/javascript/mastodon/actions/announcements.js
@@ -0,0 +1,133 @@
+import api from '../api';
+import { normalizeAnnouncement } from './importer/normalizer';
+
+export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
+export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
+export const ANNOUNCEMENTS_FETCH_FAIL    = 'ANNOUNCEMENTS_FETCH_FAIL';
+export const ANNOUNCEMENTS_UPDATE        = 'ANNOUNCEMENTS_UPDATE';
+export const ANNOUNCEMENTS_DISMISS       = 'ANNOUNCEMENTS_DISMISS';
+
+export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
+export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
+export const ANNOUNCEMENTS_REACTION_ADD_FAIL    = 'ANNOUNCEMENTS_REACTION_ADD_FAIL';
+
+export const ANNOUNCEMENTS_REACTION_REMOVE_REQUEST = 'ANNOUNCEMENTS_REACTION_REMOVE_REQUEST';
+export const ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS = 'ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS';
+export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL    = 'ANNOUNCEMENTS_REACTION_REMOVE_FAIL';
+
+export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
+
+const noOp = () => {};
+
+export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
+  dispatch(fetchAnnouncementsRequest());
+
+  api(getState).get('/api/v1/announcements').then(response => {
+    dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
+  }).catch(error => {
+    dispatch(fetchAnnouncementsFail(error));
+  }).finally(() => {
+    done();
+  });
+};
+
+export const fetchAnnouncementsRequest = () => ({
+  type: ANNOUNCEMENTS_FETCH_REQUEST,
+  skipLoading: true,
+});
+
+export const fetchAnnouncementsSuccess = announcements => ({
+  type: ANNOUNCEMENTS_FETCH_SUCCESS,
+  announcements,
+  skipLoading: true,
+});
+
+export const fetchAnnouncementsFail= error => ({
+  type: ANNOUNCEMENTS_FETCH_FAIL,
+  error,
+  skipLoading: true,
+  skipAlert: true,
+});
+
+export const updateAnnouncements = announcement => ({
+  type: ANNOUNCEMENTS_UPDATE,
+  announcement: normalizeAnnouncement(announcement),
+});
+
+export const dismissAnnouncement = announcementId => (dispatch, getState) => {
+  dispatch({
+    type: ANNOUNCEMENTS_DISMISS,
+    id: announcementId,
+  });
+
+  api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
+};
+
+export const addReaction = (announcementId, name) => (dispatch, getState) => {
+  dispatch(addReactionRequest(announcementId, name));
+
+  api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
+    dispatch(addReactionSuccess(announcementId, name));
+  }).catch(err => {
+    dispatch(addReactionFail(announcementId, name, err));
+  });
+};
+
+export const addReactionRequest = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_ADD_REQUEST,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const addReactionSuccess = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_ADD_SUCCESS,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const addReactionFail = (announcementId, name, error) => ({
+  type: ANNOUNCEMENTS_REACTION_ADD_FAIL,
+  id: announcementId,
+  name,
+  error,
+  skipLoading: true,
+});
+
+export const removeReaction = (announcementId, name) => (dispatch, getState) => {
+  dispatch(removeReactionRequest(announcementId, name));
+
+  api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
+    dispatch(removeReactionSuccess(announcementId, name));
+  }).catch(err => {
+    dispatch(removeReactionFail(announcementId, name, err));
+  });
+};
+
+export const removeReactionRequest = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const removeReactionSuccess = (announcementId, name) => ({
+  type: ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS,
+  id: announcementId,
+  name,
+  skipLoading: true,
+});
+
+export const removeReactionFail = (announcementId, name, error) => ({
+  type: ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+  id: announcementId,
+  name,
+  error,
+  skipLoading: true,
+});
+
+export const updateReaction = reaction => ({
+  type: ANNOUNCEMENTS_REACTION_UPDATE,
+  reaction,
+});
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index 78f321da4..f7cbe4c1c 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -76,7 +76,6 @@ export function normalizeStatus(status, normalOldStatus) {
 
 export function normalizePoll(poll) {
   const normalPoll = { ...poll };
-
   const emojiMap = makeEmojiMap(normalPoll);
 
   normalPoll.options = poll.options.map((option, index) => ({
@@ -87,3 +86,12 @@ export function normalizePoll(poll) {
 
   return normalPoll;
 }
+
+export function normalizeAnnouncement(announcement) {
+  const normalAnnouncement = { ...announcement };
+  const emojiMap = makeEmojiMap(normalAnnouncement);
+
+  normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
+
+  return normalAnnouncement;
+}
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 798f9b37e..8a066b896 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -157,9 +157,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 
       dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
       fetchRelatedRelationships(dispatch, response.data);
-      done();
     }).catch(error => {
       dispatch(expandNotificationsFail(error, isLoadingMore));
+    }).finally(() => {
       done();
     });
   };
@@ -188,6 +188,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
     type: NOTIFICATIONS_EXPAND_FAIL,
     error,
     skipLoading: !isLoadingMore,
+    skipAlert: !isLoadingMore,
   };
 };
 
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index c678e9393..ac325f74c 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -8,6 +8,7 @@ import {
 } from './timelines';
 import { updateNotifications, expandNotifications } from './notifications';
 import { updateConversations } from './conversations';
+import { fetchAnnouncements, updateAnnouncements, updateReaction as updateAnnouncementsReaction } from './announcements';
 import { fetchFilters } from './filters';
 import { getLocale } from '../locales';
 
@@ -44,6 +45,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
         case 'filters_changed':
           dispatch(fetchFilters());
           break;
+        case 'announcement':
+          dispatch(updateAnnouncements(JSON.parse(data.payload)));
+          break;
+        case 'announcement.reaction':
+          dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
+          break;
         }
       },
     };
@@ -51,7 +58,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
 }
 
 const refreshHomeTimelineAndNotification = (dispatch, done) => {
-  dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
+  dispatch(expandHomeTimeline({}, () =>
+    dispatch(expandNotifications({}, () =>
+      dispatch(fetchAnnouncements(done))))));
 };
 
 export const connectUserStream      = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index bc2ac5e82..054668655 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -98,9 +98,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
       const next = getLinks(response).refs.find(link => link.rel === 'next');
       dispatch(importFetchedStatuses(response.data));
       dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
-      done();
     }).catch(error => {
       dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
+    }).finally(() => {
       done();
     });
   };
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
index 800b1c270..4e1c882e2 100644
--- a/app/javascript/mastodon/components/error_boundary.js
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.PureComponent {
         <div>
           <p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
           <p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
-          <p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
+          <p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index 8d0cbe5a1..37622d4c0 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -115,6 +115,7 @@ class AccountTimeline extends ImmutablePureComponent {
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
+          timelineId='account'
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index e57c3c20c..582bb0d39 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -290,6 +290,7 @@ class EmojiPickerDropdown extends React.PureComponent {
     onPickEmoji: PropTypes.func.isRequired,
     onSkinTone: PropTypes.func.isRequired,
     skinTone: PropTypes.number.isRequired,
+    button: PropTypes.node,
   };
 
   state = {
@@ -350,18 +351,18 @@ class EmojiPickerDropdown extends React.PureComponent {
   }
 
   render () {
-    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
+    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
     const title = intl.formatMessage(messages.emoji);
     const { active, loading, placement } = this.state;
 
     return (
       <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
         <div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
-          <img
+          {button || <img
             className={classNames('emojione', { 'pulse-loading': active && loading })}
             alt='🙂'
             src={`${assetHost}/emoji/1f602.svg`}
-          />
+          />}
         </div>
 
         <Overlay show={active} placement={placement} target={this.findTarget}>
diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js
index 50ad74450..cb47d9db4 100644
--- a/app/javascript/mastodon/features/directory/components/account_card.js
+++ b/app/javascript/mastodon/features/directory/components/account_card.js
@@ -22,6 +22,7 @@ const messages = defineMessages({
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
   unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+  unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
 });
 
 const makeMapStateToProps = () => {
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js
new file mode 100644
index 000000000..ee444e3f0
--- /dev/null
+++ b/app/javascript/mastodon/features/getting_started/components/announcements.js
@@ -0,0 +1,395 @@
+import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ReactSwipeableViews from 'react-swipeable-views';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import IconButton from 'mastodon/components/icon_button';
+import Icon from 'mastodon/components/icon';
+import { defineMessages, injectIntl, FormattedMessage, FormattedDate, FormattedNumber } from 'react-intl';
+import { autoPlayGif } from 'mastodon/initial_state';
+import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
+import { mascot } from 'mastodon/initial_state';
+import unicodeMapping from 'mastodon/features/emoji/emoji_unicode_mapping_light';
+import classNames from 'classnames';
+import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+  previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
+  next: { id: 'lightbox.next', defaultMessage: 'Next' },
+});
+
+class Content extends ImmutablePureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  static propTypes = {
+    announcement: ImmutablePropTypes.map.isRequired,
+  };
+
+  setRef = c => {
+    this.node = c;
+  }
+
+  componentDidMount () {
+    this._updateLinks();
+    this._updateEmojis();
+  }
+
+  componentDidUpdate () {
+    this._updateLinks();
+    this._updateEmojis();
+  }
+
+  _updateEmojis () {
+    const node = this.node;
+
+    if (!node || autoPlayGif) {
+      return;
+    }
+
+    const emojis = node.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+
+      if (emoji.classList.contains('status-emoji')) {
+        continue;
+      }
+
+      emoji.classList.add('status-emoji');
+
+      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
+      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
+    }
+  }
+
+  _updateLinks () {
+    const node = this.node;
+
+    if (!node) {
+      return;
+    }
+
+    const links = node.querySelectorAll('a');
+
+    for (var i = 0; i < links.length; ++i) {
+      let link = links[i];
+
+      if (link.classList.contains('status-link')) {
+        continue;
+      }
+
+      link.classList.add('status-link');
+
+      let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url'));
+
+      if (mention) {
+        link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
+        link.setAttribute('title', mention.get('acct'));
+      } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
+        link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
+      } else {
+        link.setAttribute('title', link.href);
+        link.classList.add('unhandled-link');
+      }
+
+      link.setAttribute('target', '_blank');
+      link.setAttribute('rel', 'noopener noreferrer');
+    }
+  }
+
+  onMentionClick = (mention, e) => {
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.context.router.history.push(`/accounts/${mention.get('id')}`);
+    }
+  }
+
+  onHashtagClick = (hashtag, e) => {
+    hashtag = hashtag.replace(/^#/, '');
+
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.context.router.history.push(`/timelines/tag/${hashtag}`);
+    }
+  }
+
+  handleEmojiMouseEnter = ({ target }) => {
+    target.src = target.getAttribute('data-original');
+  }
+
+  handleEmojiMouseLeave = ({ target }) => {
+    target.src = target.getAttribute('data-static');
+  }
+
+  render () {
+    const { announcement } = this.props;
+
+    return (
+      <div
+        className='announcements__item__content'
+        ref={this.setRef}
+        dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
+      />
+    );
+  }
+
+}
+
+const assetHost = process.env.CDN_HOST || '';
+
+class Emoji extends React.PureComponent {
+
+  static propTypes = {
+    emoji: PropTypes.string.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+    hovered: PropTypes.bool.isRequired,
+  };
+
+  render () {
+    const { emoji, emojiMap, hovered } = this.props;
+
+    if (unicodeMapping[emoji]) {
+      const { filename, shortCode } = unicodeMapping[this.props.emoji];
+      const title = shortCode ? `:${shortCode}:` : '';
+
+      return (
+        <img
+          draggable='false'
+          className='emojione'
+          alt={emoji}
+          title={title}
+          src={`${assetHost}/emoji/${filename}.svg`}
+        />
+      );
+    } else if (emojiMap.get(emoji)) {
+      const filename  = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
+      const shortCode = `:${emoji}:`;
+
+      return (
+        <img
+          draggable='false'
+          className='emojione custom-emoji'
+          alt={shortCode}
+          title={shortCode}
+          src={filename}
+        />
+      );
+    } else {
+      return null;
+    }
+  }
+
+}
+
+class Reaction extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcementId: PropTypes.string.isRequired,
+    reaction: ImmutablePropTypes.map.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+  };
+
+  state = {
+    hovered: false,
+  };
+
+  handleClick = () => {
+    const { reaction, announcementId, addReaction, removeReaction } = this.props;
+
+    if (reaction.get('me')) {
+      removeReaction(announcementId, reaction.get('name'));
+    } else {
+      addReaction(announcementId, reaction.get('name'));
+    }
+  }
+
+  handleMouseEnter = () => this.setState({ hovered: true })
+
+  handleMouseLeave = () => this.setState({ hovered: false })
+
+  render () {
+    const { reaction } = this.props;
+
+    let shortCode = reaction.get('name');
+
+    if (unicodeMapping[shortCode]) {
+      shortCode = unicodeMapping[shortCode].shortCode;
+    }
+
+    return (
+      <button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`}>
+        <span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} /></span>
+        <span className='reactions-bar__item__count'><FormattedNumber value={reaction.get('count')} /></span>
+      </button>
+    );
+  }
+
+}
+
+class ReactionsBar extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcementId: PropTypes.string.isRequired,
+    reactions: ImmutablePropTypes.list.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+  };
+
+  handleEmojiPick = data => {
+    const { addReaction, announcementId } = this.props;
+    addReaction(announcementId, data.native.replace(/:/g, ''));
+  }
+
+  render () {
+    const { reactions } = this.props;
+    const visibleReactions = reactions.filter(x => x.get('count') > 0);
+
+    return (
+      <div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
+        {visibleReactions.map(reaction => (
+          <Reaction
+            key={reaction.get('name')}
+            reaction={reaction}
+            announcementId={this.props.announcementId}
+            addReaction={this.props.addReaction}
+            removeReaction={this.props.removeReaction}
+            emojiMap={this.props.emojiMap}
+          />
+        ))}
+
+        <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' />} />
+      </div>
+    );
+  }
+
+}
+
+class Announcement extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcement: ImmutablePropTypes.map.isRequired,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+    dismissAnnouncement: PropTypes.func.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleDismissClick = () => {
+    const { dismissAnnouncement, announcement } = this.props;
+    dismissAnnouncement(announcement.get('id'));
+  }
+
+  render () {
+    const { announcement, intl } = this.props;
+    const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
+    const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
+    const now = new Date();
+    const hasTimeRange = startsAt && endsAt;
+    const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
+    const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
+    const skipTime = announcement.get('all_day');
+
+    return (
+      <div className='announcements__item'>
+        <strong className='announcements__item__range'>
+          <FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
+          {hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
+        </strong>
+
+        <Content announcement={announcement} />
+
+        <ReactionsBar
+          reactions={announcement.get('reactions')}
+          announcementId={announcement.get('id')}
+          addReaction={this.props.addReaction}
+          removeReaction={this.props.removeReaction}
+          emojiMap={this.props.emojiMap}
+        />
+
+        <IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
+      </div>
+    );
+  }
+
+}
+
+export default @injectIntl
+class Announcements extends ImmutablePureComponent {
+
+  static propTypes = {
+    announcements: ImmutablePropTypes.list,
+    emojiMap: ImmutablePropTypes.map.isRequired,
+    fetchAnnouncements: PropTypes.func.isRequired,
+    dismissAnnouncement: PropTypes.func.isRequired,
+    addReaction: PropTypes.func.isRequired,
+    removeReaction: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    index: 0,
+  };
+
+  componentDidMount () {
+    const { fetchAnnouncements } = this.props;
+    fetchAnnouncements();
+  }
+
+  handleChangeIndex = index => {
+    this.setState({ index: index % this.props.announcements.size });
+  }
+
+  handleNextClick = () => {
+    this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
+  }
+
+  handlePrevClick = () => {
+    this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
+  }
+
+  render () {
+    const { announcements, intl } = this.props;
+    const { index } = this.state;
+
+    if (announcements.isEmpty()) {
+      return null;
+    }
+
+    return (
+      <div className='announcements'>
+        <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
+
+        <div className='announcements__container'>
+          <ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
+            {announcements.map(announcement => (
+              <Announcement
+                key={announcement.get('id')}
+                announcement={announcement}
+                emojiMap={this.props.emojiMap}
+                dismissAnnouncement={this.props.dismissAnnouncement}
+                addReaction={this.props.addReaction}
+                removeReaction={this.props.removeReaction}
+                intl={intl}
+              />
+            ))}
+          </ReactSwipeableViews>
+
+          <div className='announcements__pagination'>
+            <IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' onClick={this.handlePrevClick} size={13} />
+            <span>{index + 1} / {announcements.size}</span>
+            <IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' onClick={this.handleNextClick} size={13} />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
new file mode 100644
index 000000000..b10d1d4ce
--- /dev/null
+++ b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux';
+import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
+import Announcements from '../components/announcements';
+import { createSelector } from 'reselect';
+import { Map as ImmutableMap } from 'immutable';
+
+const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
+
+const mapStateToProps = state => ({
+  announcements: state.getIn(['announcements', 'items']),
+  emojiMap: customEmojiMap(state),
+});
+
+const mapDispatchToProps = dispatch => ({
+  fetchAnnouncements: () => dispatch(fetchAnnouncements()),
+  dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
+  addReaction: (id, name) => dispatch(addReaction(id, name)),
+  removeReaction: (id, name) => dispatch(removeReaction(id, name)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Announcements);
diff --git a/app/javascript/mastodon/features/getting_started/containers/trends_container.js b/app/javascript/mastodon/features/getting_started/containers/trends_container.js
index 1df3fb4fe..7a5268780 100644
--- a/app/javascript/mastodon/features/getting_started/containers/trends_container.js
+++ b/app/javascript/mastodon/features/getting_started/containers/trends_container.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { fetchTrends } from '../../../actions/trends';
+import { fetchTrends } from 'mastodon/actions/trends';
 import Trends from '../components/trends';
 
 const mapStateToProps = state => ({
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index 1cafb88ed..b7f9d5095 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { Link } from 'react-router-dom';
+import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
 
 const messages = defineMessages({
   title: { id: 'column.home', defaultMessage: 'Home' },
@@ -113,6 +114,8 @@ class HomeTimeline extends React.PureComponent {
         </ColumnHeader>
 
         <StatusListContainer
+          prepend={<AnnouncementsContainer />}
+          alwaysPrepend
           trackScroll={!pinned}
           scrollKey={`home_timeline-${columnId}`}
           onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index a785551c0..d7f97f210 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -211,7 +211,6 @@ class MediaModal extends ImmutablePureComponent {
             style={swipeableViewsStyle}
             containerStyle={containerStyle}
             onChangeIndex={this.handleSwipe}
-            onSwitching={this.handleSwitching}
             index={index}
           >
             {content}
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index dc4b8ce76..5ce5eb12e 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "المعدل محدود",
   "alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.",
   "alert.unexpected.title": "المعذرة!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} في الأسبوع",
   "boost_modal.combo": "يمكنك/ي ضغط {combo} لتخطّي هذه في المرّة القادمة",
   "bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "مدة استطلاع الرأي",
   "compose_form.poll.option_placeholder": "الخيار {number}",
   "compose_form.poll.remove_option": "إزالة هذا الخيار",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "عدّل استطلاع الرأي وغيّره لإتاحة الخيارات المتعددة",
+  "compose_form.poll.switch_to_single": "عدّل استطلاع الرأي وغيّره لإتاحة خيار واحد فقط",
   "compose_form.publish": "بوّق",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "تحديد الوسائط كحساسة",
@@ -142,7 +143,7 @@
   "empty_column.account_timeline": "ليس هناك تبويقات!",
   "empty_column.account_unavailable": "الملف التعريفي غير متوفر",
   "empty_column.blocks": "لم تقم بحظر أي مستخدِم بعد.",
-  "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
+  "empty_column.bookmarked_statuses": "ليس لديك أية تبويقات في الفواصل المرجعية بعد. عندما ستقوم بإضافة البعض منها، ستظهر هنا.",
   "empty_column.community": "الخط العام المحلي فارغ. أكتب شيئا ما للعامة كبداية!",
   "empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
   "empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index ba344e7ea..e570fc3b7 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -43,9 +43,10 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Asocedió un fallu inesperáu.",
   "alert.unexpected.title": "¡Meca!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per selmana",
   "boost_modal.combo": "Pues primir {combo} pa saltar esto la próxima vegada",
-  "bundle_column_error.body": "Something went wrong while loading this component.",
+  "bundle_column_error.body": "Asocedió daqué malo mentanto se cargaba esti componente.",
   "bundle_column_error.retry": "Try again",
   "bundle_column_error.title": "Network error",
   "bundle_modal_error.close": "Close",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index d980df27b..6a6f9a309 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json
index ed1a92dd2..283812fdf 100644
--- a/app/javascript/mastodon/locales/bn.json
+++ b/app/javascript/mastodon/locales/bn.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "হার সীমিত",
   "alert.unexpected.message": "সমস্যা অপ্রত্যাশিত.",
   "alert.unexpected.title": "ওহো!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "প্রতি সপ্তাহে {count}",
   "boost_modal.combo": "পরেরবার আপনি {combo} টিপলে এটি আর আসবে না",
   "bundle_column_error.body": "এই অংশটি দেখতে যেয়ে কোনো সমস্যা হয়েছে।.",
diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json
index 89ea5043e..967577f2e 100644
--- a/app/javascript/mastodon/locales/br.json
+++ b/app/javascript/mastodon/locales/br.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
   "alert.unexpected.title": "C'hem !",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} bep sizhun",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index ceb5c39e4..6516a4a80 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Límit de freqüència",
   "alert.unexpected.message": "S'ha produït un error inesperat.",
   "alert.unexpected.title": "Vaja!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per setmana",
   "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
   "bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Durada de l'enquesta",
   "compose_form.poll.option_placeholder": "Opció {number}",
   "compose_form.poll.remove_option": "Elimina aquesta opció",
-  "compose_form.poll.switch_to_multiple": "Canvía l’enquesta per a permetre diverses opcions",
-  "compose_form.poll.switch_to_single": "Canvía l’enquesta per a permetre una sola opció",
+  "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre diverses opcions",
+  "compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Marcar mèdia com a sensible",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 2afd0f1fa..091a73c85 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Ghjettu limitatu",
   "alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
   "alert.unexpected.title": "Uups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per settimana",
   "boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
   "bundle_column_error.body": "C'hè statu un prublemu caricandu st'elementu.",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 8b9b5a86b..f180b7f94 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rychlost omezena",
   "alert.unexpected.message": "Objevila se neočekávaná chyba.",
   "alert.unexpected.title": "Jejda!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} za týden",
   "boost_modal.combo": "Příště můžete pro přeskočení stisknout {combo}",
   "bundle_column_error.body": "Při načítání této komponenty se něco pokazilo.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Doba trvání ankety",
   "compose_form.poll.option_placeholder": "Volba {number}",
   "compose_form.poll.remove_option": "Odstranit tuto volbu",
-  "compose_form.poll.switch_to_multiple": "Povolit u ankety více možností",
-  "compose_form.poll.switch_to_single": "Povolit u ankety jedinou možnost",
+  "compose_form.poll.switch_to_multiple": "Povolit u ankety výběr více možností",
+  "compose_form.poll.switch_to_single": "Povolit u ankety výběr jediné možnosti",
   "compose_form.publish": "Tootnout",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Označit média jako citlivá",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 923d1947b..ee58c7168 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Ychwanegu neu Dileu o'r rhestrau",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Grŵp",
   "account.block": "Blocio @{name}",
   "account.block_domain": "Cuddio popeth rhag {domain}",
   "account.blocked": "Blociwyd",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Cyfradd gyfyngedig",
   "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
   "alert.unexpected.title": "Wps!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} yr wythnos",
   "boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
   "bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Cyfnod pleidlais",
   "compose_form.poll.option_placeholder": "Dewisiad {number}",
   "compose_form.poll.remove_option": "Tynnu'r dewisiad",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Newid pleidlais i adael mwy nag un dewis",
+  "compose_form.poll.switch_to_single": "Newid pleidlais i gyfyngu i un dewis",
   "compose_form.publish": "Tŵt",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Marcio cyfryngau fel eu bod yn sensitif",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index bd68381d9..a699aaf54 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Gradsbegrænset",
   "alert.unexpected.message": "Der opstod en uventet fejl.",
   "alert.unexpected.title": "Ups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per uge",
   "boost_modal.combo": "Du kan trykke {combo} for at springe dette over næste gang",
   "bundle_column_error.body": "Noget gik galt under indlæsningen af dette komponent.",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 0804d8033..aae5ad1c1 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Hinzufügen oder Entfernen von Listen",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Gruppe",
   "account.block": "@{name} blockieren",
   "account.block_domain": "Alles von {domain} verstecken",
   "account.blocked": "Blockiert",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Anfragelimit überschritten",
   "alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.",
   "alert.unexpected.title": "Hoppla!",
+  "announcement.announcement": "Ankündigung",
   "autosuggest_hashtag.per_week": "{count} pro Woche",
   "boost_modal.combo": "Drücke {combo}, um dieses Fenster zu überspringen",
   "bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Umfragedauer",
   "compose_form.poll.option_placeholder": "Wahl {number}",
   "compose_form.poll.remove_option": "Wahl entfernen",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Umfrage ändern, um mehrere Optionen zu erlauben",
+  "compose_form.poll.switch_to_single": "Umfrage ändern, um eine einzige Wahl zu ermöglichen",
   "compose_form.publish": "Tröt",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Medien als heikel markieren",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index fea31633a..8cd2bb8a3 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -1408,6 +1408,10 @@
         "id": "account.unmute"
       },
       {
+        "defaultMessage": "Unfollow",
+        "id": "confirmations.unfollow.confirm"
+      },
+      {
         "defaultMessage": "Are you sure you want to unfollow {name}?",
         "id": "confirmations.unfollow.message"
       },
@@ -1563,6 +1567,10 @@
       {
         "defaultMessage": "Next",
         "id": "lightbox.next"
+      },
+      {
+        "defaultMessage": "Announcement",
+        "id": "announcement.announcement"
       }
     ],
     "path": "app/javascript/mastodon/features/getting_started/components/announcements.json"
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 9ae9e9a84..d9d2ef590 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -1,9 +1,9 @@
 {
   "account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
   "account.badges.bot": "Μποτ",
-  "account.badges.group": "Group",
+  "account.badges.group": "Ομάδα",
   "account.block": "Αποκλεισμός @{name}",
-  "account.block_domain": "Απόκρυψε τα πάντα από το {domain}",
+  "account.block_domain": "Απόκρυψη όλων από {domain}",
   "account.blocked": "Αποκλεισμένος/η",
   "account.cancel_follow_request": "Ακύρωση αιτήματος παρακολούθησης",
   "account.direct": "Προσωπικό μήνυμα προς @{name}",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Περιορισμός συχνότητας",
   "alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
   "alert.unexpected.title": "Εεπ!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} ανα εβδομάδα",
   "boost_modal.combo": "Μπορείς να πατήσεις {combo} για να το προσπεράσεις αυτό την επόμενη φορά",
   "bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
   "compose_form.poll.option_placeholder": "Επιλογή {number}",
   "compose_form.poll.remove_option": "Αφαίρεση επιλογής",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Ενημέρωση δημοσκόπησης με πολλαπλές επιλογές",
+  "compose_form.poll.switch_to_single": "Ενημέρωση δημοσκόπησης με μοναδική επιλογή",
   "compose_form.publish": "Τουτ",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Σημείωσε τα πολυμέσα ως ευαίσθητα",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 2910eedbd..5871819a9 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index f921b263c..e84e59b2e 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Mesaĝkvante limigita",
   "alert.unexpected.message": "Neatendita eraro okazis.",
   "alert.unexpected.title": "Ups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} semajne",
   "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
   "bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json
index ee46e24dd..70bcef133 100644
--- a/app/javascript/mastodon/locales/es-AR.json
+++ b/app/javascript/mastodon/locales/es-AR.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Tarifa limitada",
   "alert.unexpected.message": "Ocurrió un error.",
   "alert.unexpected.title": "¡Epa!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Podés hacer clic en {combo} para saltar esto la próxima vez",
   "bundle_column_error.body": "Algo salió mal al cargar este componente.",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index d056a843c..31f190616 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Tasa limitada",
   "alert.unexpected.message": "Hubo un error inesperado.",
   "alert.unexpected.title": "¡Ups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
   "bundle_column_error.body": "Algo salió mal al cargar este componente.",
@@ -57,7 +58,7 @@
   "column.direct": "Mensajes directos",
   "column.directory": "Buscar perfiles",
   "column.domain_blocks": "Dominios ocultados",
-  "column.favourites": "Levantar la trompa",
+  "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Inicio",
   "column.lists": "Listas",
@@ -86,7 +87,7 @@
   "compose_form.poll.remove_option": "Eliminar esta opción",
   "compose_form.poll.switch_to_multiple": "Modificar encuesta para permitir múltiples opciones",
   "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
-  "compose_form.publish": "Ipoxta",
+  "compose_form.publish": "Ipotxa",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Marcar multimedia como sensible",
   "compose_form.sensitive.marked": "Material marcado como sensible",
@@ -131,7 +132,7 @@
   "emoji_button.food": "Comida y bebida",
   "emoji_button.label": "Insertar emoji",
   "emoji_button.nature": "Naturaleza",
-  "emoji_button.not_found": "No hay emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "No hay emojis!! ¯\\_(ツ)_/¯",
   "emoji_button.objects": "Objetos",
   "emoji_button.people": "Gente",
   "emoji_button.recent": "Usados frecuentemente",
@@ -205,7 +206,7 @@
   "introduction.welcome.text": "¡Bienvenido al fediverso! En unos momentos, podrás transmitir mensajes y hablar con tus amigos a través de una amplia variedad de servidores. Pero este servidor, {domain}, es especial, alberga tu perfil, así que recuerda su nombre.",
   "keyboard_shortcuts.back": "volver atrás",
   "keyboard_shortcuts.blocked": "abrir una lista de usuarios bloqueados",
-  "keyboard_shortcuts.boost": "reipoxta",
+  "keyboard_shortcuts.boost": "retootear",
   "keyboard_shortcuts.column": "enfocar un estado en una de las columnas",
   "keyboard_shortcuts.compose": "enfocar el área de texto de redacción",
   "keyboard_shortcuts.description": "Descripción",
@@ -356,7 +357,7 @@
   "status.block": "Bloquear a @{name}",
   "status.bookmark": "Marcador",
   "status.cancel_reblog_private": "Des-impulsar",
-  "status.cannot_reblog": "Este bramido no puede rebarritarse",
+  "status.cannot_reblog": "Este toot no puede retootearse",
   "status.copy": "Copiar enlace al estado",
   "status.delete": "Borrar",
   "status.detailed_status": "Vista de conversación detallada",
@@ -374,7 +375,7 @@
   "status.pin": "Fijar",
   "status.pinned": "Toot fijado",
   "status.read_more": "Leer más",
-  "status.reblog": "ReIpoxta",
+  "status.reblog": "Retootear",
   "status.reblog_private": "Implusar a la audiencia original",
   "status.reblogged_by": "Retooteado por {name}",
   "status.reblogs.empty": "Nadie impulsó este toot todavía. Cuando alguien lo haga, aparecerá aqui.",
diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json
index 94e73ae3d..9a13b4ee0 100644
--- a/app/javascript/mastodon/locales/et.json
+++ b/app/javascript/mastodon/locales/et.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Piiratud",
   "alert.unexpected.message": "Tekkis ootamatu viga.",
   "alert.unexpected.title": "Oih!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} nädalas",
   "boost_modal.combo": "Võite vajutada {combo}, et see järgmine kord vahele jätta",
   "bundle_column_error.body": "Midagi läks valesti selle komponendi laadimisel.",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 3ae39ffba..e9fbcedac 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Gehitu edo kendu zerrendetatik",
   "account.badges.bot": "Bot-a",
-  "account.badges.group": "Group",
+  "account.badges.group": "Taldea",
   "account.block": "Blokeatu @{name}",
   "account.block_domain": "Ezkutatu {domain} domeinuko guztia",
   "account.blocked": "Blokeatuta",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Abiadura mugatua",
   "alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
   "alert.unexpected.title": "Ene!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} asteko",
   "boost_modal.combo": "{combo} sakatu dezakezu hurrengoan hau saltatzeko",
   "bundle_column_error.body": "Zerbait okerra gertatu da osagai hau kargatzean.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Inkestaren iraupena",
   "compose_form.poll.option_placeholder": "{number}. aukera",
   "compose_form.poll.remove_option": "Kendu aukera hau",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Aldatu inkesta hainbat aukera onartzeko",
+  "compose_form.poll.switch_to_single": "Aldatu inkesta aukera bakarra onartzeko",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Markatu multimedia hunkigarri gisa",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 38a53e9b1..c2a8eb0de 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "افزودن یا برداشتن از فهرست",
   "account.badges.bot": "ربات",
-  "account.badges.group": "Group",
+  "account.badges.group": "گروه",
   "account.block": "مسدودسازی @{name}",
   "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
   "account.blocked": "مسدود",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "محدود شده",
   "alert.unexpected.message": "خطایی غیرمنتظره رخ داد.",
   "alert.unexpected.title": "وای!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} در هفته",
   "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
   "bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "مدت نظرسنجی",
   "compose_form.poll.option_placeholder": "گزینهٔ {number}",
   "compose_form.poll.remove_option": "برداشتن این گزینه",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "تبدیل به نظرسنجی چندگزینه‌ای",
+  "compose_form.poll.switch_to_single": "تبدیل به نظرسنجی تک‌گزینه‌ای",
   "compose_form.publish": "بوق",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "علامت‌گذاری رسانه به عنوان حساس",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index b43f18d23..8a069f2a3 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Määrää rajoitettu",
   "alert.unexpected.message": "Tapahtui odottamaton virhe.",
   "alert.unexpected.title": "Hups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} viikossa",
   "boost_modal.combo": "Ensi kerralla voit ohittaa tämän painamalla {combo}",
   "bundle_column_error.body": "Jokin meni vikaan komponenttia ladattaessa.",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index ffb77d9f6..7cd5ec0e3 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Débit limité",
   "alert.unexpected.message": "Une erreur inattendue s’est produite.",
   "alert.unexpected.title": "Oups !",
+  "announcement.announcement": "Annonce",
   "autosuggest_hashtag.per_week": "{count} par semaine",
   "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci, la prochaine fois",
   "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.",
diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json
index 4966946cc..508456736 100644
--- a/app/javascript/mastodon/locales/ga.json
+++ b/app/javascript/mastodon/locales/ga.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 695017b0e..b2715cc4b 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -4,32 +4,32 @@
   "account.badges.group": "Grupo",
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Agochar todo de {domain}",
-  "account.blocked": "Bloqueado",
+  "account.blocked": "Bloqueada",
   "account.cancel_follow_request": "Desbotar solicitude de seguimento",
   "account.direct": "Mensaxe directa @{name}",
   "account.domain_blocked": "Dominio agochado",
   "account.edit_profile": "Editar perfil",
   "account.endorse": "Amosar no perfil",
   "account.follow": "Seguir",
-  "account.followers": "Seguidores",
-  "account.followers.empty": "Aínda ninguén segue este usuario.",
+  "account.followers": "Seguidoras",
+  "account.followers.empty": "Aínda ninguén segue esta usuaria.",
   "account.follows": "Seguindo",
-  "account.follows.empty": "Este usuario aínda non segue a ninguén.",
+  "account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
   "account.follows_you": "Séguete",
-  "account.hide_reblogs": "Agochar compartidos de @{name}",
+  "account.hide_reblogs": "Agochar repeticións de @{name}",
   "account.last_status": "Última actividade",
   "account.link_verified_on": "A propiedade desta ligazón foi verificada o {date}",
-  "account.locked_info": "Esta é unha conta privada. O dono revisa de xeito manual quen pode seguilo.",
+  "account.locked_info": "Esta é unha conta privada. A propietaria revisa de xeito manual quen pode seguila.",
   "account.media": "Multimedia",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} mudouse a:",
-  "account.mute": "Silenciar @{name}",
-  "account.mute_notifications": "Silenciar notificacións de @{name}",
-  "account.muted": "Silenciado",
+  "account.mute": "Acalar @{name}",
+  "account.mute_notifications": "Acalar as notificacións de @{name}",
+  "account.muted": "Acalada",
   "account.never_active": "Nunca",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots e respostas",
-  "account.report": "Denunciar @{name}",
+  "account.report": "Informar sobre @{name}",
   "account.requested": "Agardando aprovación. Preme para desbotar a solicitude de seguimento",
   "account.share": "Compartir o perfil de @{name}",
   "account.show_reblogs": "Amosar compartidos de @{name}",
@@ -43,15 +43,16 @@
   "alert.rate_limited.title": "Límite de intentos",
   "alert.unexpected.message": "Ocorreu un erro non agardado.",
   "alert.unexpected.title": "Vaites!",
+  "announcement.announcement": "Anuncio",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Preme {combo} para ignorar isto na seguinte vez",
   "bundle_column_error.body": "Ocorreu un erro ó cargar este compoñente.",
   "bundle_column_error.retry": "Téntao de novo",
-  "bundle_column_error.title": "Erro na rede",
+  "bundle_column_error.title": "Fallo na rede",
   "bundle_modal_error.close": "Pechar",
   "bundle_modal_error.message": "Ocorreu un erro ó cargar este compoñente.",
   "bundle_modal_error.retry": "Téntao de novo",
-  "column.blocks": "Usuarios bloqueados",
+  "column.blocks": "Usuarias bloqueadas",
   "column.bookmarks": "Marcadores",
   "column.community": "Cronoloxía local",
   "column.direct": "Mensaxes directas",
@@ -59,9 +60,9 @@
   "column.domain_blocks": "Dominios agochados",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Peticións de seguimento",
-  "column.home": "Páxina inicial",
+  "column.home": "Inicio",
   "column.lists": "Listaxes",
-  "column.mutes": "Usuarios silenciados",
+  "column.mutes": "Usuarias acaladas",
   "column.notifications": "Notificacións",
   "column.pins": "Toots fixados",
   "column.public": "Cronoloxía federada",
@@ -74,7 +75,7 @@
   "column_header.unpin": "Desafixar",
   "column_subheading.settings": "Axustes",
   "community.column_settings.media_only": "Só multimedia",
-  "compose_form.direct_message_warning": "Este toot só será enviado ós usuarios mencionados.",
+  "compose_form.direct_message_warning": "Este toot só será enviado ás usuarias mencionadas.",
   "compose_form.direct_message_warning_learn_more": "Coñecer máis",
   "compose_form.hashtag_warning": "Este toot non se amosará baixo cancelos (hashtags) porque non é público. Só os toots públicos poden ser procurados por cancelos.",
   "compose_form.lock_disclaimer": "A túa conta non está {locked}. Todos poden seguirche para ollar os teus toots só para seguidores.",
@@ -86,7 +87,7 @@
   "compose_form.poll.remove_option": "Eliminar esta opción",
   "compose_form.poll.switch_to_multiple": "Mudar a enquisa para permitir múltiples escollas",
   "compose_form.poll.switch_to_single": "Mudar a enquisa para permitir unha soa escolla",
-  "compose_form.publish": "Toot",
+  "compose_form.publish": "Tootear",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Marcar coma contido multimedia sensíbel",
   "compose_form.sensitive.marked": "Contido multimedia marcado coma sensíbel",
@@ -111,7 +112,7 @@
   "confirmations.mute.message": "Tes a certeza de querer silenciar a {name}?",
   "confirmations.redraft.confirm": "Eliminar e reescribir",
   "confirmations.redraft.message": "Tes a certeza de querer eliminar este estado e reescribilo? Perderás os compartidos e favoritos, e as respostas á publicación orixinal ficarán orfas.",
-  "confirmations.reply.confirm": "Respostar",
+  "confirmations.reply.confirm": "Responder",
   "confirmations.reply.message": "Respostar agora sobrescribirá a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Desexas deixar de seguir a {name}?",
@@ -141,7 +142,7 @@
   "emoji_button.travel": "Viaxes e lugares",
   "empty_column.account_timeline": "Non hai toots aquí!",
   "empty_column.account_unavailable": "Perfil non dispoñíbel",
-  "empty_column.blocks": "Aínda non bloqueaches a ningún usuario.",
+  "empty_column.blocks": "Aínda non bloqueaches a ningún usuaria.",
   "empty_column.bookmarked_statuses": "Aínda non marcaches ningún toot. Cando o fagas, amosaranse aquí.",
   "empty_column.community": "A cronoloxía local está baleira. Escribe algo de xeito público para espallalo!",
   "empty_column.direct": "Aínda non tes mensaxes directas. Cando envíes ou recibas unha, amosarase aquí.",
@@ -150,13 +151,13 @@
   "empty_column.favourites": "A ninguén lle gostou este toot polo momento. Cando a alguén lle goste, aparecerá aquí.",
   "empty_column.follow_requests": "Non tes peticións de seguimento. Cando recibas unha, amosarase aquí.",
   "empty_column.hashtag": "Aínda non hai nada con este cancelo.",
-  "empty_column.home": "A túa cronoloxía inicial está baleira! Visita {public} ou emprega a procura para atopar outros usuarios.",
+  "empty_column.home": "A túa cronoloxía inicial está baleira! Visita {public} ou emprega a procura para atopar outras usuarias.",
   "empty_column.home.public_timeline": "a cronoloxía pública",
-  "empty_column.list": "Aínda non hai nada nesta listaxe. Cando os usuarios incluídas na listaxe publiquen mensaxes, amosaranse aquí.",
+  "empty_column.list": "Aínda non hai nada en esta lista. Cando as usuarias incluídas na lista publiquen mensaxes, aparecerán aquí.",
   "empty_column.lists": "Aínda non tes listaxes. Cando crees unha, amosarase aquí.",
-  "empty_column.mutes": "Aínda non silenciaches a ningún usuario.",
+  "empty_column.mutes": "Aínda non silenciaches a ningúnha usuaria.",
   "empty_column.notifications": "Aínda non tes notificacións. Interactúa con outros para comezar unha conversa.",
-  "empty_column.public": "Nada por aquí! Escribe algo de xeito público, ou sigue de xeito manual usuarios doutros servidores para ir enchéndoo",
+  "empty_column.public": "Nada por aquí! Escribe algo de xeito público, ou segue de xeito manual usuarias doutros servidores para ir enchéndoo",
   "error.unexpected_crash.explanation": "Debido a un erro no noso código ou a unha compatilidade co teu navegador, esta páxina non pode ser amosada correctamente.",
   "error.unexpected_crash.next_steps": "Tenta actualizar a páxina. Se esto non axuda podes tamén empregar o Mastodon noutro navegador ou aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar trazas (stacktrace) ó portapapeis",
@@ -196,16 +197,16 @@
   "introduction.interactions.action": "Rematar titorial!",
   "introduction.interactions.favourite.headline": "Favorito",
   "introduction.interactions.favourite.text": "Podes gardar un toot para depois e facer saber ó autor que che gostou marcandoo coma favorito.",
-  "introduction.interactions.reblog.headline": "Compartir na cronoloxía",
-  "introduction.interactions.reblog.text": "Podes compartir os toots doutras persoas cos teus seguidores.",
+  "introduction.interactions.reblog.headline": "Promover",
+  "introduction.interactions.reblog.text": "Podes compartir os toots doutras persoas coas túas seguidoras.",
   "introduction.interactions.reply.headline": "Respostar",
   "introduction.interactions.reply.text": "Podes respostar ós toots doutras persoas e ós teus propios, así ficarán encadeados nunha conversa.",
   "introduction.welcome.action": "Imos!",
   "introduction.welcome.headline": "Primeiros pasos",
   "introduction.welcome.text": "Benvido ó fediverso! Nun intre poderás difundir mensaxes e falar coas túas amizades nun grande número de servidores. Mais este servidor, {domain}, é especial—hospeda o teu perfil, por iso lémbrate do seu nome.",
   "keyboard_shortcuts.back": "para voltar atrás",
-  "keyboard_shortcuts.blocked": "para abrir a listaxe de usuarios bloqueados",
-  "keyboard_shortcuts.boost": "para compartir na cronoloxía",
+  "keyboard_shortcuts.blocked": "abrir lista de usuarias bloqueadas",
+  "keyboard_shortcuts.boost": "promover",
   "keyboard_shortcuts.column": "para destacar un estado nunha das columnas",
   "keyboard_shortcuts.compose": "para destacar a área de escritura",
   "keyboard_shortcuts.description": "Descrición",
@@ -221,7 +222,7 @@
   "keyboard_shortcuts.legend": "para amosar esta lenda",
   "keyboard_shortcuts.local": "para abrir a cronoloxía local",
   "keyboard_shortcuts.mention": "para mencionar ó autor",
-  "keyboard_shortcuts.muted": "para abrir a listaxe dos usuarios silenciados",
+  "keyboard_shortcuts.muted": "abrir lista de usuarias acaladas",
   "keyboard_shortcuts.my_profile": "para abrir o teu perfil",
   "keyboard_shortcuts.notifications": "para abrir a columna das notificacións",
   "keyboard_shortcuts.open_media": "para abrir o contido multimedia",
@@ -254,9 +255,9 @@
   "media_gallery.toggle_visible": "Trocar visibilidade",
   "missing_indicator.label": "Non atopado",
   "missing_indicator.sublabel": "Este recurso non foi atopado",
-  "mute_modal.hide_notifications": "Agochar notificacións deste usuario?",
+  "mute_modal.hide_notifications": "Agochar notificacións desta usuaria?",
   "navigation_bar.apps": "Aplicacións móbiles",
-  "navigation_bar.blocks": "Usuarios bloqueados",
+  "navigation_bar.blocks": "Usuarias bloqueadas",
   "navigation_bar.bookmarks": "Marcadores",
   "navigation_bar.community_timeline": "Cronoloxía local",
   "navigation_bar.compose": "Escribir un novo toot",
@@ -272,7 +273,7 @@
   "navigation_bar.keyboard_shortcuts": "Atallos do teclado",
   "navigation_bar.lists": "Listaxes",
   "navigation_bar.logout": "Pechar sesión",
-  "navigation_bar.mutes": "Usuarios silenciados",
+  "navigation_bar.mutes": "Usuarias silenciadas",
   "navigation_bar.personal": "Persoal",
   "navigation_bar.pins": "Toots fixados",
   "navigation_bar.preferences": "Preferencias",
@@ -284,7 +285,7 @@
   "notification.mention": "{name} mencionoute",
   "notification.own_poll": "A túa enquisa rematou",
   "notification.poll": "Unha enquisa na que votaches rematou",
-  "notification.reblog": "{name} compartiu o teu estado",
+  "notification.reblog": "{name} promoveu o teu estado",
   "notifications.clear": "Limpar notificacións",
   "notifications.clear_confirmation": "Tes a certeza de querer limpar de xeito permanente todas as túas notificacións?",
   "notifications.column_settings.alert": "Notificacións de escritorio",
@@ -297,7 +298,7 @@
   "notifications.column_settings.mention": "Mencións:",
   "notifications.column_settings.poll": "Resultados da enquisa:",
   "notifications.column_settings.push": "Notificacións emerxentes",
-  "notifications.column_settings.reblog": "Compartidos:",
+  "notifications.column_settings.reblog": "Promocións:",
   "notifications.column_settings.show": "Amosar en columna",
   "notifications.column_settings.sound": "Reproducir son",
   "notifications.filter.all": "Todo",
@@ -316,7 +317,7 @@
   "poll_button.add_poll": "Engadir unha enquisa",
   "poll_button.remove_poll": "Eliminar enquisa",
   "privacy.change": "Axustar privacidade",
-  "privacy.direct.long": "Só para os usuarios mencionados",
+  "privacy.direct.long": "Só para as usuarias mencionadas",
   "privacy.direct.short": "Directo",
   "privacy.private.long": "Só para os seguidores",
   "privacy.private.short": "Só seguidores",
@@ -341,11 +342,11 @@
   "report.target": "Denunciar a {target}",
   "search.placeholder": "Procurar",
   "search_popout.search_format": "Formato de procura avanzada",
-  "search_popout.tips.full_text": "Texto sinxelo que devolve estados que ti escribiches, compartiches, marcaches favorito, ou foches mencionado, así como nomes de usuario coincidentes, nomes públicos e cancelos.",
+  "search_popout.tips.full_text": "Texto simple devolve estados que ti escribiches, promoviches, marcaches  favoritos, ou foches mencionada, así como nomes de usuaria coincidentes, nomes públicos e etiquetas.",
   "search_popout.tips.hashtag": "cancelo",
   "search_popout.tips.status": "estado",
-  "search_popout.tips.text": "Texto sinxelo que devolve coincidencias con nomes públicos, nomes de usuario e cancelos",
-  "search_popout.tips.user": "usuario",
+  "search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
+  "search_popout.tips.user": "usuaria",
   "search_results.accounts": "Persoas",
   "search_results.hashtags": "Cancelos",
   "search_results.statuses": "Toots",
@@ -356,7 +357,7 @@
   "status.block": "Bloquear @{name}",
   "status.bookmark": "Marcar",
   "status.cancel_reblog_private": "Desfacer compartido",
-  "status.cannot_reblog": "Esta publicación non pode ser compartida",
+  "status.cannot_reblog": "Esta publicación non pode ser promovida",
   "status.copy": "Copiar ligazón ó estado",
   "status.delete": "Eliminar",
   "status.detailed_status": "Vista detallada da conversa",
@@ -374,10 +375,10 @@
   "status.pin": "Fixar no perfil",
   "status.pinned": "Toot fixado",
   "status.read_more": "Ler máis",
-  "status.reblog": "Compartir",
+  "status.reblog": "Promover",
   "status.reblog_private": "Compartir á audiencia orixinal",
-  "status.reblogged_by": "{name} compartiu",
-  "status.reblogs.empty": "Aínda ninguén compartiu este toot. Cando alguén o faga, amosarase aquí.",
+  "status.reblogged_by": "{name} promoveu",
+  "status.reblogs.empty": "Aínda ninguén promoveu este toot. Cando alguén o faga, amosarase aquí.",
   "status.redraft": "Eliminar e reescribir",
   "status.remove_bookmark": "Eliminar marcador",
   "status.reply": "Respostar",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index e69fa5d8f..07217364a 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
   "alert.unexpected.title": "אופס!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
   "bundle_column_error.body": "משהו השתבש בעת הצגת הרכיב הזה.",
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
index 0e8830174..ff7ea9e6a 100644
--- a/app/javascript/mastodon/locales/hi.json
+++ b/app/javascript/mastodon/locales/hi.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "सीमित दर",
   "alert.unexpected.message": "एक अप्रत्याशित त्रुटि हुई है!",
   "alert.unexpected.title": "उफ़!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} हर सप्ताह",
   "boost_modal.combo": "अगली बार स्किप करने के लिए आप {combo} दबा सकते है",
   "bundle_column_error.body": "इस कॉम्पोनेन्ट को लोड करते वक्त कुछ गलत हो गया",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index da01b92ae..31327e3d3 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 57511307a..f4280e740 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Hozzáadás vagy eltávolítás a listáról",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Csoport",
   "account.block": "@{name} letiltása",
   "account.block_domain": "Minden elrejtése innen: {domain}",
   "account.blocked": "Letiltva",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Forgalomkorlátozás",
   "alert.unexpected.message": "Váratlan hiba történt.",
   "alert.unexpected.title": "Hoppá!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count}/hét",
   "boost_modal.combo": "Hogy átugord ezt következő alkalommal, használd {combo}",
   "bundle_column_error.body": "Hiba történt a komponens betöltése közben.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Szavazás időtartama",
   "compose_form.poll.option_placeholder": "{number}. lehetőség",
   "compose_form.poll.remove_option": "Lehetőség törlése",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Szavazás megváltoztatása több választásosra",
+  "compose_form.poll.switch_to_single": "Szavazás megváltoztatása egyetlen választásosra",
   "compose_form.publish": "Tülk",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Média megjelölése szenzitívként",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index b20b744d5..1ad3cb0fd 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Աւելացնել կամ հեռացնել ցանկերից",
   "account.badges.bot": "Բոտ",
-  "account.badges.group": "Group",
+  "account.badges.group": "Խումբ",
   "account.block": "Արգելափակել @{name}֊ին",
   "account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
   "account.blocked": "Արգելափակուած է",
@@ -27,7 +27,7 @@
   "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
   "account.muted": "Լռեցուած",
   "account.never_active": "Երբեք",
-  "account.posts": "Գրառումներ",
+  "account.posts": "Թութ",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Բողոքել @{name}֊ից",
   "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Անսպասելի սխալ տեղի ունեցաւ։",
   "alert.unexpected.title": "Վա՜յ",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "շաբաթը՝ {count}",
   "boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա հաջորդ անգամ բաց թողնելու համար",
   "bundle_column_error.body": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։",
@@ -401,25 +402,25 @@
   "tabs_bar.notifications": "Ծանուցումներ",
   "tabs_bar.search": "Փնտրել",
   "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.hours": "{number, plural, one {# ժամ} other {# ժամ}} անց",
+  "time_remaining.minutes": "{number, plural, one {# րոպե} other {# րոպե}} անց",
   "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.seconds": "{number, plural, one {# վայրկյան} other {# վայրկյան}} անց",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
-  "trends.trending_now": "Trending now",
+  "trends.trending_now": "Այժմ արդիական",
   "ui.beforeunload": "Քո սեւագիրը կկորի, եթե լքես Մաստոդոնը։",
   "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
   "upload_button.label": "Ավելացնել մեդիա",
-  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.limit": "Ֆայլի վերբեռնման սահմանաչափը գերազանցված է։",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.audio_description": "Describe for people with hearing loss",
   "upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
   "upload_form.edit": "Խմբագրել",
   "upload_form.undo": "Հետարկել",
   "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
-  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.analyzing_picture": "Լուսանկարի վերլուծում…",
   "upload_modal.apply": "Կիրառել",
-  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.description_placeholder": "Ճկուն շագանակագույն աղվեսը ցատկում է ծույլ շան վրայով",
   "upload_modal.detect_text": "Հայտնբերել տեքստը նկարից",
   "upload_modal.edit_media": "Խմբագրել մեդիան",
   "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 8e7b492fe..b1605ac60 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Batasan tingkat",
   "alert.unexpected.message": "Terjadi kesalahan yang tidak terduga.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per minggu",
   "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
   "bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 673a0deb2..36bf1a588 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json
index c1f1b3720..53b2a150a 100644
--- a/app/javascript/mastodon/locales/is.json
+++ b/app/javascript/mastodon/locales/is.json
@@ -10,10 +10,10 @@
   "account.domain_blocked": "Lén falið",
   "account.edit_profile": "Breyta notandasniði",
   "account.endorse": "Birta á notandasniði",
-  "account.follow": "Fylgja",
+  "account.follow": "Fylgjast með",
   "account.followers": "Fylgjendur",
   "account.followers.empty": "Ennþá fylgist enginn með þessum notanda.",
-  "account.follows": "Fylgir",
+  "account.follows": "Fylgist með",
   "account.follows.empty": "Þessi notandi fylgist ennþá ekki með neinum.",
   "account.follows_you": "Fylgir þér",
   "account.hide_reblogs": "Fela endurbirtingar fyrir @{name}",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Með takmörkum",
   "alert.unexpected.message": "Upp kom óvænt villa.",
   "alert.unexpected.title": "Úbbs!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} á viku",
   "boost_modal.combo": "Þú getur ýtt á {combo} til að sleppa þessu næst",
   "bundle_column_error.body": "Eitthvað fór úrskeiðis við að hlaða inn þessari einingu.",
@@ -266,7 +267,7 @@
   "navigation_bar.edit_profile": "Breyta notandasniði",
   "navigation_bar.favourites": "Eftirlæti",
   "navigation_bar.filters": "Þögguð orð",
-  "navigation_bar.follow_requests": "Fylgja beiðnum",
+  "navigation_bar.follow_requests": "Beiðnir um að fylgjast með",
   "navigation_bar.follows_and_followers": "Fylgist með og fylgjendur",
   "navigation_bar.info": "Um þennan vefþjón",
   "navigation_bar.keyboard_shortcuts": "Flýtilyklar",
@@ -279,7 +280,7 @@
   "navigation_bar.public_timeline": "Sameiginleg tímalína",
   "navigation_bar.security": "Öryggi",
   "notification.favourite": "{name} setti stöðufærslu þína í eftirlæti",
-  "notification.follow": "{name} fylgdist með þér",
+  "notification.follow": "{name} fylgist með þér",
   "notification.follow_request": "{name} hefur beðið um að fylgjast með þér",
   "notification.mention": "{name} minntist á þig",
   "notification.own_poll": "Könnuninni þinni er lokið",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 3d5353f2a..1b31f90f6 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Aggiungi o togli dalle liste",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Gruppo",
   "account.block": "Blocca @{name}",
   "account.block_domain": "Nascondi tutto da {domain}",
   "account.blocked": "Bloccato",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Numero massimo di richieste superato",
   "alert.unexpected.message": "Si è verificato un errore inatteso.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per settimana",
   "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
   "bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 76ad78012..51eeb1eb6 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "制限に達しました",
   "alert.unexpected.message": "不明なエラーが発生しました。",
   "alert.unexpected.title": "エラー!",
+  "announcement.announcement": "告知",
   "autosuggest_hashtag.per_week": "{count} 回 / 週",
   "boost_modal.combo": "次からは{combo}を押せばスキップできます",
   "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index 1c6203c46..1222702a4 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "წარმოიშვა მოულოდნელი შეცდომა.",
   "alert.unexpected.title": "უპს!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "კვირაში {count}",
   "boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს",
   "bundle_column_error.body": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index edb539f4f..ec77fa9e4 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -1,15 +1,15 @@
 {
-  "account.add_or_remove_from_list": "Rnu neɣ kkes seg tebdarin",
+  "account.add_or_remove_from_list": "Rnu neγ kkes seg tebdarin",
   "account.badges.bot": "Aṛubut",
-  "account.badges.group": "Group",
+  "account.badges.group": "Agraw",
   "account.block": "Seḥbes @{name}",
   "account.block_domain": "Ffer kra i d-yekkan seg {domain}",
   "account.blocked": "Yettuseḥbes",
   "account.cancel_follow_request": "Sefsex asuter n weḍfaṛ",
   "account.direct": "Izen usrid i @{name}",
-  "account.domain_blocked": "Taɣult yeffren",
-  "account.edit_profile": "Ẓreg amaɣnu",
-  "account.endorse": "Welleh fell-as deg umaɣnu-inek",
+  "account.domain_blocked": "Taγult yeffren",
+  "account.edit_profile": "Ẓreg amaγnu",
+  "account.endorse": "Welleh fell-as deg umaγnu-inek",
   "account.follow": "Ḍfeṛ",
   "account.followers": "Imeḍfaṛen",
   "account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.",
@@ -18,33 +18,34 @@
   "account.follows_you": "Yeṭṭafaṛ-ik",
   "account.hide_reblogs": "Ffer ayen i ibeṭṭu @{name}",
   "account.last_status": "Armud aneggaru",
-  "account.link_verified_on": "Taɣara n useɣwen-a tettwasenqed de {date}",
+  "account.link_verified_on": "Taγara n useγwen-a tettwasenqed ass n {date}",
   "account.locked_info": "Amiḍan-agi uslig isekweṛ. D bab-is kan i izemren ad yeǧǧ, s ufus-is, win ara t-iḍefṛen.",
   "account.media": "Allal n teywalt",
   "account.mention": "Bder-d @{name}",
-  "account.moved_to": "{name} ibeddel ɣer:",
+  "account.moved_to": "{name} ibeddel γer:",
   "account.mute": "Sgugem @{name}",
-  "account.mute_notifications": "Ḥbes ilɣa sɣur @{name}",
+  "account.mute_notifications": "Susem ilγa sγur @{name}",
   "account.muted": "Yettwasgugem",
   "account.never_active": "Werǧin",
   "account.posts": "Tiberraḥin",
   "account.posts_with_replies": "Tibarraḥin d tririyin",
   "account.report": "Sewɛed @{name}",
   "account.requested": "Di laɛḍil ad yettwaqbel. Ssit iwakken ad yefsex usuter n weḍfar",
-  "account.share": "Bḍu amaɣnu n @{name}",
+  "account.share": "Bḍu amaγnu n @{name}",
   "account.show_reblogs": "Sken-d inebḍa n @{name}",
   "account.unblock": "Serreḥ i @{name}",
   "account.unblock_domain": "Kkes tuffra i {domain}",
-  "account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek",
+  "account.unendorse": "Ur ttwellih ara fell-as deg umaγnu-inek",
   "account.unfollow": "Ur ṭṭafaṛ ara",
-  "account.unmute": "Kkes asgugem ɣef @{name}",
-  "account.unmute_notifications": "Serreḥ ilɣa sɣur @{name}",
-  "alert.rate_limited.message": "Ma ulac aɣilif ɛreḍ tikelt-nniḍen mbeɛd {retry_time, time, medium}.",
+  "account.unmute": "Kkes asgugem γef @{name}",
+  "account.unmute_notifications": "Serreḥ ilγa sγur @{name}",
+  "alert.rate_limited.message": "Ma ulac aγilif ɛreḍ tikelt-nniḍen mbeɛd {retry_time, time, medium}.",
   "alert.rate_limited.title": "Aktum s talast",
   "alert.unexpected.message": "Tella-d tuccḍa i ɣef ur nedmi ara.",
   "alert.unexpected.title": "Ayhuh!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} i yimalas",
-  "boost_modal.combo": "Tzemreḍ ad tetekkiḍ ɣef {combo} akken ad tessurfeḍ aya tikelt-nniḍen",
+  "boost_modal.combo": "Tzemreḍ ad tetekkiḍ γef {combo} akken ad tessurfeḍ aya tikelt-nniḍen",
   "bundle_column_error.body": "Tella-d kra n tuccḍa mi d-yettali ugbur-agi.",
   "bundle_column_error.retry": "Ɛreḍ tikelt-nniḍen",
   "bundle_column_error.title": "Tuccḍa deg uẓeṭṭa",
@@ -55,24 +56,24 @@
   "column.bookmarks": "Ticraḍ",
   "column.community": "Tasuddemt tadigant",
   "column.direct": "Iznan usriden",
-  "column.directory": "Qelleb deg yimaɣnuten",
-  "column.domain_blocks": "Tiɣula yettwaffren",
+  "column.directory": "Qelleb deg imaγnuten",
+  "column.domain_blocks": "Tiγula yettwaffren",
   "column.favourites": "Ismenyifen",
   "column.follow_requests": "Isuturen n teḍfeṛt",
   "column.home": "Agejdan",
   "column.lists": "Tibdarin",
   "column.mutes": "Imiḍanen yettwasgugmen",
-  "column.notifications": "Tilɣa",
+  "column.notifications": "Tilγa",
   "column.pins": "Tiberraḥin yettwasenṭḍen",
   "column.public": "Tasuddemt tamatut",
-  "column_back_button.label": "Tuɣalin",
-  "column_header.hide_settings": "Ffer iɣewwaṛen",
-  "column_header.moveLeft_settings": "Err ajgu ɣer tama tazelmaḍt",
-  "column_header.moveRight_settings": "Err ajgu ɣer tama tayfust",
+  "column_back_button.label": "Tuγalin",
+  "column_header.hide_settings": "Ffer iγewwaṛen",
+  "column_header.moveLeft_settings": "Err ajgu γer tama tazelmaḍt",
+  "column_header.moveRight_settings": "Err ajgu γer tama tayfust",
   "column_header.pin": "Senteḍ",
-  "column_header.show_settings": "Sken iɣewwaṛen",
+  "column_header.show_settings": "Sken iγewwaṛen",
   "column_header.unpin": "Kkes asenteḍ",
-  "column_subheading.settings": "Iɣewwaṛen",
+  "column_subheading.settings": "Iγewwaṛen",
   "community.column_settings.media_only": "Allal n teywalt kan",
   "compose_form.direct_message_warning": "Taberraḥt-a ad d-tettwasken kan i yimseqdacen i d-yettwabedren.",
   "compose_form.direct_message_warning_learn_more": "Issin ugar",
@@ -91,39 +92,39 @@
   "compose_form.sensitive.hide": "Creḍ allal n teywalt d anafri",
   "compose_form.sensitive.marked": "Allal n teywalt yettwacreḍ d anafri",
   "compose_form.sensitive.unmarked": "Allal n teywalt ur yettwacreḍ ara d anafri",
-  "compose_form.spoiler.marked": "Aḍris yeffer deffir n walɣu",
+  "compose_form.spoiler.marked": "Aḍris yeffer deffir n walγu",
   "compose_form.spoiler.unmarked": "Aḍris ur yettwaffer ara",
-  "compose_form.spoiler_placeholder": "Aru alɣu-inek da",
+  "compose_form.spoiler_placeholder": "Aru alγu-inek da",
   "confirmation_modal.cancel": "Sefsex",
   "confirmations.block.block_and_report": "Sewḥel & sewɛed",
   "confirmations.block.confirm": "Sewḥel",
-  "confirmations.block.message": "Tebɣiḍ s tidet ad tesḥebseḍ {name}?",
+  "confirmations.block.message": "Tebγiḍ s tidet ad tesḥebseḍ {name}?",
   "confirmations.delete.confirm": "Kkes",
-  "confirmations.delete.message": "Tebɣiḍ s tidet ad tekkseḍ tasuffeɣt-agi?",
+  "confirmations.delete.message": "Tebγiḍ s tidet ad tekkseḍ tasuffeγt-agi?",
   "confirmations.delete_list.confirm": "Kkes",
-  "confirmations.delete_list.message": "Tebɣiḍ s tidet ad tekkseḍ tabdert-agi i lebda?",
-  "confirmations.domain_block.confirm": "Ffer taɣult meṛṛa",
+  "confirmations.delete_list.message": "Tebγiḍ s tidet ad tekkseḍ tabdert-agi i lebda?",
+  "confirmations.domain_block.confirm": "Ffer taγult meṛṛa",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
-  "confirmations.logout.confirm": "Ffeɣ",
-  "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?",
+  "confirmations.logout.confirm": "Ffeγ",
+  "confirmations.logout.message": "D tidet tebγiḍ ad teffγeḍ?",
   "confirmations.mute.confirm": "Sgugem",
-  "confirmations.mute.explanation": "Aya ad yeffer iznan-is d wid i deg d-yettwabder neɣ d-tettwabder, maca xas akka yezmer neɣ tezmer awali n yiznan-inek d uḍfaṛ-ik.",
-  "confirmations.mute.message": "Tetḥeqqeḍ belli tebɣiḍ asɛuggen n {name}?",
+  "confirmations.mute.explanation": "Aya ad yeffer iznan-is d wid i deg d-yettwabder neγ d-tettwabder, maca xas akka yezmer neγ tezmer awali n yiznan-inek d uḍfaṛ-ik.",
+  "confirmations.mute.message": "Tetḥeqqeḍ belli tebγiḍ asɛuggen n {name}?",
   "confirmations.redraft.confirm": "Sfeḍ & Ɛiwed tira",
-  "confirmations.redraft.message": "Tetḥeqqeḍ belli tebɣiḍ asfaḍ n waddad-agi iwakken ad s-tɛiwdeḍ tira? Ismenyifen d beḍḍuwat ad ṛuḥen, ma d tiririyin-is ad uɣalent d tigujilin.",
+  "confirmations.redraft.message": "Tetḥeqqeḍ belli tebγiḍ asfaḍ n waddad-agi iwakken ad s-tɛiwdeḍ tira? Ismenyifen d beḍḍuwat ad ṛuḥen, ma d tiririyin-is ad uγalent d tigujilin.",
   "confirmations.reply.confirm": "Err",
-  "confirmations.reply.message": "Tiririt akka tura ad k-degger izen-agi i tettaruḍ. Tebɣiḍ ad tkemmleḍ?",
+  "confirmations.reply.message": "Tiririt akka tura ad k-degger izen-agi i tettaruḍ. Tebγiḍ ad tkemmleḍ?",
   "confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara",
-  "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teḍḍafaṛeḍ ara {name}?",
+  "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebγiḍ ur teḍḍafaṛeḍ ara {name}?",
   "conversation.delete": "Sfeḍ adiwenni",
-  "conversation.mark_as_read": "Creḍ yettwaɣṛa",
+  "conversation.mark_as_read": "Creḍ yettwaγṛa",
   "conversation.open": "Sken adiwenni",
   "conversation.with": "Akked {names}",
   "directory.federated": "Seg fedivers yettwasnen",
   "directory.local": "Seg {domain} kan",
   "directory.new_arrivals": "Inebgawen imaynuten",
   "directory.recently_active": "Yermed xas melmi kan",
-  "embed.instructions": "Ẓẓu addad-agi deg usmel-inek s wenɣal n tangalt yellan sdaw-agi.",
+  "embed.instructions": "Ẓẓu addad-agi deg usmel-inek s wenγal n tangalt yellan sdaw-agi.",
   "embed.preview": "Akka ara d-iban:",
   "emoji_button.activity": "Aqeddic",
   "emoji_button.custom": "Udmawan",
@@ -132,7 +133,7 @@
   "emoji_button.label": "Sekcem imuji",
   "emoji_button.nature": "Agama",
   "emoji_button.not_found": "Ulac izamulen n yiḥulfan  !! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Tiɣawsiwin",
+  "emoji_button.objects": "Tiγawsiwin",
   "emoji_button.people": "Medden",
   "emoji_button.recent": "Wid yettuseqdacen s waṭas",
   "emoji_button.search": "Nadi…",
@@ -140,35 +141,35 @@
   "emoji_button.symbols": "Izamulen",
   "emoji_button.travel": "Imeḍqan d Yinigen",
   "empty_column.account_timeline": "Ulac tiberraḥin dagi!",
-  "empty_column.account_unavailable": "Ur nufi ara amaɣnu-a",
+  "empty_column.account_unavailable": "Ur nufi ara amaγnu-a",
   "empty_column.blocks": "Ur tesḥebseḍ ula yiwen n umseqdac ar tura.",
-  "empty_column.bookmarked_statuses": "Ulac tiberraḥin i terniḍ ɣer yismenyifen-ik ar tura. Ticki terniḍ yiwet, ad d-tettwasken da.",
+  "empty_column.bookmarked_statuses": "Ulac tiberraḥin i terniḍ γer yismenyifen-ik ar tura. Ticki terniḍ yiwet, ad d-tettwasken da.",
   "empty_column.community": "Tasuddemt tazayezt tadigant n yisallen d tilemt. Aru ihi kra akken ad tt-teččareḍ!",
-  "empty_column.direct": "Ulac ɣur-k ula yiwen n yizen usrid. Ad d-yettwasken da, ticki tuzneḍ neɣ teṭṭfeḍ-d yiwen.",
-  "empty_column.domain_blocks": "Ulac kra n taɣult yettwaffren ar tura.",
+  "empty_column.direct": "Ulac γur-k ula yiwen n yizen usrid. Ad d-yettwasken da, ticki tuzneḍ neγ teṭṭfeḍ-d yiwen.",
+  "empty_column.domain_blocks": "Ulac kra n taγult yettwaffren ar tura.",
   "empty_column.favourited_statuses": "Ulac ula yiwet n tberraḥt deg yismenyifen-ik ar tura. Ticki Tella-d yiwet, ad d-ban da.",
   "empty_column.favourites": "Ula yiwen ur yerri taberraḥt-agi deg yismenyifen-is. Melmi i d-yella waya, ad d-yettwasken da.",
-  "empty_column.follow_requests": "Ulac ɣur-k ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.",
-  "empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaɣ ɣer uhacṭag-agi.",
-  "empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.",
+  "empty_column.follow_requests": "Ulac γur-k ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.",
+  "empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaγ γer uhacṭag-agi.",
+  "empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neγ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.",
   "empty_column.home.public_timeline": "tasuddemt tazayezt n yisallen",
-  "empty_column.list": "Ar tura ur yelli kra deg tebdert-a. Ad d-yettwasken da ticki iɛeggalen n tebdert-a suffɣen-d kra.",
-  "empty_column.lists": "Ulac ɣur-k kra n tebdert yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.",
-  "empty_column.mutes": "Ulac ɣur-k imseqdacen i yettwasgugmen.",
-  "empty_column.notifications": "Ulac ɣur-k tilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.",
-  "empty_column.public": "Ulac kra da! Aru kra, neɣ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt",
+  "empty_column.list": "Ar tura ur yelli kra deg tebdert-a. Ad d-yettwasken da ticki iɛeggalen n tebdert-a suffγen-d kra.",
+  "empty_column.lists": "Ulac γur-k kra n tebdert yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.",
+  "empty_column.mutes": "Ulac γur-k imseqdacen i yettwasgugmen.",
+  "empty_column.notifications": "Ulac γur-k tilγa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.",
+  "empty_column.public": "Ulac kra da! Aru kra, neγ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt",
   "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
-  "error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Mastudun deg yiminig-nniḍen neɣ deg usnas anaṣli.",
+  "error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Masṭudun deg yiminig-nniḍen neγ deg usnas anaṣli.",
   "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
   "errors.unexpected_crash.report_issue": "Mmel ugur",
   "follow_request.authorize": "Ssireg",
   "follow_request.reject": "Agi",
   "getting_started.developers": "Ineflayen",
-  "getting_started.directory": "Imaɣnuten",
+  "getting_started.directory": "Imaγnuten",
   "getting_started.documentation": "Amnir",
   "getting_started.heading": "Bdu",
   "getting_started.invite": "Snebgi-d imdanen",
-  "getting_started.open_source_notice": "Mastudun d aseɣzan s uɣbalu yeldin. Tzemreḍ ad tɛiwneḍ neɣ ad temmleḍ uguren seg GitHub {github}.",
+  "getting_started.open_source_notice": "Mastudun d aseγzan s uγbalu yeldin. Tzemreḍ ad tɛiwneḍ neγ ad temmleḍ uguren deg GitHub {github}.",
   "getting_started.security": "Iγewwaṛen n umiḍan",
   "getting_started.terms": "Tiwetlin n useqdec",
   "hashtag.column_header.tag_mode.all": "d {additional}",
@@ -190,29 +191,29 @@
   "introduction.federation.federated.headline": "Amatu",
   "introduction.federation.federated.text": "Iznan izuyaz i d-yekkan seg yiqeddacen-nniḍen n fediverse ad banen deg tsuddemt tazayezt tamatut n yisallen.",
   "introduction.federation.home.headline": "Agejdan",
-  "introduction.federation.home.text": "Iznan n yemdanen i teṭṭafaṛeḍ ad banen deg tsuddemt n umagger. Tzemreḍ ad tḍefṛeḍ win tebɣiḍ deg uqeddac i tebɣiḍ!",
+  "introduction.federation.home.text": "Iznan n yemdanen i teṭṭafaṛeḍ ad banen deg tsuddemt n umagger. Tzemreḍ ad tḍefṛeḍ win tebγiḍ deg uqeddac i tebγiḍ!",
   "introduction.federation.local.headline": "Adigan",
   "introduction.federation.local.text": "Iznan izuyaz n yemdanen i yellan deg yiwen uqeddac akked kečč ad d-banen deg tsuddemt tazayezt tadigant.",
   "introduction.interactions.action": "Fakk tameskant!",
   "introduction.interactions.favourite.headline": "Ismenyifen",
-  "introduction.interactions.favourite.text": "Tzemreḍ ad teǧǧeḍ kra n tberraḥt i ticki, daɣen ad tiniḍ i bab-is d akken taɛǧeb-ik, s tmerna-ines ɣer yismenyifen-ik.",
+  "introduction.interactions.favourite.text": "Tzemreḍ ad teǧǧeḍ kra n tberraḥt i ticki, daγen ad tiniḍ i bab-is d akken taɛǧeb-ik, s tmerna-ines γer yismenyifen-ik.",
   "introduction.interactions.reblog.headline": "Bḍu tikelt-nniḍen",
   "introduction.interactions.reblog.text": "Tzemreḍ ad tebḍuḍ akked yimeḍfaṛen-ik tiberraḥin n yemdanen-nniḍen s beṭṭu-nsent tikelt-nniḍen.",
   "introduction.interactions.reply.headline": "Err",
-  "introduction.interactions.reply.text": "Tzemreḍ ad terreḍ ɣef tberraḥin-ik d tid n medden-nniḍen, d acu ara tent-id-iɛeqden ta deffir ta deg udiwenni.",
+  "introduction.interactions.reply.text": "Tzemreḍ ad terreḍ γef tberraḥin-ik d tid n medden-nniḍen, d acu ara tent-id-iɛeqden ta deffir ta deg udiwenni.",
   "introduction.welcome.action": "Bdu!",
   "introduction.welcome.headline": "Isurifen imenza",
-  "introduction.welcome.text": "Anṣuf ɣer fediverse! Deg kra n yimiren, ad tizmireḍ ad tzzuzreḍ iznan neɣ ad tmeslayeḍ i yemddukkal deg waṭas n yiqeddacen. Maca aqeddac-agi, {domain}, mačči am wiyaḍ - deg-s i yella umaɣnu-ik, ihi cfu ɣef yisem-is.",
-  "keyboard_shortcuts.back": "uɣal ar deffir",
+  "introduction.welcome.text": "Anṣuf γer fediverse! Deg kra n yimiren, ad tizmireḍ ad tzzuzreḍ iznan neɣ ad tmeslayeḍ i yemddukkal deg waṭas n yiqeddacen. Maca aqeddac-agi, {domain}, mačči am wiyaḍ - deg-s i yella umaγnu-ik, ihi cfu γef yisem-is.",
+  "keyboard_shortcuts.back": "uγal ar deffir",
   "keyboard_shortcuts.blocked": "akken ad teldiḍ tabdert n yimseqdacen yettwasḥebsen",
   "keyboard_shortcuts.boost": "i beṭṭu tikelt-nniḍen",
   "keyboard_shortcuts.column": "to focus a status in one of the columns",
   "keyboard_shortcuts.compose": "to focus the compose textarea",
   "keyboard_shortcuts.description": "Aglam",
   "keyboard_shortcuts.direct": "akken ad teldiḍ ajgu n yiznan usriden",
-  "keyboard_shortcuts.down": "i kennu ɣer wadda n tebdert",
-  "keyboard_shortcuts.enter": "i tildin n tsuffeɣt",
-  "keyboard_shortcuts.favourite": "akken ad ternuḍ ɣer yismenyifen",
+  "keyboard_shortcuts.down": "i kennu γer wadda n tebdert",
+  "keyboard_shortcuts.enter": "i tildin n tsuffeγt",
+  "keyboard_shortcuts.favourite": "akken ad ternuḍ γer yismenyifen",
   "keyboard_shortcuts.favourites": "i tildin n tebdert n yismenyifen",
   "keyboard_shortcuts.federated": "i tildin n tsuddemt tamatut n yisallen",
   "keyboard_shortcuts.heading": "Inegzumen n unasiw",
@@ -222,11 +223,11 @@
   "keyboard_shortcuts.local": "i tildin n tsuddemt tadigant n yisallen",
   "keyboard_shortcuts.mention": "akken ad d-bedreḍ ameskar",
   "keyboard_shortcuts.muted": "akken ad teldiḍ tabdert n yimseqdacen yettwasgugmen",
-  "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik",
-  "keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n tilɣa",
+  "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaγnu-ik",
+  "keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n tilγa",
   "keyboard_shortcuts.open_media": "to open media",
   "keyboard_shortcuts.pinned": "i tildin n tebdert n tberraḥin yettwasentḍen",
-  "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar",
+  "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaγnu n umeskar",
   "keyboard_shortcuts.reply": "i tririt",
   "keyboard_shortcuts.requests": "akken ad d-teldiḍ tabdert n yisuturen n teḍfeṛt",
   "keyboard_shortcuts.search": "to focus search",
@@ -235,7 +236,7 @@
   "keyboard_shortcuts.toggle_sensitivity": "i teskent/tuffra n yimidyaten",
   "keyboard_shortcuts.toot": "i beddu n tberraḥt tamaynut",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "i tulin ɣer ufella n tebdert",
+  "keyboard_shortcuts.up": "i tulin γer ufella n tebdert",
   "lightbox.close": "Mdel",
   "lightbox.next": "Γer zdat",
   "lightbox.previous": "Γer deffir",
@@ -253,8 +254,8 @@
   "loading_indicator.label": "Asali...",
   "media_gallery.toggle_visible": "Sken / Ffer",
   "missing_indicator.label": "Ulac-it",
-  "missing_indicator.sublabel": "Ur nufi ara aɣbalu-a",
-  "mute_modal.hide_notifications": "Tebɣiḍ ad teffreḍ talɣutin n umseqdac-a?",
+  "missing_indicator.sublabel": "Ur nufi ara aγbalu-a",
+  "mute_modal.hide_notifications": "Tebγiḍ ad teffreḍ talγutin n umseqdac-a?",
   "navigation_bar.apps": "Isnasen izirazen",
   "navigation_bar.blocks": "Imseqdacen yettusḥebsen",
   "navigation_bar.bookmarks": "Ticraḍ",
@@ -262,13 +263,13 @@
   "navigation_bar.compose": "Aru taberraḥt tamaynut",
   "navigation_bar.direct": "Iznan usridden",
   "navigation_bar.discover": "Ẓer",
-  "navigation_bar.domain_blocks": "Tiɣula yeffren",
-  "navigation_bar.edit_profile": "Ẓreg amaɣnu",
+  "navigation_bar.domain_blocks": "Tiγula yeffren",
+  "navigation_bar.edit_profile": "Ẓreg amaγnu",
   "navigation_bar.favourites": "Ismenyifen",
   "navigation_bar.filters": "Awalen i yettwasgugmen",
   "navigation_bar.follow_requests": "Isuturen n teḍfeṛt",
   "navigation_bar.follows_and_followers": "Imeḍfaṛen akked wid i teṭṭafaṛeḍ",
-  "navigation_bar.info": "Ɣef uqeddac-agi",
+  "navigation_bar.info": "Γef uqeddac-a",
   "navigation_bar.keyboard_shortcuts": "Inegzumen n unasiw",
   "navigation_bar.lists": "Tibdarin",
   "navigation_bar.logout": "Ffeγ",
@@ -278,16 +279,16 @@
   "navigation_bar.preferences": "Imenyafen",
   "navigation_bar.public_timeline": "Tasuddemt tazayezt tamatut",
   "navigation_bar.security": "Taγellist",
-  "notification.favourite": "{name} yesmenyef tasuffeɣt-ik",
+  "notification.favourite": "{name} yesmenyef tasuffeγt-ik",
   "notification.follow": "{name} yeṭṭafaṛ-ik",
   "notification.follow_request": "{name} yessuter-d ad k-yeḍfeṛ",
   "notification.mention": "{name} yebder-ik-id",
   "notification.own_poll": "Your poll has ended",
   "notification.poll": "A poll you have voted in has ended",
   "notification.reblog": "{name} yebḍa taberraḥ-ik i tikelt-nniḍen",
-  "notifications.clear": "Sfeḍ tilɣa",
-  "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-ik i lebda?",
-  "notifications.column_settings.alert": "Tilɣa n tnarit",
+  "notifications.clear": "Sfeḍ tilγa",
+  "notifications.clear_confirmation": "Tebγiḍ s tidet ad tekkseḍ akk tilγa-ik i lebda?",
+  "notifications.column_settings.alert": "Tilγa n tnarit",
   "notifications.column_settings.favourite": "Ismenyifen:",
   "notifications.column_settings.filter_bar.advanced": "Sken-d meṛṛa tiggayin",
   "notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib",
@@ -295,26 +296,26 @@
   "notifications.column_settings.follow": "Imeḍfaṛen imaynuten:",
   "notifications.column_settings.follow_request": "Isuturen imaynuten n teḍfeṛt:",
   "notifications.column_settings.mention": "Abdar:",
-  "notifications.column_settings.poll": "Poll results:",
-  "notifications.column_settings.push": "Tilɣa yettudemmren",
+  "notifications.column_settings.poll": "Igemmaḍ n usenqed:",
+  "notifications.column_settings.push": "Tilγa yettudemmren",
   "notifications.column_settings.reblog": "Boosts:",
-  "notifications.column_settings.show": "Sken-d tilɣa deg ujgu",
+  "notifications.column_settings.show": "Sken-d tilγa deg ujgu",
   "notifications.column_settings.sound": "Rmed imesli",
   "notifications.filter.all": "Akk",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Ismenyifen",
   "notifications.filter.follows": "Yeṭafaṛ",
   "notifications.filter.mentions": "Abdar",
-  "notifications.filter.polls": "Poll results",
-  "notifications.group": "{count} n tilɣa",
+  "notifications.filter.polls": "Igemmaḍ n usenqed",
+  "notifications.group": "{count} n tilγa",
   "poll.closed": "Ifukk",
   "poll.refresh": "Smiren",
   "poll.total_people": "{count, plural, one {# n wemdan} other {# n yemdanen}}",
-  "poll.total_votes": "{count, plural, one {# n udɣaṛ} other {# n yedɣaṛen}}",
-  "poll.vote": "Dɣeṛ",
-  "poll.voted": "Tdeɣṛeḍ ɣef tririt-agi",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll.total_votes": "{count, plural, one {# n udγaṛ} other {# n yedγaṛen}}",
+  "poll.vote": "Dγeṛ",
+  "poll.voted": "Tdeγṛeḍ γef tririt-agi",
+  "poll_button.add_poll": "Rnu asenqed",
+  "poll_button.remove_poll": "Kkes asenqed",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Bḍu gar yimseqdacen i tbedreḍ kan",
   "privacy.direct.short": "Usrid",
@@ -356,13 +357,13 @@
   "status.block": "Seḥbes @{name}",
   "status.bookmark": "Creḍ",
   "status.cancel_reblog_private": "Sefsex beṭṭu",
-  "status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
-  "status.copy": "Nɣel assaɣ ɣer tasuffeɣt",
+  "status.cannot_reblog": "Tasuffeγt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
+  "status.copy": "Nγel assaγ γer tasuffeγt",
   "status.delete": "Kkes",
   "status.detailed_status": "Detailed conversation view",
   "status.direct": "Izen usrid i @{name}",
   "status.embed": "Embed",
-  "status.favourite": "Rnu ɣer yismenyifen",
+  "status.favourite": "Rnu γer yismenyifen",
   "status.filtered": "Yettwasizdeg",
   "status.load_more": "Sali ugar",
   "status.media_hidden": "Media hidden",
@@ -370,8 +371,8 @@
   "status.more": "Ugar",
   "status.mute": "Sussem @{name}",
   "status.mute_conversation": "Mute conversation",
-  "status.open": "Semɣeṛ tasuffeɣt-agi",
-  "status.pin": "Senteḍ-itt deg umaɣnu",
+  "status.open": "Semγeṛ tasuffeγt-agi",
+  "status.pin": "Senteḍ-itt deg umaγnu",
   "status.pinned": "Tiberraḥin yettwasentḍen",
   "status.read_more": "Issin ugar",
   "status.reblog": "Bḍu",
@@ -386,13 +387,13 @@
   "status.sensitive_warning": "Agbur amḥulfu",
   "status.share": "Bḍu",
   "status.show_less": "Sken-d drus",
-  "status.show_less_all": "Semẓi akk tisuffɣin",
+  "status.show_less_all": "Semẓi akk tisuffγin",
   "status.show_more": "Sken-ed ugar",
   "status.show_more_all": "Ẓerr ugar lebda",
   "status.show_thread": "Show thread",
   "status.uncached_media_warning": "Ulac-it",
   "status.unmute_conversation": "Kkes asgugem n udiwenni",
-  "status.unpin": "Kkes asenteḍ seg umaɣnu",
+  "status.unpin": "Kkes asenteḍ seg umaγnu",
   "suggestions.dismiss": "Dismiss suggestion",
   "suggestions.header": "Ahat ad tcelgeḍ deg…",
   "tabs_bar.federated_timeline": "Amatu",
@@ -416,10 +417,10 @@
   "upload_form.description": "Glem-d i yemdaneni yesɛan ugur deg yiẓri",
   "upload_form.edit": "Ẓreg",
   "upload_form.undo": "Kkes",
-  "upload_form.video_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt neɣ deg yiẓri",
+  "upload_form.video_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt neγ deg yiẓri",
   "upload_modal.analyzing_picture": "Tasleḍt n tugna tetteddu…",
   "upload_modal.apply": "Snes",
-  "upload_modal.description_placeholder": "Aberraɣ arurad ineggez nnig n uqjun amuṭṭis",
+  "upload_modal.description_placeholder": "Aberraγ arurad ineggez nnig n uqjun amuṭṭis",
   "upload_modal.detect_text": "Detect text from picture",
   "upload_modal.edit_media": "Edit media",
   "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
@@ -427,8 +428,8 @@
   "upload_progress.label": "Asali iteddu...",
   "video.close": "Mdel tabidyutt",
   "video.download": "Sidered afaylu",
-  "video.exit_fullscreen": "Ffeɣ seg ugdil aččuran",
-  "video.expand": "Semɣeṛ tavidyut",
+  "video.exit_fullscreen": "Ffeγ seg ugdil aččuran",
+  "video.expand": "Semγeṛ tavidyut",
   "video.fullscreen": "Agdil aččuran",
   "video.hide": "Ffer tabidyutt",
   "video.mute": "Gzem imesli",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index 86f3626f9..b307a3161 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Бағалау шектеулі",
   "alert.unexpected.message": "Бір нәрсе дұрыс болмады.",
   "alert.unexpected.title": "Өй!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} аптасына",
   "boost_modal.combo": "Келесіде өткізіп жіберу үшін басыңыз {combo}",
   "bundle_column_error.body": "Бұл компонентті жүктеген кезде бір қате пайда болды.",
diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json
index 96872bbbd..278f6b14c 100644
--- a/app/javascript/mastodon/locales/kn.json
+++ b/app/javascript/mastodon/locales/kn.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 635a6c8bd..f7be26c09 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "빈도 제한",
   "alert.unexpected.message": "예측하지 못한 에러가 발생했습니다.",
   "alert.unexpected.title": "앗!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "주간 {count}회",
   "boost_modal.combo": "{combo}를 누르면 다음부터 이 과정을 건너뛸 수 있습니다",
   "bundle_column_error.body": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.",
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index 96872bbbd..278f6b14c 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index efdb23436..b2f8fedbf 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Negaidīta kļūda.",
   "alert.unexpected.title": "Ups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Nospied {combo} lai izlaistu šo nākamreiz",
   "bundle_column_error.body": "Kaut kas nogāja greizi ielādējot šo komponenti.",
diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json
index 2a31a5c22..90c8d2418 100644
--- a/app/javascript/mastodon/locales/mk.json
+++ b/app/javascript/mastodon/locales/mk.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Неочекувана грешка.",
   "alert.unexpected.title": "Упс!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} неделно",
   "boost_modal.combo": "Кликни {combo} за да го прескокниш ова нареден пат",
   "bundle_column_error.body": "Се случи проблем при вчитувањето.",
diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json
index 0705f20e3..6a042e8c4 100644
--- a/app/javascript/mastodon/locales/ml.json
+++ b/app/javascript/mastodon/locales/ml.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "തോത് പരിമിതപ്പെടുത്തിയിരിക്കുന്നു",
   "alert.unexpected.message": "അപ്രതീക്ഷിതമായി എന്തോ സംഭവിച്ചു.",
   "alert.unexpected.title": "ശ്ശോ!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "ആഴ്ച തോറും {count}",
   "boost_modal.combo": "അടുത്ത തവണ ഇത് ഒഴിവാക്കുവാൻ {combo} ഞെക്കാവുന്നതാണ്",
   "bundle_column_error.body": "ഈ ഘടകം പ്രദശിപ്പിക്കുമ്പോൾ എന്തോ കുഴപ്പം സംഭവിച്ചു.",
diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json
index a4dcbb144..f265042f2 100644
--- a/app/javascript/mastodon/locales/mr.json
+++ b/app/javascript/mastodon/locales/mr.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "अरेरे!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} प्रतिसप्ताह",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "हा घटक लोड करतांना काहीतरी चुकले आहे.",
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index 4e858d59e..3bd6e145e 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 7fbaf4204..d570f3612 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Beperkt te gebruiken",
   "alert.unexpected.message": "Er deed zich een onverwachte fout voor",
   "alert.unexpected.title": "Oeps!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
   "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index 00e323cc1..d69717292 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Begrensa rate",
   "alert.unexpected.message": "Eit uventa problem oppstod.",
   "alert.unexpected.title": "Oi sann!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per veke",
   "boost_modal.combo": "Du kan trykkja {combo} for å hoppa over dette neste gong",
   "bundle_column_error.body": "Noko gjekk gale mens denne komponenten vart lasta ned.",
@@ -170,7 +171,7 @@
   "getting_started.invite": "Byd folk inn",
   "getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidraga eller rapportera problem med GitHub på {github}.",
   "getting_started.security": "Kontoinnstillingar",
-  "getting_started.terms": "Brukarvillkår",
+  "getting_started.terms": "Brukarvilkår",
   "hashtag.column_header.tag_mode.all": "og {additional}",
   "hashtag.column_header.tag_mode.any": "eller {additional}",
   "hashtag.column_header.tag_mode.none": "utan {additional}",
@@ -354,7 +355,7 @@
   "status.admin_account": "Opne moderasjonsgrensesnitt for @{name}",
   "status.admin_status": "Opne denne statusen i moderasjonsgrensesnittet",
   "status.block": "Blokker @{name}",
-  "status.bookmark": "Bokmerke",
+  "status.bookmark": "Bokmerk",
   "status.cancel_reblog_private": "Opphev framheving",
   "status.cannot_reblog": "Denne posten kan ikkje framhevast",
   "status.copy": "Kopier lenke til status",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index a6aa5db7c..c6dc4ca0e 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Hastighetsbegrenset",
   "alert.unexpected.message": "En uventet feil oppstod.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per uke",
   "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
   "bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 601ed93c6..b7701c17e 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Taus limitat",
   "alert.unexpected.message": "Una error s’es producha.",
   "alert.unexpected.title": "Ops !",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per setmana",
   "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
   "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 9972597d6..3874f1596 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Ograniczony czasowo",
   "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
   "alert.unexpected.title": "O nie!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} co tydzień",
   "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
   "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 7dedbe2cc..f78c327fb 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Tentativas limitadas",
   "alert.unexpected.message": "Ocorreu um erro inesperado.",
   "alert.unexpected.title": "Eita!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Pressione {combo} para ignorar este diálogo na próxima vez",
   "bundle_column_error.body": "Ocorreu um problema ao carregar este componente.",
diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json
index 72eebaa1f..b2fb2a012 100644
--- a/app/javascript/mastodon/locales/pt-PT.json
+++ b/app/javascript/mastodon/locales/pt-PT.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Limite de tentativas",
   "alert.unexpected.message": "Ocorreu um erro inesperado.",
   "alert.unexpected.title": "Bolas!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
   "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index 7703d46fb..4d01ad3a5 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "A apărut o eroare neașteptată.",
   "alert.unexpected.title": "Hopa!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Poți apăsa {combo} pentru a omite asta data viitoare",
   "bundle_column_error.body": "Ceva nu a funcționat la încărcarea acestui component.",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 89f364947..1681da968 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Вы выполняете действие слишком часто",
   "alert.unexpected.message": "Что-то пошло не так.",
   "alert.unexpected.title": "Ой!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} / неделю",
   "boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз",
   "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.",
@@ -365,7 +366,7 @@
   "status.favourite": "Нравится",
   "status.filtered": "Отфильтровано",
   "status.load_more": "Загрузить остальное",
-  "status.media_hidden": "Медиа скрыто",
+  "status.media_hidden": "Файл скрыт",
   "status.mention": "Упомянуть @{name}",
   "status.more": "Больше",
   "status.mute": "Игнорировать @{name}",
@@ -390,7 +391,7 @@
   "status.show_more": "Развернуть",
   "status.show_more_all": "Развернуть все спойлеры в ветке",
   "status.show_thread": "Показать обсуждение",
-  "status.uncached_media_warning": "Недоступно",
+  "status.uncached_media_warning": "Файл недоступен",
   "status.unmute_conversation": "Не игнорировать обсуждение",
   "status.unpin": "Открепить от профиля",
   "suggestions.dismiss": "Удалить предложение",
@@ -409,7 +410,7 @@
   "trends.trending_now": "Самое актуальное",
   "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.",
   "upload_area.title": "Перетащите сюда, чтобы загрузить",
-  "upload_button.label": "Добавить файл медиа ({formats})",
+  "upload_button.label": "Прикрепить фото, видео или аудио",
   "upload_error.limit": "Достигнут лимит загруженных файлов.",
   "upload_error.poll": "К опросам нельзя прикреплять файлы.",
   "upload_form.audio_description": "Опишите аудиофайл для людей с нарушением слуха",
@@ -421,7 +422,7 @@
   "upload_modal.apply": "Применить",
   "upload_modal.description_placeholder": "На дворе трава, на траве дрова",
   "upload_modal.detect_text": "Найти текст на картинке",
-  "upload_modal.edit_media": "Изменение медиа",
+  "upload_modal.edit_media": "Изменить файл",
   "upload_modal.hint": "Нажмите и перетащите круг в предпросмотре в точку фокуса, которая всегда будет видна на эскизах.",
   "upload_modal.preview_label": "Предпросмотр ({ratio})",
   "upload_progress.label": "Загрузка...",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 689d6245c..2410daf06 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Pridaj do, alebo odober zo zoznamov",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Skupina",
   "account.block": "Blokuj @{name}",
   "account.block_domain": "Ukry všetko z {domain}",
   "account.blocked": "Blokovaný/á",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Tempo obmedzené",
   "alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
   "alert.unexpected.title": "Ups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} týždenne",
   "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} pre preskočenie",
   "bundle_column_error.body": "Pri načítaní tohto prvku nastala nejaká chyba.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Trvanie ankety",
   "compose_form.poll.option_placeholder": "Voľba {number}",
   "compose_form.poll.remove_option": "Odstráň túto voľbu",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Zmeň anketu pre povolenie viacerých možností",
+  "compose_form.poll.switch_to_single": "Zmeň anketu na takú s jedinou voľbou",
   "compose_form.publish": "Pošli",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Označ médiá ako chúlostivé",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index da816837f..bcf3b8d1a 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Zgodila se je nepričakovana napaka.",
   "alert.unexpected.title": "Uups!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Če želite preskočiti to, lahko pritisnete {combo}",
   "bundle_column_error.body": "Med nalaganjem te komponente je prišlo do napake.",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index 0b5a613c2..617c3aee3 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Ndodhi një gabim të papritur.",
   "alert.unexpected.title": "Hëm!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Mund të shtypni {combo}, që të anashkalohet kjo herës tjetër",
   "bundle_column_error.body": "Diç shkoi ters teksa ngarkohej ky përbërës.",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index aab2dfa15..09b30ff5f 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put",
   "bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 35436a93d..5b04f9826 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Појавила се неочекивана грешка.",
   "alert.unexpected.title": "Упс!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Можете притиснути {combo} да прескочите ово следећи пут",
   "bundle_column_error.body": "Нешто је пошло по злу приликом учитавања ове компоненте.",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 843ab9fc0..4e778a481 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Lägg till i eller ta bort från listor",
   "account.badges.bot": "Robot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Grupp",
   "account.block": "Blockera @{name}",
   "account.block_domain": "Dölj allt från {domain}",
   "account.blocked": "Blockerad",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Mängd begränsad",
   "alert.unexpected.message": "Ett oväntat fel uppstod.",
   "alert.unexpected.title": "Hoppsan!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per vecka",
   "boost_modal.combo": "Du kan trycka {combo} för att slippa detta nästa gång",
   "bundle_column_error.body": "Något gick fel medan denna komponent laddades.",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "Varaktighet för omröstning",
   "compose_form.poll.option_placeholder": "Val {number}",
   "compose_form.poll.remove_option": "Ta bort detta val",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Ändra enkät för att tillåta flera val",
+  "compose_form.poll.switch_to_single": "Ändra enkät för att tillåta ett enda val",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Markera media som känsligt",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index ddd1d9fd7..8d1a8f14d 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "பட்டியல்களில் சேர்/நீக்கு",
   "account.badges.bot": "பாட்",
-  "account.badges.group": "Group",
+  "account.badges.group": "குழு",
   "account.block": "@{name} -ஐத் தடு",
   "account.block_domain": "{domain} யில் இருந்து வரும் எல்லாவற்றையும் மறை",
   "account.blocked": "முடக்கப்பட்டது",
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "விகிதம் வரையறுக்கப்பட்டுள்ளது",
   "alert.unexpected.message": "எதிர்பாராத பிழை ஏற்பட்டுவிட்டது.",
   "alert.unexpected.title": "அச்சச்சோ!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "ஒவ்வொரு வாரம் {count}",
   "boost_modal.combo": "நீங்கள் இதை அடுத்தமுறை தவிர்க்க {combo} வை அழுத்தவும்",
   "bundle_column_error.body": "இக்கூற்றை ஏற்றம் செய்யும்பொழுது ஏதோ தவறு ஏற்பட்டுள்ளது.",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index 12af667c7..8b47fec52 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "అనుకోని తప్పు జరిగినది.",
   "alert.unexpected.title": "అయ్యో!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "మీరు తదుపరిసారి దీనిని దాటవేయడానికి {combo} నొక్కవచ్చు",
   "bundle_column_error.body": "ఈ భాగం లోడ్ అవుతున్నప్పుడు ఏదో తప్పు జరిగింది.",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 56e383da7..8a72783a2 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "มีการจำกัดอัตรา",
   "alert.unexpected.message": "เกิดข้อผิดพลาดที่ไม่คาดคิด",
   "alert.unexpected.title": "อุปส์!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} ต่อสัปดาห์",
   "boost_modal.combo": "คุณสามารถกด {combo} เพื่อข้ามสิ่งนี้ในครั้งถัดไป",
   "bundle_column_error.body": "มีบางอย่างผิดพลาดขณะโหลดส่วนประกอบนี้",
@@ -84,8 +85,8 @@
   "compose_form.poll.duration": "ระยะเวลาการสำรวจความคิดเห็น",
   "compose_form.poll.option_placeholder": "ทางเลือก {number}",
   "compose_form.poll.remove_option": "เอาทางเลือกนี้ออก",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายทางเลือก",
+  "compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตทางเลือกเดี่ยว",
   "compose_form.publish": "โพสต์",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "ทำเครื่องหมายสื่อว่าละเอียดอ่อน",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 514e7b956..3ade92977 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Oran sınırlıdır",
   "alert.unexpected.message": "Beklenmedik bir hata oluştu.",
   "alert.unexpected.title": "Hay aksi!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "Haftada {count}",
   "boost_modal.combo": "Bir daha ki sefere {combo} tuşuna basabilirsiniz",
   "bundle_column_error.body": "Bu bileşen yüklenirken bir şeyler ters gitti.",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 1c37f8e44..3dc69d6d6 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Швидкість обмежена",
   "alert.unexpected.message": "Трапилась неочікувана помилка.",
   "alert.unexpected.title": "Ой!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} в тиждень",
   "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
   "bundle_column_error.body": "Щось пішло не так під час завантаження компоненту.",
diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json
index 6f14ad3c6..01477906c 100644
--- a/app/javascript/mastodon/locales/ur.json
+++ b/app/javascript/mastodon/locales/ur.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "ایک غیر متوقع سہو ہوا ہے.",
   "alert.unexpected.title": "ا رے!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} فی ہفتہ",
   "boost_modal.combo": "آئیندہ یہ نہ دیکھنے کیلئے آپ {combo} دبا سکتے ہیں",
   "bundle_column_error.body": "اس عنصر کو برآمد کرتے وقت کچھ خرابی پیش آئی ہے.",
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index d94be9f5b..07dff79fa 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "Tỷ lệ giới hạn",
   "alert.unexpected.message": "Đã xảy ra lỗi không mong muốn.",
   "alert.unexpected.title": "Ốiii!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{tính} mỗi tuần",
   "boost_modal.combo": "Bạn có thể nhấn {combo} để bỏ qua lần sau",
   "bundle_column_error.body": "Có gì đó sai sai trong khi tải nội dung này",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 84670d4b5..62a86e75b 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "频率受限",
   "alert.unexpected.message": "发生了意外错误。",
   "alert.unexpected.title": "哎呀!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "每星期 {count} 条",
   "boost_modal.combo": "下次按住 {combo} 即可跳过此提示",
   "bundle_column_error.body": "载入这个组件时发生了错误。",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index a685409ee..4dcf70e6b 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "已限速",
   "alert.unexpected.message": "發生不可預期的錯誤。",
   "alert.unexpected.title": "噢!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} / 週",
   "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
   "bundle_column_error.body": "加載本組件出錯。",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index e5f740b46..ab680223e 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -43,6 +43,7 @@
   "alert.rate_limited.title": "已限速",
   "alert.unexpected.message": "發生了非預期的錯誤。",
   "alert.unexpected.title": "哎呀!",
+  "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} / 週",
   "boost_modal.combo": "下次您可以按 {combo} 跳過",
   "bundle_column_error.body": "載入此元件時發生錯誤。",
diff --git a/app/javascript/mastodon/reducers/announcements.js b/app/javascript/mastodon/reducers/announcements.js
new file mode 100644
index 000000000..aa674e516
--- /dev/null
+++ b/app/javascript/mastodon/reducers/announcements.js
@@ -0,0 +1,72 @@
+import {
+  ANNOUNCEMENTS_FETCH_REQUEST,
+  ANNOUNCEMENTS_FETCH_SUCCESS,
+  ANNOUNCEMENTS_FETCH_FAIL,
+  ANNOUNCEMENTS_UPDATE,
+  ANNOUNCEMENTS_DISMISS,
+  ANNOUNCEMENTS_REACTION_UPDATE,
+  ANNOUNCEMENTS_REACTION_ADD_REQUEST,
+  ANNOUNCEMENTS_REACTION_ADD_FAIL,
+  ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
+  ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+} from '../actions/announcements';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+});
+
+const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
+  if (announcement.get('id') === id) {
+    return announcement.update('reactions', reactions => {
+      if (reactions.find(reaction => reaction.get('name') === name)) {
+        return reactions.map(reaction => {
+          if (reaction.get('name') === name) {
+            return updater(reaction);
+          }
+
+          return reaction;
+        });
+      }
+
+      return reactions.push(updater(fromJS({ name, count: 0 })));
+    });
+  }
+
+  return announcement;
+}));
+
+const updateReactionCount = (state, reaction) => updateReaction(state, reaction.announcement_id, reaction.name, x => x.set('count', reaction.count));
+
+const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', true).update('count', y => y + 1));
+
+const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));
+
+export default function announcementsReducer(state = initialState, action) {
+  switch(action.type) {
+  case ANNOUNCEMENTS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case ANNOUNCEMENTS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('items', fromJS(action.announcements));
+      map.set('isLoading', false);
+    });
+  case ANNOUNCEMENTS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case ANNOUNCEMENTS_UPDATE:
+    return state.update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
+  case ANNOUNCEMENTS_DISMISS:
+    return state.update('items', list => list.filterNot(announcement => announcement.get('id') === action.id));
+  case ANNOUNCEMENTS_REACTION_UPDATE:
+    return updateReactionCount(state, action.reaction);
+  case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
+  case ANNOUNCEMENTS_REACTION_REMOVE_FAIL:
+    return addReaction(state, action.id, action.name);
+  case ANNOUNCEMENTS_REACTION_REMOVE_REQUEST:
+  case ANNOUNCEMENTS_REACTION_ADD_FAIL:
+    return removeReaction(state, action.id, action.name);
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index b8d608888..b9817cd38 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -34,8 +34,10 @@ import polls from './polls';
 import identity_proofs from './identity_proofs';
 import trends from './trends';
 import missed_updates from './missed_updates';
+import announcements from './announcements';
 
 const reducers = {
+  announcements,
   dropdown_menu,
   timelines,
   meta,
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index 6f1ce9602..6a48f3b3f 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -26,6 +26,7 @@ const toServerSideType = columnType => {
   case 'notifications':
   case 'public':
   case 'thread':
+  case 'account':
     return columnType;
   default:
     if (columnType.indexOf('list:') > -1) {
diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js
index 50f90d44c..fe965bcb0 100644
--- a/app/javascript/mastodon/stream.js
+++ b/app/javascript/mastodon/stream.js
@@ -2,6 +2,14 @@ import WebSocketClient from '@gamestdio/websocket';
 
 const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
 
+const knownEventTypes = [
+  'update',
+  'delete',
+  'notification',
+  'conversation',
+  'filters_changed',
+];
+
 export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) {
   return (dispatch, getState) => {
     const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
@@ -69,14 +77,42 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 
 
 export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
-  const params = [ `stream=${stream}` ];
-
-  const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
-
-  ws.onopen      = connected;
-  ws.onmessage   = e => received(JSON.parse(e.data));
-  ws.onclose     = disconnected;
-  ws.onreconnect = reconnected;
+  const params = stream.split('&');
+  stream = params.shift();
+
+  if (streamingAPIBaseURL.startsWith('ws')) {
+    params.unshift(`stream=${stream}`);
+    const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
+
+    ws.onopen      = connected;
+    ws.onmessage   = e => received(JSON.parse(e.data));
+    ws.onclose     = disconnected;
+    ws.onreconnect = reconnected;
+
+    return ws;
+  }
+
+  params.push(`access_token=${accessToken}`);
+  const es = new EventSource(`${streamingAPIBaseURL}/api/v1/streaming/${stream}?${params.join('&')}`);
+
+  let firstConnect = true;
+  es.onopen = () => {
+    if (firstConnect) {
+      firstConnect = false;
+      connected();
+    } else {
+      reconnected();
+    }
+  };
+  for (let type of knownEventTypes) {
+    es.addEventListener(type, (e) => {
+      received({
+        event: e.type,
+        payload: e.data,
+      });
+    });
+  }
+  es.onerror = disconnected;
 
-  return ws;
+  return es;
 };
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index ccfce1475..8d0a070d5 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -859,6 +859,44 @@
   }
 }
 
+.announcements__item__content {
+  word-wrap: break-word;
+
+  .emojione {
+    width: 20px;
+    height: 20px;
+    margin: -3px 0 0;
+  }
+
+  p {
+    margin-bottom: 10px;
+    white-space: pre-wrap;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  a {
+    color: $highlight-text-color;
+    text-decoration: none;
+
+    &:hover {
+      text-decoration: underline;
+    }
+
+    &.mention {
+      &:hover {
+        text-decoration: none;
+
+        span {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
+
 .status__content.status__content--collapsed {
   max-height: 20px * 15; // 15 lines is roughly above 500 characters
 }
@@ -6581,3 +6619,178 @@ noscript {
     }
   }
 }
+
+.announcements {
+  background: lighten($ui-base-color, 4%);
+  border-top: 1px solid $ui-base-color;
+  font-size: 13px;
+  display: flex;
+  align-items: flex-end;
+
+  &__mastodon {
+    width: 124px;
+    flex: 0 0 auto;
+
+    @media screen and (max-width: 124px + 300px) {
+      display: none;
+    }
+  }
+
+  &__container {
+    width: calc(100% - 124px);
+    flex: 0 0 auto;
+    position: relative;
+
+    @media screen and (max-width: 124px + 300px) {
+      width: 100%;
+    }
+  }
+
+  &__item {
+    box-sizing: border-box;
+    width: 100%;
+    padding: 15px;
+    padding-right: 15px + 18px;
+    position: relative;
+
+    &__range {
+      display: block;
+      font-weight: 500;
+      margin-bottom: 10px;
+    }
+
+    &__dismiss-icon {
+      position: absolute;
+      top: 12px;
+      right: 12px;
+    }
+  }
+
+  &__pagination {
+    padding: 15px;
+    color: $darker-text-color;
+    position: absolute;
+    bottom: 3px;
+    right: 0;
+  }
+}
+
+.layout-multiple-columns .announcements__mastodon {
+  display: none;
+}
+
+.layout-multiple-columns .announcements__container {
+  width: 100%;
+}
+
+.reactions-bar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  margin-top: 15px;
+  margin-left: -2px;
+  width: calc(100% - (90px - 33px));
+
+  &__item {
+    flex-shrink: 0;
+    background: lighten($ui-base-color, 12%);
+    border: 0;
+    border-radius: 3px;
+    margin: 2px;
+    cursor: pointer;
+    user-select: none;
+    padding: 0 6px;
+    display: flex;
+    align-items: center;
+    transition: all 100ms ease-in;
+    transition-property: background-color, color;
+
+    &__emoji {
+      display: block;
+      margin: 3px 0;
+      width: 16px;
+      height: 16px;
+
+      img {
+        display: block;
+        margin: 0;
+        width: 100%;
+        height: 100%;
+        min-width: auto;
+        min-height: auto;
+        vertical-align: bottom;
+        object-fit: contain;
+      }
+    }
+
+    &__count {
+      display: block;
+      min-width: 9px;
+      font-size: 13px;
+      font-weight: 500;
+      text-align: center;
+      margin-left: 6px;
+      color: $darker-text-color;
+    }
+
+    &:hover,
+    &:focus,
+    &:active {
+      background: lighten($ui-base-color, 16%);
+      transition: all 200ms ease-out;
+      transition-property: background-color, color;
+
+      &__count {
+        color: lighten($darker-text-color, 4%);
+      }
+    }
+
+    &.active {
+      transition: all 100ms ease-in;
+      transition-property: background-color, color;
+      background-color: mix(lighten($ui-base-color, 12%), $ui-highlight-color, 90%);
+
+      .reactions-bar__item__count {
+        color: $highlight-text-color;
+      }
+    }
+  }
+
+  .emoji-picker-dropdown {
+    margin: 2px;
+  }
+
+  &:hover .emoji-button {
+    opacity: 0.85;
+  }
+
+  .emoji-button {
+    color: $darker-text-color;
+    margin: 0;
+    font-size: 16px;
+    width: auto;
+    flex-shrink: 0;
+    padding: 0 6px;
+    height: 22px;
+    display: flex;
+    align-items: center;
+    opacity: 0.5;
+    transition: all 100ms ease-in;
+    transition-property: background-color, color;
+
+    &:hover,
+    &:active,
+    &:focus {
+      opacity: 1;
+      color: lighten($darker-text-color, 4%);
+      transition: all 200ms ease-out;
+      transition-property: background-color, color;
+    }
+  }
+
+  &--empty {
+    .emoji-button {
+      padding: 0;
+    }
+  }
+}
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 8965ce675..65cefbd7c 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -222,6 +222,12 @@ code {
     }
   }
 
+  .input.datetime .label_input select {
+    display: inline-block;
+    width: auto;
+    flex: 0;
+  }
+
   .required abbr {
     text-decoration: none;
     color: lighten($error-value-color, 12%);
diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb
index 8fff544a0..35a3773d2 100644
--- a/app/lib/entity_cache.rb
+++ b/app/lib/entity_cache.rb
@@ -8,7 +8,7 @@ class EntityCache
   MAX_EXPIRATION = 7.days.freeze
 
   def mention(username, domain)
-    Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:username, :domain, :url).find_remote(username, domain) }
+    Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:id, :username, :domain, :url).find_remote(username, domain) }
   end
 
   def emoji(shortcodes, domain)
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 36cdae9f7..f1a751f84 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -308,9 +308,9 @@ class Formatter
     end
 
     standard = Extractor.extract_entities_with_indices(text, options)
-    xmpp = Extractor.extract_xmpp_uris_with_indices(text, options)
+    extra = Extractor.extract_extra_uris_with_indices(text, options)
 
-    Extractor.remove_overlapping_entities(special + standard + xmpp)
+    Extractor.remove_overlapping_entities(special + standard + extra)
   end
 
   def html_friendly_extractor(html, options = {})
diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb
index 761a8822d..27e334a4d 100644
--- a/app/lib/inline_renderer.rb
+++ b/app/lib/inline_renderer.rb
@@ -15,6 +15,10 @@ class InlineRenderer
       serializer = REST::NotificationSerializer
     when :conversation
       serializer = REST::ConversationSerializer
+    when :announcement
+      serializer = REST::AnnouncementSerializer
+    when :reaction
+      serializer = REST::ReactionSerializer
     else
       return
     end
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index 3554d34b9..2b5d554b5 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -2,7 +2,7 @@
 
 class Sanitize
   module Config
-    HTTP_PROTOCOLS ||= ['http', 'https', 'dat', 'dweb', 'ipfs', 'ipns', 'ssb', 'gopher', 'xmpp', :relative].freeze
+    HTTP_PROTOCOLS ||= ['http', 'https', 'dat', 'dweb', 'ipfs', 'ipns', 'ssb', 'gopher', 'xmpp', 'magnet', :relative].freeze
 
     CLASS_WHITELIST_TRANSFORMER = lambda do |env|
       node = env[:node]
diff --git a/app/middleware/handle_bad_encoding_middleware.rb b/app/middleware/handle_bad_encoding_middleware.rb
deleted file mode 100644
index 6fce84b15..000000000
--- a/app/middleware/handle_bad_encoding_middleware.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-# See: https://jamescrisp.org/2018/05/28/fixing-invalid-query-parameters-invalid-encoding-in-a-rails-app/
-
-class HandleBadEncodingMiddleware
-  def initialize(app)
-    @app = app
-  end
-
-  def call(env)
-    begin
-      Rack::Utils.parse_nested_query(env['QUERY_STRING'].to_s)
-    rescue Rack::Utils::InvalidParameterError
-      env['QUERY_STRING'] = ''
-    end
-
-    @app.call(env)
-  end
-end
diff --git a/app/models/account.rb b/app/models/account.rb
index 9b9361670..b856d1c76 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -478,6 +478,12 @@ class Account < ApplicationRecord
       records
     end
 
+    def from_text(text)
+      return [] if text.blank?
+
+      text.scan(MENTION_RE).map { |match| match.first.split('@', 2) }.uniq.map { |(username, domain)| EntityCache.instance.mention(username, domain) }
+    end
+
     private
 
     def generate_query_for_search(terms)
diff --git a/app/models/announcement.rb b/app/models/announcement.rb
new file mode 100644
index 000000000..4da9f94d6
--- /dev/null
+++ b/app/models/announcement.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: announcements
+#
+#  id           :bigint(8)        not null, primary key
+#  text         :text             default(""), not null
+#  published    :boolean          default(FALSE), not null
+#  all_day      :boolean          default(FALSE), not null
+#  scheduled_at :datetime
+#  starts_at    :datetime
+#  ends_at      :datetime
+#  created_at   :datetime         not null
+#  updated_at   :datetime         not null
+#
+
+class Announcement < ApplicationRecord
+  after_commit :queue_publish, on: :create
+
+  scope :unpublished, -> { where(published: false) }
+  scope :published, -> { where(published: true) }
+  scope :without_muted, ->(account) { joins("LEFT OUTER JOIN announcement_mutes ON announcement_mutes.announcement_id = announcements.id AND announcement_mutes.account_id = #{account.id}").where('announcement_mutes.id IS NULL') }
+  scope :chronological, -> { order(Arel.sql('COALESCE(announcements.starts_at, announcements.scheduled_at, announcements.created_at) ASC')) }
+
+  has_many :announcement_mutes, dependent: :destroy
+  has_many :announcement_reactions, dependent: :destroy
+
+  validates :text, presence: true
+  validates :starts_at, presence: true, if: -> { ends_at.present? }
+  validates :ends_at, presence: true, if: -> { starts_at.present? }
+
+  before_validation :set_all_day
+  before_validation :set_starts_at, on: :create
+  before_validation :set_ends_at, on: :create
+
+  def time_range?
+    starts_at.present? && ends_at.present?
+  end
+
+  def mentions
+    @mentions ||= Account.from_text(text)
+  end
+
+  def tags
+    @tags ||= Tag.find_or_create_by_names(Extractor.extract_hashtags(text))
+  end
+
+  def emojis
+    @emojis ||= CustomEmoji.from_text(text)
+  end
+
+  def reactions(account = nil)
+    records = begin
+      scope = announcement_reactions.group(:announcement_id, :name, :custom_emoji_id).order(Arel.sql('MIN(created_at) ASC'))
+
+      if account.nil?
+        scope.select('name, custom_emoji_id, count(*) as count, false as me')
+      else
+        scope.select("name, custom_emoji_id, count(*) as count, exists(select 1 from announcement_reactions r where r.account_id = #{account.id} and r.announcement_id = announcement_reactions.announcement_id and r.name = announcement_reactions.name) as me")
+      end
+    end
+
+    ActiveRecord::Associations::Preloader.new.preload(records, :custom_emoji)
+    records
+  end
+
+  private
+
+  def set_all_day
+    self.all_day = false if starts_at.blank? || ends_at.blank?
+  end
+
+  def set_starts_at
+    self.starts_at = starts_at.change(hour: 0, min: 0, sec: 0) if all_day? && starts_at.present?
+  end
+
+  def set_ends_at
+    self.ends_at = ends_at.change(hour: 23, min: 59, sec: 59) if all_day? && ends_at.present?
+  end
+
+  def queue_publish
+    PublishScheduledAnnouncementWorker.perform_async(id) if scheduled_at.blank?
+  end
+end
diff --git a/app/models/announcement_filter.rb b/app/models/announcement_filter.rb
new file mode 100644
index 000000000..950852460
--- /dev/null
+++ b/app/models/announcement_filter.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class AnnouncementFilter
+  KEYS = %i(
+    published
+    unpublished
+  ).freeze
+
+  attr_reader :params
+
+  def initialize(params)
+    @params = params
+  end
+
+  def results
+    scope = Announcement.unscoped
+
+    params.each do |key, value|
+      next if key.to_s == 'page'
+
+      scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
+    end
+
+    scope.chronological
+  end
+
+  private
+
+  def scope_for(key, _value)
+    case key.to_s
+    when 'published'
+      Announcement.published
+    when 'unpublished'
+      Announcement.unpublished
+    else
+      raise "Unknown filter: #{key}"
+    end
+  end
+end
diff --git a/app/models/announcement_mute.rb b/app/models/announcement_mute.rb
new file mode 100644
index 000000000..46fda2f5d
--- /dev/null
+++ b/app/models/announcement_mute.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: announcement_mutes
+#
+#  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  announcement_id :bigint(8)
+#  created_at      :datetime         not null
+#  updated_at      :datetime         not null
+#
+
+class AnnouncementMute < ApplicationRecord
+  belongs_to :account
+  belongs_to :announcement, inverse_of: :announcement_mutes
+
+  validates :account_id, uniqueness: { scope: :announcement_id }
+end
diff --git a/app/models/announcement_reaction.rb b/app/models/announcement_reaction.rb
new file mode 100644
index 000000000..d22771034
--- /dev/null
+++ b/app/models/announcement_reaction.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: announcement_reactions
+#
+#  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  announcement_id :bigint(8)
+#  name            :string           default(""), not null
+#  custom_emoji_id :bigint(8)
+#  created_at      :datetime         not null
+#  updated_at      :datetime         not null
+#
+
+class AnnouncementReaction < ApplicationRecord
+  after_commit :queue_publish
+
+  belongs_to :account
+  belongs_to :announcement, inverse_of: :announcement_reactions
+  belongs_to :custom_emoji, optional: true
+
+  validates :name, presence: true
+  validates_with ReactionValidator
+
+  before_validation :set_custom_emoji
+
+  private
+
+  def set_custom_emoji
+    self.custom_emoji = CustomEmoji.local.find_by(disabled: false, shortcode: name) if name.present?
+  end
+
+  def queue_publish
+    PublishAnnouncementReactionWorker.perform_async(announcement_id, name) unless announcement.destroyed?
+  end
+end
diff --git a/app/models/backup.rb b/app/models/backup.rb
index 8eeb1748a..d242fd62c 100644
--- a/app/models/backup.rb
+++ b/app/models/backup.rb
@@ -7,11 +7,11 @@
 #  user_id           :bigint(8)
 #  dump_file_name    :string
 #  dump_content_type :string
-#  dump_file_size    :bigint
 #  dump_updated_at   :datetime
 #  processed         :boolean          default(FALSE), not null
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
+#  dump_file_size    :bigint(8)
 #
 
 class Backup < ApplicationRecord
diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb
index 01dc48ee7..916261a17 100644
--- a/app/models/bookmark.rb
+++ b/app/models/bookmark.rb
@@ -3,11 +3,11 @@
 #
 # Table name: bookmarks
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)        not null
+#  status_id  :bigint(8)        not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :integer          not null
-#  status_id  :integer          not null
 #
 
 class Bookmark < ApplicationRecord
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index f27d39483..14bcf7bb1 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -84,6 +84,7 @@ module AccountInteractions
     has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
     has_many :conversation_mutes, dependent: :destroy
     has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
+    has_many :announcement_mutes, dependent: :destroy
   end
 
   def follow!(other_account, reblogs: nil, uri: nil)
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index 0dacaf654..d177cf281 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -67,7 +67,7 @@ class CustomEmoji < ApplicationRecord
   end
 
   class << self
-    def from_text(text, domain)
+    def from_text(text, domain = nil)
       return [] if text.blank?
 
       shortcodes = text.scan(SCAN_RE).map(&:first).uniq
diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb
index 382562fb8..8df8a4fbf 100644
--- a/app/models/custom_filter.rb
+++ b/app/models/custom_filter.rb
@@ -20,6 +20,7 @@ class CustomFilter < ApplicationRecord
     notifications
     public
     thread
+    account
   ).freeze
 
   include Expireable
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index b87b1b9d3..6a0b892f6 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -142,6 +142,7 @@ class MediaAttachment < ApplicationRecord
 
   validates :account, presence: true
   validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local?
+  validates :file, presence: true, if: :local?
 
   scope :attached,   -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
   scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
diff --git a/app/models/relationship_filter.rb b/app/models/relationship_filter.rb
index 51640f494..e6859bf3d 100644
--- a/app/models/relationship_filter.rb
+++ b/app/models/relationship_filter.rb
@@ -7,5 +7,114 @@ class RelationshipFilter
     by_domain
     activity
     order
+    location
   ).freeze
+
+  attr_reader :params, :account
+
+  def initialize(account, params)
+    @account = account
+    @params  = params
+
+    set_defaults!
+  end
+
+  def results
+    scope = scope_for('relationship', params['relationship'].to_s.strip)
+
+    params.each do |key, value|
+      next if key.to_s == 'page'
+
+      scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
+    end
+
+    scope
+  end
+
+  private
+
+  def set_defaults!
+    params['relationship'] = 'following' if params['relationship'].blank?
+    params['order']        = 'recent' if params['order'].blank?
+  end
+
+  def scope_for(key, value)
+    case key
+    when 'relationship'
+      relationship_scope(value)
+    when 'by_domain'
+      by_domain_scope(value)
+    when 'location'
+      location_scope(value)
+    when 'status'
+      status_scope(value)
+    when 'order'
+      order_scope(value)
+    when 'activity'
+      activity_scope(value)
+    else
+      raise "Unknown filter: #{key}"
+    end
+  end
+
+  def relationship_scope(value)
+    case value
+    when 'following'
+      account.following.eager_load(:account_stat).reorder(nil)
+    when 'followed_by'
+      account.followers.eager_load(:account_stat).reorder(nil)
+    when 'mutual'
+      account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following))
+    when 'invited'
+      Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil)
+    else
+      raise "Unknown relationship: #{value}"
+    end
+  end
+
+  def by_domain_scope(value)
+    Account.where(domain: value)
+  end
+
+  def location_scope(value)
+    case value
+    when 'local'
+      Account.local
+    when 'remote'
+      Account.remote
+    else
+      raise "Unknown location: #{value}"
+    end
+  end
+
+  def status_scope(value)
+    case value
+    when 'moved'
+      Account.where.not(moved_to_account_id: nil)
+    when 'primary'
+      Account.where(moved_to_account_id: nil)
+    else
+      raise "Unknown status: #{value}"
+    end
+  end
+
+  def order_scope(value)
+    case value
+    when 'active'
+      Account.by_recent_status
+    when 'recent'
+      params[:relationship] == 'invited' ? Account.recent : Follow.recent
+    else
+      raise "Unknown order: #{value}"
+    end
+  end
+
+  def activity_scope(value)
+    case value
+    when 'dormant'
+      AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
+    else
+      raise "Unknown activity: #{value}"
+    end
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 794c2091c..9d5114e74 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -248,7 +248,7 @@ class User < ApplicationRecord
                                  ip: request.remote_ip).session_id
   end
 
-  def exclusive_session(id)
+  def clear_other_sessions(id)
     session_activations.exclusive(id)
   end
 
diff --git a/app/policies/announcement_policy.rb b/app/policies/announcement_policy.rb
new file mode 100644
index 000000000..0a4e4575c
--- /dev/null
+++ b/app/policies/announcement_policy.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AnnouncementPolicy < ApplicationPolicy
+  def index?
+    staff?
+  end
+
+  def create?
+    admin?
+  end
+
+  def update?
+    admin?
+  end
+
+  def destroy?
+    admin?
+  end
+end
diff --git a/app/serializers/rest/announcement_serializer.rb b/app/serializers/rest/announcement_serializer.rb
new file mode 100644
index 000000000..924d87b34
--- /dev/null
+++ b/app/serializers/rest/announcement_serializer.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class REST::AnnouncementSerializer < ActiveModel::Serializer
+  attributes :id, :content, :starts_at, :ends_at, :all_day
+
+  has_many :mentions
+  has_many :tags, serializer: REST::StatusSerializer::TagSerializer
+  has_many :emojis, serializer: REST::CustomEmojiSerializer
+  has_many :reactions, serializer: REST::ReactionSerializer
+
+  def id
+    object.id.to_s
+  end
+
+  def content
+    Formatter.instance.linkify(object.text)
+  end
+
+  def reactions
+    object.reactions(current_user&.account)
+  end
+
+  class AccountSerializer < ActiveModel::Serializer
+    attributes :id, :username, :url, :acct
+
+    def id
+      object.id.to_s
+    end
+
+    def url
+      ActivityPub::TagManager.instance.url_for(object)
+    end
+  end
+end
diff --git a/app/serializers/rest/reaction_serializer.rb b/app/serializers/rest/reaction_serializer.rb
new file mode 100644
index 000000000..1a5dca018
--- /dev/null
+++ b/app/serializers/rest/reaction_serializer.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class REST::ReactionSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :name, :count
+
+  attribute :me, if: :current_user?
+  attribute :url, if: :custom_emoji?
+  attribute :static_url, if: :custom_emoji?
+
+  def count
+    object.respond_to?(:count) ? object.count : 0
+  end
+
+  def current_user?
+    !current_user.nil?
+  end
+
+  def custom_emoji?
+    object.custom_emoji.present?
+  end
+
+  def url
+    full_asset_url(object.custom_emoji.image.url)
+  end
+
+  def static_url
+    full_asset_url(object.custom_emoji.image.url(:static))
+  end
+end
diff --git a/app/validators/reaction_validator.rb b/app/validators/reaction_validator.rb
new file mode 100644
index 000000000..de0f2c94b
--- /dev/null
+++ b/app/validators/reaction_validator.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ReactionValidator < ActiveModel::Validator
+  SUPPORTED_EMOJIS = Oj.load(File.read(Rails.root.join('app', 'javascript', 'mastodon', 'features', 'emoji', 'emoji_map.json'))).keys.freeze
+
+  def validate(reaction)
+    return if reaction.name.blank? || reaction.custom_emoji_id.present?
+
+    reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) unless unicode_emoji?(reaction.name)
+  end
+
+  private
+
+  def unicode_emoji?(name)
+    SUPPORTED_EMOJIS.include?(name)
+  end
+end
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 9c26dbabc..c312fe2bd 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -2,8 +2,6 @@
   = "#{display_name(@account)} (@#{@account.local_username_and_domain})"
 
 - content_for :header_tags do
-  %meta{ name: 'description', content: account_description(@account) }/
-
   - if @account.user&.setting_noindex
     %meta{ name: 'robots', content: 'noindex, noarchive' }/
 
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 1429f56d5..a83f77134 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -41,7 +41,7 @@
       .dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size')
       .dashboard__counters__label= t 'admin.accounts.media_attachments'
   %div
-    = link_to admin_account_followers_path(@account.id) do
+    = link_to admin_account_relationships_path(@account.id, location: 'local', relationship: 'followed_by') do
       .dashboard__counters__num= number_with_delimiter @account.local_followers_count
       .dashboard__counters__label= t 'admin.accounts.followers'
   %div
diff --git a/app/views/admin/announcements/_announcement.html.haml b/app/views/admin/announcements/_announcement.html.haml
new file mode 100644
index 000000000..75768c7ba
--- /dev/null
+++ b/app/views/admin/announcements/_announcement.html.haml
@@ -0,0 +1,14 @@
+%tr
+  %td
+    = truncate(announcement.text)
+  %td
+    = time_range(announcement) if announcement.time_range?
+  %td
+    - if announcement.scheduled_at.present?
+      = fa_icon('clock-o') if announcement.scheduled_at > Time.now.utc
+      = l(announcement.scheduled_at)
+    - else
+      = l(announcement.created_at)
+  %td
+    = table_link_to 'pencil', t('generic.edit'), edit_admin_announcement_path(announcement) if can?(:update, announcement)
+    = table_link_to 'trash', t('generic.delete'), admin_announcement_path(announcement), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, announcement)
diff --git a/app/views/admin/announcements/edit.html.haml b/app/views/admin/announcements/edit.html.haml
new file mode 100644
index 000000000..c5c605e93
--- /dev/null
+++ b/app/views/admin/announcements/edit.html.haml
@@ -0,0 +1,22 @@
+- content_for :page_title do
+  = t('.title')
+
+= simple_form_for @announcement, url: admin_announcement_path(@announcement) do |f|
+  = render 'shared/error_messages', object: @announcement
+
+  .fields-group
+    = f.input :starts_at, include_blank: true, wrapper: :with_block_label
+    = f.input :ends_at, include_blank: true, wrapper: :with_block_label
+
+  .fields-group
+    = f.input :all_day, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :text, wrapper: :with_block_label
+
+  - if @announcement.scheduled_at.present? && !@announcement.published?
+    .fields-group
+      = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/announcements/index.html.haml b/app/views/admin/announcements/index.html.haml
new file mode 100644
index 000000000..634f586fb
--- /dev/null
+++ b/app/views/admin/announcements/index.html.haml
@@ -0,0 +1,30 @@
+- content_for :page_title do
+  = t('admin.announcements.title')
+
+- content_for :heading_actions do
+  = link_to t('admin.announcements.new.title'), new_admin_announcement_path, class: 'button'
+
+.filters
+  .filter-subset
+    %strong= t('admin.relays.status')
+    %ul
+      %li= filter_link_to t('generic.all'), published: nil, unpublished: nil
+      %li= filter_link_to safe_join([t('admin.announcements.live'), "(#{number_with_delimiter(Announcement.published.count)})"], ' '), published: '1', unpublished: nil
+
+- if @announcements.empty?
+  %div.muted-hint.center-text
+    = t 'admin.announcements.empty'
+- else
+  .table-wrapper
+    %table.table
+      %thead
+        %tr
+          %th= t('simple_form.labels.announcement.text')
+          %th= t('admin.announcements.time_range')
+          %th= t('admin.announcements.published')
+          %th
+      %tbody
+        = render partial: 'announcement', collection: @announcements
+
+= paginate @announcements
+
diff --git a/app/views/admin/announcements/new.html.haml b/app/views/admin/announcements/new.html.haml
new file mode 100644
index 000000000..a5298c5f6
--- /dev/null
+++ b/app/views/admin/announcements/new.html.haml
@@ -0,0 +1,21 @@
+- content_for :page_title do
+  = t('.title')
+
+= simple_form_for @announcement, url: admin_announcements_path do |f|
+  = render 'shared/error_messages', object: @announcement
+
+  .fields-group
+    = f.input :starts_at, include_blank: true, wrapper: :with_block_label
+    = f.input :ends_at, include_blank: true, wrapper: :with_block_label
+
+  .fields-group
+    = f.input :all_day, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :text, wrapper: :with_block_label
+
+  .fields-group
+    = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label
+
+  .actions
+    = f.button :button, t('.create'), type: :submit
diff --git a/app/views/admin/followers/index.html.haml b/app/views/admin/followers/index.html.haml
deleted file mode 100644
index 25f1f290f..000000000
--- a/app/views/admin/followers/index.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-- content_for :page_title do
-  = t('admin.followers.title', acct: @account.acct)
-
-.filters
-  .filter-subset
-    %strong= t('admin.accounts.location.title')
-    %ul
-      %li= link_to t('admin.accounts.location.local'), admin_account_followers_path(@account.id), class: 'selected'
-  .back-link{ style: 'flex: 1 1 auto; text-align: right' }
-    = link_to admin_account_path(@account.id) do
-      = fa_icon 'chevron-left fw'
-      = t('admin.followers.back_to_account')
-
-%hr.spacer/
-
-.table-wrapper
-  %table.table
-    %thead
-      %tr
-        %th= t('admin.accounts.username')
-        %th= t('admin.accounts.role')
-        %th= t('admin.accounts.most_recent_ip')
-        %th= t('admin.accounts.most_recent_activity')
-        %th
-    %tbody
-      = render partial: 'admin/accounts/account', collection: @followers
-
-= paginate @followers
diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml
new file mode 100644
index 000000000..3afaff615
--- /dev/null
+++ b/app/views/admin/relationships/index.html.haml
@@ -0,0 +1,39 @@
+- content_for :page_title do
+  = t('admin.relationships.title', acct: @account.acct)
+
+.filters
+  .filter-subset
+    %strong= t 'relationships.relationship'
+    %ul
+      %li= filter_link_to t('relationships.following'), relationship: nil
+      %li= filter_link_to t('relationships.followers'), relationship: 'followed_by'
+      %li= filter_link_to t('relationships.mutual'), relationship: 'mutual'
+      %li= filter_link_to t('relationships.invited'), relationship: 'invited'
+
+  .filter-subset
+    %strong= t('admin.accounts.location.title')
+    %ul
+      %li= filter_link_to t('admin.accounts.moderation.all'), location: nil
+      %li= filter_link_to t('admin.accounts.location.local'), location: 'local'
+      %li= filter_link_to t('admin.accounts.location.remote'), location: 'remote'
+
+  .back-link{ style: 'flex: 1 1 auto; text-align: right' }
+    = link_to admin_account_path(@account.id) do
+      = fa_icon 'chevron-left fw'
+      = t('admin.statuses.back_to_account')
+
+%hr.spacer/
+
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('admin.accounts.username')
+        %th= t('admin.accounts.role')
+        %th= t('admin.accounts.most_recent_ip')
+        %th= t('admin.accounts.most_recent_activity')
+        %th
+    %tbody
+      = render partial: 'admin/accounts/account', collection: @accounts
+
+= paginate @accounts
diff --git a/app/workers/publish_announcement_reaction_worker.rb b/app/workers/publish_announcement_reaction_worker.rb
new file mode 100644
index 000000000..6f3b6dc5b
--- /dev/null
+++ b/app/workers/publish_announcement_reaction_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class PublishAnnouncementReactionWorker
+  include Sidekiq::Worker
+  include Redisable
+
+  def perform(announcement_id, name)
+    announcement = Announcement.find(announcement_id)
+
+    reaction,  = announcement.announcement_reactions.where(name: name).group(:announcement_id, :name, :custom_emoji_id).select('name, custom_emoji_id, count(*) as count, false as me')
+    reaction ||= announcement.announcement_reactions.new(name: name)
+
+    payload = InlineRenderer.render(reaction, nil, :reaction).tap { |h| h[:announcement_id] = announcement_id }
+    payload = Oj.dump(event: :'announcement.reaction', payload: payload)
+
+    Account.joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).find_each do |account|
+      redis.publish("timeline:#{account.id}", payload) if redis.exists("subscribed:timeline:#{account.id}")
+    end
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
diff --git a/app/workers/publish_scheduled_announcement_worker.rb b/app/workers/publish_scheduled_announcement_worker.rb
new file mode 100644
index 000000000..4b2014e34
--- /dev/null
+++ b/app/workers/publish_scheduled_announcement_worker.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class PublishScheduledAnnouncementWorker
+  include Sidekiq::Worker
+  include Redisable
+
+  def perform(announcement_id)
+    announcement = Announcement.find(announcement_id)
+    announcement.update(published: true)
+
+    payload = InlineRenderer.render(announcement, nil, :announcement)
+    payload = Oj.dump(event: :announcement, payload: payload)
+
+    Account.joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).find_each do |account|
+      redis.publish("timeline:#{account.id}", payload) if redis.exists("subscribed:timeline:#{account.id}")
+    end
+  end
+end
diff --git a/app/workers/scheduler/scheduled_statuses_scheduler.rb b/app/workers/scheduler/scheduled_statuses_scheduler.rb
index 1772a246b..4262f1d01 100644
--- a/app/workers/scheduler/scheduled_statuses_scheduler.rb
+++ b/app/workers/scheduler/scheduled_statuses_scheduler.rb
@@ -6,14 +6,38 @@ class Scheduler::ScheduledStatusesScheduler
   sidekiq_options unique: :until_executed, retry: 0
 
   def perform
+    publish_scheduled_statuses!
+    publish_scheduled_announcements!
+    unpublish_expired_announcements!
+  end
+
+  private
+
+  def publish_scheduled_statuses!
     due_statuses.find_each do |scheduled_status|
       PublishScheduledStatusWorker.perform_at(scheduled_status.scheduled_at, scheduled_status.id)
     end
   end
 
-  private
-
   def due_statuses
     ScheduledStatus.where('scheduled_at <= ?', Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
   end
+
+  def publish_scheduled_announcements!
+    due_announcements.find_each do |announcement|
+      PublishScheduledAnnouncementWorker.perform_at(announcement.scheduled_at, announcement.id)
+    end
+  end
+
+  def due_announcements
+    Announcement.unpublished.where('scheduled_at IS NOT NULL AND scheduled_at <= ?', Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
+  end
+
+  def unpublish_expired_announcements!
+    expired_announcements.in_batches.update_all(published: false)
+  end
+
+  def expired_announcements
+    Announcement.published.where('ends_at IS NOT NULL AND ends_at <= ?', Time.now.utc)
+  end
 end
diff --git a/config/application.rb b/config/application.rb
index c1bc18a30..bf25fa0d9 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -7,7 +7,6 @@ require 'rails/all'
 Bundler.require(*Rails.groups)
 
 require_relative '../app/lib/exceptions'
-require_relative '../app/middleware/handle_bad_encoding_middleware'
 require_relative '../lib/paperclip/lazy_thumbnail'
 require_relative '../lib/paperclip/gif_transcoder'
 require_relative '../lib/paperclip/video_transcoder'
@@ -120,7 +119,6 @@ module Mastodon
 
     config.active_job.queue_adapter = :sidekiq
 
-    config.middleware.insert_before Rack::Runtime, HandleBadEncodingMiddleware
     config.middleware.use Rack::Attack
     config.middleware.use Rack::Deflater
 
diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb
index 964526819..3dc48ef08 100644
--- a/config/initializers/simple_form.rb
+++ b/config/initializers/simple_form.rb
@@ -98,7 +98,7 @@ SimpleForm.setup do |config|
     b.use :html5
     b.use :label
     b.use :hint, wrap_with: { tag: :span, class: :hint }
-    b.use :input
+    b.use :input, wrap_with: { tag: :div, class: :label_input }
     b.use :error, wrap_with: { tag: :span, class: :error }
   end
 
diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb
index 87815d458..f84f7c0cb 100644
--- a/config/initializers/twitter_regex.rb
+++ b/config/initializers/twitter_regex.rb
@@ -47,32 +47,39 @@ module Twitter
       #{REGEXEN[:validate_url_pct_encoded]}|
       #{REGEXEN[:validate_url_sub_delims]}
     )/iox
-    REGEXEN[:valid_xmpp_uri] = %r{
-      (                                                                                     #   $1 total match
-        (#{REGEXEN[:valid_url_preceding_chars]})                                            #   $2 Preceding character
-        (                                                                                   #   $3 URL
-          ((?:xmpp):)                                                                       #   $4 Protocol
-          (//#{REGEXEN[:validate_nodeid]}+@#{REGEXEN[:valid_domain]}/)?                     #   $5 Authority (optional)
-          (#{REGEXEN[:validate_nodeid]}+@)?                                                 #   $6 Username in path (optional)
-          (#{REGEXEN[:valid_domain]})                                                       #   $7 Domain in path
-          (/#{REGEXEN[:validate_resid]}+)?                                                  #   $8 Resource in path (optional)
-          (\?#{REGEXEN[:valid_url_query_chars]}*#{REGEXEN[:valid_url_query_ending_chars]})? #   $9 Query String
+    REGEXEN[:xmpp_uri] = %r{
+      (xmpp:)                                                                           # Protocol
+      (//#{REGEXEN[:validate_nodeid]}+@#{REGEXEN[:valid_domain]}/)?                     # Authority (optional)
+      (#{REGEXEN[:validate_nodeid]}+@)?                                                 # Username in path (optional)
+      (#{REGEXEN[:valid_domain]})                                                       # Domain in path
+      (/#{REGEXEN[:validate_resid]}+)?                                                  # Resource in path (optional)
+      (\?#{REGEXEN[:valid_url_query_chars]}*#{REGEXEN[:valid_url_query_ending_chars]})? # Query String
+    }iox
+    REGEXEN[:magnet_uri] = %r{
+      (magnet:)                                                                         # Protocol
+      (\?#{REGEXEN[:valid_url_query_chars]}*#{REGEXEN[:valid_url_query_ending_chars]})  # Query String
+    }iox
+    REGEXEN[:valid_extended_uri] = %r{
+      (                                                                                 #   $1 total match
+        (#{REGEXEN[:valid_url_preceding_chars]})                                        #   $2 Preceding character
+        (                                                                               #   $3 URL
+          (#{REGEXEN[:xmpp_uri]}) | (#{REGEXEN[:magnet_uri]})
         )
       )
     }iox
   end
 
   module Extractor
-    # Extracts a list of all XMPP URIs included in the Tweet <tt>text</tt> along
+    # Extracts a list of all XMPP and magnet URIs included in the Toot <tt>text</tt> along
     # with the indices. If the <tt>text</tt> is <tt>nil</tt> or contains no
-    # XMPP URIs an empty array will be returned.
+    # XMPP or magnet URIs an empty array will be returned.
     #
     # If a block is given then it will be called for each XMPP URI.
-    def extract_xmpp_uris_with_indices(text, options = {}) # :yields: uri, start, end
+    def extract_extra_uris_with_indices(text, options = {}) # :yields: uri, start, end
       return [] unless text && text.index(":")
       urls = []
 
-      text.to_s.scan(Twitter::Regex[:valid_xmpp_uri]) do
+      text.to_s.scan(Twitter::Regex[:valid_extended_uri]) do
         valid_uri_match_data = $~
 
         start_position = valid_uri_match_data.char_begin(3)
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index 1519be44b..7309ad3db 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -86,6 +86,7 @@ ar:
     roles:
       admin: المدير
       bot: روبوت
+      group: فريق
       moderator: مُشرِف
     unavailable: الصفحة التعريفية غير متوفرة
     unfollow: إلغاء المتابعة
@@ -262,6 +263,7 @@ ar:
       shortcode_hint: على الأقل حرفين، و فقط رموز أبجدية عددية و أسطر سفلية
       title: الإيموجي الخاصة
       uncategorized: غير مصنّف
+      unlist: تنحية مِن القائمة
       unlisted: غير مدرج
       update_failed_msg: تعذرت عملية تحديث ذاك الإيموجي
       updated_msg: تم تحديث الإيموجي بنجاح!
@@ -347,9 +349,6 @@ ar:
         create: إضافة نطاق
         title: إضافة نطاق بريد جديد إلى اللائحة السوداء
       title: القائمة السوداء للبريد الإلكتروني
-    followers:
-      back_to_account: العودة إلى الحساب
-      title: "%{acct} مُتابِعون"
     instances:
       by_domain: النطاق
       delivery_available: التسليم متوفر
@@ -612,6 +611,7 @@ ar:
     set_new_password: إدخال كلمة مرور جديدة
     setup:
       email_below_hint_html: إذا كان عنوان البريد الإلكتروني التالي غير صحيح، فيمكنك تغييره هنا واستلام بريد إلكتروني جديد للتأكيد.
+      email_settings_hint_html: لقد تم إرسال رسالة بريد إلكترونية للتأكيد إلى %{email}. إن كان عنوان البريد الإلكتروني غير صحيح ، يمكنك تغييره في إعدادات حسابك.
       title: الضبط
     status:
       account_status: حالة الحساب
@@ -659,6 +659,7 @@ ar:
       before: 'يرجى قراءة هذه الملاحظات بتأنّي قبل المواصلة:'
       data_removal: سوف تُحذَف منشوراتك والبيانات الأخرى نهائيا
       email_change_html: بإمكانك <a href="%{path}">تغيير عنوان بريدك الإلكتروني</a> دون أن يُحذف حسابك
+      email_contact_html: إن لم تتلقّ أي شيء ، يمكنك مراسلة <a href="mailto:%{email}">%{email}</a> لطلب المساعدة
       irreversible: لن يكون بإمكانك استرجاع أو إعادة تنشيط حسابك
       more_details_html: للمزيد مِن التفاصيل ، يرجى الإطلاع على <a href="%{terms_path}">سياسة الخصوصية</a>.
       username_available: سيصبح اسم مستخدمك متوفرا ثانية
@@ -716,6 +717,7 @@ ar:
       invalid_irreversible: إلّا مجالات الإشعارات و الخيط الرئيسي معنية بالتصفية اللارجعية
     index:
       delete: إزالة
+      empty: ليست لديك أية عوامل تصفية.
       title: عوامل التصفية
     new:
       title: إضافة عامل تصفية جديد
@@ -815,6 +817,7 @@ ar:
     set_redirect: تعين إعادة التوجيه
     warning:
       before: 'يرجى قراءة هذه الملاحظات بتأنّي قبل المواصلة:'
+      followers: تقوم هذه العملية بنقل كافة المُتابِعين مِن الحساب الحالي إلى الحساب الجديد
       other_data: لن يتم نقل أية بيانات أخرى تلقائيا
   moderation:
     title: الإشراف
@@ -887,6 +890,7 @@ ar:
       duration_too_long: بعيد جدا في المستقبَل
       duration_too_short: مبكّر جدا
       expired: لقد انتهى استطلاع الرأي
+      invalid_choice: خيار التصويت الذي قُمتَ يتحديده غير موجود
       too_many_options: لا يمكنه أن يحتوي أكثر مِن %{max} عناصر
   preferences:
     other: إعدادات أخرى
diff --git a/config/locales/ast.yml b/config/locales/ast.yml
index 6c757558e..d64f6b005 100644
--- a/config/locales/ast.yml
+++ b/config/locales/ast.yml
@@ -305,6 +305,7 @@ ast:
     errors:
       already_voted: Yá votesti nesta encuesta
       expired: La encuesta yá finó
+      invalid_choice: El la opción de votu escoyida nun esiste
   preferences:
     public_timelines: Llinies temporales públiques
   relationships:
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index e25172d6b..ccbd16e2a 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -9,18 +9,18 @@ ca:
     administered_by: 'Administrat per:'
     api: API
     apps: Apps mòbils
-    apps_platforms: Utilitza Mastodont des de iOS, Android i altres plataformes
+    apps_platforms: Utilitza Mastodon des de iOS, Android i altres plataformes
     browse_directory: Navega per el directori de perfils i filtra segons interessos
-    browse_local_posts: Navega un flux en directe de publicacions d’aquest servidor
-    browse_public_posts: Navega per una transmissió en directe de publicacions públiques a Mastodont
+    browse_local_posts: Navega un flux en directe de publicacions públiques d’aquest servidor
+    browse_public_posts: Navega per una transmissió en directe de publicacions públiques a Mastodon
     contact: Contacte
     contact_missing: No configurat
     contact_unavailable: N/D
     discover_users: Descobreix usuaris
     documentation: Documentació
-    federation_hint_html: Amb un compte de %{instance} podràs seguir persones de qualsevol servidor Mastodont i altres.
+    federation_hint_html: Amb un compte de %{instance} podràs seguir persones de qualsevol servidor Mastodon i altres.
     get_apps: Prova una aplicació mòbil
-    hosted_on: Mastodont allotjat a %{domain}
+    hosted_on: Mastodon allotjat a %{domain}
     instance_actor_flash: |
       Aquest compte és un actor virtual utilitzat per a representar al propi servidor i no cap usuari individual.
       S'utilitza per a propòsits de federació i no ha de ser bloquejat si no voleu bloquejar tota la instància, en aquest cas hauríeu d'utilitzar un bloqueig de domini.
@@ -31,7 +31,7 @@ ca:
     source_code: Codi font
     status_count_after:
       one: estat
-      other: estats
+      other: tuts
     status_count_before: Que han escrit
     tagline: Segueix els teus amics i descobreix-ne de nous
     terms: Termes del servei
@@ -40,14 +40,14 @@ ca:
       domain: Servidor
       reason: Raó
       rejecting_media: 'Els arxius multimèdia d''aquests servidors no seran processats o emmagatzemats i cap miniatura serà mostrada, requerint clic manual a través de l''arxiu original:'
-      silenced: 'Les publicacions d''aquests servidors seran amagades en les cronologíes públiques i converses, i cap notificació serà generada de les interaccions dels seus usuaris, llevat que estiguis seguint-los:'
+      silenced: 'Les publicacions d''aquests servidors seran amagades en les línies de temps públiques i en les converses, i cap notificació serà generada de les interaccions dels seus usuaris, llevat que estiguis seguint-los:'
       suspended: 'Cap dada d''aquests servidors serà processada, emmagatzemada o intercanviada, fent impossible qualsevol interacció o comunicació amb els usuaris d''aquests servidors:'
-    unavailable_content_html: Mastodont generalment et permet per veure contingut i interaccionar amb usuaris de qualsevol altre servidor en el fedivers. Aquestes són les excepcions que s'han fet en aquest servidor particular.
+    unavailable_content_html: Mastodon generalment et permet veure el contingut i interaccionar amb els usuaris de qualsevol altre servidor en el fedivers. Aquestes són les excepcions que s'han fet en aquest servidor particular.
     user_count_after:
       one: usuari
       other: usuaris
     user_count_before: Tenim
-    what_is_mastodon: Què és Mastodont?
+    what_is_mastodon: Què és Mastodon?
   accounts:
     choices_html: 'Eleccions de %{name}:'
     endorsements_hint: Pots recomanar persones que segueixes a l'interfície de web, que apareixeran aquí.
@@ -70,7 +70,7 @@ ca:
     pin_errors:
       following: Has d'estar seguint la persona que vulguis avalar
     posts:
-      one: Barrita
+      one: Tut
       other: Tuts
     posts_tab_heading: Tuts
     posts_with_replies: Tuts i respostes
@@ -144,7 +144,7 @@ ca:
       moderation_notes: Notes de moderació
       most_recent_activity: Activitat més recent
       most_recent_ip: IP més recent
-      no_account_selected: No s'han canviat els comptes perque no s'han seleccionat
+      no_account_selected: No s'han canviat els comptes perquè no s'han seleccionat
       no_limits_imposed: Sense límits imposats
       not_subscribed: No subscrit
       pending: Revisió pendent
@@ -179,7 +179,7 @@ ca:
         targeted_reports: Informes realitzats per altres
       silence: Silenci
       silenced: Silenciat
-      statuses: Estats
+      statuses: Tuts
       subscribe: Subscriu
       suspended: Suspès
       time_in_queue: Esperant en la cua %{time}
@@ -226,7 +226,7 @@ ca:
         unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}"
         update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}"
         update_status: "%{name} estat actualitzat per %{target}"
-      deleted_status: "(estat esborrat)"
+      deleted_status: "(tut esborrat)"
       title: Registre d'auditoria
     custom_emojis:
       assign_category: Assigna una categoria
@@ -270,7 +270,7 @@ ca:
       feature_registrations: Registres
       feature_relay: Relay de la Federació
       feature_spam_check: Anti-spam
-      feature_timeline_preview: Vista previa de la cronología
+      feature_timeline_preview: Vista prèvia de línia de temps
       features: Característiques
       hidden_service: Federació amb serveis ocults
       open_reports: informes oberts
@@ -280,7 +280,7 @@ ca:
       search: Cerca de text complet
       single_user_mode: Mode d'usuari únic
       software: Programari
-      space: Ús d’espai
+      space: Ús de l’espai
       title: Panell
       total_users: usuaris en total
       trends: Tendències
@@ -299,7 +299,7 @@ ca:
       destroyed_msg: El bloqueig de domini s'ha desfet
       domain: Domini
       edit: Editar el bloqueig del domini
-      existing_domain_block_html: Ja has imposat uns limits més estrictes a %{name}, l'hauries de <a href="%{unblock_url}">desbloquejar-lo</a> primer.
+      existing_domain_block_html: Ja has imposat uns límits més estrictes a %{name}, l'hauries de <a href="%{unblock_url}">desbloquejar-lo</a> primer.
       new:
         create: Crea un bloqueig
         hint: El bloqueig de domini no impedirà la creació de nous comptes en la base de dades, però s'aplicaran de manera retroactiva mètodes de moderació específics sobre aquests comptes.
@@ -344,9 +344,6 @@ ca:
         create: Afegeix un domini
         title: Nova adreça de correu en la llista negra
       title: Llista negra de correus electrònics
-    followers:
-      back_to_account: Torna al compte
-      title: Seguidors de %{acct}
     instances:
       by_domain: Domini
       delivery_available: El lliurament està disponible
@@ -375,14 +372,16 @@ ca:
       title: Convida
     pending_accounts:
       title: Comptes pendents (%{count})
+    relationships:
+      title: relacions del %{acct}
     relays:
       add_new: Afegiu un nou relay
       delete: Esborra
-      description_html: Un <strong>relay de federació</strong> és un servidor intermediari que intercanvia grans volums de barritades públiquess entre servidors que es subscriuen i publiquen en ell. <strong>Pot ajudar a servidors petits i mitjans a descobrir contingut del fedivers</strong>, no fent necessari que els usuaris locals manualment segueixin altres persones de servidors remots.
+      description_html: Un <strong>relay de federació</strong> és un servidor intermediari que intercanvia grans volums de tuts públics entre servidors que es subscriuen i publiquen en ell. <strong>Pot ajudar a servidors petits i mitjans a descobrir contingut del fedivers</strong>, no fent necessari que els usuaris locals manualment segueixin altres persones de servidors remots.
       disable: Inhabilita
       disabled: Desactivat
       enable: Activat
-      enable_hint: Una vegada habilitat el teu servidor es subscriurà a totes les barritades públiques d'aquesta ristra i començarà a enviar-hi totes les barritades públiques d'aquest servidor.
+      enable_hint: Una vegada habilitat, el teu servidor es subscriurà a tots els tuts públics d'aquest relay i començarà a enviar-hi tots els tuts públics d'aquest servidor.
       enabled: Activat
       inbox_url: URL del Relay
       pending: S'està esperant l'aprovació del relay
@@ -431,7 +430,7 @@ ca:
       updated_at: Actualitzat
     settings:
       activity_api_enabled:
-        desc_html: Nombre d'estats publicats localment, usuaris actius i registres nous en períodes setmanals
+        desc_html: Nombre de tuts publicats localment, usuaris actius i registres nous en períodes setmanals
         title: Publica estadístiques agregades sobre l'activitat de l'usuari
       bootstrap_timeline_accounts:
         desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desblocats. El valor predeterminat quan està buit és tots els administradors locals.
@@ -486,8 +485,8 @@ ca:
           open: Qualsevol pot registrar-se
         title: Mode de registres
       show_known_fediverse_at_about_page:
-        desc_html: Quan s'activa, mostrarà tots els brams del fedivers conegut en vista prèvia. En cas contrari, només es mostraran els locals
-        title: Mostra el fedivers conegut en vista prèvia de la cronología
+        desc_html: Quan està desactivat, restringeix la línia de temps pública enllaçada des de la pàgina inicial a mostrar només contingut local
+        title: Inclou el contingut federat a la pàgina no autenticada de la línia de temps pública
       show_staff_badge:
         desc_html: Mostra una insígnia de personal en la pàgina d'usuari
         title: Mostra insígnia de personal
@@ -498,21 +497,21 @@ ca:
         desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen el teu servidor. Pots utilitzar etiquetes HTML
         title: Descripció ampliada del lloc
       site_short_description:
-        desc_html: Es mostra a la barra lateral i a metaetiquetes. Descriu en un únic paràgraf què és Mastodont i què fa que aquest servidor sigui especial. Si està buit, s'estableix per defecte la descripció del servidor.
+        desc_html: Es mostra a la barra lateral i a metaetiquetes. Descriu en un únic paràgraf què és Mastodon i què fa que aquest servidor sigui especial.
         title: Descripció curta del servidor
       site_terms:
         desc_html: Pots escriure la teva pròpia política de privadesa, els termes del servei o d'altres normes legals. Pots utilitzar etiquetes HTML
         title: Termes del servei personalitzats
       site_title: Nom del servidor
       spam_check_enabled:
-        desc_html: Mastodont pot auto-silenciar i informar automàticament de comptes basat en mesures com ara la detecció de comptes que envien missatges repetits no sol·licitats. Pot haver-hi falsos positius.
+        desc_html: Mastodon pot informar automàticament de comptes que envien repetits missatges no sol·licitats. Pot haver-hi falsos positius.
         title: Anti-spam
       thumbnail:
         desc_html: S'utilitza per obtenir visualitzacions prèvies a través d'OpenGraph i API. Es recomana 1200x630px
         title: Miniatura del servidor
       timeline_preview:
-        desc_html: Mostra la cronología pública a la pàgina inicial
-        title: Vista prèvia de la cronología
+        desc_html: Mostra l'enllaç a la línia de temps pública a la pàgina inicial i permet l'accés a l'API a la línia de temps pública sense autenticació
+        title: Permet l'accés no autenticat a la línia de temps pública
       title: Configuració del lloc
       trendable_by_default:
         desc_html: Afecta a les etiquetes que no s'havien rebutjat prèviament
@@ -578,7 +577,7 @@ ca:
     remove: Desvincula l'àlies
   appearance:
     advanced_web_interface: Interfície web avançada
-    advanced_web_interface_hint: 'Si vols fer ús de tota l''amplada de la teva pantalla, l''interfície web avançada et permet configurar diverses columnes per a veure molta més informació al mateix temps: Inici, notificacions, cronología federada i qualsevol número de llistes i etiquetes.'
+    advanced_web_interface_hint: 'Si vols fer ús de tota l''amplada de la teva pantalla, l''interfície web avançada et permet configurar diverses columnes per a veure molta més informació al mateix temps: Inici, notificacions, línia de temps federada i qualsevol número de llistes i etiquetes.'
     animations_and_accessibility: Animacions i accessibilitat
     confirmation_dialogs: Diàlegs de confirmació
     discovery: Descobriment
@@ -611,9 +610,9 @@ ca:
     delete_account: Suprimeix el compte
     delete_account_html: Si vols suprimir el compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
     description:
-      prefix_invited_by_user: "@%{name} t'ha invitat a unir-te a aquest servidor de Mastodont!"
-      prefix_sign_up: Registra't avui a Mastodont!
-      suffix: Amb un compte seràs capaç de seguir persones, publicar i intercanviar missatges amb usuaris de qualsevol servidor de Mastodont i més!
+      prefix_invited_by_user: "@%{name} t'ha invitat a unir-te a aquest servidor de Mastodon!"
+      prefix_sign_up: Registra't avui a Mastodon!
+      suffix: Amb un compte seràs capaç de seguir persones, publicar i intercanviar missatges amb usuaris de qualsevol servidor de Mastodon i més!
     didnt_get_confirmation: No has rebut el correu de confirmació?
     forgot_password: Has oblidat la contrasenya?
     invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o ha caducat. Torna-ho a provar.
@@ -709,7 +708,7 @@ ca:
       content: Ho sentim, però alguna cosa ha fallat a la nostra banda.
       title: Aquesta pàgina no es correcta
     '503': La pàgina no podria ser servida a causa d'un error temporal del servidor.
-    noscript_html: Per a utilitzar Mastodont, activa el JavaScript. També pots provar una de les <a href="%{apps_path}"> aplicacions natives</a> de Mastodont per a la vostra plataforma.
+    noscript_html: Per a utilitzar Mastodon, activa el JavaScript. També pots provar una de les <a href="%{apps_path}"> aplicacions natives</a> de Mastodon per a la teva plataforma.
   existing_username_validator:
     not_found: no s'ha pogut trobar cap usuari local amb aquest nom d'usuari
     not_found_multiple: no s'ha pogut trobar %{usernames}
@@ -717,7 +716,7 @@ ca:
     archive_takeout:
       date: Data
       download: Baixa l’arxiu
-      hint_html: Pots sol·licitar un arxiu de les teves <strong>barritades i dels fitxers multimèdia pujats</strong>. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible. Pots sol·licitar un arxiu cada 7 dies.
+      hint_html: Pots sol·licitar un arxiu dels teus <strong>tuts i dels fitxers multimèdia pujats</strong>. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible. Pots sol·licitar un arxiu cada 7 dies.
       in_progress: S'està compilant el teu arxiu...
       request: Sol·licita el teu arxiu
       size: Mida
@@ -731,12 +730,13 @@ ca:
     add_new: Afegir nova
     errors:
       limit: Ja has mostrat la quantitat màxima d'etiquetes
-    hint_html: "<strong>Què son les etiquetes destacades?</strong> Es mostren de manera destacada en el teu perfil públic i permeten a les persones navegar per les teves publicacions amb aquestes etiquetes. Són una gran eina per fer un seguiment de treballs creatius o de projectes a llarg termini."
+    hint_html: "<strong>Què son les etiquetes destacades?</strong> Es mostren de manera destacada en el teu perfil públic i permeten a les persones navegar per les teves publicacions gràcies a aquestes etiquetes. Són una gran eina per fer un seguiment de treballs creatius o de projectes a llarg termini."
   filters:
     contexts:
-      home: Cronología d'inici
+      account: Perfils
+      home: Línia de temps Inici
       notifications: Notificacions
-      public: Cronologíes públiques
+      public: Línies de temps públiques
       thread: Converses
     edit:
       title: Editar filtre
@@ -748,7 +748,7 @@ ca:
       empty: No hi tens cap filtre.
       title: Filtres
     new:
-      title: Afegeix un filtre
+      title: Afegeix un nou filtre
   footer:
     developers: Desenvolupadors
     more: Més…
@@ -776,11 +776,11 @@ ca:
         invalid_token: Els tokens de Keybase són hashs de signatures i han de tenir 66 caràcters hexadecimals
         verification_failed: Keybase no reconeix aquest token com a signatura del usuari de Keybase %{kb_username}. Si us plau prova des de Keybase.
       wrong_user: No es pot crear una prova per a %{proving} mentre es connectava com a %{current}. Inicia sessió com a %{proving} i prova de nou.
-    explanation_html: Aquí pots connectar criptogràficament les teves altres identitats com ara el teu perfil de Keybase. Això permet que altres persones t'envïin missatges xifrats i confiar en el contingut que els hi envies.
+    explanation_html: Aquí pots connectar criptogràficament les teves altres identitats com ara el teu perfil de Keybase. Això permet que altres persones t'enviïn missatges xifrats i confiar en el contingut que els hi envies.
     i_am_html: Sóc %{username} a %{service}.
     identity: Identitat
     inactive: Inactiu
-    publicize_checkbox: 'I barrita això:'
+    publicize_checkbox: 'I envia un tut d''això:'
     publicize_toot: 'Està provat! Sóc %{username} a %{service}: %{url}'
     status: Estat de verificació
     view_proof: Veure la prova
@@ -829,7 +829,7 @@ ca:
       images_and_video: No es pot adjuntar un vídeo a una publicació que ja contingui imatges
       too_many: No es poden adjuntar més de 4 fitxers
   migrations:
-    acct: usuari@domini del nou compte
+    acct: Mogut a
     cancel: Cancel·la redirecció
     cancel_explanation: Cancel·lant la redirecció reactivará el teu compte actual però no recuperarà els seguidors que han estat moguts a aquell compte.
     cancelled_msg: Redirecció cancel·lada amb èxit.
@@ -838,7 +838,7 @@ ca:
       missing_also_known_as: no fa referencia a aquest compte
       move_to_self: no pot ser el compte actual
       not_found: podria no ser trobat
-      on_cooldown: Estàs en temps de recuperació
+      on_cooldown: Estàs en temps de refredament
     followers_count: Seguidors en el moment del moviment
     incoming_migrations: Movent des d'un compte diferent
     incoming_migrations_html: Per a moure't des d'un altre compte a aquest, primer necessites <a href="%{path}">crear un àlies de compte</a>.
@@ -863,7 +863,7 @@ ca:
   notification_mailer:
     digest:
       action: Mostra totes les notificacions
-      body: Un resum del que et vas perdre desde la darrera visita el %{since}
+      body: Un resum del que et vas perdre des de la darrera visita el %{since}
       mention: "%{name} t'ha mencionat en:"
       new_followers_summary:
         one: A més, has adquirit un nou seguidor durant la teva absència! Visca!
@@ -928,12 +928,13 @@ ca:
   preferences:
     other: Altre
     posting_defaults: Valors predeterminats de publicació
-    public_timelines: Cronologíes públiques
+    public_timelines: Línies de temps públiques
   relationships:
     activity: Activitat del compte
     dormant: Inactiu
     followers: Seguidors
     following: Seguint
+    invited: Convidat
     last_active: Darrer actiu
     most_recent: Més recent
     moved: Mogut
@@ -946,7 +947,7 @@ ca:
     status: Estat del compte
   remote_follow:
     acct: Escriu el teu usuari@domini des del qual vols seguir
-    missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per al compte
+    missing_resource: No s'ha pogut trobar la URL de redirecció necessària per al compte
     no_account_html: No tens cap compte? Pots <a href='%{sign_up_path}' target='_blank'>registrar-te aquí</a>
     proceed: Comença a seguir
     prompt: 'Seguiràs a:'
@@ -954,16 +955,16 @@ ca:
   remote_interaction:
     favourite:
       proceed: Procedir a afavorir
-      prompt: 'Vols marcar com a favorit aquesta barritada:'
+      prompt: 'Vols marcar com a favorit aquest tut:'
     reblog:
       proceed: Procedir a impulsar
-      prompt: 'Vols impulsar aquesta barritada:'
+      prompt: 'Vols impulsar aquest tut:'
     reply:
       proceed: Procedir a respondre
-      prompt: 'Vols respondre a aquesta barritada:'
+      prompt: 'Vols respondre a aquest tut:'
   scheduled_statuses:
-    over_daily_limit: Has superat el límit de %{limit} barritades programades per a aquell dia
-    over_total_limit: Has superat el limit de %{limit} barritades programades
+    over_daily_limit: Has superat el límit de %{limit} tuts programats per a aquell dia
+    over_total_limit: Has superat el limit de %{limit} tuts programats
     too_soon: La data programada ha de ser futura
   sessions:
     activity: Última activitat
@@ -988,7 +989,7 @@ ca:
       weibo: Weibo
     current_session: Sessió actual
     description: "%{browser} de %{platform}"
-    explanation: Aquests són els navegadors web que actualment han iniciat la sessió amb el teu compte de Mastodont.
+    explanation: Aquests són els navegadors web que actualment han iniciat la sessió amb el teu compte de Mastodon.
     ip: IP
     platforms:
       adobe_air: Adobe Air
@@ -1012,7 +1013,7 @@ ca:
     aliases: Àlies de compte
     appearance: Aparença
     authorized_apps: Aplicacions autoritzades
-    back: Torna a Mastodont
+    back: Torna a Mastodon
     delete: Eliminació del compte
     development: Desenvolupament
     edit_profile: Edita el perfil
@@ -1047,9 +1048,9 @@ ca:
     open_in_web: Obre en la web
     over_character_limit: Límit de caràcters de %{max} superat
     pin_errors:
-      limit: Ja has fixat el màxim nombre de barritades
-      ownership: No es pot fixar la barritada d'altra persona
-      private: No es pot fixar la barritada no pública
+      limit: Ja has fixat el màxim nombre de tuts
+      ownership: No es pot fixar el tut d'algú altre
+      private: No es pot fixar un tut no públic
       reblog: No es pot fixar un impuls
     poll:
       total_people:
@@ -1068,7 +1069,7 @@ ca:
       public: Públic
       public_long: Tothom pot veure-ho
       unlisted: No llistat
-      unlisted_long: Tothom ho pot veure, però no es mostra en la cronología federada
+      unlisted_long: Tothom ho pot veure, però no es mostra en les línies de temps públiques
   stream_entries:
     pinned: Tut fixat
     reblogged: ha impulsat
@@ -1175,23 +1176,23 @@ ca:
     enabled_success: Autenticació de dos factors activada correctament
     generate_recovery_codes: Genera codis de recuperació
     instructions_html: "<strong>Escaneja aquest codi QR desde Google Authenticator o una aplicació similar del teu telèfon</strong>. Desde ara, aquesta aplicació generarà tokens que tens que ingresar quan volguis iniciar sessió."
-    lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els codis de recuperació els pots tornar a generar aquí. Els codis de recuperació anteriors s'anul·laran.
-    manual_instructions: 'Si no pots escanejar el codi QR code i necessites introduir-lo manualment, aquí tens el secret en text pla:'
+    lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els codis de recuperació els pots tornar a generar aquí. S'anul·laran els codis de recuperació anteriors.
+    manual_instructions: 'Si no pots escanejar el codi QR i necessites introduir-lo manualment, aquí tens el secret en text pla:'
     recovery_codes: Codis de recuperació de còpia de seguretat
     recovery_codes_regenerated: Codis de recuperació regenerats amb èxit
-    recovery_instructions_html: Si mai perds l'accéss al teu telèfon pots utilitzar un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. <strong>Cal mantenir els codis de recuperació en lloc segur</strong>. Per exemple, imprimint-los i guardar-los amb altres documents importants.
+    recovery_instructions_html: Si mai perds l'accés al teu telèfon pots utilitzar un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. <strong>Cal mantenir els codis de recuperació en lloc segur</strong>. Per exemple, imprimint-los i guardar-los amb altres documents importants.
     setup: Establir
     wrong_code: El codi introduït no és vàlid! És correcta l'hora del servidor i del dispositiu?
   user_mailer:
     backup_ready:
-      explanation: Has sol·licitat una copia completa del teu compte Mastodont. Ara ja està a punt per descàrrega!
+      explanation: Has sol·licitat una copia completa del teu compte Mastodon. Ara ja està a punt per a descàrrega!
       subject: El teu arxiu està preparat per a descàrrega
       title: Recollida del arxiu
     warning:
       explanation:
         disable: Mentre el teu compte estigui congelat les dades romandran intactes però no pots dur a terme cap acció fins que no estigui desbloquejat.
         silence: Mentre el teu compte estigui limitat només les persones que ja et segueixen veuen les teves dades en aquest servidor i pots ser exclòs de diverses llistes públiques. No obstant això, d'altres encara poden seguir-te manualment.
-        suspend: El teu compte s'ha suspès i totes les teves barritades i fitxers multimèdia penjats s'han eliminat irreversiblement d'aquest servidor i dels servidors on tenies seguidores.
+        suspend: El teu compte s'ha suspès i tots els teus tuts i fitxers multimèdia penjats s'han eliminat de manera irreversible d'aquest servidor i dels servidors on tenies seguidors.
       get_in_touch: Pots respondre a aquest correu electrònic per a contactar amb el personal de %{instance}.
       review_server_policies: Revisa les polítiques del servidor
       statuses: 'Concretament, per:'
@@ -1215,11 +1216,11 @@ ca:
       full_handle_hint: Això és el que has de dir als teus amics perquè puguin enviar-te missatges o seguir-te des d'un altre servidor.
       review_preferences_action: Canviar preferències
       review_preferences_step: Assegura't d'establir les teves preferències, com ara els correus electrònics que vols rebre o el nivell de privadesa per defecte que t'agradaria que tinguin les teves entrades. Si no tens malaltia de moviment, pots optar per habilitar la reproducció automàtica de GIF.
-      subject: Et donem la benvinguda a Mastodont
-      tip_federated_timeline: La cronología federada és el cabal principal de la xarxa Mastodont. Però només inclou les persones a les quals els teus veïns estan subscrits, de manera que no està complet.
+      subject: Et donem la benvinguda a Mastodon
+      tip_federated_timeline: La línia de temps federada és el cabal principal de la xarxa Mastodon. Però només inclou les persones a les quals els teus veïns estan subscrits, de manera que no està completa.
       tip_following: Per defecte segueixes als administradors del servidor. Per trobar més persones interessants, consulta les línies de temps local i federada.
       tip_local_timeline: La línia de temps local és la vista del flux de publicacions dels usuaris de %{instance}. Aquests usuaris són els teus veïns més propers!
-      tip_mobile_webapp: Si el teu navegador del mòbil t'ofereix afegir Mastodont a la teva pantalla d'inici, podràs rebre notificacions d'empènyer "push". Es comporta com una aplicació nativa en molts aspectes!
+      tip_mobile_webapp: Si el teu navegador del mòbil t'ofereix afegir Mastodon a la teva pantalla d'inici, podràs rebre notificacions "push". Es comporta com una aplicació nativa en molts aspectes!
       tips: Consells
       title: Benvingut a bord, %{name}!
   users:
@@ -1230,5 +1231,5 @@ ca:
     seamless_external_login: Has iniciat sessió via un servei extern per tant els ajustos de contrasenya i correu electrònic no estan disponibles.
     signed_in_as: 'Sessió iniciada com a:'
   verification:
-    explanation_html: 'Pots <strong>verificar-te com a propietari dels enllaços a les metadades del teu perfil</strong>. Per això, el lloc web enllaçat ha de contenir un enllaç al teu perfil de Mastodont. El vincle <strong>ha de</strong> tenir l''atribut <code>rel="me"</code>. El contingut del text de l''enllaç no importa. Aquí tens un exemple:'
+    explanation_html: 'Pots <strong>verificar-te com a propietari dels enllaços a les metadades del teu perfil</strong>. Per això, el lloc web enllaçat ha de contenir un enllaç al teu perfil de Mastodon. El vincle <strong>ha de</strong> tenir l''atribut <code>rel="me"</code>. El contingut del text de l''enllaç no importa. Aquí tens un exemple:'
     verification: Verificació
diff --git a/config/locales/co.yml b/config/locales/co.yml
index 4cc1ed5fb..b21fc8d15 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -344,9 +344,6 @@ co:
         create: Creà un blucchime
         title: Nova iscrizzione nant’a lista nera e-mail
       title: Lista nera e-mail
-    followers:
-      back_to_account: Rivene à u Contu
-      title: Abbunati à %{acct}
     instances:
       by_domain: Duminiu
       delivery_available: Rimessa dispunibule
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index a9b101758..547468e50 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -354,9 +354,6 @@ cs:
         create: Přidat doménu
         title: Nová položka pro černou listinu e-mailů
       title: Černá listina e-mailů
-    followers:
-      back_to_account: Zpět na účet
-      title: Sledující uživatele %{acct}
     instances:
       by_domain: Doména
       delivery_available: Doručení je k dispozici
diff --git a/config/locales/cy.yml b/config/locales/cy.yml
index 5e63f1702..ee8807d20 100644
--- a/config/locales/cy.yml
+++ b/config/locales/cy.yml
@@ -359,13 +359,11 @@ cy:
       delete: Dileu
       destroyed_msg: Llwyddwyd i ddileu parth e-bost o'r gosbrestr
       domain: Parth
+      empty: Dim parthiau ebost ar y rhestr rhwystro.
       new:
         create: Ychwanegu parth
         title: Cofnod newydd yng nghosbrestr e-byst
       title: Cosbrestr e-bost
-    followers:
-      back_to_account: Nôl i'r gyfrif
-      title: Dilynwyr %{acct}
     instances:
       by_domain: Parth
       delivery_available: Mae'r cyflenwad ar gael
@@ -776,6 +774,7 @@ cy:
       invalid_irreversible: Mae hidlo anadferadwy ond yn gweithio yng nghyd-destun cartref neu hysbysiadau
     index:
       delete: Dileu
+      empty: Nid oes gennych chi hidlyddion.
       title: Hidlyddion
     new:
       title: Ychwanegu hidlydd newydd
@@ -967,6 +966,7 @@ cy:
       duration_too_long: yn rhy bell yn y dyfodol
       duration_too_short: yn rhy fuan
       expired: Mae'r pleidlais wedi gorffen yn barod
+      invalid_choice: Nid yw'r dewis pleidlais hyn yn bodoli
       over_character_limit: ni all fod yn hirach na %{max} cymeriad yr un
       too_few_options: rhaid cael fwy nag un eitem
       too_many_options: ni all cynnwys fwy na %{max} o eitemau
diff --git a/config/locales/da.yml b/config/locales/da.yml
index e20496918..1cdf7722e 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -335,9 +335,6 @@ da:
         create: Tilføj domæne
         title: Ny email blokade opslag
       title: Email sortliste
-    followers:
-      back_to_account: Tilbage til konto
-      title: "%{acct}'s følgere"
     instances:
       by_domain: Domæne
       delivery_available: Levering er tilgængelig
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 9361ad4f3..218267cd3 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -198,11 +198,13 @@ de:
         change_email_user: "%{name} hat die E-Mail-Adresse des Nutzers %{target} geändert"
         confirm_user: "%{name} hat die E-Mail-Adresse von %{target} bestätigt"
         create_account_warning: "%{name} hat eine Warnung an %{target} gesendet"
+        create_announcement: "%{name} hat die neue Ankündigung %{target} erstellt"
         create_custom_emoji: "%{name} hat neues Emoji %{target} hochgeladen"
         create_domain_allow: "%{name} hat die Domain %{target} gewhitelistet"
         create_domain_block: "%{name} hat die Domain %{target} blockiert"
         create_email_domain_block: "%{name} hat die E-Mail-Domain %{target} geblacklistet"
         demote_user: "%{name} stufte Benutzer_in %{target} herunter"
+        destroy_announcement: "%{name} hat die neue Ankündigung %{target} gelöscht"
         destroy_custom_emoji: "%{name} zerstörte Emoji %{target}"
         destroy_domain_allow: "%{name} hat die Domain %{target} von der Whitelist entfernt"
         destroy_domain_block: "%{name} hat die Domain %{target} entblockt"
@@ -224,10 +226,22 @@ de:
         unassigned_report: "%{name} hat die Zuweisung der Meldung %{target} entfernt"
         unsilence_account: "%{name} hat die Stummschaltung von %{target} aufgehoben"
         unsuspend_account: "%{name} hat die Verbannung von %{target} aufgehoben"
+        update_announcement: "%{name} aktualisierte Ankündigung %{target}"
         update_custom_emoji: "%{name} hat das %{target} Emoji geändert"
         update_status: "%{name} hat einen Beitrag von %{target} aktualisiert"
       deleted_status: "(gelöschter Beitrag)"
       title: Überprüfungsprotokoll
+    announcements:
+      edit:
+        title: Ankündigung bearbeiten
+      empty: Keine Ankündigungen gefunden.
+      live: Live
+      new:
+        create: Ankündigung erstellen
+        title: Neue Ankündigung
+      published: Veröffentlicht
+      time_range: Zeitraum
+      title: Ankündigungen
     custom_emojis:
       assign_category: Kategorie zuweisen
       by_domain: Domain
@@ -344,9 +358,6 @@ de:
         create: Blockade erstellen
         title: Neue E-Mail-Domain-Blockade
       title: E-Mail-Domain-Blockade
-    followers:
-      back_to_account: Zurück zum Konto
-      title: "%{acct}'s Follower"
     instances:
       by_domain: Domain
       delivery_available: Zustellung funktioniert
@@ -375,6 +386,8 @@ de:
       title: Einladungen
     pending_accounts:
       title: Ausstehende Konten (%{count})
+    relationships:
+      title: Beziehungen von %{acct}
     relays:
       add_new: Neues Relay hinzufügen
       delete: Löschen
@@ -658,6 +671,9 @@ de:
     hint_html: "<strong>Hinweis:</strong> Wir werden dich für die nächste Stunde nicht erneut nach deinem Passwort fragen."
     invalid_password: Ungültiges Passwort
     prompt: Gib dein Passwort ein um fortzufahren
+  date:
+    formats:
+      default: "%d. %b %Y"
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -734,6 +750,7 @@ de:
     hint_html: "<strong>Was sind empfohlene Hashtags?</strong> Sie werden in deinem öffentlichen Profil deutlich angezeigt und ermöglichen es den Menschen, deine öffentlichen Beiträge speziell unter diesen Hashtags zu durchsuchen. Sie sind ein großartiges Werkzeug, um kreative Werke oder langfristige Projekte zu verfolgen."
   filters:
     contexts:
+      account: Profile
       home: Startseite
       notifications: Benachrichtigungen
       public: Öffentliche Zeitleisten
@@ -758,6 +775,8 @@ de:
     all: Alle
     changes_saved_msg: Änderungen gespeichert!
     copy: Kopieren
+    delete: Löschen
+    edit: Bearbeiten
     no_batch_actions_available: Keine Massenaktionen auf dieser Seite verfügbar
     order_by: Sortieren nach
     save_changes: Änderungen speichern
@@ -921,6 +940,7 @@ de:
       duration_too_long: ist zu weit in der Zukunft
       duration_too_short: ist zu früh
       expired: Die Umfrage ist bereits vorbei
+      invalid_choice: Die gewählte Stimmenoption existiert nicht
       over_character_limit: kann nicht länger als jeweils %{max} Zeichen sein
       too_few_options: muss mindestens einen Eintrag haben
       too_many_options: kann nicht mehr als %{max} Einträge beinhalten
@@ -928,11 +948,15 @@ de:
     other: Weiteres
     posting_defaults: Standardeinstellungen für Beiträge
     public_timelines: Öffentliche Zeitleisten
+  reactions:
+    errors:
+      unrecognized_emoji: ist kein anerkanntes Emoji
   relationships:
     activity: Kontoaktivität
     dormant: Inaktiv
     followers: Folgende
     following: Folgt
+    invited: Eingeladen
     last_active: Zuletzt aktiv
     most_recent: Neuste
     moved: Umgezogen
diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml
index 204ac429b..49c7cade9 100644
--- a/config/locales/doorkeeper.ar.yml
+++ b/config/locales/doorkeeper.ar.yml
@@ -38,6 +38,7 @@ ar:
         application: تطبيق
         callback_url: رابط رد النداء
         delete: حذف
+        empty: ليس لديك أية تطبيقات.
         name: التسمية
         new: تطبيق جديد
         scopes: المجالات
diff --git a/config/locales/doorkeeper.ast.yml b/config/locales/doorkeeper.ast.yml
index db6eb1e51..5c54b5353 100644
--- a/config/locales/doorkeeper.ast.yml
+++ b/config/locales/doorkeeper.ast.yml
@@ -27,6 +27,7 @@ ast:
       help:
         native_redirect_uri: Usa %{native_redirect_uri} pa pruebes llocales
       index:
+        empty: Nun tienes aplicaciones.
         name: Nome
         new: Aplicación nueva
         scopes: Ámbitos
diff --git a/config/locales/doorkeeper.ca.yml b/config/locales/doorkeeper.ca.yml
index 3c69cf8e7..3de9d4bab 100644
--- a/config/locales/doorkeeper.ca.yml
+++ b/config/locales/doorkeeper.ca.yml
@@ -38,6 +38,7 @@ ca:
         application: Aplicació
         callback_url: URL de retorn
         delete: Suprimeix
+        empty: No tens cap aplicació.
         name: Nom
         new: Aplicació nova
         scopes: Àmbits
diff --git a/config/locales/doorkeeper.co.yml b/config/locales/doorkeeper.co.yml
index a64a07931..4f03c0c32 100644
--- a/config/locales/doorkeeper.co.yml
+++ b/config/locales/doorkeeper.co.yml
@@ -38,6 +38,7 @@ co:
         application: Applicazione
         callback_url: URL di richjama
         delete: Toglie
+        empty: Ùn avete micca d'applicazione.
         name: Nome
         new: Applicazione nova
         scopes: Scopi
diff --git a/config/locales/doorkeeper.cs.yml b/config/locales/doorkeeper.cs.yml
index 8c5c175f5..00345db76 100644
--- a/config/locales/doorkeeper.cs.yml
+++ b/config/locales/doorkeeper.cs.yml
@@ -38,6 +38,7 @@ cs:
         application: Aplikace
         callback_url: Zpáteční URL
         delete: Smazat
+        empty: Nemáte žádné aplikace.
         name: Název
         new: Nová aplikace
         scopes: Rozsahy
diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml
index 65fb2de88..8b850b56a 100644
--- a/config/locales/doorkeeper.de.yml
+++ b/config/locales/doorkeeper.de.yml
@@ -38,6 +38,7 @@ de:
         application: Anwendung
         callback_url: Callback-URL
         delete: Löschen
+        empty: Du hast keine Anwendungen.
         name: Name
         new: Neue Anwendung
         scopes: Befugnisse
diff --git a/config/locales/doorkeeper.el.yml b/config/locales/doorkeeper.el.yml
index d4bf0ae77..7423606d4 100644
--- a/config/locales/doorkeeper.el.yml
+++ b/config/locales/doorkeeper.el.yml
@@ -38,6 +38,7 @@ el:
         application: Εφαρμογή
         callback_url: URL επιστροφής (Callback)
         delete: Διαγραφή
+        empty: Δεν έχετε αιτήσεις.
         name: Όνομα
         new: Νέα εφαρμογή
         scopes: Εύρος εφαρμογής
diff --git a/config/locales/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml
index cb12d0e82..89a579ae9 100644
--- a/config/locales/doorkeeper.eo.yml
+++ b/config/locales/doorkeeper.eo.yml
@@ -38,6 +38,7 @@ eo:
         application: Aplikaĵo
         callback_url: Revena URL
         delete: Forigi
+        empty: Vi havas neniun aplikaĵon.
         name: Nomo
         new: Nova aplikaĵo
         scopes: Ampleksoj
diff --git a/config/locales/doorkeeper.es-AR.yml b/config/locales/doorkeeper.es-AR.yml
index 61b14ba16..85ab7729d 100644
--- a/config/locales/doorkeeper.es-AR.yml
+++ b/config/locales/doorkeeper.es-AR.yml
@@ -38,6 +38,7 @@ es-AR:
         application: Aplicación
         callback_url: Dirección web de respuesta ("callback")
         delete: Eliminar
+        empty: No tenés aplicaciones.
         name: Nombre
         new: Nueva aplicación
         scopes: Ámbitos
diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml
index 75a04eccf..61e6cb6a1 100644
--- a/config/locales/doorkeeper.es.yml
+++ b/config/locales/doorkeeper.es.yml
@@ -38,6 +38,7 @@ es:
         application: Aplicación
         callback_url: URL de callback
         delete: Eliminar
+        empty: No tienes aplicaciones.
         name: Nombre
         new: Nueva aplicación
         scopes: Ámbitos
@@ -139,7 +140,7 @@ es:
       write:accounts: modifica tu perfil
       write:blocks: bloquear cuentas y dominios
       write:bookmarks: guardar estados como marcadores
-      write:favourites: estados favoritos
+      write:favourites: toots favoritos
       write:filters: crear filtros
       write:follows: seguir usuarios
       write:lists: crear listas
diff --git a/config/locales/doorkeeper.et.yml b/config/locales/doorkeeper.et.yml
index 8fb944631..d3b011a67 100644
--- a/config/locales/doorkeeper.et.yml
+++ b/config/locales/doorkeeper.et.yml
@@ -38,6 +38,7 @@ et:
         application: Rakendus
         callback_url: Ümbersuunamise URL
         delete: Kustuta
+        empty: Teil pole rakendusi.
         name: Nimi
         new: Uus rakendus
         scopes: Ulatused
diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml
index 19cc40992..07fc13983 100644
--- a/config/locales/doorkeeper.eu.yml
+++ b/config/locales/doorkeeper.eu.yml
@@ -38,6 +38,7 @@ eu:
         application: Aplikazioa
         callback_url: Itzulera URLa
         delete: Ezabatu
+        empty: Ez duzu aplikaziorik.
         name: Izena
         new: Aplikazio berria
         scopes: Irismena
diff --git a/config/locales/doorkeeper.fa.yml b/config/locales/doorkeeper.fa.yml
index 03a8d7963..c9ca1895e 100644
--- a/config/locales/doorkeeper.fa.yml
+++ b/config/locales/doorkeeper.fa.yml
@@ -38,6 +38,7 @@ fa:
         application: برنامه
         callback_url: نشانی Callback
         delete: حذف
+        empty: شما هیچ برنامه‌ای ندارید.
         name: نام
         new: برنامهٔ تازه
         scopes: دامنه‌ها
diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml
index 11bbe8cf1..e9bf70cd2 100644
--- a/config/locales/doorkeeper.fr.yml
+++ b/config/locales/doorkeeper.fr.yml
@@ -38,6 +38,7 @@ fr:
         application: Application
         callback_url: URL de retour d’appel
         delete: Effacer
+        empty: Vous n’avez pas d’application.
         name: Nom
         new: Nouvelle application
         scopes: Permissions
diff --git a/config/locales/doorkeeper.gl.yml b/config/locales/doorkeeper.gl.yml
index 9cb5d754c..281f03f84 100644
--- a/config/locales/doorkeeper.gl.yml
+++ b/config/locales/doorkeeper.gl.yml
@@ -6,7 +6,7 @@ gl:
         name: Nome do aplicativo
         redirect_uri: URI a redireccionar
         scopes: Permisos
-        website: Sitio web do aplicativo
+        website: Sitio web da aplicación
     errors:
       models:
         doorkeeper/application:
@@ -27,31 +27,32 @@ gl:
       confirmations:
         destroy: Está segura?
       edit:
-        title: Editar aplicativo
+        title: Editar aplicación
       form:
-        error: Eeeeepa! Comprobe os posibles erros no formulario
+        error: Eeeeepa! Comproba os posibles erros no formulario
       help:
-        native_redirect_uri: Utilice %{native_redirect_uri} para probas locais
-        redirect_uri: Utilice unha liña por URI
-        scopes: Separar permisos con espazos. Deixar en blanco para utilizar os permisos por omisión.
+        native_redirect_uri: Utiliza %{native_redirect_uri} para probas locais
+        redirect_uri: Utiliza unha liña por URI
+        scopes: Separar permisos con espazos. Deixar en branco para utilizar os permisos por omisión.
       index:
-        application: Aplicativo
+        application: Aplicación
         callback_url: URL de chamada
         delete: Eliminar
+        empty: Non tes aplicacións.
         name: Nome
-        new: Novo aplicativo
-        scopes: Permisos
+        new: Nova aplicación
+        scopes: Ámbitos
         show: Mostrar
-        title: Os seus aplicativos
+        title: As túas aplicacións
       new:
-        title: Novo aplicativo
+        title: Nova aplicación
       show:
         actions: Accións
         application_id: Chave do cliente
         callback_urls: URLs de chamada
-        scopes: Permisos
-        secret: Chave secreta do cliente
-        title: 'Aplicativo: %{name}'
+        scopes: Ámbitos
+        secret: Chave segreda do cliente
+        title: 'Aplicación: %{name}'
     authorizations:
       buttons:
         authorize: Autorizar
@@ -60,21 +61,21 @@ gl:
         title: Algo fallou
       new:
         able_to: Poderá
-        prompt: O aplicativo %{client_name} solicita acceso a súa conta
+        prompt: A aplicación %{client_name} solicita acceso a túa conta
         title: Autorización necesaria
       show:
-        title: Copie este código de autorización e pégueo no aplicativo.
+        title: Copia este código de autorización e pégao na aplicación.
     authorized_applications:
       buttons:
         revoke: Retirar autorización
       confirmations:
-        revoke: Está segura?
+        revoke: Estás segura?
       index:
-        application: Aplicativo
+        application: Aplicación
         created_at: Autorizado
         date_format: "%d-%m-%Y %H:%M:%S"
-        scopes: Permisos
-        title: Os seus aplicativos autorizados
+        scopes: Ámbitos
+        title: As túas aplicacións autorizadas
     errors:
       messages:
         access_denied: O propietario do recurso ou o servidor autorizado denegaron a petición.
diff --git a/config/locales/doorkeeper.hu.yml b/config/locales/doorkeeper.hu.yml
index 61e7dd5f1..32709299f 100644
--- a/config/locales/doorkeeper.hu.yml
+++ b/config/locales/doorkeeper.hu.yml
@@ -38,6 +38,7 @@ hu:
         application: Alkalmazás
         callback_url: Callback URL
         delete: Eltávolítás
+        empty: Nincsenek alkalmazásaid.
         name: Név
         new: Új alkalmazás
         scopes: Hatáskör
diff --git a/config/locales/doorkeeper.id.yml b/config/locales/doorkeeper.id.yml
index efaeaae16..840390481 100644
--- a/config/locales/doorkeeper.id.yml
+++ b/config/locales/doorkeeper.id.yml
@@ -38,6 +38,7 @@ id:
         application: Aplikasi
         callback_url: URL Callback
         delete: Hapus
+        empty: Anda tidak memiliki aplikasi.
         name: Nama
         new: Aplikasi baru
         scopes: Cakupan
diff --git a/config/locales/doorkeeper.is.yml b/config/locales/doorkeeper.is.yml
index 31c4bca9f..0d15479c5 100644
--- a/config/locales/doorkeeper.is.yml
+++ b/config/locales/doorkeeper.is.yml
@@ -38,6 +38,7 @@ is:
         application: Forrit
         callback_url: URL-slóð baksvörunar (callback)
         delete: Eyða
+        empty: Þú ert ekki með nein forrit.
         name: Heiti
         new: Nýtt forrit
         scopes: Gildissvið
@@ -78,12 +79,22 @@ is:
     errors:
       messages:
         access_denied: Eigandi tilfangs eða auðkenningarþjónn höfnuðu beininni.
+        credential_flow_not_configured: Flæði á lykilorðsauðkennum eiganda tilfangs (Resource Owner) brást vegna þess að Doorkeeper.configure.resource_owner_from_credentials er óskilgreint.
+        invalid_client: Auðkenning á biðlara brást vegna þess að biðlarinn er óþekktur, að auðkenning biðlarans fylgdi ekki með, eða að notuð var óstudd auðkenningaraðferð.
+        invalid_grant: Uppgefin auðkenningarheimild er ógild, útrunnin, afturkölluð, samsvarar ekki endurbirtingarslóðinni í auðkenningarbeiðninni, eða var gefin út til annars biðlara.
         invalid_redirect_uri: Endurbeiningarslóðin sem fylgdi er ekki gild.
+        invalid_request: Í beiðnina vantar nauðsynlega færibreytu, hún inniheldur óleyfilegt gildi á færibreytu, eða er gölluð á einhvern annan hátt.
+        invalid_resource_owner: Uppgefin auðkenni eiganda tilfangs eru ekki gild, eða að eigandi tilfangs finnst ekki
         invalid_scope: Umbeðið gildissvið er ógilt, óþekkt eða rangt uppsett.
         invalid_token:
           expired: Auðkenningarteiknið er útrunnið
           revoked: Auðkenningarteiknið var aturkallað
           unknown: Auðkenningarteiknið er ógilt
+        resource_owner_authenticator_not_configured: Leit að eiganda tilfangs (Resource Owner) brást vegna þess að Doorkeeper.configure.resource_owner_authenticator er óskilgreint.
+        server_error: Auðkenningarþjónninn rakst á óvænt skilyrði sem kom í veg fyrir að hægt væri að uppfylla beiðnina.
+        temporarily_unavailable: Auðkenningarþjónninn hefur ekki tök á að meðhöndla beiðnina vegna of mikils tímabundins álags eða viðhalds á vefþvóninum.
+        unauthorized_client: Biðlaraforritið hefur ekki heimild til að framkvæma beiðnina með þessari aðferð.
+        unsupported_grant_type: Þessi gerð auðkenningaraðferðar er ekki studd af auðkenningarþjóninum.
         unsupported_response_type: Auðkenningarþjónninn styður ekki þessa tegund svars.
     flash:
       applications:
diff --git a/config/locales/doorkeeper.it.yml b/config/locales/doorkeeper.it.yml
index 122b38c04..68e2b57f3 100644
--- a/config/locales/doorkeeper.it.yml
+++ b/config/locales/doorkeeper.it.yml
@@ -38,6 +38,7 @@ it:
         application: Applicazione
         callback_url: URL di callback
         delete: Elimina
+        empty: Non hai applicazioni.
         name: Nome
         new: Nuova applicazione
         scopes: Visibilità
diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml
index 67eadbf2d..73932bafd 100644
--- a/config/locales/doorkeeper.ja.yml
+++ b/config/locales/doorkeeper.ja.yml
@@ -38,6 +38,7 @@ ja:
         application: アプリ
         callback_url: コールバックURL
         delete: 削除
+        empty: アプリはありません
         name: 名前
         new: 新規アプリ
         scopes: アクセス権
@@ -125,7 +126,7 @@ ja:
       read: アカウントのすべてのデータの読み取り
       read:accounts: アカウント情報の読み取り
       read:blocks: ブロックの読み取り
-      read:bookmarks: ブックマークを見る
+      read:bookmarks: ブックマークの読み取り
       read:favourites: お気に入りの読み取り
       read:filters: フィルターの読み取り
       read:follows: フォローの読み取り
diff --git a/config/locales/doorkeeper.kk.yml b/config/locales/doorkeeper.kk.yml
index 2c3346b6e..75f8de542 100644
--- a/config/locales/doorkeeper.kk.yml
+++ b/config/locales/doorkeeper.kk.yml
@@ -38,6 +38,7 @@ kk:
         application: Қосымша
         callback_url: Callbаck URL
         delete: Өшіру
+        empty: Сізде ешқандай қосымша жоқ.
         name: Аты
         new: Жаңа қосымша
         scopes: Scopеs
diff --git a/config/locales/doorkeeper.ko.yml b/config/locales/doorkeeper.ko.yml
index 3f9e12857..6f4192ebe 100644
--- a/config/locales/doorkeeper.ko.yml
+++ b/config/locales/doorkeeper.ko.yml
@@ -38,6 +38,7 @@ ko:
         application: 애플리케이션
         callback_url: 콜백 URL
         delete: 삭제
+        empty: 어플리케이션이 없습니다.
         name: 이름
         new: 새 애플리케이션
         scopes: 범위
diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml
index 215c8795d..90d8f9358 100644
--- a/config/locales/doorkeeper.pt-BR.yml
+++ b/config/locales/doorkeeper.pt-BR.yml
@@ -38,6 +38,7 @@ pt-BR:
         application: Aplicativos
         callback_url: Link de retorno
         delete: Excluir
+        empty: Não tem aplicações.
         name: Nome
         new: Novo aplicativo
         scopes: Autorizações
diff --git a/config/locales/doorkeeper.pt-PT.yml b/config/locales/doorkeeper.pt-PT.yml
index e23310a18..2433f23e9 100644
--- a/config/locales/doorkeeper.pt-PT.yml
+++ b/config/locales/doorkeeper.pt-PT.yml
@@ -38,6 +38,7 @@ pt-PT:
         application: Aplicações
         callback_url: URL de retorno
         delete: Eliminar
+        empty: Não tem aplicações.
         name: Nome
         new: Nova Aplicação
         scopes: Autorizações
diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml
index f04a1306d..532e2c9ac 100644
--- a/config/locales/doorkeeper.ru.yml
+++ b/config/locales/doorkeeper.ru.yml
@@ -38,6 +38,7 @@ ru:
         application: Приложение
         callback_url: Callback URL
         delete: Удалить
+        empty: У вас нет созданных приложений.
         name: Название
         new: Новое приложение
         scopes: Разрешения
diff --git a/config/locales/doorkeeper.sv.yml b/config/locales/doorkeeper.sv.yml
index af98020c1..d9367ce5e 100644
--- a/config/locales/doorkeeper.sv.yml
+++ b/config/locales/doorkeeper.sv.yml
@@ -38,6 +38,7 @@ sv:
         application: Applikation
         callback_url: Återkalls URL
         delete: Ta bort
+        empty: Du har inga program.
         name: Namn
         new: Ny applikation
         scopes: Omfattning
diff --git a/config/locales/doorkeeper.th.yml b/config/locales/doorkeeper.th.yml
index 33d6944f0..597a65038 100644
--- a/config/locales/doorkeeper.th.yml
+++ b/config/locales/doorkeeper.th.yml
@@ -36,6 +36,7 @@ th:
         application: แอปพลิเคชัน
         callback_url: URL เรียกกลับ
         delete: ลบ
+        empty: คุณไม่มีแอปพลิเคชัน
         name: ชื่อ
         new: แอปพลิเคชันใหม่
         scopes: ขอบเขต
diff --git a/config/locales/doorkeeper.tr.yml b/config/locales/doorkeeper.tr.yml
index b4362f2a2..a218e3157 100644
--- a/config/locales/doorkeeper.tr.yml
+++ b/config/locales/doorkeeper.tr.yml
@@ -38,6 +38,7 @@ tr:
         application: Uygulama
         callback_url: Geri Dönüş URL
         delete: Sil
+        empty: Hiç uygulamanız yok.
         name: İsim
         new: Yeni uygulama
         scopes: Kapsam
diff --git a/config/locales/el.yml b/config/locales/el.yml
index 97ae2bd6c..53d475523 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -339,13 +339,11 @@ el:
       delete: Διαγραφή
       destroyed_msg: Επιτυχής διαγραφή email τομέα από τη μαύρη λίστα
       domain: Τομέας
+      empty: Δεν έχουν οριστεί αποκλεισμένοι τομείς email.
       new:
         create: Πρόσθεση τομέα
         title: Νέα εγγραφή email στη μαύρη λίστα
       title: Μαύρη λίστα email
-    followers:
-      back_to_account: Επιστροφή στον λογαριασμό
-      title: Ακόλουθοι του/της %{acct}
     instances:
       by_domain: Τομέας
       delivery_available: Διαθέσιμη παράδοση
@@ -920,6 +918,7 @@ el:
       duration_too_long: είναι πολύ μακριά στο μέλλον
       duration_too_short: είναι πολύ σύντομα
       expired: Η ψηφοφορία έχει ήδη λήξει
+      invalid_choice: Αυτή η επιλογή ψήφου δεν υπάρχει
       over_character_limit: δε μπορεί να υπερβαίνει τους %{max} χαρακτήρες έκαστη
       too_few_options: πρέπει να έχει περισσότερες από μια επιλογές
       too_many_options: δεν μπορεί να έχει περισσότερες από %{max} επιλογές
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8262a5868..18a96ccbc 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -198,11 +198,13 @@ en:
         change_email_user: "%{name} changed the e-mail address of user %{target}"
         confirm_user: "%{name} confirmed e-mail address of user %{target}"
         create_account_warning: "%{name} sent a warning to %{target}"
+        create_announcement: "%{name} created new announcement %{target}"
         create_custom_emoji: "%{name} uploaded new emoji %{target}"
         create_domain_allow: "%{name} whitelisted domain %{target}"
         create_domain_block: "%{name} blocked domain %{target}"
         create_email_domain_block: "%{name} blacklisted e-mail domain %{target}"
         demote_user: "%{name} demoted user %{target}"
+        destroy_announcement: "%{name} deleted announcement %{target}"
         destroy_custom_emoji: "%{name} destroyed emoji %{target}"
         destroy_domain_allow: "%{name} removed domain %{target} from whitelist"
         destroy_domain_block: "%{name} unblocked domain %{target}"
@@ -224,10 +226,22 @@ en:
         unassigned_report: "%{name} unassigned report %{target}"
         unsilence_account: "%{name} unsilenced %{target}'s account"
         unsuspend_account: "%{name} unsuspended %{target}'s account"
+        update_announcement: "%{name} updated announcement %{target}"
         update_custom_emoji: "%{name} updated emoji %{target}"
         update_status: "%{name} updated status by %{target}"
       deleted_status: "(deleted status)"
       title: Audit log
+    announcements:
+      edit:
+        title: Edit announcement
+      empty: No announcements found.
+      live: Live
+      new:
+        create: Create announcement
+        title: New announcement
+      published: Published
+      time_range: Time range
+      title: Announcements
     custom_emojis:
       assign_category: Assign category
       by_domain: Domain
@@ -345,9 +359,6 @@ en:
         create: Add domain
         title: New e-mail blacklist entry
       title: E-mail blacklist
-    followers:
-      back_to_account: Back To Account
-      title: "%{acct}'s Followers"
     instances:
       by_domain: Domain
       delivery_available: Delivery is available
@@ -376,6 +387,8 @@ en:
       title: Invites
     pending_accounts:
       title: Pending accounts (%{count})
+    relationships:
+      title: "%{acct}'s relationships"
     relays:
       add_new: Add new relay
       delete: Delete
@@ -671,6 +684,9 @@ en:
     hint_html: "<strong>Tip:</strong> We won't ask you for your password again for the next hour."
     invalid_password: Invalid password
     prompt: Confirm password to continue
+  date:
+    formats:
+      default: "%b %d, %Y"
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -747,6 +763,7 @@ en:
     hint_html: "<strong>What are featured hashtags?</strong> They are displayed prominently on your public profile and allow people to browse your public posts specifically under those hashtags. They are a great tool for keeping track of creative works or long-term projects."
   filters:
     contexts:
+      account: Profiles
       home: Home timeline
       notifications: Notifications
       public: Public timelines
@@ -771,6 +788,8 @@ en:
     all: All
     changes_saved_msg: Changes successfully saved!
     copy: Copy
+    delete: Delete
+    edit: Edit
     no_batch_actions_available: No batch actions available on this page
     order_by: Order by
     save_changes: Save changes
@@ -944,11 +963,15 @@ en:
     other: Other
     posting_defaults: Posting defaults
     public_timelines: Public timelines
+  reactions:
+    errors:
+      unrecognized_emoji: is not a recognized emoji
   relationships:
     activity: Account activity
     dormant: Dormant
     followers: Followers
     following: Following
+    invited: Invited
     last_active: Last active
     most_recent: Most recent
     moved: Moved
@@ -1013,7 +1036,7 @@ en:
       firefox_os: Firefox OS
       ios: iOS
       linux: Linux
-      mac: Mac
+      mac: macOS
       other: unknown platform
       windows: Windows
       windows_mobile: Windows Mobile
diff --git a/config/locales/eo.yml b/config/locales/eo.yml
index f9374272f..fb4d5c8be 100644
--- a/config/locales/eo.yml
+++ b/config/locales/eo.yml
@@ -11,6 +11,7 @@ eo:
     apps: Poŝtelefonaj aplikaĵoj
     apps_platforms: Uzu Mastodon ĉe iOS, Android kaj aliajn platformojn
     browse_directory: Esplori profilujo kaj filtri per interesoj
+    browse_local_posts: Vidi vivantan fluon de publikaj mesaĝoj al Mastodon
     browse_public_posts: Vidi vivantan fluon de publikaj mesaĝoj al Mastodon
     contact: Kontakti
     contact_missing: Ne elektita
@@ -267,6 +268,7 @@ eo:
       features: Funkcioj
       hidden_service: Federacio kun kaŝitaj servoj
       open_reports: nesolvitaj signaloj
+      pending_tags: kradvortoj atendantaj revizion
       pending_users: uzantoj atendantaj revizion
       recent_users: Lastatempaj uzantoj
       search: Tutteksta serĉado
@@ -333,9 +335,6 @@ eo:
         create: Aldoni domajnon
         title: Nova blokado de retadresa domajno
       title: Nigra listo de retadresaj domajnoj
-    followers:
-      back_to_account: Reen al la konto
-      title: Sekvantoj de %{acct}
     instances:
       by_domain: Domajno
       delivery_available: Liverado disponeblas
diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml
index da1ad126f..f298cfbf5 100644
--- a/config/locales/es-AR.yml
+++ b/config/locales/es-AR.yml
@@ -344,9 +344,6 @@ es-AR:
         create: Agregar dominio
         title: Nueva desaprobación de correo electrónico
       title: Desaprobación de correo electrónico
-    followers:
-      back_to_account: Volver a la cuenta
-      title: Seguidores de %{acct}
     instances:
       by_domain: Dominio
       delivery_available: La entrega está disponible
@@ -375,6 +372,8 @@ es-AR:
       title: Invitaciones
     pending_accounts:
       title: Cuentas pendientes (%{count})
+    relationships:
+      title: Relaciones de %{acct}
     relays:
       add_new: Agregar nuevo relé
       delete: Eliminar
@@ -734,6 +733,7 @@ es-AR:
     hint_html: "<strong>¿Qué son las etiquetas destacadas?</strong> Se muestran de forma prominente en tu perfil público y permiten a los usuarios navegar por tus toots públicos específicamente bajo esas etiquetas. Son una gran herramienta para hacer un seguimiento de trabajos creativos o proyectos a largo plazo."
   filters:
     contexts:
+      account: Perfiles
       home: Línea temporal principal
       notifications: Notificaciones
       public: Líneas temporales públicas
@@ -934,6 +934,7 @@ es-AR:
     dormant: Inactivas
     followers: Seguidores
     following: Siguiendo
+    invited: Invitado
     last_active: Última actividad
     most_recent: Más reciente
     moved: Mudada
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 113ea3672..cc8857bfd 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -7,7 +7,7 @@ es:
     active_count_after: activo
     active_footnote: Usuarios Activos Mensuales (UAM)
     administered_by: 'Administrado por:'
-    api: API
+    api: Interfaz de Programación de la Aplicación
     apps: Aplicaciones móviles
     apps_platforms: Utiliza Mastodonte desde iOS, Android y otras plataformas
     browse_directory: Navega por el directorio de perfiles y filtra por intereses
@@ -344,9 +344,6 @@ es:
         create: Añadir dominio
         title: Nueva entrada en la lista negra de correo
       title: Lista negra de correo
-    followers:
-      back_to_account: Volver a la cuenta
-      title: Seguidores de %{acct}
     instances:
       by_domain: Dominio
       delivery_available: Entrega disponible
@@ -375,6 +372,8 @@ es:
       title: Invitaciones
     pending_accounts:
       title: Cuentas pendientes (%{count})
+    relationships:
+      title: Relaciones de %{acct}
     relays:
       add_new: Añadir un nuevo relés
       delete: Borrar
@@ -486,7 +485,7 @@ es:
           open: Cualquiera puede registrarse
         title: Modo de registros
       show_known_fediverse_at_about_page:
-        desc_html: Cuando esté desactivado, mostrará solamente la cronología local, y no la federada
+        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 cronología
       show_staff_badge:
         desc_html: Mostrar un parche de staff en la página de un usuario
@@ -587,7 +586,7 @@ es:
       guide_link: https://es.crowdin.com/project/mastodon
       guide_link_text: Todos pueden contribuir.
     sensitive_content: Contenido sensible
-    toot_layout: Diseño para barritar
+    toot_layout: Diseño de los toots
   application_mailer:
     notification_preferences: Cambiar preferencias de correo electrónico
     salutation: "%{name},"
@@ -734,6 +733,7 @@ es:
     hint_html: "<strong>¿Qué son las etiquetas destacadas?</strong> Se muestran de forma prominente en tu perfil público y permiten a las personas usuarias navegar por tus publicaciones públicas específicamente bajo esas etiquetas. Son una gran herramienta para hacer un seguimiento de obras creativas o proyectos a largo plazo."
   filters:
     contexts:
+      account: Perfiles
       home: Cronología propia
       notifications: Notificaciones
       public: Cronología pública
@@ -780,7 +780,7 @@ es:
     i_am_html: Soy %{username} en %{service}.
     identity: Identidad
     inactive: Inactivo
-    publicize_checkbox: 'Y barrite esto:'
+    publicize_checkbox: 'Y tootee esto:'
     publicize_toot: "¡Comprobado! Soy %{username} en %{service}: %{url}"
     status: Estado de la verificación
     view_proof: Ver prueba
@@ -934,6 +934,7 @@ es:
     dormant: Inactivo
     followers: Seguidores
     following: Siguiendo
+    invited: Invitado
     last_active: Última actividad
     most_recent: Más reciente
     moved: Movido
@@ -954,16 +955,16 @@ es:
   remote_interaction:
     favourite:
       proceed: Proceder a marcar como favorito
-      prompt: 'Quieres marcar como favorito este bramido:'
+      prompt: 'Quieres marcar como favorito este toot:'
     reblog:
-      proceed: Proceder a rebarritar
-      prompt: 'Quieres rebarritar este bramido:'
+      proceed: Proceder a retootear
+      prompt: 'Quieres retootear este toot:'
     reply:
       proceed: Proceder a responder
-      prompt: 'Quieres responder a este bramido:'
+      prompt: 'Quieres responder a este toot:'
   scheduled_statuses:
-    over_daily_limit: Ha superado el límite de %{limit} bramidos programados para ese día
-    over_total_limit: Ha superado el límite de %{limit} bramidos programados
+    over_daily_limit: Ha superado el límite de %{limit} toots programados para ese día
+    over_total_limit: Ha superado el límite de %{limit} toots programados
     too_soon: La fecha programada debe estar en el futuro
   sessions:
     activity: Última actividad
@@ -1190,8 +1191,8 @@ es:
     warning:
       explanation:
         disable: Mientras su cuenta esté congelada, la información de su cuenta permanecerá intacta, pero no puede realizar ninguna acción hasta que se desbloquee.
-        silence: Mientras su cuenta está limitada, sólo las personas que ya le están siguiendo verán sus bramidos en este servidor, y puede que se le excluya de varios listados públicos. Sin embargo, otros pueden seguirle manualmente.
-        suspend: Su cuenta ha sido suspendida, y todos tus bramidos y tus archivos multimedia subidos han sido irreversiblemente eliminados de este servidor, y de los servidores donde tenías seguidores.
+        silence: Mientras su cuenta está limitada, sólo las personas que ya le están siguiendo verán sus toots en este servidor, y puede que se le excluya de varios listados públicos. Sin embargo, otros pueden seguirle manualmente.
+        suspend: Su cuenta ha sido suspendida, y todos tus toots y tus archivos multimedia subidos han sido irreversiblemente eliminados de este servidor, y de los servidores donde tenías seguidores.
       get_in_touch: Puede responder a esta dirección de correo electrónico para ponerse en contacto con el personal de %{instance}.
       review_server_policies: Revisar las políticas del servidor
       statuses: 'Específicamente, para:'
diff --git a/config/locales/et.yml b/config/locales/et.yml
index 16e80ae35..e0e861c33 100644
--- a/config/locales/et.yml
+++ b/config/locales/et.yml
@@ -347,9 +347,6 @@ et:
         create: Lisa domeen
         title: Uus e-posti keelunimekirja sisend
       title: E-posti keelunimekiri
-    followers:
-      back_to_account: Tagasi minu kontole
-      title: "%{acct}-i jälgijad"
     instances:
       by_domain: Domeen
       delivery_available: Üleandmine on saadaval
@@ -378,6 +375,8 @@ et:
       title: Kutsed
     pending_accounts:
       title: Ootel olevad kasutajad (%{count})
+    relationships:
+      title: "%{acct}-i suhted"
     relays:
       add_new: Lisa uus relee
       delete: Kustuta
@@ -737,6 +736,7 @@ et:
     hint_html: "<strong>Mis on esile toodud sildid?</strong> Need sildid näidatakse nähtavalt Teie avalikul profiilil ning nad aitavad inimestel leida postitusi, millel on antud sildid. Nad on hea viis kuidas näiteks hoida järge loovtöödel või pikaajalistel projektidel."
   filters:
     contexts:
+      account: Profiilid
       home: Kodu ajajoon
       notifications: Teated
       public: Avalikud ajajooned
@@ -937,6 +937,7 @@ et:
     dormant: Seisev
     followers: Jälgijad
     following: Jälgib
+    invited: Kutsutud
     last_active: Viimati aktiivne
     most_recent: Viimased
     moved: Kolinud
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index bfa91beb8..999772cfe 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -344,9 +344,6 @@ eu:
         create: Gehitu domeinua
         title: Sarrera berria e-mail zerrenda beltzean
       title: E-mail zerrenda beltza
-    followers:
-      back_to_account: Itzuli kontura
-      title: "%{acct} kontuaren jarraitzaileak"
     instances:
       by_domain: Domeinua
       delivery_available: Bidalketa eskuragarri dago
@@ -921,6 +918,7 @@ eu:
       duration_too_long: etorkizunean urrunegi dago
       duration_too_short: goizegi da
       expired: Inkesta amaitu da jada
+      invalid_choice: Hautatutako boto aukera ez da existitzen
       over_character_limit: bakoitzak gehienez %{max} karaktere izan ditzake
       too_few_options: elementu bat baino gehiago izan behar du
       too_many_options: ezin ditu %{max} elementu baino gehiago izan
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index 81c163f7f..ecf3bc391 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -346,9 +346,6 @@ fa:
         create: ساختن مسدودسازی
         title: مسدودسازی دامین ایمیل تازه
       title: مسدودسازی دامین‌های ایمیل
-    followers:
-      back_to_account: بازگشت به حساب
-      title: پیگیران %{acct}
     instances:
       by_domain: دامین
       delivery_available: پیام آماده است
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 9c8f56889..09a599494 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -198,11 +198,13 @@ fr:
         change_email_user: "%{name} a modifié l’adresse de courriel de l’utilisateur·rice %{target}"
         confirm_user: "%{name} adresse courriel confirmée de l’utilisateur·ice %{target}"
         create_account_warning: "%{name} a envoyé un avertissement à %{target}"
+        create_announcement: "%{name} a créé une nouvelle annonce %{target}"
         create_custom_emoji: "%{name} a importé de nouveaux émojis %{target}"
         create_domain_allow: "%{name} a inscrit le domaine %{target} sur liste blanche"
         create_domain_block: "%{name} a bloqué le domaine %{target}"
         create_email_domain_block: "%{name} a mis le domaine de courriel %{target} sur liste noire"
         demote_user: "%{name} a rétrogradé l’utilisateur·ice %{target}"
+        destroy_announcement: "%{name} a supprimé l’annonce %{target}"
         destroy_custom_emoji: "%{name} a détruit l’émoticône %{target}"
         destroy_domain_allow: "%{name} a supprimé le domaine %{target} de la liste blanche"
         destroy_domain_block: "%{name} a débloqué le domaine %{target}"
@@ -224,10 +226,22 @@ fr:
         unassigned_report: "%{name} a désassigné le signalement %{target}"
         unsilence_account: "%{name} a mis fin au mode silence de %{target}"
         unsuspend_account: "%{name} a réactivé le compte de %{target}"
+        update_announcement: "%{name} a actualisé l’annonce %{target}"
         update_custom_emoji: "%{name} a mis à jour l’émoji %{target}"
         update_status: "%{name} a mis à jour le statut de %{target}"
       deleted_status: "(statut supprimé)"
       title: Journal d’audit
+    announcements:
+      edit:
+        title: Modifier l’annonce
+      empty: Aucune annonce trouvée.
+      live: En direct
+      new:
+        create: Créer une annonce
+        title: Nouvelle annonce
+      published: Publié
+      time_range: Intervalle de temps
+      title: Annonces
     custom_emojis:
       assign_category: Attribuer une catégorie
       by_domain: Domaine
@@ -344,9 +358,6 @@ fr:
         create: Créer le blocage
         title: Nouveau blocage de domaine de courriel
       title: Blocage de domaines de courriel
-    followers:
-      back_to_account: Retour au compte
-      title: Abonné⋅e⋅s de %{acct}
     instances:
       by_domain: Domaine
       delivery_available: Livraison disponible
@@ -375,6 +386,8 @@ fr:
       title: Invitations
     pending_accounts:
       title: Comptes en attente (%{count})
+    relationships:
+      title: Relations de %{acct}
     relays:
       add_new: Ajouter un nouveau relais
       delete: Effacer
@@ -658,6 +671,9 @@ fr:
     hint_html: "<strong>Astuce :</strong> Nous ne vous demanderons plus votre mot de passe pour la prochaine heure."
     invalid_password: Mot de passe invalide
     prompt: Confirmez votre mot de passe pour continuer
+  date:
+    formats:
+      default: "%d %b %Y"
   datetime:
     distance_in_words:
       about_x_hours: "%{count} h"
@@ -734,6 +750,7 @@ fr:
     hint_html: "<strong>Que sont les hashtags vedettes ?</strong> Ils sont affichés avec emphase sur votre flux d'actualités publique et permettent aux gens de parcourir vos messages publics spécifiquement sous ces hashtags. Ils sont un excellent outil pour garder trace des œuvres créatives ou des projets à long terme."
   filters:
     contexts:
+      account: Profils
       home: Accueil
       notifications: Notifications
       public: Fils publics
@@ -758,6 +775,8 @@ fr:
     all: Tous
     changes_saved_msg: Les modifications ont été enregistrées avec succès !
     copy: Copier
+    delete: Supprimer
+    edit: Modifier
     no_batch_actions_available: Aucune action par lots disponible sur cette page
     order_by: Classer par
     save_changes: Enregistrer les modifications
@@ -929,11 +948,15 @@ fr:
     other: Autre
     posting_defaults: Paramètres par défaut des pouets
     public_timelines: Fils publics
+  reactions:
+    errors:
+      unrecognized_emoji: n’est pas un émoji reconnu
   relationships:
     activity: Activité du compte
     dormant: Dormant
     followers: Abonné·e·s
     following: Abonnements
+    invited: Invité·e
     last_active: Dernière activité
     most_recent: Plus récent
     moved: Déménagé
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 7e8776a5b..9fa44456e 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -2,26 +2,26 @@
 gl:
   about:
     about_hashtag_html: Estes son toots públicos etiquetados con <strong>#%{hashtag}</strong>. Podes interactuar con eles se tes unha conta nalgures do fediverso.
-    about_mastodon_html: O Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada coma o correo electrónico!
+    about_mastodon_html: Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada como o correo electrónico.
     about_this: Acerca de
-    active_count_after: activo
-    active_footnote: Usuarios Activos Mensuais (UAM)
-    administered_by: 'Administrado por:'
+    active_count_after: activas
+    active_footnote: Usuarias Activas no Mes (UAM)
+    administered_by: 'Administrada por:'
     api: API
     apps: Aplicacións móbiles
-    apps_platforms: Emprega o Mastodon dende iOS, Android e outras plataformas
+    apps_platforms: Emprega Mastodon dende iOS, Android e outras plataformas
     browse_directory: Navega polo directorio de perfís e filtra por intereses
     browse_local_posts: Navega polas publicacións públicas deste servidor en tempo real
-    browse_public_posts: Navega polas publicacións públicas do Mastodon en tempo real
+    browse_public_posts: Navega polas publicacións públicas de Mastodon en tempo real
     contact: Contacto
-    contact_missing: Non estabelecido
+    contact_missing: Non establecido
     contact_unavailable: Non dispoñíbel
-    discover_users: Descobrir usuarios
+    discover_users: Descubrir usuarias
     documentation: Documentación
     federation_hint_html: Cunha conta en %{instance} poderás seguir ás persoas en calquera servidor do Mastodon e alén.
     get_apps: Probar unha aplicación móbil
-    hosted_on: O Mastodon está aloxado en %{domain}
-    instance_actor_flash: 'Esta conta é un actor virtual empregado para representar ó servidor e non a un usuario individual. Utilízase para propósitos de federación e non debería estar bloqueada a menos que queiras bloquear a toda a instancia, en tal caso deberías empregar o bloqueo de dominio.
+    hosted_on: Mastodon aloxado en %{domain}
+    instance_actor_flash: 'Esta conta é un actor virtual utilizado para representar ao servidor e non a unha usuaria individual. Utilízase para propósitos de federación e non debería estar bloqueada a menos que queiras bloquear a toda a instancia, en tal caso deberías utilizar o bloqueo do dominio.
 
 '
     learn_more: Saber máis
@@ -32,7 +32,7 @@ gl:
     status_count_after:
       one: estado
       other: estados
-    status_count_before: Que escribiron
+    status_count_before: Que publicaron
     tagline: Segue ás túas amizades e coñece novas
     terms: Termos do servizo
     unavailable_content: Contido non dispoñíbel
@@ -40,22 +40,22 @@ gl:
       domain: Servidor
       reason: Razón
       rejecting_media: 'Os ficheiros multimedia deste servidor non serán procesados e non se amosarán miniaturas, o que require un clic manual no ficheiro orixinal:'
-      silenced: 'As publicacións deste servidor non se amosarán en ningún lugar agás que as sigas:'
-      suspended: 'Non se procesarán, almacenarán nin intercambiarán datos destes servidores, o que fai imposíbel calquera interacción ou comunicación cos usuarios dende estes servidores:'
-    unavailable_content_html: O Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar cos seus usuarios. Estas son as excepcións que se estabeleceron neste servidor en particular.
+      silenced: 'As publicacións deste servidor non se amosarán en conversas e liñas temporais, nin terás notificacións das súas usuarias agás que as sigas:'
+      suspended: 'Non se procesarán, almacenarán nin intercambiarán datos destes servidores, o que fai imposíbel calquera interacción ou comunicación coas usuarias dende estes servidores:'
+    unavailable_content_html: O Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar coas súas usuarias. Estas son as excepcións que se estabeleceron neste servidor en particular.
     user_count_after:
-      one: usuario
-      other: usuarios
+      one: usuaria
+      other: usuarias
     user_count_before: Fogar de
-    what_is_mastodon: Que é o Mastodon?
+    what_is_mastodon: Qué é Mastodon?
   accounts:
-    choices_html: 'Suxestións de %{name}:'
+    choices_html: 'Escollas de %{name}:'
     endorsements_hint: Podes suxerir a persoas que segues dende a interface web, e amosaranse aquí.
-    featured_tags_hint: Podes destacar determinados cancelos (hashtags) que se amosarán aquí.
+    featured_tags_hint: Podes destacar determinadas etiquetas que se amosarán aquí.
     follow: Seguir
     followers:
-      one: Seguidor
-      other: Seguidores
+      one: Seguidora
+      other: Seguidoras
     following: Seguindo
     joined: Uniuse en %{date}
     last_active: última actividade
@@ -74,18 +74,18 @@ gl:
       other: Toots
     posts_tab_heading: Toots
     posts_with_replies: Toots e respostas
-    reserved_username: O nome de usuario está reservado
+    reserved_username: O nome de usuaria está reservado
     roles:
-      admin: Administrador
+      admin: Administradora
       bot: Bot
       group: Grupo
-      moderator: Moderador
+      moderator: Moderadora
     unavailable: Perfil non dispoñíbel
     unfollow: Deixar de seguir
   admin:
     account_actions:
       action: Executar acción
-      title: Executar acción de moderación en %{acct}
+      title: Executar acción de moderación a %{acct}
     account_moderation_notes:
       create: Deixar nota
       created_msg: Nota de moderación creada de xeito correcto!
@@ -94,7 +94,7 @@ gl:
     accounts:
       approve: Aprobar
       approve_all: Aprobar todos
-      are_you_sure: Estás seguro?
+      are_you_sure: Está segura?
       avatar: Imaxe de perfil
       by_domain: Dominio
       change_email:
@@ -110,7 +110,7 @@ gl:
       deleted: Eliminado
       demote: Rebaixar
       disable: Desactivar
-      disable_two_factor_authentication: Desactivar dobre factor
+      disable_two_factor_authentication: Desactivar 2FA
       disabled: Desactivado
       display_name: Nome a amosar
       domain: Dominio
@@ -119,13 +119,13 @@ gl:
       email_status: Estado do email
       enable: Activar
       enabled: Activado
-      followers: Seguidores
+      followers: Seguidoras
       follows: Seguindo
       header: Cabeceira
       inbox_url: URL da caixa de entrada
-      invited_by: Convidado por
+      invited_by: Convidada por
       ip: IP
-      joined: Unido
+      joined: Uniuse
       location:
         all: Todo
         local: Local
@@ -135,7 +135,7 @@ gl:
       media_attachments: Multimedia adxunta
       memorialize: Converter en lembranza
       moderation:
-        active: Activo
+        active: Activa
         all: Todo
         pending: Pendente
         silenced: Silenciados
@@ -159,7 +159,7 @@ gl:
       remove_avatar: Eliminar imaxe de perfil
       remove_header: Eliminar cabeceira
       resend_confirmation:
-        already_confirmed: Este usuario xa está confirmado
+        already_confirmed: Esta usuaria xa está confirmada
         send: Reenviar o email de confirmación
         success: Email de confirmación enviado de xeito correcto!
       reset: Restabelecer
@@ -170,9 +170,9 @@ gl:
         admin: Administrador
         moderator: Moderador
         staff: Persoal (staff)
-        user: Usuario
+        user: Usuaria
       search: Procurar
-      search_same_ip: Outros usuarios co mesmo IP
+      search_same_ip: Outras usuarias co mesmo IP
       shared_inbox_url: URL da caixa de entrada compartida
       show:
         created_reports: Denuncias feitas
@@ -188,46 +188,60 @@ gl:
       undo_silenced: Desfacer silencio
       undo_suspension: Desfacer suspensión
       unsubscribe: Desbotar a subscrición
-      username: Nome de usuario
+      username: Nome de usuaria
       warn: Aviso
       web: Web
       whitelisted: Listaxe branca
     action_logs:
       actions:
         assigned_to_self_report: "%{name} atribuíu a denuncia %{target} a el mesmo"
-        change_email_user: "%{name} mudou o enderezo de email do usuario %{target}"
-        confirm_user: "%{name} confirmou o enderezo de email do usuario %{target}"
+        change_email_user: "%{name} cambiou o enderezo de correo-e da usuaria %{target}"
+        confirm_user: "%{name} comfirmou o enderezo de correo da usuaria %{target}"
         create_account_warning: "%{name} enviou un aviso a %{target}"
+        create_announcement: "%{name} creou un novo anuncio %{target}"
         create_custom_emoji: "%{name} subiu unha nova emoticona %{target}"
         create_domain_allow: "%{name} engadiu á listaxe branca o dominio %{target}"
         create_domain_block: "%{name} bloqueou o dominio %{target}"
         create_email_domain_block: "%{name} engadiu á listaxe negra o dominio de email %{target}"
-        demote_user: "%{name} rebaixou ó usuario %{target}"
+        demote_user: "%{name} degradou a usuaria %{target}"
+        destroy_announcement: "%{name} eliminou o anuncio %{target}"
         destroy_custom_emoji: "%{name} eliminou a emoticona %{target}"
         destroy_domain_allow: "%{name} eliminou o dominio %{target} da listaxe branca"
         destroy_domain_block: "%{name} desbloqueou o dominio %{target}"
         destroy_email_domain_block: "%{name} engadiu á lista branca o dominio de email %{target}"
         destroy_status: "%{name} eliminou o estado de %{target}"
-        disable_2fa_user: "%{name} desactivou o requirimento de dobre factor para o usuario %{target}"
+        disable_2fa_user: "%{name} desactivou o requirimento de dobre factor para a usuaria %{target}"
         disable_custom_emoji: "%{name} desactivou a emoticona %{target}"
-        disable_user: "%{name} desactivou o acceso á conta para o usuario %{target}"
+        disable_user: "%{name} desactivou o acceso á conta para a usuaria %{target}"
         enable_custom_emoji: "%{name} activou a emoticona %{target}"
-        enable_user: "%{name} activou o acceso á conta para o usuario %{target}"
+        enable_user: "%{name} activou o acceso á conta para a usuaria %{target}"
         memorialize_account: "%{name} converteu a conta de %{target} nunha páxina para a lembranza"
-        promote_user: "%{name} promocionou o usuario %{target}"
+        promote_user: "%{name} promoveu a usuaria %{target}"
         remove_avatar_user: "%{name} eliminou a imaxe de perfil de %{target}"
         reopen_report: "%{name} reabriu a denuncia %{target}"
-        reset_password_user: "%{name} restabeleceu o contrasinal do usuario %{target}"
+        reset_password_user: "%{name} restableceu o contrasinal da usuaria %{target}"
         resolve_report: "%{name} resolveu a denuncia %{target}"
         silence_account: "%{name} silenciou a conta de %{target}"
         suspend_account: "%{name} suspendeu a conta de %{target}"
         unassigned_report: "%{name} deixou de atribuír a denuncia %{target}"
         unsilence_account: "%{name} deixou de silenciar a conta de %{target}"
         unsuspend_account: "%{name} desactivou a suspensión da conta de %{target}"
+        update_announcement: "%{name} actualizou o anuncio %{target}"
         update_custom_emoji: "%{name} actualizou a emoticona %{target}"
         update_status: "%{name} actualizou o estado de %{target}"
       deleted_status: "(estado eliminado)"
       title: Rexistro de auditoría
+    announcements:
+      edit:
+        title: Editar anuncio
+      empty: Ningún anuncio atopado.
+      live: Ao vivo
+      new:
+        create: Crear anuncio
+        title: Novo anuncio
+      published: Publicado
+      time_range: Intre de tempo
+      title: Anuncios
     custom_emojis:
       assign_category: Atribuír categoría
       by_domain: Dominio
@@ -275,18 +289,18 @@ gl:
       hidden_service: Federación con servizos agochados
       open_reports: denuncias abertas
       pending_tags: cancelos agardando revisión
-      pending_users: usuarios agardando revisión
-      recent_users: Usuarios recentes
+      pending_users: usuarias agardando revisión
+      recent_users: Usuarias recentes
       search: Procura por texto completo
-      single_user_mode: Modo de usuario único
+      single_user_mode: Modo de usuaria única
       software: Software
       space: Uso de almacenamento
       title: Taboleiro
-      total_users: usuarios en total
+      total_users: usuarias en total
       trends: Tendencias
       week_interactions: interaccións desta semana
       week_users_active: activos desta semana
-      week_users_new: usuarios desta semana
+      week_users_new: usuarias esta semana
       whitelist_mode: Modo de listaxe branca
     domain_allows:
       add_new: Engadir dominio á listaxe branca
@@ -344,9 +358,6 @@ gl:
         create: Engadir dominio
         title: Nova entrada na listaxe negra de email
       title: Listaxe negra de email
-    followers:
-      back_to_account: Voltar á conta
-      title: Seguidores de %{acct}
     instances:
       by_domain: Dominio
       delivery_available: Entrega dispoñíbel
@@ -375,94 +386,96 @@ gl:
       title: Convites
     pending_accounts:
       title: Contas pendentes (%{count})
+    relationships:
+      title: Relacións de %{acct}
     relays:
       add_new: Engadir un novo repetidor
       delete: Eliminar
       description_html: Un <strong>repetidor da federación</strong> é un servidor intermedio que intercambia grandes volumes de toots públicos entre servidores que se suscriban e publiquen nel. <strong>Pode axudar a servidores pequenos e medios a descubrir contido no fediverso</strong>, o que de outro xeito precisaría que as usuarias locais seguisen a outra xente en servidores remotos.
       disable: Desactivar
-      disabled: Desactivada
+      disabled: Desactivado
       enable: Activar
-      enable_hint: Unha vez activado, o seu servidor suscribirase a todos os toots públicos de este servidor, e tamén comezará a eviar a el os toots públicos do servidor.
-      enabled: Activada
+      enable_hint: Unha vez activado, o teu servidor subscribirase a todos os toots públicos deste repetidor, e tamén comezará a enviar a el os toots públicos do servidor.
+      enabled: Activado
       inbox_url: URL do repetidor
-      pending: Agardando polo permiso do repetidor
+      pending: Agardando pola aprobación do repetidor
       save_and_enable: Gardar e activar
-      setup: Configurar a conexión ao repetidor
-      signatures_not_enabled: Os repetidores non funcionarán correctamente se o modo seguro ou lista branca están activados
+      setup: Configurar unha conexión ó repetidor
+      signatures_not_enabled: Os repetidores non funcionarán de xeito correcto se o modo seguro ou listaxe branca están activados
       status: Estado
       title: Repetidores
     report_notes:
-      created_msg: Creouse correctamente a nota do informe!
-      destroyed_msg: Nota do informe eliminouse con éxito!
+      created_msg: A nota da denuncia creouse de xeito correcto!
+      destroyed_msg: A nota da denuncia borrouse de xeito correcto!
     reports:
       account:
         notes:
           one: "%{count} nota"
           other: "%{count} notas"
         reports:
-          one: "%{count} informe"
-          other: "%{count} informes"
+          one: "%{count} denuncia"
+          other: "%{count} denuncias"
       action_taken_by: Acción tomada por
-      are_you_sure: Está segura?
-      assign_to_self: Asignarmo
+      are_you_sure: Estás seguro?
+      assign_to_self: Asignarme
       assigned: Moderador asignado
-      by_target_domain: Dominio da conta sobre a que informa
+      by_target_domain: Dominio da conta denunciada
       comment:
-        none: Nada
-      created_at: Reportado
+        none: Ningún
+      created_at: Denunciado
       mark_as_resolved: Marcar como resolto
       mark_as_unresolved: Marcar como non resolto
       notes:
         create: Engadir nota
-        create_and_resolve: Resolver con nota
-        create_and_unresolve: Voltar a abrir con nota
+        create_and_resolve: Resolver cunha nota
+        create_and_unresolve: Reabrir cunha nota
         delete: Eliminar
-        placeholder: Describe qué medidas foron tomadas, ou calquer outra información relacionada...
-      reopen: Voltar a abrir o informe
-      report: 'Informe #%{id}'
-      reported_account: Conta reportada
-      reported_by: Reportada por
+        placeholder: Describir que accións foron tomadas ou calquera outra novidade sobre esta denuncia...
+      reopen: Reabrir denuncia
+      report: 'Denuncia #%{id}'
+      reported_account: Conta denunciada
+      reported_by: Denunciado por
       resolved: Resolto
-      resolved_msg: Resolveuse con éxito o informe!
+      resolved_msg: Resolveuse con éxito a denuncia!
       status: Estado
-      title: Informes
+      title: Denuncias
       unassign: Non asignar
       unresolved: Non resolto
       updated_at: Actualizado
     settings:
       activity_api_enabled:
-        desc_html: Conta de estados publicados localmente, usuarias activas, e novos rexistros por semana
+        desc_html: Conta de estados publicados de xeito local, usuarias activas, e novos rexistros en períodos semanais
         title: Publicar estatísticas agregadas sobre a actividade da usuaria
       bootstrap_timeline_accounts:
-        desc_html: Separar múltiples nomes de usuaria con vírgulas. Só funcionarán as contas locais non bloqueadas. Si baldeiro, por omisión son todos os local admin.
-        title: Seguimentos por omisión para novas usuarias
+        desc_html: Separar os múltiples nomes de usuaria con vírgulas. Só funcionarán as contas locais non bloqueadas. Se fica baleiro, serán todos os administradores locais.
+        title: Seguimentos por defecto para novas contas
       contact_information:
-        email: e-mail de traballo
+        email: Email de negocios
         username: Nome de usuaria de contacto
       custom_css:
-        desc_html: Modificar o aspecto con CSS cargado en cada páxina
+        desc_html: Modificar a aparencia con CSS cargado en cada páxina
         title: CSS personalizado
       default_noindex:
         desc_html: Aféctalle a todas as usuarias que non cambiaron os axustes elas mesmas
         title: Por omisión exclúe as usuarias do indexado por servidores de busca
       domain_blocks:
-        all: Para todas
+        all: Para todos
         disabled: Para ninguén
-        title: Mostrar dominios bloqueados
+        title: Amosar dominios bloqueados
         users: Para usuarias locais conectadas
       domain_blocks_rationale:
-        title: Mostrar razón
+        title: Amosar motivo
       enable_bootstrap_timeline_accounts:
         title: Activar seguimentos por omisión para novas usuarias
       hero:
-        desc_html: Mostrado na portada. Recoméndase 600x100px como mínimo. Se non se establece, mostrará a imaxe por omisión do servidor
-        title: Imáxe Heróe
+        desc_html: Amosado na páxina principal. Polo menos 600x100px recomendados. Se non está definido, estará por defecto a miniatura do servidor
+        title: Imaxe do heroe
       mascot:
-        desc_html: Mostrado en varias páxinas. Recoméndase 293x205 como mínimo. Se non se establece publícase a mascota por omisión
+        desc_html: Amosado en varias páxinas. Polo menos 293x205px recomendados. Se non está definido, estará a mascota por defecto
         title: Imaxe da mascota
       peers_api_enabled:
         desc_html: Nomes de dominio que este servidor atopou no fediverso
-        title: Publicar lista de servidores descubertos
+        title: Publicar listaxe de servidores descobertos
       preview_sensitive_media:
         desc_html: A vista previa de ligazóns de outros sitios web mostrará unha imaxe incluso si os medios están marcados como sensibles
         title: Mostrar medios sensibles con vista previa OpenGraph
@@ -658,6 +671,9 @@ gl:
     hint_html: "<strong>Nota:</strong> Non che pediremos o contrasinal na seguinte hora."
     invalid_password: Contrasinal incorrecto
     prompt: Confirma o contrasinal para continuar
+  date:
+    formats:
+      default: "%d %b, %Y"
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -734,6 +750,7 @@ gl:
     hint_html: "<strong>¿Qué son as etiquetas destacadas?</strong> Móstranse destacadas no seu perfil público e permítenlle a outras persoas ver os seus toots públicos nos que as utilizou. Son unha ferramenta moi útil para facer seguimento de traballos creativos e proxectos a longo prazo."
   filters:
     contexts:
+      account: Perfís
       home: Liña temporal inicial
       notifications: Avisos
       public: Liñas temporais públicas
@@ -758,6 +775,8 @@ gl:
     all: Todo
     changes_saved_msg: Cambios gardados correctamente!!
     copy: Copiar
+    delete: Eliminar
+    edit: Editar
     no_batch_actions_available: Non hai accións en pila dispoñibles nesta páxina
     order_by: Ordenar por
     save_changes: Gardar cambios
@@ -916,11 +935,11 @@ gl:
     truncate: "&hellip;"
   polls:
     errors:
-      already_voted: Xa votou en esta sondaxe
+      already_voted: Xa votaches nesta enquisa
       duplicate_options: contén elementos duplicados
       duration_too_long: está moi lonxe no futuro
       duration_too_short: é demasiado cedo
-      expired: A sondaxe rematou
+      expired: A enquisa rematou
       invalid_choice: A opción de voto escollida non existe
       over_character_limit: non poden ter máis de %{max} caracteres cada unha
       too_few_options: debe ter máis de unha opción
@@ -929,11 +948,15 @@ gl:
     other: Outro
     posting_defaults: Valores por omisión
     public_timelines: Liñas temporais públicas
+  reactions:
+    errors:
+      unrecognized_emoji: non é unha emoticona recoñecida
   relationships:
     activity: Actividade da conta
     dormant: En repouso
     followers: Seguidoras
     following: Seguindo
+    invited: Convidado
     last_active: Último activo
     most_recent: Máis recente
     moved: Movida
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index ae3cc479c..9f0e2f948 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -341,13 +341,11 @@ hu:
       delete: Törlés
       destroyed_msg: E-mail domain sikeresen eltávolítva a feketelistáról
       domain: Domain
+      empty: Nincs email domain a feketelistán.
       new:
         create: Domain hozzáadása
         title: Új e-mail feketelista bejegyzés
       title: E-mail feketelista
-    followers:
-      back_to_account: Vissza a fiókhoz
-      title: "%{acct} követői"
     instances:
       by_domain: Domain
       delivery_available: Kézbesítés elérhető
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 2a759bc5f..8cdb2f1fc 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -308,6 +308,7 @@ id:
       private_comment: Komentar pribadi
       private_comment_hint: Komentar tentang pembatasan domain ini untuk penggunaan internal oleh moderator.
       public_comment: Komentar publik
+      public_comment_hint: Komentar tentang pembatasan domain ini untuk publik umum, jika mengiklankan daftar pembatasan domain diaktifkan.
       reject_media: Tolak berkas media
       reject_media_hint: Hapus file media yang tersimpan dan menolak semua unduhan nantinya. Tidak terpengaruh dengan suspen
       reject_reports: Tolak laporan
@@ -338,9 +339,6 @@ id:
         create: Tambah domain
         title: Entri daftar hitam surel baru
       title: Daftar hitam surel
-    followers:
-      back_to_account: Kembali Ke Akun
-      title: Pengikut %{acct}
     instances:
       by_domain: Domain
       delivery_available: Pengiriman tersedia
@@ -440,6 +438,9 @@ id:
         all: Kepada semua orang
         disabled: Tidak kepada siapa pun
         title: Lihat blokir domain
+        users: Ke pengguna lokal yang sudah login
+      domain_blocks_rationale:
+        title: Tampilkan alasan
       enable_bootstrap_timeline_accounts:
         title: Aktifkan opsi ikuti otomatis untuk pengguna baru
       hero:
diff --git a/config/locales/is.yml b/config/locales/is.yml
index a4d21deb2..70d6e4e75 100644
--- a/config/locales/is.yml
+++ b/config/locales/is.yml
@@ -52,7 +52,7 @@ is:
     choices_html: "%{name} hefur valið:"
     endorsements_hint: Þú getur auglýst efni frá fólki sem þú fylgir í vefviðmótinu og mun það birtast hér.
     featured_tags_hint: Þú getur gefið sérstökum myllumerkjum aukið vægi og birtast þau þá hér.
-    follow: Fylgja
+    follow: Fylgjast með
     followers:
       one: fylgjandi
       other: fylgjendur
@@ -344,9 +344,6 @@ is:
         create: Bæta við léni
         title: Ný færsla á bannlista fyrir tölvupóstföng
       title: Bannlisti yfir tölvupóstföng
-    followers:
-      back_to_account: Til baka í notandaaðgang
-      title: Fylgjast með %{acct}
     instances:
       by_domain: Lén
       delivery_available: Afhending er til taks
@@ -495,7 +492,7 @@ is:
         desc_html: Kynningarmálsgrein í API. Lýstu því hvað það er sem geri þennan Mastodon-þjón sérstakan, auk annarra mikilvægra upplýsinga. Þú getur notað HTML-einindi, sér í lagi <code>&lt;a&gt;</code> og <code>&lt;em&gt;</code>.
         title: Lýsing á vefþjóni
       site_description_extended:
-        desc_html: góður staður fyrir siðareglur, almennt regluverk, leiðbeiningar og annað sem gerir netþjóninni þinn sérstakann. Þú getur notað HTML-einindi
+        desc_html: Góður staður fyrir siðareglur, almennt regluverk, leiðbeiningar og annað sem gerir netþjóninni þinn sérstakann. Þú getur notað HTML-einindi
         title: Sérsniðnar ítarlegar upplýsingar
       site_short_description:
         desc_html: Birt á hliðarspjaldi og í lýsigögnum. Lýstu því hvað Mastodon gerir og hvað það er sem geri þennan vefþjón sérstakan, í einni málsgrein.
@@ -645,7 +642,7 @@ is:
   authorize_follow:
     already_following: Þú ert að þegar fylgjast með þessum aðgangi
     error: Því miður, það kom upp villa við að fletta upp fjartengda notandaaðgangnum
-    follow: Fylgja
+    follow: Fylgjast með
     follow_request: 'Þú sendir beiðni um að fylgjast með til:'
     following: 'Tókst! Þú ert núna að fylgjast með:'
     post_follow:
@@ -731,6 +728,7 @@ is:
     add_new: Bæta við nýju
     errors:
       limit: Þú ert þegar búin/n að gefa hámarksfjölda myllumerkja aukið vægi
+    hint_html: "<strong>Hvað eru myllumerki með aukið vægi?</strong> Þau eru birt áberandi á opinbera notandasniðinu þínu og gera fólki kleift að fletta í gegnum opinberu færslurnar þínar sérstaklega undir þessum myllumerkjum. Þau eru frábær aðferð við að halda utan um skapandi vinnu eða langtíma verkefni."
   filters:
     contexts:
       home: Heimatímalína
@@ -741,6 +739,7 @@ is:
       title: Breyta síu
     errors:
       invalid_context: Ekkert eða ógilt samhengi var gefið
+      invalid_irreversible: Óendurkræf síun virkar bara í sambandi við heimasvæði eða tilkynningar
     index:
       delete: Eyða
       empty: Þú ert ekki með neinar síur.
@@ -770,6 +769,11 @@ is:
     authorize_connection_prompt: Auðkenna dulkóðaða tengingu?
     errors:
       failed: Dulrituð tenging mistókst, endilega reyndu aftur frá %{provider}.
+      keybase:
+        invalid_token: Keybase-teikn eru tætigildi undirritana og verða að vera 66 hex-stafir
+        verification_failed: Keybase skilur þetta teikn ekki sem undirritun Keybase-notandans %{kb_username}. Prófaðu aftur í Keybase.
+      wrong_user: Get ekki búið til sannvottun fyrir %{proving} á meðan skráð er inn sem %{current}. Skráðu inn sem %{proving} og prófaðu aftur.
+    explanation_html: Hér geturðu tengt dulritað önnur auðkenni þín, eins og t.d. Keybase-notandasnið. Þetta gerir öðru fólki kleift að senda þér dulrituð skilaboð og að treysta efni sem þú sendir þeim.
     i_am_html: Ég er %{username} á %{service}.
     identity: Auðkenni
     inactive: Óvirkt
@@ -783,10 +787,12 @@ is:
       merge_long: Halda fyrirliggjandi færslum og bæta við nýjum
       overwrite: Skrifa yfir
       overwrite_long: Skipta út fyrirliggjandi færslum með þeim nýju
+    preface: Þú getur flutt inn gögn sem þú hefur flutt út frá öðrum vefþjóni, svo sem lista yfir fólk sem þú fylgist með eða útilokar.
+    success: Það tókst að senda inn gögnin þín og verður unnið með þau þegar færi gefst
     types:
       blocking: Listi yfir útilokanir
       domain_blocking: Listi yfir útilokanir léna
-      following: Listi yfir fylgjendur
+      following: Listi yfir þá sem fylgst er með
       muting: Listi yfir þagganir
     upload: Senda inn
   in_memoriam_html: Minning.
@@ -844,9 +850,11 @@ is:
       backreference_required: Það verður fyrst að stilla nýja aðganginn til að bakvísa á þennan aðgang
       before: 'Áður en haldið er áfram, skaltu lesa þessa minnispunkta gaumgæfilega:'
       cooldown: Eftir yfirfærslu/flutning kemur kælingartímabil á meðan þú getur ekki flutt þig aftur
+      disabled_account: Núverandi aðgangur þinn verður ekki nothæfur að fullu eftir þetta. Hinsvegar muntu geta flutt út gögn af honum og einnig endurvirkjað hann.
       followers: Þessi aðgerð mun flytja alla fylgjendur af núverandi aðgangi yfir á nýja aðganginn
       only_redirect_html: Einnig geturðu <a href="%{path}">einungis sett upp endurbeiningu á notandasniðið þitt</a>.
       other_data: Engin önnur gögn munu flytjast sjálfvirkt
+      redirect: Notandasnið aðgangsins verður uppfært með athugasemd um endurbeininguna og verður undanþegið frá leitum
   moderation:
     title: Umsjón
   notification_mailer:
@@ -854,6 +862,9 @@ is:
       action: Skoða allar tilkynningar
       body: Hér er stutt yfirlit yfir þau skilaboð sem þú gætir hafa misst af síðan þú leist inn síðast %{since}
       mention: "%{name} minntist á þig í:"
+      new_followers_summary:
+        one: Að auki, þú hefur fengið einn nýjan fylgjanda á meðan þú varst fjarverandi! Húh!
+        other: Að auki, þú hefur fengið %{count} nýja fylgjendur á meðan þú varst fjarverandi! Frábært!
       subject:
         one: "1 ný tilkynning síðan þú leist inn síðast \U0001F418"
         other: "%{count} nýjar tilkynningar síðan þú leist inn síðast \U0001F418"
@@ -873,6 +884,9 @@ is:
       title: Ný beiðni um að fylgjast með
     mention:
       action: Svara
+      body: "%{name} minntist á þig í:"
+      subject: "%{name} minntist á þig"
+      title: Ný tilvísun
     reblog:
       body: "%{name} endurbirti stöðufærsluna þína:"
       subject: "%{name} endurbirti stöðufærsluna þína"
@@ -904,6 +918,7 @@ is:
       duration_too_long: er of langt inn í framtíðina
       duration_too_short: er of snemma
       expired: Könnuninni er þegar lokið
+      invalid_choice: Þessi valkostur er ekki til
       over_character_limit: geta ekki verið lengri en %{max} stafir hvert
       too_few_options: verður að vera með fleiri en eitt atriði
       too_many_options: getur ekki innihaldið meira en %{max} atriði
@@ -928,8 +943,9 @@ is:
     status: Staða aðgangs
   remote_follow:
     acct: Settu inn notandanafn@lén þaðan sem þú vilt vera virk/ur
+    missing_resource: Gat ekki fundið endurbeiningarslóðina fyrir notandaaðganginn þinn
     no_account_html: Ertu ekki með aðgang? Þú getur <a href='%{sign_up_path}' target='_blank'>nýskráð þig hér</a>
-    proceed: Halda áfram í að fylgja
+    proceed: Halda áfram í að fylgjast með
     prompt: 'Þú ætlar að fara að fylgjast með:'
     reason_html: "<strong>Hvers vegna er þetta skref nauðsynlegt?</strong> <code>%{instance}</code> er ekki endilega netþjónninn þar sem þú ert skráð/ur, þannig að við verðum að endurbeina þér á heimaþjóninn þinn fyrst."
   remote_interaction:
@@ -998,7 +1014,7 @@ is:
     development: Þróun
     edit_profile: Breyta notandasniði
     export: Útflutningur gagna
-    featured_tags: Myllumerki í umræðunni
+    featured_tags: Myllumerki með aukið vægi
     identity_proofs: Sannanir á auðkenni
     import: Flytja inn
     import_and_export: Inn- og útflutningur
@@ -1148,21 +1164,32 @@ is:
       default: "%d. %b, %Y, %H:%M"
       month: "%b %Y"
   two_factor_authentication:
+    code_hint: Settu inn kóðann sem auðkenningarforritið útbjó til staðfestingar
+    description_html: Ef þú virkjar <strong>tvíþátta auðkenningu</strong> mun innskráning krefjast þess að þú hafir símann þinn við hendina, með honum þarf að útbúa öryggisteikn sem þú þarft að setja inn.
     disable: Gera óvirkt
     enable: Virkja
     enabled: Tveggja-þátta auðkenning er virk
     enabled_success: Það tókst að virkja tveggja-þátta auðkenningu
     generate_recovery_codes: Útbúa endurheimtukóða
+    instructions_html: "<strong>Skannaðu þennar QR-kóða inn í Google Authenticator eða álíka TOTP-forrit á símanum þínum</strong>. Héðan í frá mun það forrit útbúa teikn sem þú verður að setja inn til að geta skráð þig inn."
+    lost_recovery_codes: Endurheimtukóðar gera þér kleift að fá aftur samband við notandaaðganginn þinn ef þú tapar símanum þínum. Ef þú aftur hefur tapað endurheimtukóðunum, geturðu endurgert þá hér. Gömlu endurheimtukóðarnir verða þá ógiltir.
+    manual_instructions: 'Ef þú getur ekki skannað QR-kóðann og verður að setja hann inn handvirkt, þá er hér leyniorðið á textaformi:'
     recovery_codes: Kóðar fyrir endurheimtingu öryggisafrits
+    recovery_codes_regenerated: Það tókst að endurgera endurheimtukóða
+    recovery_instructions_html: Ef þú tapar símanum þínum geturðu notað einn af endurheimtukóðunum hér fyrir neðan til að fá aftur samband við notandaaðganginn þinn. <strong>Geymdu endurheimtukóðana á öruggum stað</strong>. Sem dæmi gætirðu prentað þá út og geymt með öðrum mikilvægum skjölum.
     setup: Setja upp
     wrong_code: Kóðinn sem þú settir inn er ógildur! Eru klukkur netþjónsins og tækisins réttar?
   user_mailer:
     backup_ready:
+      explanation: Þú baðst um fullt öryggisafrit af Mastodon notandaaðgangnum þínum. Það er núna tilbúið til niðurhals!
       subject: Safnskráin þín er tilbúin til niðurhals
       title: Taka út í safnskrá
     warning:
       explanation:
+        disable: Á meðan aðgangurinn þinn er frystur, eru gögn aðgangsins ósnert, en þú getur ekki framkvæmt neinar aðgerðir fyrr en honum hefur verið aflæst.
+        silence: Á meðan aðgangurinn þinn er takmarkaður, mun aðeins fólk sem þegar fylgist með þér sjá tístin þín á þessum vefþjóni, auk þess sem lokað gæti verið á þig á ýmsum opinberum listum. Aftur á móti geta aðrir gerst fylgjendur þínir handvirkt.
         suspend: Aðgangurinn þinn hefur verið settur í biðstöðu, öll þín tíst og innsent myndefni hafa verið óafturkræft fjarlægð af þessum vefþjóni, sem og af þeim vefþjónum þar sem þú áttir þér fylgjendur.
+      get_in_touch: Þú getur svarað þessum tölvupósti til að setja þig í samband við umsjónarmenn %{instance}.
       review_server_policies: Yfirfara reglur vefþjónsins
       statuses: 'Sérstaklega fyrir:'
       subject:
@@ -1177,17 +1204,28 @@ is:
         suspend: Notandaaðgangur í bið
     welcome:
       edit_profile_action: Setja upp notandasnið
+      edit_profile_step: Þú getur sérsniðið notandasniðið þitt með því að senda inn auðkennismynd, síðuhaus, breytt birtingarnafninu þínu og ýmislegt fleira. Ef þú vilt yfirfara nýja fylgjendur áður en þeim er leyft að fylgjast með þér geturðu læst aðgangnum þínum.
       explanation: Hér eru nokkrar ábendingar til að koma þér í gang
       final_action: Byrjaðu að skrifa
+      final_step: 'Byrjaðu að tjá þig! Jafnvel án fylgjenda geta aðrir séð opinberar færslur frá þér, til dæmis á staðværu tímalínunni og í myllumerkjum. Þú gætir jafnvel viljað kynna þig með myllumerkinu #introductions.'
+      full_handle: Fullt auðkenni þitt
+      full_handle_hint: Þetta er það sem þú myndir gefa upp við vini þína svo þeir geti sent þér skilaboð eða fylgst með þér af öðrum netþjóni.
       review_preferences_action: Breyta kjörstillingum
+      review_preferences_step: Gakktu úr skugga um að kjörstillingarnar séu eins og þú vilt hafa þær, eins og t.d. hvaða tölvupóst þú vilt fá, eða hvaða stig friðhelgi þú vilt að færslurnar þínar hafi sjálfgefið. Ef þú hefur ekkert á móti sjónrænu áreiti geturðu virkjað sjálvirka spilun GIF-hreyfimynda.
       subject: Velkomin í Mastodon
+      tip_federated_timeline: Sameiginlega tímalínan er færibandasýn á Mastodon netkerfið. En hún inniheldur bara fólk sem nágrannar þínir eru áskrifendur að, þannig að hún er ekki tæmandi.
+      tip_following: Sjálfgefið er að þú fylgist með stjórnanda eða stjórnendum vefþjónsins. Til að finna fleira áhugavert fólk ættirðu að kíkja á staðværu og sameiginlegu tímalínurnar.
       tip_local_timeline: Staðværa tímalínan er færibandasýn á allt fólkið á %{instance}. Þetta eru þínir næstu nágrannar!
+      tip_mobile_webapp: Ef farsímavafrinn býður þér að bæta Mastodon á heimaskjáinn þinn, muntu geta tekið á móti ýti-tilkynningum. Það virkar á ýmsa vegu eins og um uppsett forrit sé að ræða!
       tips: Ábendingar
       title: Velkomin/n um borð, %{name}!
   users:
     follow_limit_reached: Þú getur ekki fylgst með fleiri en %{limit} aðilum
     invalid_email: Tölvupóstfangið er ógilt
     invalid_otp_token: Ógildur tveggja-þátta kóði
+    otp_lost_help_html: Ef þú hefur misst aðganginn að hvoru tveggja, geturðu sett þig í samband við %{email}
+    seamless_external_login: Innskráning þín er í gegnum utanaðkomandi þjónustu, þannig að stillingar fyrir lykilorð og tölvupóst eru ekki aðgengilegar.
     signed_in_as: 'Skráð inn sem:'
   verification:
+    explanation_html: 'Þú getur <strong>vottað að þú sért eigandi og ábyrgur fyrir tenglunum í lýsigögnum notandasniðsins þíns</strong>. Til að það virki, þurfa vefsvæðin sem vísað er í að innihalda tengil til baka í Mastodon-notandasniðið. Tengillinn sem vísar til baka <strong>verður</strong> að vera með <code>rel="me"</code> eigindi. Textinn í tenglinum skiptir ekki máli. Hérna er dæmi:'
     verification: Sannprófun
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 7bce8d3d6..a7e811e22 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -344,9 +344,6 @@ it:
         create: Aggiungi dominio
         title: Nuova voce della lista nera delle email
       title: Lista nera email
-    followers:
-      back_to_account: Torna all'account
-      title: Seguaci di %{acct}
     instances:
       by_domain: Dominio
       delivery_available: Distribuzione disponibile
@@ -922,6 +919,7 @@ it:
       duration_too_long: è troppo lontano nel futuro
       duration_too_short: è troppo presto
       expired: Il sondaggio si è già concluso
+      invalid_choice: L'opzione di voto scelta non esiste
       over_character_limit: non possono essere più lunghi di %{max} caratteri ciascuno
       too_few_options: deve avere più di un elemento
       too_many_options: non può contenere più di %{max} elementi
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index e26fe3942..ce15f4195 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -224,6 +224,8 @@ ja:
         update_status: "%{name} さんが %{target} さんの投稿を更新しました"
       deleted_status: "(削除済)"
       title: 操作履歴
+    announcements:
+      title: 告知
     custom_emojis:
       assign_category: カテゴリーを割り当て
       by_domain: ドメイン
@@ -339,9 +341,6 @@ ja:
         create: ドメインを追加
         title: メールアドレス用ブラックリスト新規追加
       title: メールブラックリスト
-    followers:
-      back_to_account: 戻る
-      title: "%{acct}さんのフォロワー"
     instances:
       by_domain: ドメイン
       delivery_available: 配送可能
@@ -750,6 +749,8 @@ ja:
     all: すべて
     changes_saved_msg: 正常に変更されました!
     copy: コピー
+    delete: 削除
+    edit: 編集
     no_batch_actions_available: このページに一括操作はありません
     order_by: 並び順
     save_changes: 変更を保存
@@ -910,6 +911,7 @@ ja:
       duration_too_long: が長過ぎます
       duration_too_short: が短過ぎます
       expired: アンケートは既に終了しました
+      invalid_choice: 選択された項目は存在しません
       over_character_limit: は%{max}文字より長くすることはできません
       too_few_options: は複数必要です
       too_many_options: は%{max}個までです
diff --git a/config/locales/kab.yml b/config/locales/kab.yml
index 629793736..5cedbd364 100644
--- a/config/locales/kab.yml
+++ b/config/locales/kab.yml
@@ -1,7 +1,7 @@
 ---
 kab:
   about:
-    about_hashtag_html: Tigi d tiberraḥin tizuyaz, ɣur-sent <strong>#%{hashtag}</strong>. Tzemreḍ ad tesdemreḍ akked yid-sent ma tesɛiḍ amiḍan deg kra n umḍiq deg fediverse.
+    about_hashtag_html: Tigi d tiberraḥin tizuyaz, γur-sent <strong>#%{hashtag}</strong>. Tzemreḍ ad tesdemreḍ akked yid-sent ma tesɛiḍ amiḍan deg kra n umḍiq deg fediverse.
     about_mastodon_html: 'Azeṭṭa ametti n uzekka: Ulac deg-s asussen, ulac taɛessast n tsuddiwin fell-ak, yebna ɣef leqder d ttrebga, daɣen d akeslemmas! Akked Mastudun, isefka-inek ad qimen inek!'
     about_this: Ɣef
     active_count_after: yekker
@@ -215,9 +215,6 @@ kab:
         create: Rnu taγult
         title: Timerna n taɣult tamaynut n imayl ɣer tebdart taberkant
       title: Tabdart taberkant n imayl
-    followers:
-      back_to_account: Uγal γer umiḍan
-      title: Imeḍfaṛen n %{acct}
     instances:
       by_domain: Taγult
       delivery_available: Yella usiweḍ
@@ -259,6 +256,9 @@ kab:
         all: Ɣef medden akk
         disabled: Ɣef yiwen ala
         users: Ɣef yimseqdacen idiganen i yeqqnen
+      site_description:
+        title: Aglam n uqeddac
+      site_title: Isem n uqeddac
       title: Iγewwaṛen n usmel
     statuses:
       batch:
diff --git a/config/locales/kk.yml b/config/locales/kk.yml
index 4ef87a5bc..74c486ef6 100644
--- a/config/locales/kk.yml
+++ b/config/locales/kk.yml
@@ -344,9 +344,6 @@ kk:
         create: Add dоmain
         title: New e-mail blаcklist entry
       title: E-mail қаратізімі
-    followers:
-      back_to_account: Back To Accоunt
-      title: "%{acct} оқырмандары"
     instances:
       by_domain: Domаin
       delivery_available: Жеткізу қол жетімді
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 25bb714ef..21ea5b554 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -341,9 +341,6 @@ ko:
         create: 차단 규칙 생성
         title: 새 이메일 도메인 차단
       title: Email 도메인 차단
-    followers:
-      back_to_account: 계정으로 돌아가기
-      title: "%{acct}의 팔로워"
     instances:
       by_domain: 도메인
       delivery_available: 전송 가능
@@ -924,6 +921,7 @@ ko:
     dormant: 휴면
     followers: 팔로워
     following: 팔로잉
+    invited: 초대됨
     last_active: 마지막 활동
     most_recent: 가장 최근
     moved: 이동함
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index 41f0284d8..9af094c01 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -261,9 +261,6 @@ lt:
         create: Pridėto domeną
         title: Naujas el pašto juodojo sąrašo įtraukimas
       title: El pašto juodasis sąrašas
-    followers:
-      back_to_account: Atgal Į Paskyrą
-      title: "%{acct} Sekėjai"
     instances:
       by_domain: Domenas
       delivery_available: Pristatymas galimas
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 5975ba68e..092a46d57 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -343,9 +343,6 @@ nl:
         create: Blokkeren
         title: Nieuw e-maildomein blokkeren
       title: E-maildomeinen blokkeren
-    followers:
-      back_to_account: Terug naar account
-      title: Volgers van %{acct}
     instances:
       by_domain: Domein
       delivery_available: Bezorging is mogelijk
diff --git a/config/locales/nn.yml b/config/locales/nn.yml
index 4eb8c69b3..c61523efe 100644
--- a/config/locales/nn.yml
+++ b/config/locales/nn.yml
@@ -331,9 +331,6 @@ nn:
         create: Legg til domene
         title: Ny blokkeringsoppføring av e-postdomene
       title: Blokkerte e-postadresser
-    followers:
-      back_to_account: Tilbake til konto
-      title: "%{acct} sine fylgjarar"
     instances:
       by_domain: Domene
       delivery_available: Levering er tilgjengelig
diff --git a/config/locales/no.yml b/config/locales/no.yml
index 51d0eb1bd..12772f335 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -317,9 +317,6 @@
         create: Legg til domene
         title: Ny blokkeringsoppføring av e-postdomene
       title: Blokkering av e-postdomene
-    followers:
-      back_to_account: Tilbake til kontoen
-      title: "%{acct} sine følgere"
     instances:
       by_domain: Domene
       delivery_available: Levering er tilgjengelig
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index 985d2a311..ea0729f74 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -331,9 +331,6 @@ oc:
         create: Crear un blocatge
         title: Nòu blocatge de domeni de corrièl
       title: Blocatge de domeni de corrièl
-    followers:
-      back_to_account: Tornar al compte
-      title: Seguidors de %{acct}
     instances:
       by_domain: Domeni
       delivery_available: Liurason disponibla
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index e243162e8..774319a5a 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -346,9 +346,6 @@ pl:
         create: Utwórz blokadę
         title: Nowa blokada domeny e-mail
       title: Blokowanie domen e-mail
-    followers:
-      back_to_account: Wróć do konta
-      title: Śledzący %{acct}
     instances:
       by_domain: Domena
       delivery_available: Doręczanie jest dostępne
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 7fe2c8946..fb741b0d0 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -344,9 +344,6 @@ pt-BR:
         create: Adicionar domínio
         title: Novo domínio de e-mail bloqueado
       title: Lista de bloqueio de domínios de e-mail
-    followers:
-      back_to_account: Voltar para a conta
-      title: Seguidores de %{acct}
     instances:
       by_domain: Domínio
       delivery_available: Envio disponível
@@ -375,6 +372,8 @@ pt-BR:
       title: Convites
     pending_accounts:
       title: Contas pendentes (%{count})
+    relationships:
+      title: Relações de %{acct}
     relays:
       add_new: Adicionar novo repetidor
       delete: Excluir
@@ -734,6 +733,7 @@ pt-BR:
     hint_html: "<strong>O que são hashtags em destaque?</strong> Elas são mostradas no seu perfil público e permitem que as pessoas acessem seus toots públicos que contenham especificamente essas hashtags. São uma excelente ferramenta para acompanhar os trabalhos criativos ou os projetos de longo prazo."
   filters:
     contexts:
+      account: Perfis
       home: Página inicial
       notifications: Notificações
       public: Linhas públicas
@@ -934,6 +934,7 @@ pt-BR:
     dormant: Inativo
     followers: Seguidores
     following: Seguindo
+    invited: Convidado
     last_active: Última atividade
     most_recent: Mais recente
     moved: Mudou-se
diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml
index 8628c5774..deee43287 100644
--- a/config/locales/pt-PT.yml
+++ b/config/locales/pt-PT.yml
@@ -344,9 +344,6 @@ pt-PT:
         create: Adicionar domínio
         title: Novo bloqueio de domínio de email
       title: Bloqueio de Domínio de Email
-    followers:
-      back_to_account: Voltar à conta
-      title: Seguidores de %{acct}
     instances:
       by_domain: Domínio
       delivery_available: Entrega disponível
@@ -375,6 +372,8 @@ pt-PT:
       title: Convites
     pending_accounts:
       title: Contas pendentes (%{count})
+    relationships:
+      title: Relações de %{acct}
     relays:
       add_new: Adicionar novo repetidor
       delete: Apagar
@@ -734,6 +733,7 @@ pt-PT:
     hint_html: "<strong>O que são hashtags em destaque?</strong> Elas são exibidas de forma bem visível no seu perfil público e permitem que as pessoas consultem as suas publicações públicas especificamente sob essas hashtags. São uma ótima ferramenta para manter o controlo de trabalhos criativos ou projetos de longo prazo."
   filters:
     contexts:
+      account: Perfis
       home: Cronologia inicial
       notifications: Notificações
       public: Cronologias públicas
@@ -934,6 +934,7 @@ pt-PT:
     dormant: Inativo
     followers: Seguidores
     following: A seguir
+    invited: Convidado
     last_active: Última atividade
     most_recent: Mais recente
     moved: Mudou-se
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index a62995721..bd019ee17 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -140,7 +140,7 @@ ru:
         remote: Удаленные
         title: Размещение
       login_status: Статус учётной записи
-      media_attachments: Мультимедийные вложения
+      media_attachments: Файлы мультимедиа
       memorialize: Сделать мемориалом
       moderation:
         active: Действующие
@@ -359,9 +359,6 @@ ru:
         create: Создать блокировку
         title: Новая блокировка по домену
       title: Блокировка e-mail доменов
-    followers:
-      back_to_account: Вернуться к учётной записи
-      title: Подписчики пользователя %{acct}
     instances:
       by_domain: Домен
       delivery_available: Доставка возможна
@@ -392,6 +389,8 @@ ru:
       title: Приглашения
     pending_accounts:
       title: Ожидающие учетные записи (%{count})
+    relationships:
+      title: Связи %{acct}
     relays:
       add_new: Добавить ретранслятор
       delete: Удалить
@@ -550,11 +549,11 @@ ru:
       deleted: Удалено
       failed_to_execute: Не удалось выполнить
       media:
-        title: Файлы медиа
-      no_media: Без медиа
+        title: Файлы мультимедиа
+      no_media: Без файлов
       no_status_selected: Ничего не изменилось, так как ни один пост не был выделен
       title: Посты пользователя
-      with_media: С медиа
+      with_media: С файлами
     tags:
       accounts_today: Уникальных использований за сегодня
       accounts_week: Уникальных использований за эту неделю
@@ -755,6 +754,7 @@ ru:
     hint_html: "<strong>Особенные хэштеги</strong> отображаются в вашем профиле и позволяют людям просматривать ваши посты, отмеченные ими. Это отличный инструмент для отслеживания долгосрочных проектов и творческих работ."
   filters:
     contexts:
+      account: Посты в профилях
       home: Домашняя лента
       notifications: Уведомления
       public: Публичные ленты
@@ -963,6 +963,7 @@ ru:
     dormant: Заброшенная
     followers: Подписчики
     following: Подписки
+    invited: Приглашённые
     last_active: По последней активности
     most_recent: По недавности
     moved: Мигрировавшая
diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml
index e3355e8c5..f7a38a92c 100644
--- a/config/locales/simple_form.ar.yml
+++ b/config/locales/simple_form.ar.yml
@@ -42,7 +42,7 @@ ar:
         setting_use_pending_items: إخفاء تحديثات الخط وراء نقرة بدلًا مِن التمرير التلقائي للتدفق
         username: اسم المستخدم الخاص بك سوف يكون فريدا مِن نوعه على %{domain}
       featured_tag:
-        name: 'رُبَّما تريد·ين استخدام واحد مِن هذه:'
+        name: 'رُبَّما تريد·ين استخدام واحد مِن بين هذه:'
       form_challenge:
         current_password: إنك بصدد الدخول إلى منطقة آمنة
       imports:
@@ -123,7 +123,7 @@ ar:
         setting_theme: سمة الموقع
         setting_trends: اعرض ما يُتداوَل اليوم
         setting_unfollow_modal: إظهار مربع حوار للتأكيد قبل إلغاء متابعة أي حساب
-        setting_use_blurhash: أظهر ألوانًا متدرّجة على الوسائط الحساسة
+        setting_use_blurhash: أظهر ألوانًا متدرّجة على الوسائط المَخفية
         setting_use_pending_items: الوضع البطيء
         severity: القوّة
         type: صيغة الاستيراد
diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml
index 3fae05830..d43ab04fe 100644
--- a/config/locales/simple_form.ca.yml
+++ b/config/locales/simple_form.ca.yml
@@ -31,7 +31,7 @@ ca:
         locale: El llenguatge de l’interfície d’usuari, els correus i les notificacions push
         locked: Requereix que aprovis manualment els seguidors
         password: Utilitza com a mínim 8 caràcters
-        phrase: Es combinarà independentment del format en el text o l'avís de contingut del bram
+        phrase: Es combinarà independentment del format en el text o l'avís de contingut del tut
         scopes: A quines API es permetrà a l'aplicació accedir. Si selecciones un àmbit d'alt nivell, no cal que seleccionis un d'individual.
         setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts)
         setting_default_sensitive: Els mèdia sensibles estan ocults per defecte i es poden revelar amb un clic
diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml
index 84787a6c5..0863cc4a8 100644
--- a/config/locales/simple_form.de.yml
+++ b/config/locales/simple_form.de.yml
@@ -14,6 +14,12 @@ de:
         text_html: Optional. Du kannst Beitragssyntax nutzen. Du kannst <a href="%{path}">Warnungsvorlagen</a> benutzen um Zeit zu sparen
         type_html: Wähle aus, was du mit <strong>%{acct}</strong> machen möchtest
         warning_preset_id: Optional. Du kannst immer noch eigenen Text an das Ende der Vorlage hinzufügen
+      announcement:
+        all_day: Wenn aktiviert werden nur die Daten des Zeitraums angezeigt
+        ends_at: Optional. Die Ankündigung wird zu diesem Zeitpunkt automatisch zurückgezogen
+        scheduled_at: Leer lassen um die Ankündigung sofort zu veröffentlichen
+        starts_at: Optional. Falls deine Ankündigung an einen bestimmten Zeitraum gebunden ist
+        text: Du kannst die Toot-Syntax verwenden. Bitte beachte den Platz, den die Ankündigung auf dem Bildschirm des Benutzers einnehmen wird
       defaults:
         autofollow: Leute, die sich über deine Einladung registrieren, werden dir automatisch folgen
         avatar: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
@@ -83,6 +89,12 @@ de:
           silence: Stummschalten
           suspend: Deaktivieren und Benutzerdaten unwiderruflich löschen
         warning_preset_id: Benutze eine Warnungsvorlage
+      announcement:
+        all_day: Ganztägiges Ereignis
+        ends_at: Ereignisende
+        scheduled_at: Veröffentlichung planen
+        starts_at: Beginn des Ereignisses
+        text: Ankündigung
       defaults:
         autofollow: Eingeladene Nutzer sollen dir automatisch folgen
         avatar: Profilbild
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 8386c8cf1..2f0820906 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -14,6 +14,12 @@ en:
         text_html: Optional. You can use toot syntax. You can <a href="%{path}">add warning presets</a> to save time
         type_html: Choose what to do with <strong>%{acct}</strong>
         warning_preset_id: Optional. You can still add custom text to end of the preset
+      announcement:
+        all_day: When checked, only the dates of the time range will be displayed
+        ends_at: Optional. Announcement will be automatically unpublished at this time
+        scheduled_at: Leave blank to publish the announcement immediately
+        starts_at: Optional. In case your announcement is bound to a specific time range
+        text: You can use toot syntax. Please be mindful of the space the announcement will take up on the user's screen
       defaults:
         autofollow: People who sign up through the invite will automatically follow you
         avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
@@ -88,6 +94,12 @@ en:
           silence: Silence
           suspend: Suspend and irreversibly delete account data
         warning_preset_id: Use a warning preset
+      announcement:
+        all_day: All-day event
+        ends_at: End of event
+        scheduled_at: Schedule publication
+        starts_at: Begin of event
+        text: Announcement
       defaults:
         autofollow: Invite to follow your account
         avatar: Avatar
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index da3904680..20467dba4 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -7,11 +7,11 @@ es:
       account_migration:
         acct: Especifique el nombre de usuario@dominio de la cuenta a la cual se desea migrar
       account_warning_preset:
-        text: Puede usar sintaxis de barritadas, como URLs, etiquetas y menciones
+        text: Puede usar sintaxis de toots, como URLs, hashtags y menciones
       admin_account_action:
-        include_statuses: El usuario verá qué bramidos han causado la acción de moderación o advertencia
+        include_statuses: El usuario verá qué toots han causado la acción de moderación o advertencia
         send_email_notification: El usuario recibirá una explicación de lo que sucedió con respecto a su cuenta
-        text_html: Opcional. Puede usar sintaxis de bramidos. Puede añadir <a href="%{path}">configuraciones predefinidas de advertencia</a> para ahorrar tiempo
+        text_html: Opcional. Puede usar sintaxis de toots. Puede añadir <a href="%{path}">configuraciones predefinidas de advertencia</a> para ahorrar tiempo
         type_html: Elige qué hacer con <strong>%{acct}</strong>
         warning_preset_id: Opcional. Aún puede añadir texto personalizado al final de la configuración predefinida
       defaults:
@@ -27,20 +27,20 @@ es:
         fields: Puedes tener hasta 4 elementos mostrándose como una tabla en tu perfil
         header: PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px
         inbox_url: Copia la URL de la página principal del relés que quieres utilizar
-        irreversible: Las bramidos filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante
+        irreversible: Los toots filtrados desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante
         locale: El idioma de la interfaz de usuario, correos y notificaciones push
         locked: Requiere que manualmente apruebes seguidores y las publicaciones serán mostradas solamente a tus seguidores
         password: Utilice al menos 8 caracteres
-        phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una bramido
+        phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de un toot
         scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales.
-        setting_aggregate_reblogs: No mostrar nuevas rebramidos para las bramidos que han sido recientemente rebramidos (sólo afecta a las rebramidos recibidos recientemente)
+        setting_aggregate_reblogs: No mostrar nuevos retoots para los toots que han sido recientemente retooteados (sólo afecta a los retoots recibidos recientemente)
         setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click
         setting_display_media_default: Ocultar contenido multimedia marcado como sensible
         setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia
         setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible
         setting_hide_network: A quién sigues y quién te sigue no será mostrado en tu perfil
         setting_noindex: Afecta a tu perfil público y páginas de estado
-        setting_show_application: La aplicación que utiliza usted para publicar bramidos se mostrará en la vista detallada de sus bramidos
+        setting_show_application: La aplicación que utiliza usted para publicar toots se mostrará en la vista detallada de sus toots
         setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
         setting_use_pending_items: Ocultar actualizaciones cronológicas tras un clic en lugar de desplazar automáticamente la ristra
         username: Tu nombre de usuario será único en %{domain}
@@ -60,7 +60,7 @@ es:
       tag:
         name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible
       user:
-        chosen_languages: Cuando se marca, solo se mostrarán las barritadas en los idiomas seleccionados en las cronologías públicas
+        chosen_languages: Cuando se marca, solo se mostrarán los toots en los idiomas seleccionados en los timelines públicos
     labels:
       account:
         fields:
@@ -73,7 +73,7 @@ es:
       account_warning_preset:
         text: Texto predefinido
       admin_account_action:
-        include_statuses: Incluir en el correo electrónico a los bramidos denunciados
+        include_statuses: Incluir en el correo electrónico a los toots denunciados
         send_email_notification: Notificar al usuario por correo electrónico
         text: Aviso personalizado
         type: Acción
@@ -112,21 +112,21 @@ es:
         setting_advanced_layout: Habilitar interfaz web avanzada
         setting_aggregate_reblogs: Agrupar rebarritadas en las cronologías
         setting_auto_play_gif: Reproducir automáticamente los GIFs animados
-        setting_boost_modal: Mostrar ventana de confirmación antes de un Rebramido
-        setting_crop_images: Recortar a 16x9 las imágenes de los bramidos no expandidos
+        setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot
+        setting_crop_images: Recortar a 16x9 las imágenes de los toots no expandidos
         setting_default_language: Idioma de publicación
         setting_default_privacy: Privacidad de publicaciones
         setting_default_sensitive: Marcar siempre imágenes como sensibles
-        setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un bramido
+        setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un toot
         setting_display_media: Visualización multimedia
         setting_display_media_default: Por defecto
         setting_display_media_hide_all: Ocultar todo
         setting_display_media_show_all: Mostrar todo
-        setting_expand_spoilers: Siempre expandir los bramidos marcados con advertencias de contenido
+        setting_expand_spoilers: Siempre expandir los toots marcados con advertencias de contenido
         setting_hide_network: Ocultar tu red
         setting_noindex: Excluirse del indexado de motores de búsqueda
         setting_reduce_motion: Reducir el movimiento de las animaciones
-        setting_show_application: Mostrar aplicación usada para publicar bramidos
+        setting_show_application: Mostrar aplicación usada para publicar toots
         setting_system_font_ui: Utilizar la tipografía por defecto del sistema
         setting_theme: Tema del sitio
         setting_trends: Mostrar las tendencias de hoy
@@ -162,7 +162,7 @@ es:
         listable: Permitir que esta etiqueta aparezca en las búsquedas y en el directorio del perfil
         name: Etiqueta
         trendable: Permitir que esta etiqueta aparezca bajo tendencias
-        usable: Permitir a las barritadas usar esta etiqueta
+        usable: Permitir a los toots usar esta etiqueta
     'no': 'No'
     recommended: Recomendado
     required:
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index 7b9e466ee..0e5332f1e 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -14,6 +14,12 @@ fr:
         text_html: Optionnel. Vous pouvez utilisez la syntaxe des pouets. Vous pouvez <a href="%{path}">ajouter des présélections d’attention</a> pour économiser du temps
         type_html: Choisir que faire avec <strong>%{acct}</strong>
         warning_preset_id: Optionnel. Vous pouvez toujours ajouter un texte personnalisé à la fin de la présélection
+      announcement:
+        all_day: Si coché, seules les dates de l’intervalle de temps seront affichées
+        ends_at: Optionnel. L’annonce sera automatiquement dépubliée à ce moment
+        scheduled_at: Laisser vide pour publier l’annonce immédiatement
+        starts_at: Optionnel. Si votre annonce est liée à une période spécifique
+        text: Vous pouvez utiliser la syntaxe d’un pouet. Veuillez prendre en compte l’espace que l'annonce prendra sur l’écran de l'utilisateur
       defaults:
         autofollow: Les personnes qui s’inscrivent grâce à l’invitation vous suivront automatiquement
         avatar: Au format PNG, GIF ou JPG. %{size} maximum. Sera réduit à %{dimensions}px
@@ -83,6 +89,12 @@ fr:
           silence: Masquer
           suspend: Suspendre et effacer les données du compte de manière irréversible
         warning_preset_id: Utiliser un modèle d’avertissement
+      announcement:
+        all_day: Événement de toute la journée
+        ends_at: Fin de l’événement
+        scheduled_at: Planifier la publication
+        starts_at: Début de l’événement
+        text: Annonce
       defaults:
         autofollow: Invitation à suivre votre compte
         avatar: Image de profil
diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml
index 0fdf3e74e..0af6bd690 100644
--- a/config/locales/simple_form.gl.yml
+++ b/config/locales/simple_form.gl.yml
@@ -14,6 +14,12 @@ gl:
         text_html: Optativo. Pode utilizar formato no toot. Pode <a href="%{path}">engadir avisos preestablecidos</a> para aforrar tempo
         type_html: Escolla que facer con <strong>%{acct}</strong>
         warning_preset_id: Optativo. Poderá engadir texto persoalizado ao final do preestablecido
+      announcement:
+        all_day: Cando se marca, só serán amosadas as datas do intre de tempo
+        ends_at: Opcional. O anuncio non se publicará de xeito automático neste intre
+        scheduled_at: Déixao baleiro para publicar o anuncio de xeito inmediato
+        starts_at: Opcional. No caso de que o teu anuncio estea vinculado a un intre de tempo específico
+        text: Podes empregar a sintaxe do toot. Ten en conta o espazo que ocupará o anuncio na pantalla do usuario
       defaults:
         autofollow: As persoas que se conectaron a través de un convite seguirana automáticamente a vostede
         avatar: PNG, GIF ou JPG.  Máximo %{size}. Será reducida a %{dimensions}px
@@ -83,6 +89,12 @@ gl:
           silence: Acalar
           suspend: Suspender e eliminar irreversiblemente datos da conta
         warning_preset_id: Utilizar un aviso preestablecido
+      announcement:
+        all_day: Acontecemento diario
+        ends_at: Final do acontecemento
+        scheduled_at: Publicación programada
+        starts_at: Comezo do acontecemento
+        text: Anuncio
       defaults:
         autofollow: Convide a seguir a súa conta
         avatar: Avatar
diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml
index c6a89fb40..b7aabd5d1 100644
--- a/config/locales/simple_form.is.yml
+++ b/config/locales/simple_form.is.yml
@@ -6,31 +6,61 @@ is:
         acct: Tilgreindu notandanafn@lén á notandaaðgangnum sem þú vilt flytjast frá
       account_migration:
         acct: Tilgreindu notandanafn@lén á notandaaðgangnum sem þú vilt flytjast til
+      account_warning_preset:
+        text: Þú getur notað sömu skilgreiningar og fyrir tíst, svo sem URL-slóðir, myllumerki og tilvísanir
       admin_account_action:
+        include_statuses: Notandinn mun sjá hvaða tíst hafa valdið viðbrögðum umsjónarmanns eða aðvörun kerfisins
+        send_email_notification: Notandinn mun fá útskýringar á því hvað gerðist með notandaaðganginn hans
+        text_html: Valfrjálst. Þú getur notað sömu skilgreiningar og fyrir tíst. Þú getur <a href="%{path}">bætt inn forstilltum aðvörunum</a> til að spara tíma
         type_html: Veldu hvað eigi að gera við <strong>%{acct}</strong>
+        warning_preset_id: Valkvætt. Þú getur ennþá bætt sérsniðnum texta við enda forstillinga
       defaults:
+        autofollow: Fólk sem skráir sig í gegnum boðið mun sjálfkrafa fylgjast með þér
         avatar: PNG, GIF eða JPG. Mest %{size}. Verður smækkað í %{dimensions}px
         bot: Þessi aðgangur er aðallega til að framkvæma sjálfvirkar aðgerðir og gæti verið án þess að hann sé vaktaður reglulega
-        current_password: Í öryggisskyni skaltu setja inn lykiloðið fyrir þennan notandaaðgang
+        context: Eitt eða fleiri samhengi þar sem sían ætti að gilda
+        current_password: Í öryggisskyni skaltu setja inn lykilorðið fyrir þennan notandaaðgang
+        current_username: Til að staðfesta skaltu setja inn notandanafnið fyrir þennan notandaaðgang
+        digest: Er aðeins sent eftir lengri tímabil án virkni og þá aðeins ef þú hefur fengið persónuleg skilaboð á meðan þú hefur ekki verið á línunni
         discoverable: Persónusniðamappan er önnur leið til að láta notandaaðganginn þinn ná til fleiri lesenda
         email: Þú munt fá sendan staðfestingarpóst
+        fields: Þú getur birt allt að 4 atriði sem töflu á notandasniðinu þínu
         header: PNG, GIF eða JPG. Mest %{size}. Verður smækkað í %{dimensions}px
+        inbox_url: Afritaðu slóðina af forsíðu endurvarpans sem þú vilt nota
+        irreversible: Síuð tíst munu hverfa óendurkræft, jafnvel þó sían sé seinna fjarlægð
         locale: Tungumál notandaviðmótsins, tölvupósts og ýti-tilkynninga
         locked: Krefst þess að þú samþykkir fylgjendur handvirkt
         password: Notaðu minnst 8 stafi
+        phrase: Verður notað til samsvörunar burtséð frá stafstöðu texta eða viðvörunar vegna efnis í tísti
+        scopes: Að hvaða API-kerfisviðmótum forritið fær aðgang. Ef þú velur efsta-stigs svið, þarftu ekki að gefa einstakar heimildir.
+        setting_aggregate_reblogs: Ekki sýna nýjar endurbirtingar á tístum sem hafa nýlega verið endurbirt (hefur bara áhrif á ný-mótteknar endurbirtingar)
         setting_default_sensitive: Viðkvæmt myndefni er sjálfgefið falið og er hægt að birta með smelli
         setting_display_media_default: Fela myndefni sem merkt er viðkvæmt
         setting_display_media_hide_all: Alltaf fela allt myndefni
         setting_display_media_show_all: Alltaf birta myndefni sem merkt er viðkvæmt
-        setting_noindex: Hefur áhrip á opinbera notandasniðið þitt og stöðusíður
+        setting_hide_network: Hverjum þú fylgist með og hverjir fylgjast með þér verður ekki birt á notandasniðinu þínu
+        setting_noindex: Hefur áhrif á opinbera notandasniðið þitt og stöðusíður
+        setting_show_application: Nafnið á forritinu sem þú notar til að tísta mun birtast í ítarlegri sýn á tístunum þínum
         setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr
-        setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er í stað þess að hún skruni streyminu sjálfvirkt
+        setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt
+        username: Notandanafnið þitt verður einstakt á %{domain}
+        whole_word: Þegar stikkorð eða frasi er einungis tölur og bókstafir, verður það aðeins notað ef það samsvarar heilu orði
+      domain_allow:
+        domain: Þetta lén mun geta sótt gögn af þessum vefþjóni og tekið verður á móti innsendum gögnum frá léninu til vinnslu og geymslu
       featured_tag:
         name: 'Þú gætir viljað nota eitt af þessum:'
+      form_challenge:
+        current_password: Þú ert að fara inn á öryggissvæði
       imports:
         data: CSV-skrá sem flutt hefur verið út af öðrum Mastodon-þjóni
       invite_request:
         text: Þetta mun hjálpa okkur við að yfirfara umsóknina þína
+      sessions:
+        otp: 'Settu inn tveggja-þátta kóðann sem farsímaforritið útbjó eða notaðu einn af endurheimtukóðunum þínum:'
+      tag:
+        name: Þú getur aðeins breytt stafstöði mill há-/lágstafa, til gæmis til að gera þetta læsilegra
+      user:
+        chosen_languages: Þegar merkt er við þetta, birtast einungis tíst á völdum tungumálum á opinberum tímalínum
     labels:
       account:
         fields:
@@ -38,6 +68,10 @@ is:
           value: Efni
       account_alias:
         acct: Auðkenni gamla aðgangsins
+      account_migration:
+        acct: Auðkenni nýja aðgangsins
+      account_warning_preset:
+        text: Forstilltur texti
       admin_account_action:
         include_statuses: Innifela kærð tíst í tölvupóstinum
         send_email_notification: Láta notanda vita með tölvupósti
@@ -65,6 +99,7 @@ is:
         expires_in: Rennur út eftir
         fields: Lýsigögn notandasniðs
         header: Síðuhaus
+        inbox_url: URL-slóð á innhólf endurvarpa
         irreversible: Fella niður í staðinn fyrir að fela
         locale: Tungumál viðmóts
         locked: Læsa aðgangi
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index 3e2855e58..503610652 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -83,6 +83,8 @@ ja:
           silence: サイレンス
           suspend: 停止しアカウントのデータを恒久的に削除する
         warning_preset_id: プリセット警告文を使用
+      announcement:
+        text: 告知
       defaults:
         autofollow: 招待から参加後、あなたをフォロー
         avatar: アイコン
@@ -158,7 +160,7 @@ ja:
         pending_account: 新しいアカウントの承認が必要な時
         reblog: トゥートがブーストされた時
         report: 通報を受けた時
-        trending_tag: 未審査のハッシュタグが人気の時にメールで通知する
+        trending_tag: 未審査のハッシュタグが人気の時
       tag:
         listable: 検索とディレクトリへの使用を許可する
         name: ハッシュタグ
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index b127bee0f..b02ee8eca 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -350,9 +350,6 @@ sk:
         create: Pridaj doménu
         title: Nový email na zablokovanie
       title: Blokované emailové adresy
-    followers:
-      back_to_account: Späť na účet
-      title: Sledovatielia užívateľa %{acct}
     instances:
       by_domain: Doména
       delivery_available: Je v dosahu doručovania
@@ -917,6 +914,7 @@ sk:
       duration_too_long: je príliš ďaleko do budúcnosti
       duration_too_short: je príliš skoro
       expired: Anketa už skončila
+      invalid_choice: Zvolená hlasovacia možnosť neexistuje
       over_character_limit: každá nemôže byť dlhšia ako %{max} znakov
       too_few_options: musí mať viac ako jednu položku
       too_many_options: nemôže zahŕňať viac ako %{max} položiek
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index c078cea1b..afb928f11 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -320,9 +320,6 @@ sl:
         create: Dodaj domeno
         title: Nov vnos e-pošte na črni seznam
       title: Črni seznam e-pošt
-    followers:
-      back_to_account: Nazaj na račun
-      title: Sledilci od %{acct}
     instances:
       by_domain: Domena
       delivery_available: Na voljo je dostava
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index c4d95a6f7..6a7a945c4 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -266,9 +266,6 @@ sq:
         create: Shtoni përkatësi
         title: Zë i ri email në listë bllokimesh
       title: Listë bllokimesh email-esh
-    followers:
-      back_to_account: Mbrapsht Te Llogaria
-      title: Ndjekës të %{acct}
     instances:
       delivery_available: Ka shpërndarje të mundshme
       known_accounts:
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 23a826685..c68681215 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -280,9 +280,6 @@ sr:
         create: Додај домен
         title: Нова ставка е-поштe у црној листи
       title: Црна листа E-поште
-    followers:
-      back_to_account: Назад на налог
-      title: "%{acct} Пратиоци"
     instances:
       delivery_available: Достава је доступна
       known_accounts:
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index b94277825..0094ff06b 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -328,9 +328,6 @@ sv:
         create: Skapa domän
         title: Ny E-postdomänblocklistningsinmatning
       title: E-postdomänblock
-    followers:
-      back_to_account: Tillbaka till konto
-      title: "%{acct}'s följare"
     instances:
       by_domain: Domän
       moderation:
diff --git a/config/locales/th.yml b/config/locales/th.yml
index b7f4a5f34..02b8fc97e 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -306,9 +306,6 @@ th:
         create: เพิ่มโดเมน
         title: รายการบัญชีดำอีเมลใหม่
       title: บัญชีดำอีเมล
-    followers:
-      back_to_account: กลับไปที่บัญชี
-      title: ผู้ติดตามของ %{acct}
     instances:
       by_domain: โดเมน
       known_accounts:
@@ -386,7 +383,10 @@ th:
       custom_css:
         title: CSS ที่กำหนดเอง
       domain_blocks:
+        all: ให้กับทุกคน
+        disabled: ให้กับไม่มีใคร
         title: แสดงการปิดกั้นโดเมน
+        users: ให้กับผู้ใช้ในเว็บที่เข้าสู่ระบบ
       domain_blocks_rationale:
         title: แสดงคำชี้แจงเหตุผล
       enable_bootstrap_timeline_accounts:
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 1e80315c0..0fa4750bc 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -6,7 +6,7 @@ tr:
     about_this: Bu sunucu hakkında
     active_count_after: etkin
     active_footnote: Aylık Aktif Kullanıcılar (AAK)
-    administered_by: 'Tarafından yönetildi:'
+    administered_by: 'Yöneten:'
     api: API
     apps: Mobil uygulamalar
     apps_platforms: İos, Android ve diğer platformlardaki Mastodon'u kullanın
@@ -344,9 +344,6 @@ tr:
         create: Alan adı ekle
         title: Yeni e-posta kara liste girişi
       title: E-posta kara listesi
-    followers:
-      back_to_account: Hesaba Geri Dön
-      title: "%{acct} Takipçileri"
     instances:
       by_domain: Alan adı
       delivery_available: Teslimat mevcut
@@ -375,6 +372,8 @@ tr:
       title: Davetler
     pending_accounts:
       title: Bekleyen hesaplar (%{count})
+    relationships:
+      title: "%{acct} kişisinin ilişkileri"
     relays:
       add_new: Yeni aktarıcı ekle
       delete: Sil
@@ -692,7 +691,7 @@ tr:
   directories:
     directory: Profil dizini
     explanation: Kullanıcıları ilgi alanlarına göre keşfedin
-    explore_mastodon: "%{title} keşfet"
+    explore_mastodon: "%{title} sunucusunu keşfet"
   domain_validator:
     invalid_domain: geçerli bir alan adı değil
   errors:
@@ -721,7 +720,7 @@ tr:
       in_progress: Arşivinizi derliyoruz...
       request: Arşiv isteği
       size: Boyut
-    blocks: Blokladıklarınız
+    blocks: Engelledikleriniz
     csv: CSV
     domain_blocks: Alan adı blokları
     lists: Listeler
@@ -734,6 +733,7 @@ tr:
     hint_html: "<strong>Öne çıkan etiketler nelerdir?</strong> Genel profilinizde belirgin bir şekilde görüntülenirler ve kişilerin genel yayınlarınıza özellikle bu etiketler altında göz atmalarına izin verir. Yaratıcı çalışmaları veya uzun vadeli projeleri takip etmek için harika bir araçtır."
   filters:
     contexts:
+      account: Profiller
       home: Ana zaman çizelgesi
       notifications: Bildirimler
       public: Genel zaman çizelgesi
@@ -934,6 +934,7 @@ tr:
     dormant: Atıl
     followers: Takipçiler
     following: Takip edilenler
+    invited: Davet edildi
     last_active: Son aktivite
     most_recent: En son
     moved: Taşındı
@@ -1215,7 +1216,7 @@ tr:
       full_handle_hint: Arkadaşlarınıza, size başka bir sunucudan mesaj atabilmeleri veya sizi takip edebilmeleri için söyleyeceğiniz şey budur.
       review_preferences_action: Tercihleri değiştirin
       review_preferences_step: Hangi e-postaları almak veya gönderilerinizin varsayılan olarak hangi gizlilik seviyesinde olmasını istediğiniz gibi tercihlerinizi ayarladığınızdan emin olun. Hareket hastalığınız yoksa, GIF otomatik oynatmayı etkinleştirmeyi seçebilirsiniz.
-      subject: Mastodon'a hoşgeldiniz
+      subject: Mastodon'a hoş geldiniz
       tip_federated_timeline: Federe zaman tüneli, Mastodon ağının genel bir görüntüsüdür. Ancak yalnızca komşularınızın abone olduğu kişileri içerir, bu yüzden tamamı değildir.
       tip_following: Sunucu yönetici(ler)ini varsayılan olarak takip edersiniz. Daha ilginç insanlar bulmak için yerel ve federe zaman çizelgelerini kontrol edin.
       tip_local_timeline: Yerel zaman çizelgesi, %{instance} üzerindeki kişilerin genel bir görüntüsüdür. Bunlar senin en yakın komşularındır!
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 32b38067b..7df8abcce 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -352,9 +352,6 @@ uk:
         create: Додати домен
         title: Нове блокування поштового домену
       title: Чорний список поштових доменів
-    followers:
-      back_to_account: Повернутися до Облікового запису
-      title: Підписники %{acct}
     instances:
       by_domain: Домен
       delivery_available: Доставлення доступне
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index b01c1ea20..ec8e853fe 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -336,9 +336,6 @@ vi:
         create: Thêm tên miền
         title: Mục nhập danh sách đen e-mail mới
       title: Danh sách đen e-mail
-    followers:
-      back_to_account: Quay lại tài khoản
-      title: Người theo dõi của %{acct}
     instances:
       by_domain: Miền
       delivery_available: Giao hàng tận nơi
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index a1deb13e2..21b0ecf78 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -338,9 +338,6 @@ zh-CN:
         create: 添加域名
         title: 添加电子邮件域名屏蔽
       title: 电子邮件域名屏蔽
-    followers:
-      back_to_account: 返回帐户
-      title: "%{acct} 的关注者"
     instances:
       by_domain: 域名
       delivery_available: 无法投递
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index 8dd4c346b..ffd5ba5e9 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -294,9 +294,6 @@ zh-HK:
         create: 新增網域
         title: 新增電郵網域阻隔
       title: 電郵網域阻隔
-    followers:
-      back_to_account: 返回帳戶
-      title: "%{acct} 的關注者"
     instances:
       moderation:
         all: 全部
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 1ba2c82c8..5b25688ed 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -294,9 +294,6 @@ zh-TW:
         create: 新增站點
         title: 新增電子信箱黑名單項目
       title: 電子信箱黑名單
-    followers:
-      back_to_account: 返回帳戶
-      title: "%{acct} 的關注者"
     instances:
       moderation:
         all: 全部
diff --git a/config/navigation.rb b/config/navigation.rb
index ab4262182..bd172f25f 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -52,6 +52,7 @@ SimpleNavigation::Configuration.run do |navigation|
     n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s|
       s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
       s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
+      s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}
       s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
       s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays}
       s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
diff --git a/config/routes.rb b/config/routes.rb
index 545c07255..322d66aec 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -175,9 +175,12 @@ Rails.application.routes.draw do
         get :edit
       end
     end
+
     resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
     resources :action_logs, only: [:index]
     resources :warning_presets, except: [:new]
+    resources :announcements, except: [:show]
+
     resource :settings, only: [:edit, :update]
 
     resources :invites, only: [:index, :create, :destroy] do
@@ -225,7 +228,7 @@ Rails.application.routes.draw do
       resource :reset, only: [:create]
       resource :action, only: [:new, :create], controller: 'account_actions'
       resources :statuses, only: [:index, :show, :create, :update, :destroy]
-      resources :followers, only: [:index]
+      resources :relationships, only: [:index]
 
       resource :confirmation, only: [:create] do
         collection do
@@ -320,6 +323,16 @@ Rails.application.routes.draw do
       resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
       resources :preferences, only: [:index]
 
+      resources :announcements, only: [:index] do
+        scope module: :announcements do
+          resources :reactions, only: [:update, :destroy]
+        end
+
+        member do
+          post :dismiss
+        end
+      end
+
       resources :conversations, only: [:index, :destroy] do
         member do
           post :read
diff --git a/db/migrate/20191218153258_create_announcements.rb b/db/migrate/20191218153258_create_announcements.rb
new file mode 100644
index 000000000..58e143c92
--- /dev/null
+++ b/db/migrate/20191218153258_create_announcements.rb
@@ -0,0 +1,16 @@
+class CreateAnnouncements < ActiveRecord::Migration[5.2]
+  def change
+    create_table :announcements do |t|
+      t.text :text, null: false, default: ''
+
+      t.boolean :published, null: false, default: false
+      t.boolean :all_day, null: false, default: false
+
+      t.datetime :scheduled_at
+      t.datetime :starts_at
+      t.datetime :ends_at
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20200113125135_create_announcement_mutes.rb b/db/migrate/20200113125135_create_announcement_mutes.rb
new file mode 100644
index 000000000..c588e7fcd
--- /dev/null
+++ b/db/migrate/20200113125135_create_announcement_mutes.rb
@@ -0,0 +1,12 @@
+class CreateAnnouncementMutes < ActiveRecord::Migration[5.2]
+  def change
+    create_table :announcement_mutes do |t|
+      t.belongs_to :account, foreign_key: { on_delete: :cascade, index: false }
+      t.belongs_to :announcement, foreign_key: { on_delete: :cascade }
+
+      t.timestamps
+    end
+
+    add_index :announcement_mutes, [:account_id, :announcement_id], unique: true
+  end
+end
diff --git a/db/migrate/20200114113335_create_announcement_reactions.rb b/db/migrate/20200114113335_create_announcement_reactions.rb
new file mode 100644
index 000000000..226c81a18
--- /dev/null
+++ b/db/migrate/20200114113335_create_announcement_reactions.rb
@@ -0,0 +1,15 @@
+class CreateAnnouncementReactions < ActiveRecord::Migration[5.2]
+  def change
+    create_table :announcement_reactions do |t|
+      t.belongs_to :account, foreign_key: { on_delete: :cascade, index: false }
+      t.belongs_to :announcement, foreign_key: { on_delete: :cascade }
+
+      t.string :name, null: false, default: ''
+      t.belongs_to :custom_emoji, foreign_key: { on_delete: :cascade }
+
+      t.timestamps
+    end
+
+    add_index :announcement_reactions, [:account_id, :announcement_id, :name], unique: true, name: :index_announcement_reactions_on_account_id_and_announcement_id
+  end
+end
diff --git a/db/migrate/20200119112504_add_public_index_to_statuses.rb b/db/migrate/20200119112504_add_public_index_to_statuses.rb
new file mode 100644
index 000000000..db007848e
--- /dev/null
+++ b/db/migrate/20200119112504_add_public_index_to_statuses.rb
@@ -0,0 +1,11 @@
+class AddPublicIndexToStatuses < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    add_index :statuses, [:id, :account_id], name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
+  end
+
+  def down
+    remove_index :statuses, name: :index_statuses_public_20200119
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b7ab74033..2f41dee97 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_12_12_003415) do
+ActiveRecord::Schema.define(version: 2020_01_19_112504) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -196,15 +196,49 @@ ActiveRecord::Schema.define(version: 2019_12_12_003415) do
     t.index ["target_type", "target_id"], name: "index_admin_action_logs_on_target_type_and_target_id"
   end
 
+  create_table "announcement_mutes", force: :cascade do |t|
+    t.bigint "account_id"
+    t.bigint "announcement_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["account_id", "announcement_id"], name: "index_announcement_mutes_on_account_id_and_announcement_id", unique: true
+    t.index ["account_id"], name: "index_announcement_mutes_on_account_id"
+    t.index ["announcement_id"], name: "index_announcement_mutes_on_announcement_id"
+  end
+
+  create_table "announcement_reactions", force: :cascade do |t|
+    t.bigint "account_id"
+    t.bigint "announcement_id"
+    t.string "name", default: "", null: false
+    t.bigint "custom_emoji_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["account_id", "announcement_id", "name"], name: "index_announcement_reactions_on_account_id_and_announcement_id", unique: true
+    t.index ["account_id"], name: "index_announcement_reactions_on_account_id"
+    t.index ["announcement_id"], name: "index_announcement_reactions_on_announcement_id"
+    t.index ["custom_emoji_id"], name: "index_announcement_reactions_on_custom_emoji_id"
+  end
+
+  create_table "announcements", force: :cascade do |t|
+    t.text "text", default: "", null: false
+    t.boolean "published", default: false, null: false
+    t.boolean "all_day", default: false, null: false
+    t.datetime "scheduled_at"
+    t.datetime "starts_at"
+    t.datetime "ends_at"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+  end
+
   create_table "backups", force: :cascade do |t|
     t.bigint "user_id"
     t.string "dump_file_name"
     t.string "dump_content_type"
-    t.bigint "dump_file_size"
     t.datetime "dump_updated_at"
     t.boolean "processed", default: false, null: false
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.bigint "dump_file_size"
   end
 
   create_table "blocks", force: :cascade do |t|
@@ -693,6 +727,7 @@ ActiveRecord::Schema.define(version: 2019_12_12_003415) do
     t.datetime "deleted_at"
     t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
     t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
+    t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
     t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
     t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
     t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id"
@@ -820,6 +855,11 @@ ActiveRecord::Schema.define(version: 2019_12_12_003415) do
   add_foreign_key "account_warnings", "accounts", on_delete: :nullify
   add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
   add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
+  add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade
+  add_foreign_key "announcement_mutes", "announcements", on_delete: :cascade
+  add_foreign_key "announcement_reactions", "accounts", on_delete: :cascade
+  add_foreign_key "announcement_reactions", "announcements", on_delete: :cascade
+  add_foreign_key "announcement_reactions", "custom_emojis", on_delete: :cascade
   add_foreign_key "backups", "users", on_delete: :nullify
   add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade
   add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade
diff --git a/lib/cli.rb b/lib/cli.rb
index fbdf49fc3..19cc5d6b5 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -96,6 +96,8 @@ module Mastodon
 
       prompt.warn('Do NOT interrupt this process...')
 
+      Setting.registrations_mode = 'none'
+
       Account.local.without_suspended.find_each do |account|
         payload = ActiveModelSerializers::SerializableResource.new(
           account,
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index a8e5f0b79..e0549d0a5 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -9,15 +9,15 @@ module Mastodon
     end
 
     def minor
-      0
+      1
     end
 
     def patch
-      1
+      0
     end
 
     def flags
-      ''
+      'rc1'
     end
 
     def suffix
diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake
index fb9c89aa4..a374e33ad 100644
--- a/lib/tasks/auto_annotate_models.rake
+++ b/lib/tasks/auto_annotate_models.rake
@@ -4,6 +4,7 @@ if Rails.env.development?
   task :set_annotation_options do
     Annotate.set_defaults(
       'routes'                  => 'false',
+      'models'                  => 'true',
       'position_in_routes'      => 'before',
       'position_in_class'       => 'before',
       'position_in_test'        => 'before',
diff --git a/spec/controllers/api/v1/announcements/reactions_controller_spec.rb b/spec/controllers/api/v1/announcements/reactions_controller_spec.rb
new file mode 100644
index 000000000..72620e242
--- /dev/null
+++ b/spec/controllers/api/v1/announcements/reactions_controller_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Api::V1::Announcements::ReactionsController, type: :controller do
+  render_views
+
+  let(:user)   { Fabricate(:user) }
+  let(:scopes) { 'write:favourites' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+
+  let!(:announcement) { Fabricate(:announcement) }
+
+  describe 'PUT #update' do
+    context 'without token' do
+      it 'returns http unauthorized' do
+        put :update, params: { announcement_id: announcement.id, id: '😂' }
+        expect(response).to have_http_status :unauthorized
+      end
+    end
+
+    context 'with token' do
+      before do
+        allow(controller).to receive(:doorkeeper_token) { token }
+        put :update, params: { announcement_id: announcement.id, id: '😂' }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'creates reaction' do
+        expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to_not be_nil
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    before do
+      announcement.announcement_reactions.create!(account: user.account, name: '😂')
+    end
+
+    context 'without token' do
+      it 'returns http unauthorized' do
+        delete :destroy, params: { announcement_id: announcement.id, id: '😂' }
+        expect(response).to have_http_status :unauthorized
+      end
+    end
+
+    context 'with token' do
+      before do
+        allow(controller).to receive(:doorkeeper_token) { token }
+        delete :destroy, params: { announcement_id: announcement.id, id: '😂' }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'creates reaction' do
+        expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/announcements_controller_spec.rb b/spec/controllers/api/v1/announcements_controller_spec.rb
new file mode 100644
index 000000000..6ee46b60e
--- /dev/null
+++ b/spec/controllers/api/v1/announcements_controller_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Api::V1::AnnouncementsController, type: :controller do
+  render_views
+
+  let(:user)   { Fabricate(:user) }
+  let(:scopes) { 'read' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+
+  let!(:announcement) { Fabricate(:announcement) }
+
+  describe 'GET #index' do
+    context 'without token' do
+      it 'returns http unprocessable entity' do
+        get :index
+        expect(response).to have_http_status :unprocessable_entity
+      end
+    end
+
+    context 'with token' do
+      before do
+        allow(controller).to receive(:doorkeeper_token) { token }
+        get :index
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+    end
+  end
+
+  describe 'POST #dismiss' do
+    context 'without token' do
+      it 'returns http unauthorized' do
+        post :dismiss, params: { id: announcement.id }
+        expect(response).to have_http_status :unauthorized
+      end
+    end
+
+    context 'with token' do
+      let(:scopes) { 'write:accounts' }
+
+      before do
+        allow(controller).to receive(:doorkeeper_token) { token }
+        post :dismiss, params: { id: announcement.id }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'dismisses announcement' do
+        expect(announcement.announcement_mutes.find_by(account: user.account)).to_not be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/trends_controller_spec.rb b/spec/controllers/api/v1/trends_controller_spec.rb
new file mode 100644
index 000000000..91e0d18fe
--- /dev/null
+++ b/spec/controllers/api/v1/trends_controller_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Api::V1::TrendsController, type: :controller do
+  render_views
+
+  describe 'GET #index' do
+    before do
+      allow(TrendingTags).to receive(:get).and_return(Fabricate.times(10, :tag))
+      get :index
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+  end
+end
diff --git a/spec/fabricators/announcement_fabricator.rb b/spec/fabricators/announcement_fabricator.rb
new file mode 100644
index 000000000..5a3871d90
--- /dev/null
+++ b/spec/fabricators/announcement_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:announcement) do
+  text      { Faker::Lorem.paragraph(sentence_count: 2) }
+  published true
+  starts_at nil
+  ends_at   nil
+end
diff --git a/spec/fabricators/announcement_mute_fabricator.rb b/spec/fabricators/announcement_mute_fabricator.rb
new file mode 100644
index 000000000..c4eafe8f4
--- /dev/null
+++ b/spec/fabricators/announcement_mute_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:announcement_mute) do
+  account
+  announcement
+end
diff --git a/spec/fabricators/announcement_reaction_fabricator.rb b/spec/fabricators/announcement_reaction_fabricator.rb
new file mode 100644
index 000000000..f923c59c6
--- /dev/null
+++ b/spec/fabricators/announcement_reaction_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:announcement_reaction) do
+  account
+  announcement
+  name '🌿'
+end
diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb
index bb938e36d..651927c2d 100644
--- a/spec/fabricators/media_attachment_fabricator.rb
+++ b/spec/fabricators/media_attachment_fabricator.rb
@@ -1,16 +1,12 @@
 Fabricator(:media_attachment) do
   account
+
   file do |attrs|
-    [
-      case attrs[:type]
-      when :gifv
-        attachment_fixture ['attachment.gif', 'attachment.webm'].sample
-      when :image
-        attachment_fixture 'attachment.jpg'
-      when nil
-        attachment_fixture ['attachment.gif', 'attachment.jpg', 'attachment.webm'].sample
-      end,
-      nil
-    ].sample
+    case attrs[:type]
+    when :gifv, :video
+      attachment_fixture('attachment.webm')
+    else
+      attachment_fixture('attachment.jpg')
+    end
   end
 end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index 83be0a588..633d59c2a 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -258,6 +258,14 @@ RSpec.describe Formatter do
         is_expected.to include 'href="xmpp:muc@instance.com?join"'
       end
     end
+
+    context 'given text containing a magnet: URI' do
+      let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
+
+      it 'matches the full URI' do
+        is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
+      end
+    end
   end
 
   describe '#format_spoiler' do
diff --git a/spec/middleware/handle_bad_encoding_middleware_spec.rb b/spec/middleware/handle_bad_encoding_middleware_spec.rb
deleted file mode 100644
index 8c0d24f18..000000000
--- a/spec/middleware/handle_bad_encoding_middleware_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe HandleBadEncodingMiddleware do
-  let(:app) { double() }
-  let(:middleware) { HandleBadEncodingMiddleware.new(app) }
-
-  it "request with query string is unchanged" do
-    expect(app).to receive(:call).with("PATH" => "/some/path", "QUERY_STRING" => "name=fred")
-    middleware.call("PATH" => "/some/path", "QUERY_STRING" => "name=fred")
-  end
-
-  it "request with no query string is unchanged" do
-    expect(app).to receive(:call).with("PATH" => "/some/path")
-    middleware.call("PATH" => "/some/path")
-  end
-
-  it "request with invalid encoding in query string drops query string" do
-    expect(app).to receive(:call).with("QUERY_STRING" => "", "PATH" => "/some/path")
-    middleware.call("QUERY_STRING" => "q=%2Fsearch%2Fall%Forder%3Ddescending%26page%3D5%26sort%3Dcreated_at", "PATH" => "/some/path")
-  end
-end
diff --git a/spec/models/announcement_mute_spec.rb b/spec/models/announcement_mute_spec.rb
new file mode 100644
index 000000000..9d0e4c903
--- /dev/null
+++ b/spec/models/announcement_mute_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe AnnouncementMute, type: :model do
+end
diff --git a/spec/models/announcement_reaction_spec.rb b/spec/models/announcement_reaction_spec.rb
new file mode 100644
index 000000000..f6e151584
--- /dev/null
+++ b/spec/models/announcement_reaction_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe AnnouncementReaction, type: :model do
+end
diff --git a/spec/models/announcement_spec.rb b/spec/models/announcement_spec.rb
new file mode 100644
index 000000000..7f7b647a9
--- /dev/null
+++ b/spec/models/announcement_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe Announcement, type: :model do
+end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index a275621a1..456bc4216 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -31,14 +31,6 @@ RSpec.describe MediaAttachment, type: :model do
     context 'file is blank' do
       let(:file) { nil }
 
-      context 'remote_url is blank' do
-        let(:remote_url) { '' }
-
-        it 'returns false' do
-          is_expected.to be false
-        end
-      end
-
       context 'remote_url is present' do
         let(:remote_url) { 'remote_url' }
 
@@ -153,6 +145,11 @@ RSpec.describe MediaAttachment, type: :model do
     end
   end
 
+  it 'is invalid without file' do
+    media = MediaAttachment.new(account: Fabricate(:account))
+    expect(media.valid?).to be false
+  end
+
   describe 'descriptions for remote attachments' do
     it 'are cut off at 1500 characters' do
       media = Fabricate(:media_attachment, description: 'foo' * 1000, remote_url: 'http://example.com/blah.jpg')
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index bf06f50e9..025a3da40 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -212,14 +212,18 @@ RSpec.describe PostStatusService, type: :service do
 
   it 'does not allow attaching both videos and images' do
     account = Fabricate(:account)
+    video   = Fabricate(:media_attachment, type: :video, account: account)
+    image   = Fabricate(:media_attachment, type: :image, account: account)
+
+    video.update(type: :video)
 
     expect do
       subject.call(
         account,
         text: "test status update",
         media_ids: [
-          Fabricate(:media_attachment, type: :video, account: account),
-          Fabricate(:media_attachment, type: :image, account: account),
+          video,
+          image,
         ].map(&:id),
       )
     end.to raise_error(